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?
3
  • Object Initializers does not work with dynamic objects Commented Dec 6, 2011 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. Commented Dec 6, 2011 at 23:58
  • True, for "normal" properties ti'll work fine. I guess you colud add a constructor that takes in IEnumerable<KeyValuePari<string, object>> and use that to create the Dictionary. Commented Dec 7, 2011 at 8:42

4 Answers 4

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 } };
    
Sign up to request clarification or add additional context in comments.

Comments

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.

Comments

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

Comments

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.

1 Comment

p => p. && p.CanRead You seem to be missing something here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.