Comment rédiger une classe DynamicObject personnalisée qui prend en charge les initialiseurs d'objets

StackOverflow https://stackoverflow.com/questions/8408403

Question

Dans la documentation pour Dynamicobject, il y a un exemple de DynamicDictionary Cela vous permet de travailler avec un dictionnaire comme s'il s'agissait d'une classe avec des propriétés.

Voici la classe (modifiée légèrement pour Brevity):

public class DynamicDictionary : DynamicObject
{
    Dictionary<string, object> _dictionary = new Dictionary<string, object>();

    public int Count
    {
        get { return _dictionary.Count; }
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        string name = binder.Name.ToLower();
        return _dictionary.TryGetValue(name, out result);
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _dictionary[binder.Name.ToLower()] = value;
        return true;
    }
}

Ce que j'aimerais faire, c'est modifier la classe, afin que je puisse faire ce qui suit:

public class Test
{
    public Test()
    {
        var result = Enumerable.Range(1, 5).Select(i => new DynamicDictionary
        {
            Id = i,
            Foo = "Foo",
            Bar = 2
        });
    }
}

Des questions

  1. Est-ce possible?
  2. Si oui, comment?
Était-ce utile?

La solution 3

Il s'avère que j'ai pu résoudre mon problème en utilisant le linq intégré ToDictionary() méthode.

Exemple:

public Test()
{
    var data = Enumerable.Range(1, 5).Select(i => new
    {
        Id = i,
        Foo = "Foo",
        Bar = 2
    });
    var result = data
        .Select(d => d.GetType().GetProperties()
            .Select(p => new { Name = p.Name, Value = p.GetValue(pd, null) })
            .ToDictionary(
                pair => pair.Name,
                pair => pair.Value == null ? string.Empty : pair.Value.ToString()));
}

Autres conseils

DynamicObject proposer TryCreateInstance(), qui est destiné à des situations comme celle-ci, mais ce n'est pas utilisable de C #.

Je vois quelques façons de ceci:

  1. Créez une classe d'usine dynamique. Quand tu appelles c'est Create() Méthode avec des arguments nommés, il le passe au dictionnaire:

    class DynamicDictionaryFactory : DynamicObject
    {
        public override bool TryInvokeMember(
            InvokeMemberBinder binder, object[] args, out object result)
        {
            if (binder.Name == "Create")
            {
                // use binder.CallInfo.ArgumentNames and args
                // to create the dynamic dictionary
                result = …;
                return true;
            }
    
            return base.TryInvokeMember(binder, args, out result);
        }
    }
    
    …
    
    dynamic factory = new DynamicDictionaryFactory();
    
    dynamic dict = factory.Create(Id: 42);
    
  2. Utilisez l'initialisateur de collection non dynamique. Cela signifie avoir les noms de propriété comme des chaînes dans le code:

    // has to implement IEnumerable, so that collection initializer works
    class DynamicDictionary
        : DynamicObject, IEnumerable<KeyValuePair<string, object>>
    {
        public void Add(string name, object value)
        {
            m_dictionary.Add(name, value);
        }
    
        // IEnumerable implmentation and actual DynamicDictionary code here
    }
    
    …
    
    dynamic dict = new DynamicDictionary { { "Id", 42 } };
    
  3. Probablement le plus proche de ce que vous avez demandé serait d'utiliser imbriqué Initialisateur d'objet. C'est-à-dire que la classe aura un dynamic propriété (disons, Values), dont les propriétés peuvent être définies à l'aide de l'initialisateur d'objet:

    class DynamicDictionary : DynamicObject
    {
        private readonly IDictionary<string, object> m_expandoObject =
            new ExpandoObject();
    
        public dynamic Values
        {
            get { return m_expandoObject; }
        }
    
        // DynamicDictionary implementation that uses m_expandoObject here
    }
    
    …
    
    dynamic dict = new DynamicDictionary { Values = { Id = 42 } };
    

En utilisant l'open source ImpromptuInterface (via Nuget), il a un Syntaxe du constructeur. Cela vous permet de faire quelque chose près de la syntaxe d'initialisation. Spécifiquement après avoir inclus ImpromptuInterface.Dynamic vous pourriez faire

   var result = Enumerable.Range(1, 5).Select(i => Build<DynamicDictionary>.NewObject
    (
        Id: i,
        Foo: "Foo",
        Bar: 2
    ));

Il existe également d'autres options répertoriées sur cette page de syntaxe si vous supprimez le <DynamicDictionary> il utilisera un ImpromptuDictionary ce qui est essentiellement la même chose. Et vous pouvez regarder le la source pour la syntaxe de construction aussi.

Quand je vois un modèle de langage intégré comme celui-ci, j'ai tendance à utiliser des méthodes d'extension, afin que je puisse écrire le code suivant pour atteindre mon objectif:

public Test()
{
    var data = Enumerable.Range(1, 5).Select(i => new
    {
        Id = i,
        Foo = "Foo",
        Bar = 2
    }.AsDynamicDictionary());
}

Ensuite, je définirais la méthode d'extension avec le code pour convertir n'importe quel object (y compris les dactylographiés de manière anonyme) pour DynamicDictionary:

public static class DynamicDictionaryExtensions
{
    public static DynamicDictionary AsDynamicDictionary(this object data)
    {
        if (data == null) throw new ArgumentNullException("data");
        return new DynamicDictionary(
               data.GetType().GetProperties()
               .Where(p => p. && p.CanRead)
               .Select(p => new {Name: p.Name, Value: p.GetValue(data, null)})
               .ToDictionary(p => p.Name, p => p.Value)
        );
    }
}

Vous devrez implémenter un constructeur dans DynamicDictionary pour recevoir un IDictionary<string, object>, mais c'est un morceau de gâteau.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top