Reputation: 3667
Using the code below I'm trying to get the override correct for BindConverter to allow casting to the interface 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);
}
}
}
Upvotes: 4
Views: 953
Reputation: 42353
Here's a working implementation of 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);
}
And here's a test:
[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);
}
And a test for exactly what you've asked for - i.e. where T
is an interface - also works just as well:
[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);
}
As the tests show, you not only get a dynamic cast to T
, but also to any base or interface of T
. However, note that the implementation first does a check to see if the target type is a base/interface of the DynamicProxy<T>
- so a dynamic cast to object
(although why you'd do that I don't know) will actually return the proxy instance. You can disable that behaviour by getting rid of the first if
statement after the first line of BindConvert
.
The use of LimitType
in the BindConvert
method is crucial, as it gives you the runtime type of the object hiding behind the dynamic expression. The Expression
property of the meta object will typically only have a type of Object
- which is no good for fishing into the object and calling methods or reading properties/fields, which is what we need to do to support dynamic casting.
So, using LimitType
enables us to peer inside actual DynamicProxy<T>
instance, getting both it's T
but also access to the instance field t
(it's private, but the expression compiler can cope with that). After confirming that the desired target type of the conversion is compatible with the T
of the DynamicProxy<T>
, we emit an expression that reads that field and returns the object as the result of the conversion.
Incidentally - in the second test we can't currently do a dynamic cast to List<string>
, even though the proxied object we pass in is List<string>
- because that would require a slight change to the initial type-checking logic, and then introspection on the instance stored in the field 't' to check that it's actual type is compatible with the requested conversion type. I haven't done the implementation that way because I think it's much less likely that you'd want to do that.
Upvotes: 5