16

我注意到新ExpandoObject实现IDictionary<string,object>具有必要条件IEnumerable<KeyValuePair<string, object>>Add(string, object)方法,因此应该可以使用集合初始化器语法向 expando 对象添加属性,就像向字典添加项目一样。

Dictionary<string,object> dict = new Dictionary<string,object>() 
{
    { "Hello", "World" }
};

dynamic obj = new ExpandoObject()
{
    { "foo", "hello" },
    { "bar", 42 },
    { "baz", new object() }
};

int value = obj.bar;

但似乎没有办法做到这一点。错误:

“System.Dynamic.ExpandoObject”不包含“添加”的定义

我认为这不起作用,因为接口是显式实现的。但是有没有办法解决这个问题?这工作正常,

IDictionary<string, object> exdict = new ExpandoObject() as IDictionary<string, object>();
exdict.Add("foo", "hello");
exdict.Add("bar", 42);
exdict.Add("baz", new object());

但集合初始值设定项语法要简洁得多。

4

6 回答 6

6

我之前多次需要一个简单的 ExpandoObject 初始化程序,通常使用以下两种扩展方法来完成初始化程序语法之类的事情:

public static KeyValuePair<string, object> WithValue(this string key, object value)
{
    return new KeyValuePair<string, object>(key, value);
}

public static ExpandoObject Init(
    this ExpandoObject expando, params KeyValuePair<string, object>[] values)
{
    foreach(KeyValuePair<string, object> kvp in values)
    {
        ((IDictionary<string, Object>)expando)[kvp.Key] = kvp.Value;
    }
    return expando;
}

然后您可以编写以下内容:

dynamic foo = new ExpandoObject().Init(
    "A".WithValue(true),
    "B".WithValue("Bar"));

一般来说,我发现有一个KeyValuePair<string, object>从字符串键构建实例的扩展方法会派上用场。您显然可以将名称更改为类似这样的名称,以便在您需要语法更加简洁时进行Is编写。"Key".Is("Value")

于 2012-05-01T18:31:35.767 回答
3

开源框架Dynamitey具有用于内联构建实例的替代语法。ExpandoObject

    dynamic obj = Builder.New<ExpandoObject>(
        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;

也可以。

于 2011-05-17T21:04:17.157 回答
3

据我所知,语言规范(集合初始化器上的 7.5.10.3)在这一点上有点含糊。它说

对于按顺序指定的每个元素,集合初始值设定项在目标对象上调用 Add 方法,并将元素初始值设定项的表达式列表作为参数列表,对每个调用应用正常的重载决议。因此,集合对象必须包含适用于每个元素初始值设定项的 Add 方法

不幸的是,文本没有详细说明适用的 Add 方法是什么,但似乎显式实现的接口方法不符合要求,因为它们本质上被认为是私有的(参见 13.4.1):

在方法调用、属性访问或索引器访问中,无法通过其完全限定名称访问显式接口成员实现。显式接口成员实现只能通过接口实例访问,并且在这种情况下仅通过其成员名称引用。

...

显式接口成员实现具有与其他成员不同的可访问性特征。因为在方法调用或属性访问中,显式接口成员实现永远不能通过它们的完全限定名称访问,所以它们在某种意义上是私有的。但是,由于可以通过接口实例访问它们,因此它们在某种意义上也是公共的。

于 2011-05-06T11:35:33.153 回答
2

首先,你是当场的。已IDictionary<string,object>明确实施。

你甚至不需要铸造。这有效:

IDictionary<string,object> exdict = new ExpandoObject() 

现在集合语法不起作用的原因是因为它是Dictionary<T,T> 构造函数中的实现,而不是接口的一部分,因此它不适用于 expando。

上面的说法错误。你是对的,它使用添加功能:

static void Main(string[] args)
{
Dictionary<string,object> dictionary = new Dictionary<string, object>()
                                                {
                                                    {"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<string,object> dictionary,
           [1] class [mscorlib]System.Collections.Generic.Dictionary`2<string,object> '<>g__initLocal0')
  IL_0000:  nop
  IL_0001:  newobj     instance void class [mscorlib]System.Collections.Generic.Dictionary`2<string,object>::.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<string,object>::Add(!0,
                                                                                                                 !1)
  IL_0017:  nop
  IL_0018:  ldloc.1
  IL_0019:  stloc.0
  IL_001a:  ret
} // end of method Program::Main

更新

主要原因是Add已实现为protected(没有成为 的修饰符protected)。

由于在Add不可见ExpandoObject,因此不能像上面那样调用它。

于 2011-05-06T11:11:20.710 回答
2

使用@samedave的想法,并添加反射,我得到了这个:

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<string, object>)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中投票支持此功能。

更新:

使用 Rick Strahl 的Expando实现,您应该能够执行以下操作:

dynamic baz = new Expando(new
{
    A = false,
    B = "Bar2"
});

Console.WriteLine(baz.A);
Console.WriteLine(baz.B);
于 2015-02-06T22:21:49.570 回答
2

遗憾的是,添加动态属性(其名称仅在运行时才知道)ExpandoObject并不像应有的那么容易。所有转换为字典的内容都很丑陋。没关系,你总是可以编写一个DynamicObject实现的自定义,Add它可以帮助你使用整洁的对象初始化器,如语法。

一个粗略的例子:

public sealed class Expando : DynamicObject, IDictionary<string, object>
{
    readonly Dictionary<string, object> _properties = new Dictionary<string, object>();

    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<string, object>.this[string key]
    {
        get
        {
            return _properties[key];
        }
        set
        {
            _properties[key] = value;
        }
    }

    int ICollection<KeyValuePair<string, object>>.Count
    {
        get { return _properties.Count; }
    }

    bool ICollection<KeyValuePair<string, object>>.IsReadOnly
    {
        get { return false; }
    }

    ICollection<string> IDictionary<string, object>.Keys
    {
        get { return _properties.Keys; }
    }

    ICollection<object> IDictionary<string, object>.Values
    {
        get { return _properties.Values; }
    }



    public void Add(string key, object value)
    {
        _properties.Add(key, value);
    }

    bool IDictionary<string, object>.ContainsKey(string key)
    {
        return _properties.ContainsKey(key);
    }

    bool IDictionary<string, object>.Remove(string key)
    {
        return _properties.Remove(key);
    }

    bool IDictionary<string, object>.TryGetValue(string key, out object value)
    {
        return _properties.TryGetValue(key, out value);
    }

    void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
    {
        ((ICollection<KeyValuePair<string, object>>)_properties).Add(item);
    }

    void ICollection<KeyValuePair<string, object>>.Clear()
    {
        _properties.Clear();
    }

    bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
    {
        return ((ICollection<KeyValuePair<string, object>>)_properties).Contains(item);
    }

    void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
    {
        ((ICollection<KeyValuePair<string, object>>)_properties).CopyTo(array, arrayIndex);
    }

    bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
    {
        return ((ICollection<KeyValuePair<string, object>>)_properties).Remove(item);
    }

    IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
    {
        return _properties.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return ((IEnumerable)_properties).GetEnumerator();
    }
}

你可以随心所欲地打电话:

dynamic obj = new Expando()
{
    { "foo", "hello" },
    { "bar", 42 },
    { "baz", new object() }
};

int value = obj.bar;

这种方法的警告是,您不能将Add具有与 Dictionary.Add 相同签名的“方法”添加到您的 expando 对象,因为它已经是Expando该类的有效成员(这是集合初始化程序语法所必需的)。如果你这样做,代码会引发异常

obj.Add = 1; // runs
obj.Add = new Action<string, object>(....); // throws, same signature
obj.Add = new Action<string, int>(....); // throws, same signature for expando class
obj.Add = new Action<string, object, object>(....); // runs, different signature
obj.Add = new Func<string, object, int>(....); // runs, different signature

如果属性名称不需要真正动态,那么另一种选择是使用ToDynamic扩展方法,以便您可以在线初始化。

public static dynamic ToDynamic(this object item)
{
    var expando = new ExpandoObject() as IDictionary<string, object>;
    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() });

将是微不足道的实施。


旁注:如果您静态地知道属性名称并且您不想在初始化后进一步添加,那么总会有匿名类型。

于 2015-07-30T14:53:39.493 回答