如何编写支持对象初始值设定项的Custom DynamicObject类

时间:2011-12-06 23:28:40

标签: c# linq dynamic object-initializers

DynamicObject的文档中,有一个DynamicDictionary的示例,它允许您使用字典,就像它是一个具有属性的类一样。

这是课程(为简洁起见稍作修改):

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

我想做的是修改课程,以便我可以执行以下操作:

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

问题

  1. 这可能吗?
  2. 如果是,怎么样?

4 个答案:

答案 0 :(得分:4)

DynamicObject提供TryCreateInstance(),适用于此类情况,但无法在C#中使用。

我看到了一些方法:

  1. 创建动态工厂类。当您使用命名的参数调用其Create()方法时,它会将其传递给字典:

    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. 使用非动态集合初始值设定项。这意味着在代码中将属性名称作为字符串:

    // 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. 可能与您要求的最接近的是使用嵌套对象初始值设定项。也就是说,该类将具有dynamic属性(例如,Values),其属性可以使用对象初始值设定项进行设置:

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

答案 1 :(得分:1)

使用开源ImpromptuInterface(通过nuget)它有一个Builder Syntax。这使你可以做一些接近初始化语法的事情。特别是在包括ImpromptuInterface.Dynamic之后你可以做

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

如果你删除<DynamicDictionary>,那么语法页面上还会列出其他选项,它将使用ImpromptuDictionary,这基本上是相同的。您也可以查看source的构建语法。

答案 2 :(得分:0)

事实证明,我能够通过使用Linq的内置ToDictionary()方法来解决我的问题。

示例:

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

答案 3 :(得分:0)

当我看到这样的嵌入式语言模式时,我倾向于使用扩展方法,所以我可以编写以下代码来实现我的目标:

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

然后我会使用代码定义扩展方法,将任何object(包括匿名类型的)转换为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)
        );
    }
}

你必须在DynamicDictionary中实现一个构造函数来接收IDictionary<string, object>,但这是件小事。