这是 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);
}
这是一个测试:
[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);
}
并且测试您所要求的确切内容 - 即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);
}
正如测试所示,您不仅可以动态转换为T
.,还可以动态转换为T
. 但是,请注意,实现首先会检查目标类型是否是 的基/接口DynamicProxy<T>
- 所以动态转换object
(尽管我不知道为什么要这样做)实际上会返回代理实例。您可以通过删除if
第一行之后的第一条语句来禁用该行为BindConvert
。
LimitType
方法中的使用BindConvert
至关重要,因为它为您提供了隐藏在动态表达式后面的对象的运行时类型。元对象的Expression
属性通常只有一个类型Object
- 这不利于钓鱼到对象和调用方法或读取属性/字段,这是我们需要做的以支持动态转换。
因此,使用LimitType
使我们能够查看实际DynamicProxy<T>
实例内部,既T
可以获取实例字段,也可以访问实例字段t
(它是私有的,但表达式编译器可以处理)。在确认所需的转换目标类型与 的 兼容T
后DynamicProxy<T>
,我们发出一个表达式,该表达式读取该字段并返回对象作为转换结果。
顺便说一句 - 在第二个测试中,我们目前无法动态转换为List<string>
,即使我们传入的代理对象是List<string>
- 因为这需要对初始类型检查逻辑进行轻微更改,然后对存储在中的实例进行自省't' 字段检查它的实际类型是否与请求的转换类型兼容。我没有以这种方式完成实施,因为我认为您不太可能这样做。