Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to create a Generic Method on an dynamic / ExpandoObject

I suspect this isn't possible, but I haven't seen a definitive no.

My current (working) implementation is as followed..

public static Main(param args[])
{
    dynamic Repository = GetRepository();

    var query = (Repository.QueryUser() as IQueryable<User>)
                .Where(user => user.Name.ToLower().Contains("jack"));
}

public static dynamic GetRepository()
{
    dynamic repo = new System.Dynamic.ExpandoObject();        
    repo.QueryUser = new [] { new User() { Name = "Jack Sparrow"}}.AsQueryable<User>();
    return repo;
}

To better mock the (commonly used) Repository interface with Generic methods, I would like to implement something like the following:

public interface IRepository
{
    IQueryable<T> Query<T>();
}

public static Main(param args[])
{
    IRepository Repository = GetRepository();  // return a dynamic

    var query = Repository.Query<User>() 
                 .Where(user => user.Name.ToLower().Contains("jack"));
}


public static dynamic GetRepository()
{
    dynamic repo = new System.Dynamic.ExpandoObject();

    // the issue is on the following line
    repo.Query<User> = new [] {
                          new User() {
                              Name = "Jack Sparrow"
                          }
                      }.AsQueryable<User>();
    return repo;
}

Is there a possible workaround for this (using something in the System.Dynamic namespace perhaps)?

like image 592
Brett Caswell Avatar asked Jul 12 '15 23:07

Brett Caswell


Video Answer


2 Answers

I doubt it, you can only get or set "properties" (or events) of an ExpandoObject, not define new methods. If you wanted to do so, you'll have to create your own dynamic object for which you add your own members.

But I feel I have to say this, why not just use composition instead? Create a class to which you add your methods, and have a property for the expando.

class MyClass
{
    public dynamic Expando { get; } = new ExpandoObject();
    public void MyMethod<T>() { }
}

If you absolutely wanted to do this, you could create a dynamic object with a dynamic meta object wrapper over an ExpandoObject. Then in the wrapper, forward all bindings to your members to your dynamic object, and all others to the expando. The object would be what you define it, with the functionality of an expando.

e.g.,

// be sure to explicitly implement IDictionary<string, object>
// if needed forwarding all calls to the expando
class ExtendedExpandoObject : IDynamicMetaObjectProvider
{
    DynamicMetaObject IDynamicMetaObjectProvider.GetMetaObject(Expression parameter) => new MyMetaObject(parameter, this);

    public ExtendedExpandoObject(ExpandoObject expandoObject = null)
    {
        Value = expandoObject ?? new ExpandoObject();
    }
    public ExpandoObject Value { get; }

    // the new methods
    public string GetMessage() => "GOT IT!";
    public string GetTypeName<T>() => typeof(T).Name;

    // be sure to implement methods to combine results (e.g., GetDynamicMemberNames())
    class MyMetaObject : DynamicMetaObjectWrapper
    {
        public MyMetaObject(Expression parameter, ExtendedExpandoObject value)
                : base(new DynamicMetaObject(parameter, BindingRestrictions.Empty, value))
        {
            var valueParameter = Expression.Property(
                Expression.Convert(parameter, typeof(ExtendedExpandoObject)),
                "Value"
            );
            IDynamicMetaObjectProvider provider = value.Value;
            ValueMetaObject = provider.GetMetaObject(valueParameter);
        }
        protected DynamicMetaObject ValueMetaObject { get; }

        public override DynamicMetaObject BindGetMember(GetMemberBinder binder)
        {
            if (IsMember(binder.Name))
                return base.BindGetMember(binder);
            return ValueMetaObject.BindGetMember(binder);
        }

        public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value)
        {
            if (IsMember(binder.Name))
                return base.BindSetMember(binder, value);
            return ValueMetaObject.BindSetMember(binder, value);
        }

        private bool IsMember(string name) => typeof(ExtendedExpandoObject).GetMember(name).Any();
    }
}

With this, you could use it like you would with any expando object.

dynamic expando = new ExtendedExpandoObject();
Console.WriteLine(expando.Value);          // the original expando
expando.Length = 12;                       // set a new property on the expando
Console.WriteLine(expando.Length);         // get a property on the expando
Console.WriteLine(expando.GetMessage());   // call the new method
Console.WriteLine(expando.GetTypeName<ExtendedExpandoObject>());  // call the generic method
Console.WriteLine(expando.Value.Length);   // get the property on the original expando

You'll just need the DynamicMetaObjectWrapper:

public abstract class DynamicMetaObjectWrapper : DynamicMetaObject
{
    protected DynamicMetaObjectWrapper(DynamicMetaObject metaObject)
            : base(metaObject.Expression, metaObject.Restrictions, metaObject.Value)
    {
        BaseMetaObject = metaObject;
    }
    public DynamicMetaObject BaseMetaObject { get; }

    public override DynamicMetaObject BindBinaryOperation(BinaryOperationBinder binder, DynamicMetaObject arg) => BaseMetaObject.BindBinaryOperation(binder, arg);
    public override DynamicMetaObject BindConvert(ConvertBinder binder) => BaseMetaObject.BindConvert(binder);
    public override DynamicMetaObject BindCreateInstance(CreateInstanceBinder binder, DynamicMetaObject[] args) => BaseMetaObject.BindCreateInstance(binder, args);
    public override DynamicMetaObject BindDeleteIndex(DeleteIndexBinder binder, DynamicMetaObject[] indexes) => BaseMetaObject.BindDeleteIndex(binder, indexes);
    public override DynamicMetaObject BindDeleteMember(DeleteMemberBinder binder) => BaseMetaObject.BindDeleteMember(binder);
    public override DynamicMetaObject BindGetIndex(GetIndexBinder binder, DynamicMetaObject[] indexes) => BaseMetaObject.BindGetIndex(binder, indexes);
    public override DynamicMetaObject BindGetMember(GetMemberBinder binder) => BaseMetaObject.BindGetMember(binder);
    public override DynamicMetaObject BindInvoke(InvokeBinder binder, DynamicMetaObject[] args) => BaseMetaObject.BindInvoke(binder, args);
    public override DynamicMetaObject BindInvokeMember(InvokeMemberBinder binder, DynamicMetaObject[] args) => BaseMetaObject.BindInvokeMember(binder, args);
    public override DynamicMetaObject BindSetIndex(SetIndexBinder binder, DynamicMetaObject[] indexes, DynamicMetaObject value) => BaseMetaObject.BindSetIndex(binder, indexes, value);
    public override DynamicMetaObject BindSetMember(SetMemberBinder binder, DynamicMetaObject value) => BaseMetaObject.BindSetMember(binder, value);
    public override DynamicMetaObject BindUnaryOperation(UnaryOperationBinder binder) => BaseMetaObject.BindUnaryOperation(binder);
    public override IEnumerable<string> GetDynamicMemberNames() => BaseMetaObject.GetDynamicMemberNames();
}
like image 57
Jeff Mercado Avatar answered Oct 03 '22 08:10

Jeff Mercado


Your hypothetical expando object implementation doesn't "satisfy" the interface; it doesn't have a generic Query<T> method that can be called as Query<Foo>() or Query<Bar>(). It only has a non generic Query() method that will return IQueryable<User>. That said, it doesn't seem like Query<T> is a reasonable thing to put in your interface anyway, especially without type constraints.

I would suggest using either using non generic methods like QueryUsers, QueryFoos etc., or having an IRepository<T> interface that specifies a IQueryable<T> Query() method. Here is how you could use the Impromptu-Interface library to implement the latter approach:

using ImpromptuInterface;
using ImpromptuInterface.Dynamic;
using System.Linq;

public interface IRepository<T>
{
    IQueryable<T> Query();
}

public class User
{
    public string Name { get; set; }
}

public class Program
{
    public static void Main(params string[] args)
    {
        IRepository<User> repo = GetUserRepository();  // dynamically construct user repository

        var query = repo.Query()
                     .Where(user => user.Name.ToLower().Contains("jack"));
    }

    public static IRepository<User> GetUserRepository()
    {
        var repo = new
        {
            Query = Return<IQueryable<User>>.Arguments(() => new[] {
                new User() {
                    Name = "Jack Sparrow"
                }
            }.AsQueryable())
        }.ActLike<IRepository<User>>();

        return repo;
    }
}

Using this approach, your concrete repository would simply implement an IRepository<T> interface for each T that is actually a model type.

like image 36
Asad Saeeduddin Avatar answered Oct 03 '22 08:10

Asad Saeeduddin