如何在ExpandoObject中使用集合初始化程序语法?

时间:2011-05-06 10:54:16

标签: c# dynamic syntactic-sugar expandoobject object-initializer

我注意到新的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());

但是集合初始化程序语法更整洁。

6 个答案:

答案 0 :(得分: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")

答案 1 :(得分:3)

就我所知,语言规范(关于Collection Initializers的7.5.10.3)在这一点上有点模糊。它说

  

按顺序为每个指定的元素,   集合初始化程序调用   用目标对象添加方法   元素的表达式列表   初始化器作为参数列表,应用   每个的正常重载分辨率   调用。因此,集合   对象必须包含适用的Add   每个元素初始值设定项的方法

不幸的是,文本没有详细说明适用的Add方法是什么,但似乎明确实现的接口方法不适合该法案,因为它们基本上被认为是私有的(见13.4.1):

  

无法访问   显式接口成员   完全实施   方法调用中的限定名称,   属性访问或索引器访问。一个   显式接口成员   只能访问实现   通过接口实例,是   在这种情况下简单地引用它   会员名称。

     

...

     

显式接口成员   实现有不同   可访问性特征比   其他成员。因为明确   接口成员实现是   从来没有完全可以访问   方法调用中的限定名称   或财产访问,他们在一个   感觉私密。但是,因为他们可以   通过界面访问   例如,它们在某种意义上也是如此   公共

答案 2 :(得分:3)

开源框架Dynamitey有一个替代syntax,用于内联构建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;

也有效。

答案 3 :(得分: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 不可见,因此无法按上述方式调用它。

答案 4 :(得分:2)

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

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

答案 5 :(得分:1)

遗憾的是,将动态属性(其名称仅在运行时知道)添加到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&#34;方法&#34;与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() });

实施起来很简单。

旁注:如果您静态了解属性名称,并且您不希望在初始化后进一步添加,则始终存在匿名类型。