是否可以在动态/ ExpandoObject上创建一个泛型方法?

24 浏览
0 Comments

是否可以在动态/ ExpandoObject上创建一个泛型方法?

我怀疑这是不可能的,但我没有看到明确的否定。\n我目前(正在工作中)的实现如下:\n

public static Main(param args[])
{
    dynamic Repository = GetRepository();
    var query = (Repository.QueryUser() as IQueryable)
                .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();
    return repo;
}

\n为了更好地模拟(常用的)带有泛型方法的Repository接口,我想实现类似以下的内容:\n

public interface IRepository
{
    IQueryable Query();
}
public static Main(param args[])
{
    IRepository Repository = GetRepository();  // 返回一个dynamic
    var query = Repository.Query() 
                 .Where(user => user.Name.ToLower().Contains("jack"));
}
public static dynamic GetRepository()
{
    dynamic repo = new System.Dynamic.ExpandoObject();
    // 问题出在下面的代码行
    repo.Query = new [] {
                          new User() {
                              Name = "Jack Sparrow"
                          }
                      }.AsQueryable();
    return repo;
}

\n是否有可能绕过这个问题(也许使用System.Dynamic命名空间中的某些东西)?

0
0 Comments

问题的原因是,动态对象(dynamic / ExpandoObject)的实现不满足接口的要求,它没有一个可以作为`Query()`或`Query()`调用的泛型`Query`方法。它只有一个非泛型的`Query()`方法,返回`IQueryable`。而且,`Query`似乎不是一个合理的放在接口中的东西,特别是没有类型约束。

解决方法是使用非泛型方法,如`QueryUsers`、`QueryFoos`等,或者使用指定了`IQueryable Query()`方法的`IRepository`接口。下面是使用[Impromptu-Interface](https://github.com/ekonbenefits/impromptu-interface)库实现后一种方法的示例代码:

using ImpromptuInterface;
using ImpromptuInterface.Dynamic;
using System.Linq;
public interface IRepository
{
    IQueryable Query();
}
public class User
{
    public string Name { get; set; }
}
public class Program
{
    public static void Main(params string[] args)
    {
        IRepository repo = GetUserRepository();  // dynamically construct user repository
        var query = repo.Query()
                     .Where(user => user.Name.ToLower().Contains("jack"));
    }
    public static IRepository GetUserRepository()
    {
        var repo = new
        {
            Query = Return>.Arguments(() => new[] {
                new User() {
                    Name = "Jack Sparrow"
                }
            }.AsQueryable())
        }.ActLike>();
        return repo;
    }
}

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

一个非常有趣的建议和方法。

0
0 Comments

有人在stackoverflow上提出了一个问题:“在动态/ExpandoObject上创建一个通用方法是否可能?”问题的原因是动态类型和ExpandoObject在C#中的使用限制。在C#中,ExpandoObject只能获取或设置属性(或事件),而不能定义新方法。要实现这个功能,需要创建自己的动态对象,并为其添加自己的成员。

然而,有人提出使用组合来解决这个问题。创建一个类,并在其中添加自定义的方法,然后将ExpandoObject作为属性添加进来。这样就可以通过调用该类的方法来实现动态行为。

如果非常希望实现这个功能,可以创建一个带有动态元对象包装器的动态对象,并将其封装在ExpandoObject中。在包装器中,将所有绑定转发到自定义的成员上,其他的绑定则转发到ExpandoObject上。这样,该对象既具有自定义成员的功能,又具有ExpandoObject的特性。

以下是一个示例代码:

// 确保按需显式实现IDictionary
// 如果需要,将所有调用转发到ExpandoObject上
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; }
    // 添加的新方法
    public string GetMessage() => "GOT IT!";
    public string GetTypeName() => typeof(T).Name;
    // 确保实现方法以组合结果(如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();
    }
}

使用这个类,就可以像使用任何ExpandoObject一样使用它。

dynamic expando = new ExtendedExpandoObject();
Console.WriteLine(expando.Value);          // 原始的ExpandoObject
expando.Length = 12;                       // 在ExpandoObject上设置一个新属性
Console.WriteLine(expando.Length);         // 获取ExpandoObject上的属性
Console.WriteLine(expando.GetMessage());   // 调用新方法
Console.WriteLine(expando.GetTypeName());  // 调用通用方法
Console.WriteLine(expando.Value.Length);   // 获取原始ExpandoObject上的属性

需要注意的是,还需要添加一个DynamicMetaObjectWrapper类。这个类是DynamicMetaObject的抽象子类,用于将所有的绑定转发到基本的DynamicMetaObject对象上。

虽然某些情况下在ExpandoObject上添加委托属性来模拟方法,但这并不是真正的方法。委托只是对方法的引用,它无法参与方法重载等功能。

最后,还某些情况下在ExpandoObject上添加委托属性的文档链接。但是需要注意,这只是将委托存储在属性上,而不是真正的方法。委托只是对方法的引用,无法与真正的方法相提并论。

尽管ExpandoObject有一些限制,但还是可以通过创建自定义的动态对象来实现类似的功能。这样就可以在动态对象上定义新的方法。

0