【问题标题】:Why can't I do this: dynamic x = new ExpandoObject { Foo = 12, Bar = "twelve" }为什么我不能这样做:动态 x = new ExpandoObject { Foo = 12, Bar = "twelve" }
【发布时间】:2011-11-20 15:20:53
【问题描述】:

是我做错了什么,还是下面的代码真的不行?

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;

【问题讨论】:

标签: c# dynamic expandoobject


【解决方案1】:

是我做错了什么,还是下面的代码真的不行?

这真的是不可能的。赋值运算符左边的东西必须是编译时已知的属性或字段,显然这不是expando对象的情况。

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

您提出问题的方式表明存在逻辑错误。默认情况下未实现功能,然后我们几乎禁止所有功能,因为我们认为它们是个坏主意!默认情况下,功能未实现,必须实现才能工作。

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

无论如何,这是一个好主意,所以感谢分享。既然有人已经想到了,我们就可以开始下一步了,比如确定它是否是我们可以花费有限预算的最佳想法,设计它,编写规范,实现它,测试它,记录它并运送给客户。

我不指望这会很快发生;我们有点忙于上周在 Build 上宣布的整个异步和 WinRT 业务。

【讨论】:

  • n00bs,不记得未来了。 :-) jk,+1
  • 对不起,埃里克,我不是故意要攻击的。我只是想向一个 python 爱好者的同事炫耀 C# 很酷的新动态特性,但后来我因为无法做我认为是 ExpandoObject 的逻辑实例化而停滞不前。无论如何,感谢您的回答,我希望您在使用 async 和 WinRT 解决问题时考虑使用此功能。
  • 我将把它标记为答案,因为它在技术上更适合我提出的确切问题,但我想提一下 Hans 的回答更好地解决了我的基本问题,即,你好吗使用 C# 的动态特性从一个方法一次返回两个命名值?正如 Hans 指出的那样,您可以只使用 dyanmic--ExpandoObject 似乎适用于更复杂的场景(例如在运行时根据 XML 文件的内容构造一个类)。所以,既然我明白了这一点,我不再对ExpandoObject 不支持对象初始化器感到惊讶。
  • “赋值运算符左边的东西必须是编译时已知的属性或字段,显然expando对象不是这种情况。” -我不太清楚你的意思……
  • 请在 Visual Studio UserVoice 中投票支持此功能。
【解决方案2】:

有一个比 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 可访问性。这意味着当您尝试访问另一个程序集中的成员时会出现运行时错误。无赖。

考虑 a tuple,在 C# v7 中有很大改进。

【讨论】:

  • +1。这太棒了,汉斯。谢谢。我没有意识到你可以使用dynamic 作为返回类型,但看起来你可以。因此,对于我的特定场景,我什至不需要 ExpandoObject
  • 只知道你必须小心将匿名类型传递到你的方法之外,它们不能跨越程序集边界。
  • 请注意,匿名类型成员值一旦设置就无法更改,但使用 ExpandoObject 可以。这是 2 之间的一个很大的区别。
【解决方案3】:

Dynamitey(开源 PCL,可在 nuget 中找到)具有可内联的 initializing expandos 语法。

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

【讨论】:

  • 这怎么可能,是 Foo 和 Bar 参数名称吗?
  • 是的,参数名称是通过 dlr 调用发送的。所以我的构建对象代理,将参数名称转换为属性设置器调用。
  • ExpandoObject 是一个动态原型对象,它就像 Javascript 中的一个对象,你可以动态添加属性,它们永远不会被预定义。
  • 这是我的赢家。当我想做的只是View(new { Items = model } )
  • 同意这个答案很酷。本来想要一点语法糖,可能是var x = New.Expando(Foo:12, Bar:"twelve");
【解决方案4】:

对我有用的一种解决方法是 Yan Cui (source) 的 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:请投票here 支持 ExpandoObjects 上的对象初始化器。

【讨论】:

    【解决方案5】:

    当然可以!!! 使用下面的扩展方法,您可以进行如下操作:

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

    【讨论】:

      【解决方案6】:

      这种初始化器语法是可能的,因为已经有带有 get 和 setter 的属性。使用 expando 对象,据我所知还没有这些属性。

      【讨论】:

        【解决方案7】:

        更简单的方法:

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

        然后只需调用 parse 函数并传入字典作为参数。 这当然可以实现为方法而不是函数。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-04-22
          • 1970-01-01
          • 2015-10-17
          • 2020-10-03
          • 2017-05-15
          • 2014-03-24
          • 1970-01-01
          • 2017-06-23
          相关资源
          最近更新 更多