【问题标题】:How to write a Custom DynamicObject class that supports object initializers如何编写支持对象初始值设定项的自定义 DynamicObject 类
【发布时间】:2012-01-14 13:21:03
【问题描述】:

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. 如果是,怎么做?

【问题讨论】:

  • 对象初始化器不适用于动态对象
  • @Magnus,从技术上讲,它可以处理动态对象,但只能以非动态方式。因此,如果您的动态对象具有非动态 Id 属性,则可以使用对象初始化器。但这无助于回答问题。
  • 是的,对于“正常”属性,它可以正常工作。我猜你应该添加一个接受IEnumerable&lt;KeyValuePari&lt;string, object&gt;&gt; 的构造函数并使用它来创建字典。

标签: c# linq dynamic object-initializers


【解决方案1】:

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. 可能最接近您要求的是使用 nested 对象初始化程序。也就是说,该类将有一个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 } };
    

【讨论】:

    【解决方案2】:

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

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

    如果您删除&lt;DynamicDictionary&gt;,该语法页面上还会列出其他选项,它将使用ImpromptuDictionary,这本质上是相同的。您也可以查看source 的构建语法。

    【讨论】:

      【解决方案3】:

      事实证明,我能够通过使用 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()));
      }
      

      【讨论】:

        【解决方案4】:

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

        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&lt;string, object&gt;,但这只是小菜一碟。

        【讨论】:

        • p =&gt; p. &amp;&amp; p.CanRead 你好像漏掉了什么。
        猜你喜欢
        • 2020-09-21
        • 2013-08-03
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-09-30
        • 1970-01-01
        • 2019-09-23
        相关资源
        最近更新 更多