如何覆盖 DynamicMetaObject.BindConvert 以接受 Cast to Interface

Aar*_*ack 4 c# proxy dynamic

使用下面的代码,我试图让 BindConverter 的覆盖正确,以允许转换到接口 T

class DynamicProxy<T> : DynamicObject
{
    private T t;
    public DynamicProxy(T t)
    {
        this.t = t;
    }
    public override DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new DynamicProxyMetaObject(parameter, this);
    }
    class DynamicProxyMetaObject : DynamicMetaObject
    {
        public DynamicProxyMetaObject(Expression expression, DynamicObject value)
            : base(expression, BindingRestrictions.Empty, (object)value)
        {                
        }
        public override DynamicMetaObject BindConvert(ConvertBinder binder)
        {
            return base.BindConvert(binder);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

And*_*tan 5

这是 BindConvert 的工作实现:

public override DynamicMetaObject BindConvert(ConvertBinder binder)
{
    BindingRestrictions restrictions 
        = BindingRestrictions.GetTypeRestriction(Expression, LimitType);
    //if the type requested is compatible with the 
    //instance, there's no conversion to be done.
    if (binder.Type.IsAssignableFrom(LimitType))
        return binder.FallbackConvert(
            new DynamicMetaObject(Expression, restrictions, Value));

    if (LimitType.IsGenericType && 
        LimitType.GetGenericTypeDefinition().Equals(typeof(DynamicProxy<>)))
    {
        //get the type parameter for T
        Type proxiedType = LimitType.GetGenericArguments()[0];
        //now check that the proxied type is compatible 
        //with the desired conversion type
        if(binder.ReturnType.IsAssignableFrom(proxiedType))
        {
            //this FieldInfo lookup can be cached by 
            //proxiedType in a static ConcurrentDictionary
            //to cache the reflection for future use
            FieldInfo tField = LimitType.GetField("t", 
                BindingFlags.Instance | BindingFlags.NonPublic);
            //return a field expression that retrieves the 
            //private 't' field of the DynamicProxy
            //note that we also have to convert 'Expression'
            //to the proxy type - which we've already ascertained
            //is the LimitType for this dynamic operation.
            var fieldExpr = Expression.Field(
                Expression.Convert(Expression, LimitType), tField);
            //but because we're allowing bases or interfaces of 'T',
            //it's a good idea to chuck in a 'Convert'
            return new DynamicMetaObject(
                Expression.Convert(fieldExpr, binder.ReturnType),
                restrictions);
        }
    }

    return base.BindConvert(binder);
}
Run Code Online (Sandbox Code Playgroud)

这是一个测试:

[TestMethod]
public void TestConvert()
{
    List<string> myList = new List<string>() { "Hello", "World" };
    //proxy a List<string>
    DynamicProxy<List<string>> proxy1 =
        new DynamicProxy<List<string>>(myList);

    dynamic proxyDynamic = proxy1;
    //dynamic 'cast' to List<string> (the actual 'T')
    //should return same instance, because the conversion
    //simply gets the private 't' field.
    List<string> fromDynamic1 = (List<string>)proxyDynamic;
    Assert.AreSame(myList, fromDynamic1);
    //dynamic 'cast' to a base or interface of T
    //In this case, IEnumerable<string>
    IEnumerable<string> fromDynamic2 = (IEnumerable<string>)proxyDynamic;
    Assert.AreSame(myList, fromDynamic2);
}
Run Code Online (Sandbox Code Playgroud)

并且对您所要求的内容进行测试 - 即T接口在哪里- 也可以正常工作:

[TestMethod]
public void TestConvert2()
{
  List<string> myList = new List<string>() { "Hello", "World" };
  DynamicProxy<IEnumerable<string>> proxy =
    new DynamicProxy<IEnumerable<string>>(myList);

  dynamic proxyDynamic = proxy;
  var fromDynamic = (IEnumerable<string>)proxyDynamic;
  Assert.AreSame(myList, fromDynamic);      
}
Run Code Online (Sandbox Code Playgroud)

正如测试所示,您不仅可以动态转换为T,还可以动态转换为T. 但是,请注意,实现首先会检查目标类型是否是 - 的基础/接口DynamicProxy<T>- 因此动态转换object(尽管我不知道为什么要这样做)实际上会返回代理实例。您可以通过删除if第一行之后的第一条语句来禁用该行为BindConvert

LimitTypeinBindConvert方法的使用至关重要,因为它为您提供了隐藏在动态表达式后面的对象的运行时类型。Expression元对象的属性通常只有一种类型Object- 这对深入对象和调用方法或读取属性/字段没有好处,这是我们需要做的来支持动态转换。

因此, usingLimitType使我们能够查看实际DynamicProxy<T>实例的内部,获取它T并访问实例字段t(它是私有的,但表达式编译器可以处理)。在确认所需的转换目标类型与 的 兼容TDynamicProxy<T>,我们发出一个表达式,读取该字段并返回作为转换结果的对象。

顺便说一句 - 在第二个测试中,我们目前不能对 进行动态转换List<string>,即使我们传入的代理对象是List<string>- 因为这需要对初始类型检查逻辑稍作更改,然后对存储在中的实例进行自省用于检查其实际类型是否与请求的转换类型兼容的字段“t”。我没有以这种方式完成实现,因为我认为您想要这样做的可能性要小得多。