58

IDynamicMetaObjectProviderC# in Depth の第 2 版の短い例を挙げようとしていますが、問題が発生しています。

無効な呼び出しを表現できるようにしたいのですが、失敗しています。リフレクション バインダーを使用して 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.Void' は、結果の型 'System.呼び出しサイトが期待するオブジェクトです。

オブジェクトを返し、null を返すようにメソッドを変更すると、正常に動作しますが、結果を null にしたくないので、void にしたいのです。これはリフレクション バインダーでは問題なく機能しますが (Main の最初の呼び出しを参照)、動的オブジェクトでは失敗します。リフレクション バインダーのように動作するようにしたい - 結果を使用しない限り、メソッドを呼び出しても問題ありません。

ターゲットとして使用できる特定の種類の表現を見逃していませんか?

4

4 に答える 4

26

これは次のようになります。

DLR 戻り型

プロパティで指定された戻り値の型と一致する必要がありReturnTypeます。すべての標準バイナリについて、これはほぼすべてのオブジェクトまたは void (削除操作の場合) に固定されています。無効な呼び出しを行っていることがわかっている場合は、次のようにラップすることをお勧めします。

Expression.Block(
    call,
    Expression.Default(typeof(object))
);

以前の DLR は、許可する内容について非常に緩く、最小限の強制を自動的に提供していました。各言語に意味があるかもしれないし、意味がないかもしれない一連の規則を提供したくなかったので、私たちはそれを取り除きました。

防止したいようです:

dynamic x = obj.SomeMember();

それを行う方法はありません。ユーザーが動的に対話を続けることができる値が常に返されます。

于 2009-12-02T23:20:54.180 に答える
11

私はこれが好きではありませんが、うまくいくようです。本当の問題は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);
于 2009-12-02T22:02:42.103 に答える
6

おそらく、コールサイトは 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 メソッド自体が戻り値の型としてオブジェクトを想定しており、最後の行が動的呼び出しの結果を返すため、このコードのフレーミングは少し奇妙に思えます。 . 私はおそらく間違った [expression] ツリーを吠えています。

-オイシン

于 2009-12-02T22:18:45.133 に答える
5

C# バインダー (Microsoft.CSharp.dll 内) は、結果が使用されるかどうかを認識します。x0n (+1) が言うように、フラグで追跡します。CSharpInvokeMemberBinder残念ながら、フラグはプライベート タイプであるインスタンス内に埋め込まれています。

C# バインディング メカニズムはICSharpInvokeOrInvokeMemberBinder.ResultDiscarded(内部インターフェイスのプロパティ) を使用してそれを読み取るようです。CSharpInvokeMemberBinderインターフェイス (およびプロパティ) を実装します。仕事は で行われているようMicrosoft.CSharp.RuntimeBinder.BinderHelper.ConvertResult()です。ResultDiscardedそのメソッドには、式の型が void の場合に前述のプロパティが true を返さない場合にスローするコードがあります。

したがって、少なくとも Beta 2 では、式の結果が C# バインダーから削除されるという事実を簡単に特定する方法があるようには思えません。

于 2009-12-02T23:21:51.620 に答える