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>
ちなみに、2 番目のテストでは、渡すプロキシ オブジェクトが であっても、現在 への動的キャストを行うことはできません。これは、最初の型チェック ロジックをわずかに変更し、次に格納されているインスタンスをイントロスペクションする必要があるためです。フィールド「t」は、実際の型が要求された変換型と互換性があることを確認します。あなたがそうしたいと思う可能性ははるかに低いと思うので、私はそのように実装していません。List<string>
List<string>