开发者

Dynamically add properties to a existing object

I create the person object like this.

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

It has properties like this.

person.Dob
person.Address

But now I want to add properties like this and set the values at the run time after creating the object. person.Age person.Sex

How can I add those extra properties after creating the object. Those property name can be changed time to time. Therefor can't hardcode 开发者_StackOverflowthe "Age" and "Sex".


It's not possible with a "normal" object, but you can do it with an ExpandoObject and the dynamic keyword:

dynamic person = new ExpandoObject();
person.FirstName = "Sam";
person.LastName = "Lewis";
person.Age = 42;
person.Foo = "Bar";
...

If you try to assign a property that doesn't exist, it is added to the object. If you try to read a property that doesn't exist, it will raise an exception. So it's roughly the same behavior as a dictionary (and ExpandoObject actually implements IDictionary<string, object>)


Take a look at the ExpandoObject.

For example:

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

Additional reading here.


If you only need the dynamic properties for JSON serialization/deserialization, eg if your API accepts a JSON object with different fields depending on context, then you can use the JsonExtensionData attribute available in Newtonsoft.Json or System.Text.Json.

Example:

public class Pet
{
    public string Name { get; set; }
    public string Type { get; set; }

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

Then you can deserialize 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"}
    }
}


If you can't use the dynamic type with ExpandoObject, then you could use a 'Property Bag' mechanism, where, using a dictionary (or some other key / value collection type) you store string key's that name the properties and values of the required type.

See here for an example implementation.


Consider using the decorator pattern http://en.wikipedia.org/wiki/Decorator_pattern

You can change the decorator at runtime with one that has different properties when an event occurs.


Another implementation, using it to combine parameters when calling ASP helpers:

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


Another alternative to solve this problem (depending on your circumstances) is to leverage Newtonsoft.Json's ExpandoObjectConverter JSON converter class.

Serialize your object to JSON, then deserialize it as an ExpandoObject into a dynamic field using the converter:

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

The new object you end up with will have the properties just as you've added them:

{
  "FirstName": "Sam",
  "LastName": "Lewis",
  "Dob": "1/1/1900",
  "Address": "777 Pearly Gates Dr."
}

Do note, however, that the original person object is unchanged; we didn't actually mutate that object at all.

The normal caveats for JSON serialization apply, so your mileage may vary, but if your fields are properly serializable and your object isn't deeply nested, this should present no problems.


As an aside, you should absolutely expand or create a suitable contract for your object's fields instead of using this in basically all cases. It will be more robust, more maintainable, and will save you from bugs and type-related errors that can come with workarounds.

That said, sometimes things aren't so neat, so having an escape hatch like this can be useful for exceptional situations– just don't use it without due cause.


Take a look at the Clay library:

http://clay.codeplex.com/

It provides something similar to the ExpandoObject but with a bunch of extra features. Here is blog post explaining how to use it:

http://weblogs.asp.net/bleroy/archive/2010/08/18/clay-malleable-c-dynamic-objects-part-2.aspx

(be sure to read the IPerson interface example)


If you have a class with an object property, or if your property actually casts to an object, you can reshape the object by reassigning its properties, as in:

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

With this approach, you basically rewrite the object property adding or removing properties as if you were first creating the object with the

new { ... }

syntax.

In your particular case, you're probably better off creating an actual object to which you assign properties like "dob" and "address" as if it were a person, and at the end of the process, transfer the properties to the actual "Person" object.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜