动态地向现有对象添加属性

67

我像这样创建了一个人的对象。

 Person person=new Person("Sam","Lewis") 

它具有以下属性。

person.Dob
person.Address

但是现在我想添加类似这样的属性,并在创建对象后在运行时设置值。 person.Age person.Sex

我如何在创建对象后添加这些额外的属性。这些属性名称可能会随时更改。因此无法硬编码“Age”和“Sex”。


希望这可以帮到你:http://www.gamedev.net/topic/396996-c-adding-properties-to-objects-at-runtime/ - Zenwalker
可能是向类动态添加属性的重复问题。 - nawfal
10个回答

90

对于“普通”对象而言这是不可能的,但你可以使用ExpandoObjectdynamic关键字来实现:

dynamic person = new ExpandoObject();
person.FirstName = "Sam";
person.LastName = "Lewis";
person.Age = 42;
person.Foo = "Bar";
...
如果你试图赋值一个不存在的属性,它会被添加到对象中。如果你试图读取一个不存在的属性,它将抛出异常。因此,这与字典的行为大致相同(实际上,ExpandoObject实现了IDictionary<string, object>)。

1
谢谢您的回复。我的问题只出现在运行时,我只能获取属性名称。就像您的示例中的FirstName、LastName、Age和Foo一样,我必须知道如何实现这些属性。我从表格中获取属性名称,然后需要根据这些名称创建属性。 - Snj
4
在这种情况下,你最好使用“Dictionary<string, object>”来解决问题。如果在运行时不知道属性名称,“ExpandoObject”将没有太大的帮助。 - Thomas Levesque
真的吗?不能使用反射来实现吗?在我的情况下,ExpandoObject不是一个选项,因为它实际上是将属性存储为字典结构的对象。这会导致序列化为JSON时,结果是一个数组的数组,这与纯对象属性的JSON表示非常不同。有什么解决方法吗?当然不能手动创建JSON字符串。 - jstuardo
1
@jstuardo,不,无法向现有对象添加属性;C#是一种静态类型语言,而不是像JavaScript这样的动态语言。但我不确定您所说的JSON序列化是什么意思。我刚试过将ExpandoObject序列化为JSON,并得到了预期的结果。请参见https://gist.github.com/thomaslevesque/63a7ae1a8880a440bfe88334b5aa7c35。 - Thomas Levesque
1
@jstuardo 那我不知道你所说的“纯对象序列化”是什么意思。在 JSON 中,对象基本上就是字典... - Thomas Levesque
显示剩余2条评论

14

看一下ExpandoObject

例如:

dynamic person = new ExpandoObject();
person.Name = "Mr bar";
person.Sex = "No Thanks";
person.Age = 123;

更多阅读请点击这里


12
如果您只需要动态属性进行JSON序列化/反序列化,例如如果您的API接受一个JSON对象,其字段取决于上下文,则可以使用Newtonsoft.Json或System.Text.Json中可用的JsonExtensionData属性。
示例:
public class Pet
{
    public string Name { get; set; }
    public string Type { get; set; }

    [JsonExtensionData]
    public IDictionary<string, object> AdditionalData { get; set; }
}

那么你可以反序列化JSON:

public class Program
{
    public static void Main()
    {
        var bingo = JsonConvert.DeserializeObject<Pet>("{\"Name\": \"Bingo\", \"Type\": \"Dog\", \"Legs\": 4 }");
        Console.WriteLine(bingo.AdditionalData["Legs"]);        // 4

        var tweety = JsonConvert.DeserializeObject<Pet>("{\"Name\": \"Tweety Pie\", \"Type\": \"Bird\", \"CanFly\": true }");
        Console.WriteLine(tweety.AdditionalData["CanFly"]);     // True

        tweety.AdditionalData["Color"] = "#ffff00";

        Console.WriteLine(JsonConvert.SerializeObject(tweety)); // {"Name":"Tweety Pie","Type":"Bird","CanFly":true,"Color":"#ffff00"}
    }
}

2
这不是问题的答案,但非常有用的信息。 - brtb

10

如果您不能使用dynamic类型与ExpandoObject,那么可以使用“属性包”机制,在其中使用字典(或其他键/值集合类型)存储字符串key,用于命名属性和所需类型的value

请参见此处 以获取示例实现。


4

另一种实现方式是在调用ASP助手时使用它来组合参数:

    public static object CombineObjects(this object item, object add)
    {
        var ret = new ExpandoObject() as IDictionary<string, Object>;

        var props = item.GetType().GetProperties();
        foreach (var property in props)
        {
            if (property.CanRead)
            {
                ret[property.Name]= property.GetValue(item);
            }
        }

        props = add.GetType().GetProperties();
        foreach (var property in props)
        {
            if (property.CanRead)
            {
                ret[property.Name] = property.GetValue(add);
            }
        }

        return ret;
    }

3

解决这个问题的另一个选择(取决于您的情况)是利用Newtonsoft.Json的ExpandoObjectConverter JSON转换器类

将对象序列化为JSON,然后使用转换器将其反序列化为dynamic字段中的ExpandoObject

Person person = new Person("Sam", "Lewis");
var personJson = JsonConvert.SerializeObject(person);
dynamic personExpando = 
    JsonConvert.DeserializeObject<ExpandoObject>(personJson, new ExpandoObjectConverter());

personExpando.Dob = "1/1/1900";
personExpando.Address = "777 Pearly Gates Dr.";

你最终得到的新对象将具有你添加的属性,就像这样:
{
  "FirstName": "Sam",
  "LastName": "Lewis",
  "Dob": "1/1/1900",
  "Address": "777 Pearly Gates Dr."
}

请注意,原始的person对象未更改;我们实际上没有改变该对象。
普通的JSON序列化警告适用,所以您可能会有所不同,但如果您的字段正确可序列化且对象没有嵌套太深,那么这应该不会出现问题。
另外,在几乎所有情况下,您都应该扩展或创建一个合适的合约来代替使用此方法。这将更加健壮、易于维护,并且可以避免出现与类型相关的错误和漏洞。
尽管如此,有时事情并不是那么简单,因此像这样的后门在特殊情况下可能会很有用——只要有正当理由就可以使用它。

2

考虑使用装饰器模式(Decorator Pattern)

当事件发生时,您可以在运行时更改具有不同属性的装饰器。


0

0

我不明白为什么有人想要在作为 DataTemplateclass 中动态添加新属性,根据数据的需要,你应该始终紧密定义 class

有些情况下,您可以使用 ExpandoObject,但类型是在运行时解析的,没有人知道它具有哪些属性,这样会更容易出错。

您可以考虑将 property collection 视为更好的设计思路,其中您可以向 class 添加一个 Dictionary<string,Object> 并使用 indexerget/set 其值,如下所示:

public class Person
{
    public string FirstName {get;set;}
    public string LastName {get;set;}
    public Dictionary<string,object> Fields; //propertyCollection

    public Person(string firstName,string lastName) :base()
    {
        FirstName = firstName;
        LastName = lastName;
        Fields = new Dictionary<string,object>(); //initialize Fields
    }
    
    public object this[string name] //indexer to access Fields
    {
        get { return Fields[name]; }
        set { Fields[name] = value; }
    }
}

这将允许您添加任何新属性,如下:

var person = new Person("Sam","Lewis");
person["age"] = 10;
person["sex"] = "male";

enter image description here


0
如果你有一个带有对象属性的类,或者如果你的属性实际上可以转换为对象,你可以通过重新分配其属性来改变对象的形状,例如:
  MyClass varClass = new MyClass();
  varClass.propObjectProperty = new { Id = 1, Description = "test" };

  //if you need to treat the class as an object
  var varObjectProperty = ((dynamic)varClass).propObjectProperty;
  ((dynamic)varClass).propObjectProperty = new { Id = varObjectProperty.Id, Description = varObjectProperty.Description, NewDynamicProperty = "new dynamic property description" };

  //if your property is an object, instead
  var varObjectProperty = varClass.propObjectProperty;
  varClass.propObjectProperty = new { Id = ((dynamic)varObjectProperty).Id, Description = ((dynamic)varObjectProperty).Description, NewDynamicProperty = "new dynamic property description" };

使用这种方法,您基本上会重写对象属性,添加或删除属性,就像您首次创建该对象一样。

new { ... }

语法。

针对您的特定情况,最好创建一个实际对象,将“dob”和“address”等属性分配给它,就像它是一个人一样,处理结束时,将属性转移给实际的“Person”对象。


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