IT story

DynamicMetaObject.BindInvokeMember의 결과로 void 메서드 호출을 어떻게 표현합니까?

hot-time 2020. 12. 31. 22:57
반응형

DynamicMetaObject.BindInvokeMember의 결과로 void 메서드 호출을 어떻게 표현합니까?


IDynamicMetaObjectProviderDepth in Depth의 두 번째 버전에 대한 간단한 예제를 제공하려고하는데 문제가 발생했습니다.

나는 공허한 부름을 표현할 수 있기를 원하는데 실패하고있다. 리플렉션 바인더를 사용하여 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.Void'와 호환되지 않습니다. 호출 사이트에서 예상되는 Object '입니다.

객체를 반환하고 null을 반환하도록 메서드를 변경하면 제대로 작동하지만 결과가 null이되는 것을 원하지 않고 무효가되기를 원합니다. 리플렉션 바인더에서는 잘 작동하지만 (Main의 첫 번째 호출 참조) 내 동적 개체에서는 실패합니다. 반사 바인더처럼 작동하기를 원합니다. 결과를 사용하지 않는 한 메서드를 호출하는 것이 좋습니다.

타겟으로 사용할 수있는 특정 표현을 놓친 적이 있습니까?


이것은 다음과 유사합니다.

DLR 반환 유형

ReturnType속성에서 지정한 반환 유형과 일치해야 합니다. 모든 표준 바이너리의 경우 거의 모든 객체 또는 삭제 작업의 경우 무효로 고정됩니다. 무효 전화를 걸고 있다는 것을 알고 있다면 다음과 같이 포장하는 것이 좋습니다.

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

The DLR used to be quite lax about what it would allow and it would provide some minimal amount of coercion automatically. We got rid of that because we didn't want to provide a set of convensions which may or may not have made sense for each language.

It sounds like you want to prevent:

dynamic x = obj.SomeMember();

There's no way to do that, there'll always be a value returned that the user can attempt to continue to interact with dynamically.


I don't like this, but it seems to work; the real problem seems to be the binder.ReturnType coming in oddly (and not being dropped ("pop") automatically), but:

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);

Perhaps the callsite expects null to be returned but discards the result - This enum looks interesting, particularly the "ResultDiscarded" flag...

[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
}

Food for thought...

UPDATE:

More hints can be gleaned from Microsoft / CSharp / RuntimeBinder / DynamicMetaObjectProviderDebugView which is used (I presume) as a visualizer for debuggers. The method TryEvalMethodVarArgs examines the delegate and creates a binder with the result discarded flag (???)

 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);

... I'm at the end of my Reflector-foo here, but the framing of this code seems a bit odd since the TryEvalMethodVarArgs method itself expects an object as a return type, and the final line returns the result of the dynamic invoke. I'm probably barking up the wrong [expression] tree.

-Oisin


The C# binder (in Microsoft.CSharp.dll) knows whether or not the result is used; as x0n (+1) says, it keeps track of it in a flag. Unfortunately, the flag is buried inside a CSharpInvokeMemberBinder instance, which is a private type.

It looks like the C# binding mechanism uses ICSharpInvokeOrInvokeMemberBinder.ResultDiscarded (a property on an internal interface) to read it out; CSharpInvokeMemberBinder implements the interface (and property). The job appears to be done in Microsoft.CSharp.RuntimeBinder.BinderHelper.ConvertResult(). That method has code that throws if the aforementioned ResultDiscarded property doesn't return true if the type of the expression is void.

So it doesn't look to me like there's an easy way to tease out the fact that the result of the expression is dropped from the C# binder, in Beta 2 at least.

ReferenceURL : https://stackoverflow.com/questions/1835912/how-do-i-express-a-void-method-call-as-the-result-of-dynamicmetaobject-bindinvok

반응형