65

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 the "Age" and "Sex".

Carlos Landeras
  • 11,025
  • 11
  • 56
  • 82
Snj
  • 883
  • 3
  • 9
  • 20
  • This should help, i hope http://www.gamedev.net/topic/396996-c-adding-properties-to-objects-at-runtime/ – Zenwalker Jun 13 '11 at 10:59
  • possible duplicate of [Adding properties dynamically to a class](http://stackoverflow.com/questions/6196022/adding-properties-dynamically-to-a-class) – nawfal Jul 20 '14 at 06:17

10 Answers10

88

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>)

Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • 1
    Thanks for the reply. My problem is run time only I get the property names. As your example FirstName, LastName,Age, Foo I must know to implement the properties. I get property names from the table and I need to create properties from that. – Snj Jun 14 '11 at 10:27
  • 4
    @Sanjeewa, in that case your best bet is probably to use a `Dictionary`. `ExpandoObject` won't be very helpful if you don't know the property names before runtime. – Thomas Levesque Jun 14 '11 at 11:24
  • Then can I use icustomtypedescriptor for that – Snj Jun 16 '11 at 09:56
  • Really? cannot it be made by using Reflection? in my case, ExpandoObject is not an option because it is really an object that stores properties as a dictionary structure. This has the problem that when serializing to JSON, result is an array of arrays, which is very different from the JSON representation of a pure object properties.Any idea of how to solve this? without creating JSON string by hand, of course, – jstuardo Dec 22 '17 at 23:11
  • 1
    @jstuardo, no, it's really not possible to add properties to an existing object; C# is a statically typed language, not a dynamic language like JavaScript. But I'm not sure what you mean about JSON serialization. I just tried serializing an `ExpandoObject` to JSON, and it gave the expected result. See https://gist.github.com/thomaslevesque/63a7ae1a8880a440bfe88334b5aa7c35 – Thomas Levesque Dec 23 '17 at 12:04
  • Thomas Levesque a dictionary serialization is different from pure object serialization. I have finally built Json manually using Newtonsoft library... that way I returned a ContentResult to the view. – jstuardo Dec 25 '17 at 20:12
  • 1
    @jstuardo then I don't know what you call "pure object serialization". In JSON objects are basically dictionaries... – Thomas Levesque Dec 26 '17 at 08:27
14

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.

George Duckett
  • 31,770
  • 9
  • 95
  • 162
12

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"}
    }
}
warrickh
  • 1,616
  • 1
  • 16
  • 14
10

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.

Grant Thomas
  • 44,454
  • 10
  • 85
  • 129
4

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;
    }
Nick Kovalsky
  • 5,378
  • 2
  • 23
  • 50
3

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.

zcoop98
  • 2,590
  • 1
  • 18
  • 31
2

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.

0

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)

santi
  • 487
  • 2
  • 4
  • 10
0

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.

Itaca
  • 39
  • 6
0

I don't see a reason why anyone want to add new properties dynamically for class being a DataTemplate, you should always define the class tightly as per data.

There are cased where you may go with ExpandoObject but then the type is resolved in runtime and not one tell what properties it has this will be more error prone.

You may consider property collection as a better design idea where you can add a Dictionary<string,Object> to the class and get/set its value with indexer as:

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

This will allow you to add any new properties as:

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

enter image description here

Vinod Srivastav
  • 3,644
  • 1
  • 27
  • 40