为什么我不能这样做:动态x = new ExpandoObject { Foo = 12, Bar = "twelve" }

78

我是否做错了什么,或者以下代码真的是不可能的吗?

dynamic x = new ExpandoObject { Foo = 12, Bar = "twelve" };
如果这确实不可能,是否有另一种一行代码的方法来实例化具有两个属性的ExpandoObject?
为什么C#团队选择禁止与常规对象、匿名对象和可枚举/列表相同的初始化语法?
更新
我提出这个问题是因为我试图向一个Pearl爱好者展示C#的酷炫新动态特性,但是由于我无法做到我认为是逻辑上的实例化一个ExpandoObject而被耽搁了。感谢Hans Passant的答案,我意识到ExpandoObject不是这项工作的正确工具。我的真正目标是使用C#的动态特性从方法中返回两个命名值。正如Hans所指出的那样,dynamic关键字非常适合此任务。我不需要使用ExpandoObject来做这件事,因为那会带来所有的开销。
因此,如果您想从方法中返回一对命名值,并且不关心类型安全、智能感知、重构或性能,那么这个方法非常有效:
public dynamic CreateFooBar()
{
    return new { Foo = 42, Bar = "Hello" };
}

用法:

dynamic fooBar = CreateFooBar();
var foo = fooBar.Foo;
var bar = fooBar.Bar;

10
注意!你更新的示例只能在同一个程序集中工作,因为生成的匿名类型是 internal - configurator
可能是[C#动态对象初始化器无法编译]的重复问题(https://dev59.com/bG855IYBdhLWcg3wp2N0)。 - nawfal
8个回答

55

我做错了什么,还是以下代码真的不可能实现?

确实不可能。赋值运算符左侧必须是编译时已知的属性或字段,而对于可扩展对象来说显然不是这种情况。

为什么 C# 团队选择禁止与普通对象、匿名对象和可枚举/列表相同的初始化语法?

你提出问题的方式表明了逻辑错误。特性并非默认实现,然后我们四处禁止几乎所有的特性,因为我们认为它们是个坏主意!默认情况下特性是未实现的,必须要先进行实现才能使用。

任何特性的实现的第一步是有人必须首先想到它。据我所知,我们从未这样做过。特别是在2006年设计对象初始化器时,很难知道我们将在2010年向语言中添加“动态”,并根据此设计该功能。特性总是由前进时间的设计师设计的,而不是倒退时间。我们只记得过去,而不是未来。

无论如何,这是一个好主意,感谢您分享。既然有人想到了,我们可以继续下一步,例如决定是否是我们可以花费有限预算的最佳主意,设计它,编写规范,实现它,测试它,文档化它,并将其发送给客户。

我不希望这些事情很快发生;我们正在繁忙地处理上周在 Build 上宣布的整个异步和 WinRT 业务。


12
新手无法记住未来。 :-) 开玩笑,+1 - user541686
6
抱歉,艾瑞克,我并不是有攻击的意思。我只是想向一位Python爱好者的同事炫耀C#的新动态特性,但是我却无法进行我认为逻辑合理的ExpandoObject实例化。无论如何,感谢你的回答,希望当异步和WinRT稳定下来后你能考虑这个功能。 - devuxer
2
我将把这个标记为答案,因为它在技术上更好地回答了我所问的确切问题,但我想提一下,Hans的答案更好地解决了我的潜在问题,即如何使用C#的动态特性从方法中同时返回两个命名值?正如Hans指出的那样,你可以使用dynamic--ExpandoObject似乎是用于更复杂的情况(例如根据XML文件的内容在运行时构造一个类)。所以,现在我理解了这一点,我不再感到惊讶ExpandoObject不支持对象初始化器。 - devuxer
1
“赋值运算符左侧的内容必须是在编译时已知的属性或字段,而对于动态对象来说显然不是这种情况。” - 我不太确定你的意思是什么... - configurator
1
这就像是编译器的过度优化,没有考虑未来功能。语法暗示它只是在构造对象后分配成员,而实际上它正在执行编译时检查。这现在是一个编译器问题(或者你称之为什么其他部分),在这种特定情况下,它没有考虑到DLR的存在。编译器没有理由不能弄清楚当对象类型是IDynamicMetaObjectProvider时需要做一些不同的事情。在我看来,这应该得到修复和支持。 - Triynko
显示剩余2条评论

44

有比ExpandoObject更好的方法。使用dynamic关键字可以轻松处理匿名类型:

class Program {      
    static void Main(string[] args) {
        dynamic x = new { Foo = 12, Bar = "twelve" };
        Display(x);
    }
    static void Display(dynamic x) {
        Console.WriteLine(x.Foo);
        Console.WriteLine(x.Bar);
    }
}

不幸的是,C#编译器生成的匿名类型只具有 internal 访问权限。 这意味着当您尝试在另一个程序集中访问成员时,将会发生运行时错误。 很烦人。

考虑使用元组,在C# v7中得到了大幅改进。


1
+1。太好了,汉斯。谢谢。我没想到你可以将dynamic用作返回类型,但看起来确实可以。所以,在我的特定情况下,我甚至不需要ExpandoObject - devuxer
7
要知道,在方法外部传递匿名类型时需要小心,因为它们无法跨越程序集边界。 - jbtule
5
请注意,匿名类型成员一旦设置就无法更改,但使用ExpandoObject可以更改。这是两者之间的重大区别。 - user441521

10

Dynamitey(PCL开源库,在NuGet中可找到)有一种语法可以内联地初始化expandos

 //using Dynamitey.DynamicObjects
 var x = Build<ExpandoObject>.NewObject(Foo:12, Bar:"twelve");

这怎么可能呢,Foo和Bar是参数名称吗? - nawfal
是的,参数名称随着dlr调用一起发送。因此,我的构建对象代理将参数名称转换为属性设置器调用。 - jbtule
ExpandoObject是一种动态原型对象,就像Javascript中的对象一样,您可以动态添加属性,它们从未预定义。 - jbtule
2
这对我来说是赢家。当我只想做 View(new { Items = model } ) 时,我肯定很烦恼必须跳过这些障碍。 - Chris Marisic
同意这个答案很酷。希望有一点语法糖,比如 var x = New.Expando(Foo:12, Bar:"twelve"); 这样的东西。 - nawfal
你也可以使用 dynamic New = Builder.New<ExpandoObject>();,然后使用你表达的语法。 - jbtule

6
当然可以! 使用下面的扩展方法,你可以做出像这样的东西:
dynamic x = (new { Foo = 12, Bar = "twelve" }).ToExpando();

这里放置代码

public static class MyExpando
{

    public static void Set(this ExpandoObject obj, string propertyName, object value)
    {
        IDictionary<string, object> dic = obj;
        dic[propertyName] = value;
    }

    public static ExpandoObject ToExpando(this object initialObj)
    {
        ExpandoObject obj = new ExpandoObject();
        IDictionary<string, object> dic = obj;
        Type tipo = initialObj.GetType();
        foreach(var prop in tipo.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))
        {
            dic.Add(prop.Name, prop.GetValue(initialObj));
        }
        return obj;
    }
}

Set方法旨在添加更多属性(适用于您在编译时不知道属性名称的场景)

x.Set("Name", "Bill");

而且由于它是动态的,所以只需进行

编写即可。

x.LastName = "Gates";

3

我发现一个可行的解决方案是使用 Yan Cui (来源) 提供的 ToExpando() 扩展方法:

public static class DictionaryExtensionMethods
{
    /// <summary>
    /// Extension method that turns a dictionary of string and object to an ExpandoObject
    /// </summary>
    public static ExpandoObject ToExpando(this IDictionary<string, object> dictionary)
    {
        var expando = new ExpandoObject();
        var expandoDic = (IDictionary<string, object>) expando;

        // go through the items in the dictionary and copy over the key value pairs)
        foreach (var kvp in dictionary)
        {
            // if the value can also be turned into an ExpandoObject, then do it!
            if (kvp.Value is IDictionary<string, object>)
            {
                var expandoValue = ((IDictionary<string, object>) kvp.Value).ToExpando();
                expandoDic.Add(kvp.Key, expandoValue);
            }
            else if (kvp.Value is ICollection)
            {
                // iterate through the collection and convert any strin-object dictionaries
                // along the way into expando objects
                var itemList = new List<object>();
                foreach (var item in (ICollection) kvp.Value)
                {
                    if (item is IDictionary<string, object>)
                    {
                        var expandoItem = ((IDictionary<string, object>) item).ToExpando();
                        itemList.Add(expandoItem);
                    }
                    else
                    {
                        itemList.Add(item);
                    }
                }

                expandoDic.Add(kvp.Key, itemList);
            }
            else
            {
                expandoDic.Add(kvp);
            }
        }

        return expando;
    }
}

示例用法:

public const string XEntry = "ifXEntry";
public static readonly dynamic XEntryItems = new Dictionary<string, object>
{
    { "Name",                     XEntry + ".1" },
    { "InMulticastPkts",          XEntry + ".2" },
    { "InBroadcastPkts",          XEntry + ".3" },
    { "OutMulticastPkts",         XEntry + ".4" },
    { "OutBroadcastPkts",         XEntry + ".5" },
    { "HCInOctets",               XEntry + ".6" },
    { "HCInUcastPkts",            XEntry + ".7" },
    { "HCInMulticastPkts",        XEntry + ".8" },
    { "HCInBroadcastPkts",        XEntry + ".9" },
    { "HCOutOctets",              XEntry + ".10" },
    { "HCOutUcastPkts",           XEntry + ".11" },
    { "HCOutMulticastPkts",       XEntry + ".12" },
    { "HCOutBroadcastPkts",       XEntry + ".13" },
    { "LinkUpDownTrapEnable",     XEntry + ".14" },
    { "HighSpeed",                XEntry + ".15" },
    { "PromiscuousMode",          XEntry + ".16" },
    { "ConnectorPresent",         XEntry + ".17" },
    { "Alias",                    XEntry + ".18" },
    { "CounterDiscontinuityTime", XEntry + ".19" },
}.ToExpando();

那么可以使用属性,例如XEntryItems.Name

PS:请在此处投票支持ExpandoObjects上的对象初始化程序


0
public static class ExpandoObjectExtentions
{
    public static void Add(this ExpandoObject obj, string key, object val)
    {
        ((IDictionary<string, object>)obj).Add(key, val);
    }
}

0

这种初始化语法是可能的,因为已经有了具有getter和setter的属性。根据我的观察,使用expando对象时还没有这些属性。


-1

有一个更简单的方法来做到这一点:

Func<Dictionary<string, object>, ExpandoObject> parse = dict =>
{
    var expando = new ExpandoObject();
    foreach (KeyValuePair<string, object> entry in dict)
    {
        expando.Add(entry.Key, entry.Value);
    }
    return expando;
};

然后只需调用解析函数并将字典作为参数传递进去。 当然,这也可以实现为一个方法而不是一个函数。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接