【问题标题】:Adding members to a dynamic object at runtime在运行时向动态对象添加成员
【发布时间】:2011-04-25 10:50:29
【问题描述】:

我正在探索 .NET 4.0 中的 DynamicObject 模型。该应用程序是通过某种文本/xml 文件描述对象的应用程序,程序必须在读取该文件时创建一个对象。

使用 DynamicObject,我们可以很容易地添加成员,因为我们先验地知道成员的名称。但是,如果我们甚至不知道要添加的成员的名称怎么办?有没有办法使这种动态化?

例如,假设我需要创建一个包含成员“Property1”、“Property2”的对象,以及另一个包含“PropertyA”和“PropertyB”的对象,如文本/XML 文件所述。如何根据这些信息动态创建对象?

更新 我从这篇文章中得到了一些想法:http://www.codeproject.com/KB/cs/dynamicincsharp.aspx

此实现允许我执行以下操作:

dynamic d = new PFDynamicChannel();
PFCouplings c = ((PFChannel)d).Coupling;
d.NewProperty = "X";

我不想使用字典的原因是利用 TryGetMember 和我可以覆盖的 TrySetMember 方法,在这些方法中我可以引发对程序至关重要的事件。

这样,我可以从基类 (PFChannel) 继承,但也可以动态添加成员。但是,我的问题是直到运行时我才会知道新的属性名称。而且,我实际上不认为动态对象允许我动态添加新属性。如果是这种情况,我该如何利用 ExpandoObject 来赋予我这种能力?

【问题讨论】:

  • 你还是不明白为什么 dynamic 和 ExpandoObject 会进入 .NET。原因与您想要的完全相反。 ExpandoObject 仍然使用 Dictionary,它是字典。编译器仅将所有“属性”调用转换为 Dictionary[NameOfProperty]。这使代码变得更好。但这就是全部,它唯一的代码糖。即使没有任何动态输入,也可以通过字典实现您想要的。
  • 设置字典项需要引发的事件怎么办?
  • @Euphoric 比无法重构的魔术字符串要好得多。

标签: c# .net dynamic .net-4.0


【解决方案1】:

如果您需要这样做,您应该查看ExpandoObject。如果您需要这样做并且仍然使用DynamicObject,您将需要编写代码来记住属性值,基本上......您可能会使用嵌入式ExpandoObject

我不清楚你想在之后对这个对象做什么 - 你确定你需要动态类型吗? Dictionary<string, object> 实际上会更糟吗?这基本上取决于稍后将要消耗该对象的内容。

【讨论】:

  • 嗯,对象实际上需要继承一个基类。基类定义了许多对程序其余部分至关重要的事件和属性。我对 DynamicObject 感兴趣的一个原因是我可以重写 TryGetMember 和 TrySetMember 方法,我需要在其中引发事件。但是,添加一个名字未知的成员的代码会是什么样子?
  • @sbenderli - 然后您可以简单地创建从您的基类派生的新对象,并且该对象可以使用 Dictionary 来保存有关“未知”属性的信息。
  • @sbenderli:您可以将 TrySetMember 等用于动态属性,但也有一个索引器,当名称仅从文件等中获知时允许设置值。您仍然可以使用索引器。
  • 我会试试你的建议。非常感谢!
【解决方案2】:

据此:Adding properties and methods to an ExpandoObject, dynamically!

...当您想要添加动态命名的属性时,您可以使用 expando 对象作为您的值持有者,并将其转换为 IDictionary

例子

dynamic myobject = new ExpandoObject();

IDictionary<string, object> myUnderlyingObject = myobject;

myUnderlyingObject.Add("IsDynamic", true); // Adding dynamically named property

Console.WriteLine(myobject.IsDynamic); // Accessing the property the usual way

这是经过测试的,将在控制台屏幕上打印出“true”。

当然,在你的情况下,你的底层对象必须从另一个类继承,这个例子只是为了给你一个潜在的自定义实现的想法。

也许在您的类实现中包含一个 expando 对象,并将对 tryget 和 tryset 的调用重定向到您的类中的 expando 对象的实例?

更新

如果您的基类派生自 DynamicObject(这意味着您可以覆盖所有 TrySet/Get/Invoke 方法),那么您也可以在内部使用字典。在 try get/set 覆盖中,您可以执行任何您想要的事件触发,并将设置委托给内部字典。

要添加新属性(或删除现有属性),您可以覆盖 TryInvoke。例如,当方法名称是“AddProperty”并且有一个字符串类型的参数时,您将在字典中添加一个带有参数名称的新项目。同样,您可以动态定义“RemoveProperty”等。您甚至不需要 expando 对象。

class MyBaseClass: DynamicObject
{
    // usefull functionality
}

class MyClass: MyBaseClass
{
    Dictionary<string, object> dynamicProperties = new Dictionary<string, object>();

    override bool TryGetMember(...)
    {
       // read the value of the requested property from the dictionary
       // fire any events and return
    }

    override bool TrySetMember(...)
    {
       // set the value of the requested property to the dictionary
       // if the property does not exist,
       // add it to the dictionary (compile time dynamic property naming)
       // fire any events
    }

    override bool TryInvoke(...)
    {
       // check what method is requested to be invoked
       // is it "AddProperty"??
       // if yes, check if the first argument is a string
       // if yes, add a new property to the dictionary
       // with the name given in the first argument (runtime dynamic property naming)
       // if there is also a second argument of type object,
       // set the new property's value to that object.

       // if the method to be invoked is "RemoveProperty"
       // and the first argument is a string,
       // remove from the Dictionary the property
       // with the name given in the first argument.

       // fire any events
    }
}

// USAGE
static class Program
{
    public static void Main()
    {
        dynamic myObject = new MyClass();

        myObject.FirstName = "John"; // compile time naming - TrySetMember
        Console.WriteLine(myObject.FirstName); // TryGetMember

        myObject.AddProperty("Salary");  // runtime naming (try invoke "AddProperty" with argument "Salary")
        myObject.Salary = 35000m;
        Console.WriteLine(myObject.Salary); // TryGetMember

        myObject.AddProperty("DateOfBirth", new DateTime(1980,23,11)); // runtime naming (try invoke "AddProperty" with fisrt argument "DateOfBirth" and second argument the desired value)
        Console.WriteLine(myObject.DateOfBirth); // TryGetMember

        myObject.RemoveProperty("FirstName"); // runtime naming (try invoke "RemoveProperty" with argument "FirstName")

        Console.WriteLine(myObject.FirstName); // Should print out empty string (or throw, depending on the desired bahavior) because the "FirstName" property has been removed from the internal dictionary.

    }
}

当然,正如我所说,这只有在您的基类派生自 DynamicObject 时才有效。

【讨论】:

    【解决方案3】:

    我不确定在这种情况下你想使用动态对象。

    c# 中的动态允许您执行以下操作:

      dynamic something = GetUnknownObject();
      something.aPropertyThatShouldBeThere = true;
    

    如果您使用ExpandoObject,您可以:

      var exp = GetMyExpandoObject();
      exp.APropertyThatDidntExist = "something";
    

    这两种方法都可以让您使用属性名,就好像它在编译时确实存在一样。在您的情况下,您根本不需要使用这种语法,所以我不确定它是否会给您带来任何好处。相反,为什么不直接使用Dictionary&lt;string, object&gt;,并且:

    var props = new Dictionary<string, object>();
       foreach( var someDefinintion in aFile)
       {
          props[ someDefinition.Name] = someDefinition.value;
       }
    

    因为Dictionary&lt;string,object&gt; 基本上就是expando 对象——但它支持不同的语法。是的,我正在简化 - 但如果您没有从其他代码或通过绑定 / 等使用它,那么这基本上是正确的。

    【讨论】:

      【解决方案4】:

      与关于变体“如果您的基类派生自 DynamicObject”的 Thanasis Ioannidis 回答相关,有一种更简单的方法,只需在“MyClass”类中添加方法 AddProperty,如下所示:

          class MyClass: MyBaseClass
          {
              Dictionary<string, object> dynamicProperties = new Dictionary<string, object>();
      
              public void AddProperty(string key, object value){
                  dynamicProperties[key] = value;
              }
      

      然后就可以使用了

      dynamic myObject = new MyClass();
      myObject.AddProperty("DateOfBirth", new DateTime(1980,23,11));
      

      您不需要对“TryInvoke”进行任何覆盖。

      【讨论】:

        【解决方案5】:

        dynamic 类型基本上是作为属性包实现的。这意味着它的键(属性名)和对象字典(这里以非类型安全的方式)。如果你可以直接访问这个字典,我就不用了,但是你可以自己使用字典。

        该死,Jon Skeet 更快:(

        【讨论】:

          猜你喜欢
          • 2011-01-05
          • 1970-01-01
          • 2010-10-11
          • 1970-01-01
          • 2012-04-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-03-24
          相关资源
          最近更新 更多