如何将 void 方法调用表示为 DynamicMetaObject.BindInvokeMember 的结果?

发布于 2024-08-13 09:36:27 字数 2011 浏览 7 评论 0原文

我试图给出一个简短的例子 IDynamicMetaObjectProvider 用于 C# 深入研究第二版,我遇到了问题。

我希望能够表达一个无效的呼叫,但我失败了。我确信这是可能的,因为如果我使用反射绑定器动态调用 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: 结果类型“System.Void” 对象产生的动态绑定 活页夹类型为“DynamicDemo” 'Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder' 与预期的结果类型“System.Object”不兼容 调用站点。

如果我将方法更改为返回对象并返回 null,它可以正常工作...但我不希望结果为 null,我希望它为 void。这对于反射绑定器来说效果很好(请参阅 Main 中的第一个调用),但对于我的动态对象来说却失败了。我希望它像反射绑定器一样工作 - 调用该方法就可以,只要您不尝试使用结果即可。

我是否错过了可以用作目标的特定表达方式?

I'm trying to give a short example of IDynamicMetaObjectProvider for the second edition of C# in Depth, and I'm running into issues.

I want to be able to express a void call, and I'm failing. I'm sure it's possible, because if I dynamically call a void method using the reflection binder, all is fine. Here's a short but complete example:

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

This throws an exception:

Unhandled Exception:
System.InvalidCastException: The
result type 'System.Void' of the
dynamic binding produced by the object
with type 'DynamicDemo' for the binder
'Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder'
is not compatible with the result type 'System.Object' expected by the
call site.

If I change the method to return object and return null, it works fine... but I don't want the result to be null, I want it to be void. That works fine for the reflection binder (see the first call in Main) but it fails for my dynamic object. I want it to work like the reflection binder - it's fine to call the method, so long as you don't try to use the result.

Have I missed a particular kind of expression I can use as the target?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

晨光如昨 2024-08-20 09:36:27

这类似于:

DLR 返回类型

您确实需要匹配 指定的返回类型ReturnType 属性。对于所有标准二进制文件,这几乎被固定为对象或无效(对于删除操作)。如果您知道您正在进行无效调用,我建议将其包含在内:

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

DLR 过去对于允许的内容非常宽松,并且会自动提供一些最少量的强制。我们放弃了它,因为我们不想提供一组对每种语言可能有意义或没有意义的约定。

听起来您想阻止:

dynamic x = obj.SomeMember();

没有办法做到这一点,总是会返回一个用户可以尝试继续动态交互的值。

This is similar to:

DLR return type

You do need to match the return type specified by the ReturnType property. For all of the standard binaries this is fixed to object for almost everything or void (for the deletion operations). If you know you're making a void call I'd suggest wrapping it in:

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.

等数载,海棠开 2024-08-20 09:36:27

我不喜欢这个,但它似乎有效;真正的问题似乎是 binder.ReturnType 奇怪地出现(并且不会自动删除(“pop”)),但是:

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

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);
太阳哥哥 2024-08-20 09:36:27

也许调用点期望返回 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 方法,此代码的框架似乎有点奇怪本身需要一个对象作为返回类型,最后一行返回动态调用的结果。我可能喊错了[表达]树。

-奥辛

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

萌能量女王 2024-08-20 09:36:27

C# 绑定器(在 Microsoft.CSharp.dll 中)知道结果是否被使用;正如 x0n (+1) 所说,它在标志中跟踪它。不幸的是,该标志隐藏在 CSharpInvokeMemberBinder 实例中,该实例是私有类型。

看起来 C# 绑定机制使用 ICSharpInvokeOrInvokeMemberBinder.ResultDiscarded(内部接口上的属性)来读取它; CSharpInvokeMemberBinder 实现接口(和属性)。该工作似乎是在 Microsoft.CSharp.RuntimeBinder.BinderHelper.ConvertResult() 中完成的。如果表达式的类型为 void,则上述 ResultDiscarded 属性不返回 true,该方法包含抛出异常的代码。

因此,在我看来,没有一种简单的方法可以弄清楚表达式的结果已从 C# 绑定器中删除这一事实,至少在 Beta 2 中是这样。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文