Een aangepaste klasse DynamicObject schrijven die objectinitializers ondersteunt

In de documentatie voor DynamicObject is er een voorbeeld van een DynamicDictionary waarmee u met een woordenboek kunt werken alsof het een klasse met eigenschappen is.

Hier is de klasse (enigszins aangepast voor beknoptheid):

public class DynamicDictionary : DynamicObject
{
    Dictionary _dictionary = new Dictionary();

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

Wat ik zou willen doen is de klas aanpassen, zodat ik het volgende kan doen:

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

Vraag

  1. Is dit mogelijk?
  2. Zo ja, hoe?
3
@Magnus, technisch gezien kan het werken met dynamische objecten, maar alleen op een niet-dynamische manier. Dus als uw dynamische object niet-dynamische Id -eigenschap heeft, kunt u de objectinitialisatie daarvoor gebruiken. Maar dit zal niet helpen om de vraag te beantwoorden.
toegevoegd de auteur svick, de bron
Object Initializers werkt niet met dynamische objecten
toegevoegd de auteur Magnus, de bron
Toegegeven, voor "normale" eigenschappen zal het prima werken. Ik denk dat je een constructor toevoegt die IEnumerable > meeneemt en gebruikt om het woordenboek te maken.
toegevoegd de auteur Magnus, de bron

4 antwoord

DynamicObject provides TryCreateInstance(), which is meant for situations like this, but it's not usable from C#.

Ik zie hier een paar manieren omheen:

  1. Create a dynamic factory class. When you call its Create() method with named argumets, it passes it to the dictionary:

    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. Use non-dynamic collection initializer. This means having the property names as strings in the code:

    // has to implement IEnumerable, so that collection initializer works
    class DynamicDictionary
        : DynamicObject, IEnumerable>
    {
        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. Probably the closest to what you asked for would be to use nested object initializer. That is, the class will have a dynamic property (say, Values), whose properties can be set using object initializer:

    class DynamicDictionary : DynamicObject
    {
        private readonly IDictionary 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 } };
    
4
toegevoegd

Het gebruik van de open source ImpromptuInterface (via nuget) heeft een Syntaxis van de bouwer . waarmee u iets kunt doen dat dicht in de buurt komt van de initialisatiesyntaxis. Specifiek na het opnemen van ImpromptuInterface.Dynamic zou dat kunnen

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

There are other options too listed on that syntax page if you drop the it will use an ImpromptuDictionary which is essentially the same thing. And you can look at the source for the build syntax too.

1
toegevoegd

Het bleek dat ik mijn probleem kon oplossen door Linq's ingebouwde ToDictionary() methode te gebruiken.

Voorbeeld:

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

Wanneer ik een patroon van ingebedde taal zie, gebruik ik uitbreidingsmethoden, zodat ik de volgende code kan schrijven om mijn doel te bereiken:

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

Vervolgens zou ik de uitbreidingsmethode definiëren met de code om elk -object (inclusief anoniem getypte exemplaren) te converteren naar 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)
        );
    }
}

You would have to implement a constructor in DynamicDictionary to receive a IDictionary, but that's a piece of cake.

0
toegevoegd
p => p. && p.CanRead U lijkt hier iets te missen.
toegevoegd de auteur svick, de bron