Come scrivere una classe DynamicObject personalizzata che supporti gli inizializzatori di oggetti

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

Domanda

Nella documentazione per Oggetto dinamico, c'è un esempio di a DynamicDictionary che ti consente di lavorare con un dizionario come se fosse una classe con proprietà.

Ecco la classe (leggermente modificata per brevità):

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;
    }
}

Quello che vorrei fare è modificare la classe, in modo da poter fare quanto segue:

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

Domande

  1. È possibile?
  2. Se sì, come?
È stato utile?

Soluzione 3

Si è scoperto che sono riuscito a risolvere il mio problema utilizzando il metodo ToDictionary() integrato di Linq.

Esempio:

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()));
}

Altri suggerimenti

DynamicObject fornisce TryCreateInstance(), pensato per situazioni come questa, ma non è utilizzabile da C#.

Vedo alcuni modi per aggirare questo:

  1. Crea una classe factory dinamica.Quando lo chiami Create() metodo con argomenti con nome, lo passa al dizionario:

    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. Utilizza l'inizializzatore di raccolta non dinamico.Ciò significa avere i nomi delle proprietà come stringhe nel codice:

    // 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. Probabilmente il più vicino a quello che hai chiesto sarebbe usare nidificato inizializzatore di oggetto.Cioè, la classe avrà a dynamic proprietà (ad esempio, Values), le cui proprietà possono essere impostate utilizzando l'inizializzatore di oggetto:

    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 } };
    

Utilizzando l'impromptuInterface open source (tramite nuget) ha una sintassi del builder .che ti consente di fare qualcosa di simile alla sintassi di inizializzazione.In particolare dopo aver incluso ImpromptuInterface.Dynamic potresti fare

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

Ci sono anche altre opzioni elencate in quella pagina di sintassi se si rilascia il <DynamicDictionary>, verrà utilizzato un ImpromptuDictionary che è essenzialmente la stessa cosa.E puoi guardare la fonte anche per la sintassi di compilazione.

Quando vedo un modello di linguaggio incorporato come questo, tendo a utilizzare metodi di estensione, quindi potrei scrivere il seguente codice per raggiungere il mio obiettivo:

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

Quindi definirei il metodo di estensione con il codice per convertire qualsiasi object (compresi quelli digitati in modo anonimo) in 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)
        );
    }
}

Dovresti implementare un costruttore in DynamicDictionary per ricevere un IDictionary<string, object>, ma è un gioco da ragazzi.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top