0

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

stackMeUp
  • 522
  • 4
  • 16
  • 1
    Does this answer your question? [Convert class to dynamic and add properties](https://stackoverflow.com/questions/42836936/convert-class-to-dynamic-and-add-properties) – Martheen Mar 20 '20 at 09:59
  • @Martheen, great pointer for Properties, but I also need all the methods. – stackMeUp Mar 20 '20 at 10:38

1 Answers1

1

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.

Martheen
  • 5,198
  • 4
  • 32
  • 55
  • Thanks @Martheen. I came up with something very similar on Friday, but I used "TryInvoke" instead of "TryInvokeMember". Don't really understand the difference though. Would you mind explaining? – stackMeUp Mar 23 '20 at 09:58
  • 1
    The example MSDN article for them should be enough https://learn.microsoft.com/en-us/dotnet/api/system.dynamic.dynamicobject.tryinvoke?view=netframework-4.8 https://learn.microsoft.com/en-us/dotnet/api/system.dynamic.dynamicobject.tryinvokemember?view=netframework-4.8 Essentially if you're calling, say, expand.Do(blala), you'll need TryInvokeMember, while for expand(blabla) you'll need TryInvoke – Martheen Mar 23 '20 at 10:02
  • Great, worked beautifully! The genius was really to pass in the object (referring to the base class/module) to the constructor (as you have done with "instance"). I had made all my base classes inherit from DynamicObject on Friday, which was poor as this meant that all my implementation had to be dynamic. However I really wanted my base classes to remain static (as dynamic objects are slower)! Thanks again that did the trick :-) – stackMeUp Mar 24 '20 at 11:36