是否可以在动态/ ExpandoObject上创建通用方法

时间:2015-07-12 23:16:21

标签: c# generics dynamic

我怀疑这是不可能的,但我还没有看到明确的否定。

我目前的(工作)实施如下:

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

为了更好地使用Generic方法模拟(常用的)Repository接口,我想实现如下内容:

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

是否有可能的解决方法(使用System.Dynamic命名空间中的某些内容)?

2 个答案:

答案 0 :(得分:4)

我对此表示怀疑,您只能获取或设置ExpandoObject的“属性”(或events),而不是定义新方法。如果您想这样做,则必须创建自己的动态对象,并为其添加自己的成员。

但是我觉得我必须这样说,为什么不只是使用构图呢?创建一个添加方法的类,并为expando创建一个属性。

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

如果您绝对想要这样做,可以在ExpandoObject上创建一个动态元对象包装器的动态对象。然后在包装器中,将所有绑定转发给您的动态对象,将所有其他绑定转发给expando。该对象将是您使用expando的功能定义的对象。

如,

// 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();
    }
}

有了这个,你可以像使用任何expando对象一样使用它。

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

你只需要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();
}

答案 1 :(得分:4)

您假设的expando对象实现不“满足”接口;它没有可以称为Query<T>Query<Foo>()的通用Query<Bar>()方法。它只有一个非通用的Query()方法,它将返回IQueryable<User>。也就是说,无论如何,Query<T>似乎都不合理,尤其是没有类型约束。

我建议使用非通用方法,如QueryUsersQueryFoos等,或者使用IRepository<T>接口指定IQueryable<T> Query()方法。以下是如何使用Impromptu-Interface库实现后一种方法:

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

使用这种方法,您的具体存储库只会为每个实际上是模型类型的IRepository<T>实现T接口。