Search code examples
c#expandoobject

Building Expando Objects out of base classes (so that base Constructor, methods and properties are used) and add other properties and methods to it


Is there a way to build an Expando Object out of a non-expandoObject base class?
In other words, I am trying to do something of the sort:

// Instantiate my Expando object
dynamic myDynamicObject = new ExpandoObject();
// Add some base class to it - SomeBaseClass() can contain anything (Properties and methods)
myDynamicObject =  Add SomeBaseClass(); ???
// Add some other properties that are not in the base class
myDynamicObject.Name = "My Dynamic Object";
// Add some methods
myDynamicObject.SomeMethods = (Func<bool>)(() => {
   return true;
});

EDIT

As suggested by Martheen, we can construct an Expando out of another object as per this stackoverflow thread. Here is what they do:

public static dynamic ToDynamic<T>(this T obj)
{
   IDictionary<string, object> expando = new ExpandoObject();

   foreach (var propertyInfo in typeof(T).GetProperties())
   {
       var currentValue = propertyInfo.GetValue(obj);
       expando.Add(propertyInfo.Name, currentValue);
   }
   return expando as ExpandoObject;
}

However, this example only takes care of the properties, but I also need the methods.

So I have tried to do something similar for the methods, but I don't really know what I am doing here:

public static dynamic ToDynamic<T>(this T obj)
{
   IDictionary<string, object> expando = new ExpandoObject();

   MethodInfo[] info = typeof(T).GetMethods();

   foreach (var methodInfo in info)
   {
      if (methodInfo != null)
      {
          var currentMethod = methodInfo.GetMethodBody();
          expando.Add(methodInfo.Name, currentMethod);
      }
   }
   return expando as ExpandoObject;
}

But then when trying to execute methods I get this error:

An unhandled exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in Unknown Module.

Additional information: Cannot invoke a non-delegate type


Solution

  • Based on this answer, with slight replacement to not rely on the additional class

    public class Expando : DynamicObject
    {
        public object Instance;
        Dictionary<string, dynamic> ExtraProperties = new Dictionary<string, dynamic>();
    
        public Expando(object instance)
        {
            Instance = instance;
        }
    
        public override bool TryGetMember(GetMemberBinder binder, out object result)
        {
            try
            {
                result = Instance.GetType().GetProperty(binder.Name).GetValue(Instance,null);
                return true;
            }
            catch
            {
                if (ExtraProperties.Keys.Contains(binder.Name))
                {
                    result = ExtraProperties[binder.Name];
                    return true;
                }
            }
    
            result = null;
            return false;
        }
    
        public override bool TrySetMember(SetMemberBinder binder, object value)
        {
            try
            {
                Instance.GetType().GetProperty(binder.Name).SetValue(Instance, value,null);
            }
            catch (Exception ex)
            {
                ExtraProperties[binder.Name] = value;
            }
    
            return true;
        }
    
        public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
        {
            try
            {
                result = Instance.GetType().GetMethod(binder.Name,args.Select(a=>a.GetType()).ToArray()).Invoke(Instance, args);
                return true;
            }
            catch
            { }
    
            result = null;
            return false;
        }
    
        public override string ToString()
        {
            return Instance.ToString();
        }
    
    }
    

    Should it crash & burn on the classes you're using, try the original answer (if the additional class link haven't been updated, use https://github.com/RickStrahl/Westwind.Utilities/blob/master/Westwind.Utilities/Utilities/ReflectionUtils.cs

    Note that the return type of the members isn't changed. This means if, for example, you're using Expando on String, your Expando responds to Replace and PadLeft, but the returned result is a String, not your Expando, which may or may not be what you want. You'd have to wrangle it back into your Expando should you still need it. Also, this solution is blind to extension methods that might apply to the original class.