4

In the documentation for DynamicObject, there is an example of a DynamicDictionary that allows you to work with a dictionary as if it's a class with properties.

Here is the class (modified slightly for 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;
    }
}

What I'd like to do is modify the class, so that I can do the following:

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

Questions

  1. Is this possible?
  2. If yes, how?
devuxer
  • 41,681
  • 47
  • 180
  • 292
  • Object Initializers does not work with dynamic objects – Magnus Dec 06 '11 at 23:43
  • @Magnus, technically, it can work with dynamic objects, but only in a non-dynamic way. So if your dynamic object had non-dynamic `Id` property, you can use object initializer for it. But this won't help to answer the question. – svick Dec 06 '11 at 23:58
  • True, for "normal" properties ti'll work fine. I guess you colud add a constructor that takes in `IEnumerable>` and use that to create the Dictionary. – Magnus Dec 07 '11 at 08:42

4 Answers4

4

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

I see some ways around this:

  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<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. 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<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 } };
    
svick
  • 236,525
  • 50
  • 385
  • 514
2

Using the open source ImpromptuInterface (via nuget) it has a Builder Syntax. that lets you do something close to the initialization syntax. Specifically after including ImpromptuInterface.Dynamic you could do

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

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

jbtule
  • 31,383
  • 12
  • 95
  • 128
0

When I see a pattern of embedded language like this, I tend to use extension methods, so I could write the following code to achieve my goal:

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

Then I would define the extension method with the code to convert any object (including anonymously typed ones) to 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<string, object>, but that's a piece of cake.

svick
  • 236,525
  • 50
  • 385
  • 514
isierra
  • 93
  • 6
0

It turns out, I was able to solve my problem by using Linq's built-in ToDictionary() method.

Example:

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()));
}
devuxer
  • 41,681
  • 47
  • 180
  • 292