如何在ExpandoObject中使用集合初始化程序语法?
我注意到新的ExpandoObject
实现了IDictionary
,它具有必需的IEnumerable<KeyValuePair>
和Add(string, object)
方法,所以应该可以使用集合初始化语法来添加expando对象的属性与向字典中添加项目的方式相同。
Dictionary dict = new Dictionary() { { "Hello", "World" } }; dynamic obj = new ExpandoObject() { { "foo", "hello" }, { "bar", 42 }, { "baz", new object() } }; int value = obj.bar;
但似乎没有办法做到这一点。 错误:
‘System.Dynamic.ExpandoObject’不包含’添加’的定义
我认为这不起作用,因为接口是明确实现的。 但有没有办法解决这个问题? 这工作正常,
IDictionary exdict = new ExpandoObject() as IDictionary(); exdict.Add("foo", "hello"); exdict.Add("bar", 42); exdict.Add("baz", new object());
但集合初始化程序语法更整洁。
我以前需要多次使用简单的ExpandoObject初始化程序,并且通常使用以下两种扩展方法来完成初始化语法之类的操作:
public static KeyValuePair WithValue(this string key, object value) { return new KeyValuePair(key, value); } public static ExpandoObject Init( this ExpandoObject expando, params KeyValuePair[] values) { foreach(KeyValuePair kvp in values) { ((IDictionary)expando)[kvp.Key] = kvp.Value; } return expando; }
然后你可以写下面的内容:
dynamic foo = new ExpandoObject().Init( "A".WithValue(true), "B".WithValue("Bar"));
一般来说,我发现使用扩展方法从字符串键构建KeyValuePair
实例会派上用场。 您显然可以将名称更改为Is
以便您可以编写"Key".Is("Value")
如果您需要更简洁的语法。
就我所知,语言规范(关于Collection Initializers的7.5.10.3)在这一点上有点模糊。 它说
对于按顺序的每个指定元素,集合初始值设定项在目标对象上调用Add方法,并将元素初始值设定项的表达式列表作为参数列表,为每个调用应用正常的重载决策。 因此, 集合对象必须包含每个元素初始值设定项的适用Add方法 。
遗憾的是,该文本没有详细说明适用的Add方法是什么,但似乎明确实现的接口方法不适合该法案,因为它们基本上被认为是私有的(见13.4.1):
在方法调用,属性访问或索引器访问中,无法通过其完全限定名称访问显式接口成员实现。 显式接口成员实现只能通过接口实例访问,并且在这种情况下仅通过其成员名称引用。
…
显式接口成员实现具有与其他成员不同的可访问性特征。 因为显式接口成员实现永远不能通过方法调用或属性访问中的完全限定名来访问,所以它们在某种意义上是私有的。 但是,由于它们可以通过接口实例访问,因此它们在某种意义上也是公开的。
开源框架Dynamitey有一种替代语法,用于构建内联的ExpandoObject
实例。
dynamic obj = Builder.New( foo:"hello", bar: 42 , baz: new object() ); int value = obj.bar;
它还有一个基于字典的动态原型对象Dynamitey.DynamicObjects.Dictionary
这样
dynamic obj = new Dynamitey.DynamicObjects.Dictionary() { { "foo", "hello" }, { "bar", 42 }, { "baz", new object() } }; int value = obj.bar;
也有效。
首先,你是现场。 IDictionary
已明确实现。
你甚至不需要铸造。 这有效:
IDictionary exdict = new ExpandoObject()
现在收集语法不起作用的原因是因为它是 Dictionary
构造函数中的实现而不是接口的一部分,因此它不适用于expando。
上面的错误陈述。 你是对的,它使用添加function:
static void Main(string[] args) { Dictionary dictionary = new Dictionary() { {"Ali", "Ostad"} }; }
获取编译为
.method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 27 (0x1b) .maxstack 3 .locals init ([0] class [mscorlib]System.Collections.Generic.Dictionary`2 dictionary, [1] class [mscorlib]System.Collections.Generic.Dictionary`2 '<>g__initLocal0') IL_0000: nop IL_0001: newobj instance void class [mscorlib]System.Collections.Generic.Dictionary`2::.ctor() IL_0006: stloc.1 IL_0007: ldloc.1 IL_0008: ldstr "Ali" IL_000d: ldstr "Ostad" IL_0012: callvirt instance void class [mscorlib]System.Collections.Generic.Dictionary`2::Add(!0, !1) IL_0017: nop IL_0018: ldloc.1 IL_0019: stloc.0 IL_001a: ret } // end of method Program::Main
UPDATE
主要原因是Add
已实现为protected
(没有protected
修饰符)。
由于Add
在ExpandoObject
上不可见 ,因此无法像上面那样调用它。
使用@samedave的想法 ,并添加reflection,我得到了这个:
void Main() { dynamic foo = new ExpandoObject().Init(new { A = true, B = "Bar" }); Console.WriteLine(foo.A); Console.WriteLine(foo.B); } public static class ExtensionMethods { public static ExpandoObject Init(this ExpandoObject expando, dynamic obj) { var expandoDic = (IDictionary)expando; foreach (System.Reflection.PropertyInfo fi in obj.GetType().GetProperties()) { expandoDic[fi.Name] = fi.GetValue(obj, null); } return expando; } }
但是能够像下面这样做会更好:
dynamic foo = new ExpandoObject { A = true, B = "Bar" });
请在Visual Studio UserVoice中为此function投票。
更新:
使用Rick Strahl的Expando实现,你应该能够做到这样的事情:
dynamic baz = new Expando(new { A = false, B = "Bar2" }); Console.WriteLine(baz.A); Console.WriteLine(baz.B);
遗憾的是,向ExpandoObject
添加动态属性(其名称仅在运行时已知)并不像应该的那样容易。 所有对字典的演员都很难看。 没关系,你总是可以编写一个实现Add
的自定义DynamicObject
,它可以帮助你使用整洁的对象初始化器,就像语法一样。
一个粗略的例子:
public sealed class Expando : DynamicObject, IDictionary { readonly Dictionary _properties = new Dictionary(); public override bool TryGetMember(GetMemberBinder binder, out object result) { return _properties.TryGetValue(binder.Name, out result); } public override bool TrySetMember(SetMemberBinder binder, object value) { if (binder.Name == "Add") { var del = value as Delegate; if (del != null && del.Method.ReturnType == typeof(void)) { var parameters = del.Method.GetParameters(); if (parameters.Count() == 2 && parameters.First().ParameterType == typeof(string)) throw new RuntimeBinderException("Method signature cannot be 'void Add(string, ?)'"); } } _properties[binder.Name] = value; return true; } object IDictionary.this[string key] { get { return _properties[key]; } set { _properties[key] = value; } } int ICollection>.Count { get { return _properties.Count; } } bool ICollection>.IsReadOnly { get { return false; } } ICollection IDictionary.Keys { get { return _properties.Keys; } } ICollection
你可以像你想要的那样打电话:
dynamic obj = new Expando() { { "foo", "hello" }, { "bar", 42 }, { "baz", new object() } }; int value = obj.bar;
使用这种方法的警告是,您不能将具有与Dictionary.Add相同签名的Add
“method” Add
到您的expando对象,因为它已经是Expando
类的有效成员(这是集合初始化程序语法所必需的)。 如果你这样做,代码会引发exception
obj.Add = 1; // runs obj.Add = new Action(....); // throws, same signature obj.Add = new Action(....); // throws, same signature for expando class obj.Add = new Action(....); // runs, different signature obj.Add = new Func(....); // runs, different signature
如果属性名称不需要真正动态,那么另一种选择是使用ToDynamic
扩展方法,以便您可以在线初始化。
public static dynamic ToDynamic(this object item) { var expando = new ExpandoObject() as IDictionary; foreach (var propertyInfo in item.GetType().GetProperties()) expando[propertyInfo.Name] = propertyInfo.GetValue(item, null); return expando; }
所以你可以打电话:
var obj = new { foo = "hello", bar = 42, baz = new object() }.ToDynamic(); int value = obj.bar;
有一百种方法可以为此设计API,另一种方法(在orad的答案中提到)是:
dynamic obj = new Expando(new { foo = "hello", bar = 42, baz = new object() });
将是微不足道的实施。
旁注:如果您静态地知道属性名称并且您不希望在初始化后进一步添加,则始终存在匿名类型。