DynamicMetaObject.BindInvokeMember の結果として void メソッド呼び出しを表現するにはどうすればよいですか?
-
11-09-2019 - |
質問
短い例を挙げようとしています IDynamicMetaObjectProvider
C# in Depth の第 2 版に挑戦しましたが、問題が発生しました。
void コールを表現できるようにしたいのですが、失敗しています。リフレクションバインダーを使用して void メソッドを動的に呼び出すと、すべて問題ないため、それは可能だと確信しています。短いですが完全な例を次に示します。
using System;
using System.Dynamic;
using System.Linq.Expressions;
class DynamicDemo : IDynamicMetaObjectProvider
{
public DynamicMetaObject GetMetaObject(Expression expression)
{
return new MetaDemo(expression, this);
}
public void TestMethod(string name)
{
Console.WriteLine(name);
}
}
class MetaDemo : DynamicMetaObject
{
internal MetaDemo(Expression expression, DynamicDemo demo)
: base(expression, BindingRestrictions.Empty, demo)
{
}
public override DynamicMetaObject BindInvokeMember
(InvokeMemberBinder binder, DynamicMetaObject[] args)
{
Expression self = this.Expression;
Expression target = Expression.Call
(Expression.Convert(self, typeof(DynamicDemo)),
typeof(DynamicDemo).GetMethod("TestMethod"),
Expression.Constant(binder.Name));
var restrictions = BindingRestrictions.GetTypeRestriction
(self, typeof(DynamicDemo));
return new DynamicMetaObject(target, restrictions);
}
}
class Test
{
public void Foo()
{
}
static void Main()
{
dynamic x = new Test();
x.Foo(); // Works fine!
x = new DynamicDemo();
x.Foo(); // Throws
}
}
これにより例外がスローされます。
未処理の例外:System.InvalidCastException:バインダー「microsoft.csharp.runtimebinder.csharpinvokememberbinder」のタイプ「dynamicdemo」を持つオブジェクトによって生成された動的バインディングの結果タイプ 'system'は、コールサイトが予想される結果タイプ 'system.object'と互換性がありません。 。
オブジェクトを返し、nullを返すようにメソッドを変更すると、正常に動作します...しかし、結果を null にするのではなく、 void にしたいのです。これはリフレクション バインダーでは正常に機能しますが (Main の最初の呼び出しを参照)、動的オブジェクトでは失敗します。これをリフレクションバインダーのように機能させたいのですが、結果を使用しようとしない限り、メソッドを呼び出しても問題ありません。
ターゲットとして使用できる特定の種類の表現を見逃していませんか?
解決
これは次のようなものです。
で指定された戻り値の型と一致する必要があります。 ReturnType
財産。すべての標準バイナリでは、これは、ほとんどすべての場合は object に、または void (削除操作の場合) に固定されます。void 呼び出しを行っていることがわかっている場合は、次のようにラップすることをお勧めします。
Expression.Block(
call,
Expression.Default(typeof(object))
);
DLR は以前、何を許可するかについて非常に緩く、最小限の強制を自動的に提供していました。私たちは、各言語にとって意味があるかどうかにかかわらず、一連の規則を提供したくなかったため、これを削除しました。
次のことを防止したいようです。
dynamic x = obj.SomeMember();
それを行う方法はありません。常に、ユーザーが動的に対話を続行できる値が返されることになります。
他のヒント
私はこれが好きではありませんが、うまくいくようです。本当の問題はどうやら binder.ReturnType
奇妙に入ってきます(そして自動的にドロップ(「ポップ」)されません)、しかし:
if (target.Type != binder.ReturnType) {
if (target.Type == typeof(void)) {
target = Expression.Block(target, Expression.Default(binder.ReturnType));
} else if (binder.ReturnType == typeof(void)) {
target = Expression.Block(target, Expression.Empty());
} else {
target = Expression.Convert(target, binder.ReturnType);
}
}
return new DynamicMetaObject(target, restrictions);
おそらく、コールサイトは null が返されることを期待していますが、結果を破棄します。この列挙型、特に「ResultDiscarded」フラグは興味深いようです...
[Flags, EditorBrowsable(EditorBrowsableState.Never)]
public enum CSharpBinderFlags
{
BinaryOperationLogical = 8,
CheckedContext = 1,
ConvertArrayIndex = 0x20,
ConvertExplicit = 0x10,
InvokeSimpleName = 2,
InvokeSpecialName = 4,
None = 0,
ResultDiscarded = 0x100,
ResultIndexed = 0x40,
ValueFromCompoundAssignment = 0x80
}
思考の糧...
アップデート:
さらに多くのヒントは、デバッガーのビジュアライザーとして使用される (おそらく) Microsoft / CSharp / RuntimeBinder / DynamicMetaObjectProviderDebugView から収集できます。TryEvalMethodVarArgs メソッドはデリゲートを検査し、結果破棄フラグ (???) を持つバインダーを作成します。
Type delegateType = Expression.GetDelegateType(list.ToArray());
if (string.IsNullOrEmpty(name))
{
binder = new CSharpInvokeBinder(CSharpCallFlags.ResultDiscarded, AccessibilityContext, list2.ToArray());
}
else
{
binder = new CSharpInvokeMemberBinder(CSharpCallFlags.ResultDiscarded, name, AccessibilityContext, types, list2.ToArray());
}
CallSite site = CallSite.Create(delegateType, binder);
...ここで Reflector-foo の終わりに来ていますが、TryEvalMethodVarArgs メソッド自体が戻り値の型としてオブジェクトを想定しており、最後の行では動的呼び出しの結果を返すため、このコードの構成は少し奇妙に思えます。私はおそらく間違った[表現]木を立てているでしょう。
-オイシン
C# バインダー (Microsoft.CSharp.dll 内) は、結果が使用されるかどうかを認識します。x0n (+1) が言うように、フラグで追跡します。残念ながら、旗は建物の中に埋もれています。 CSharpInvokeMemberBinder
インスタンスはプライベート型です。
C#バインディングメカニズムが使用しているようです ICSharpInvokeOrInvokeMemberBinder.ResultDiscarded
(内部インターフェースのプロパティ)それを読み出します。 CSharpInvokeMemberBinder
インターフェイス (およびプロパティ) を実装します。仕事は次で完了するようです Microsoft.CSharp.RuntimeBinder.BinderHelper.ConvertResult()
. 。このメソッドには、前述の場合にスローするコードが含まれています。 ResultDiscarded
式の型が void の場合、プロパティは true を返しません。
したがって、少なくとも Beta 2 では、式の結果が C# バインダーから削除されるという事実を簡単に解明する方法があるとは思えません。