28

I've read a lot about how ExpandoObject can be used to dynamically create objects from scratch by adding properties, but I haven't yet found how you do the same thing starting from a non-dynamic C# object that you already have.

For instance, I have this trivial class:

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }
    public string Telephone { get; set; }
}

I would like to convert this to ExpandoObject so that I can add or remove properties based on what it has already, rather than rebuilding the same thing from scratch. Is this possible?

Edit: the questions marked as duplicate are clearly NOT duplicates of this one.

11
  • Maybe you could Create a Dictionary<string, object> containing all properties from the Person-class and convert it to a dynamic object? stackoverflow.com/questions/15819720/… Commented Oct 26, 2017 at 7:47
  • @PanagiotisKanavos dynamic "doesn't magically make your object expandable" stackoverflow.com/a/36558165 Commented Oct 26, 2017 at 7:57
  • This is not practical. You could get somewhere if Person only had public fields, but properties are a no-go. You can't get the getter and setter invoked through ExpandoObject. It is really only a class that was made to work well with the dynamic binder, the part of the C# compiler that helps to translate statements like eo["foo"] = 42 into DLR calls. The kind of statement that is common in a dynamic language like Javascript or Python. Also evident from ExpandoObject having no public members other than the constructor. Commented Oct 26, 2017 at 7:57
  • 2
    @PanagiotisKanavos with all due respect, please don't give me this "too broad" rubbish. My question couldn't be any more clear and can be answered very specifically. If you're confident about what you say, you can post an answer. But there's no reason to be pedantic about questions being asked, as this has become a big problem on Stack Overflow. Commented Oct 26, 2017 at 8:16
  • 1
    Correct, those others are not dublicates. And my searchresult suggested this one first. So welcome to the big downside of SO, with it's unreasonable downvotes. Commented Jan 25, 2018 at 14:15

2 Answers 2

43

It could be done like this:

var person = new Person { Id = 1, Name = "John Doe" };

var expando = new ExpandoObject();
var dictionary = (IDictionary<string, object>)expando;

foreach (var property in person.GetType().GetProperties())
    dictionary.Add(property.Name, property.GetValue(person));
Sign up to request clarification or add additional context in comments.

4 Comments

One thing I want to make clear about this solution, this is not making Person a expando object, it is copying the properties from Person in to a new expando object. If you do expando.Id = 2 the value of person.Id will still be 1
@Scott Chamberlain. Yes. You are right.
My question did ask how to create an ExpandoObject based on the values of an existing object, so I think this is fine.
Explanation: If you look at ExpandoObject, you can see it implements IDictionary<string, object>. If you watch the object in runtime, you can see a Results View, which is the enumeration of that dictionary. So what is happening here is: You create an empty ExpandoObject, then take an up-casted instance of that object to manually manipulate its IDictionary Form. The expando object is being modified directly, even if it seems like you'd only be modifying a dictionary instance. In the end, you have manually added properties to the expando instance.
9

You cannot "convert" a Person class into an expando object. However, you could create a wrapper DynamicObject that contains a Person and forwards all of the fields.

using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Reflection;

namespace SandboxConsole
{
    public class ExpandoWrapper : DynamicObject
    {
        private readonly object _item;
        private readonly Dictionary<string, PropertyInfo> _lookup = new Dictionary<string, PropertyInfo>(StringComparer.InvariantCulture);
        private readonly Dictionary<string, PropertyInfo> _ignoreCaseLookup = new Dictionary<string, PropertyInfo>(StringComparer.InvariantCultureIgnoreCase);

        private readonly Dictionary<string, Box> _lookupExtra = new Dictionary<string, Box>(StringComparer.InvariantCulture);
        private readonly Dictionary<string, Box> _ignoreCaseLookupExtra = new Dictionary<string, Box>(StringComparer.InvariantCultureIgnoreCase);

        private class Box
        {
            public Box(object item)
            {
                Item = item;
            }
            public object Item { get; }
        }

        public ExpandoWrapper(object item)
        {
            _item = item;
            var itemType = item.GetType();
            foreach (var propertyInfo in itemType.GetProperties())
            {
                _lookup.Add(propertyInfo.Name, propertyInfo);
                _ignoreCaseLookup.Add(propertyInfo.Name, propertyInfo);
            }
        }
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            result = null;
            PropertyInfo lookup;
            if (binder.IgnoreCase)
            {
                _ignoreCaseLookup.TryGetValue(binder.Name, out lookup);
            }
            else
            {
                _lookup.TryGetValue(binder.Name, out lookup);
            }

            if (lookup != null)
            {
                result = lookup.GetValue(_item);
                return true;
            }

            Box box;
            if (binder.IgnoreCase)
            {
                _ignoreCaseLookupExtra.TryGetValue(binder.Name, out box);
            }
            else
            {
                _lookupExtra.TryGetValue(binder.Name, out box);
            }

            if (box != null)
            {
                result = box.Item;
                return true;
            }

            return false;
        }

        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            PropertyInfo lookup;
            if (binder.IgnoreCase)
            {
                _ignoreCaseLookup.TryGetValue(binder.Name, out lookup);
            }
            else
            {
                _lookup.TryGetValue(binder.Name, out lookup);
            }

            if (lookup != null)
            {
                lookup.SetValue(_item, value);
                return true;
            }

            var box = new Box(value);
            _ignoreCaseLookupExtra[binder.Name] = box;
            _lookupExtra[binder.Name] = box;

            return true;
        }
    }
}

Example usage:

using System;

namespace SandboxConsole
{
    class Program
    {
        static void Main(string[] args)
        {
            var person = new Person() {Id = 1};
            dynamic wrapper = new ExpandoWrapper(person);

            wrapper.Id = 2;
            wrapper.NewField = "Foo";

            Console.WriteLine(wrapper.Id);
            Console.WriteLine(person.Id);
            Console.WriteLine(wrapper.NewField);
        }
    }
    public class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Address { get; set; }
        public string Telephone { get; set; }
    }
}

4 Comments

I was thinking about something like this :)
JsonConvert.SerializeObject(wrapper) becomes {} with this example.
@Suamere well this is kind of a hack workaround, i am not surprised that the error happens.
@ScottChamberlain how can fix serialze problem?

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.