58

我正在尝试IDynamicMetaObjectProvider为第二版 C# 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:由具有类型“DynamicDemo”的对象为活页夹“Microsoft.CSharp.RuntimeBinder.CSharpInvokeMemberBinder”生成的动态绑定的结果类型“System.Void”与结果类型“System.Void”不兼容。调用站点预期的对象'。

如果我将方法更改为返回对象并返回 null,它可以正常工作......但我不希望结果为 null,我希望它为 void。这适用于反射绑定器(参见 Main 中的第一个调用),但对于我的动态对象却失败了。我希望它像反射活页夹一样工作——调用方法很好,只要你不尝试使用结果。

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

4

4 回答 4

26

这类似于:

DLR 返回类型

您确实需要匹配ReturnType属性指定的返回类型。对于所有标准二进制文件,这对于几乎所有内容都固定为 object 或 void(对于删除操作)。如果你知道你正在打一个无效的电话,我建议你把它包装起来:

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

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

听起来你想防止:

dynamic x = obj.SomeMember();

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

于 2009-12-02T23:20:54.180 回答
11

我不喜欢这个,但它似乎有效;真正的问题似乎是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);
于 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 方法本身需要一个对象作为返回类型,最后一行返回动态调用的结果. 我可能在吠叫错误的[表达式]树。

-Oisin

于 2009-12-02T22:18:45.133 回答
5

C# binder(在 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 回答