C# 4.0中的'dynamic'无法设置ref/out参数

11

我正在尝试使用DynamicObject。其中一件事是设置ref/out参数的值,如下所示的代码。然而,我无法正确设置Main()ij的值(即使它们在TryInvokeMember()中被正确设置)。有人知道如何使用ref/out参数调用DynamicObject对象并能够检索方法内设置的值吗?

class Program
{
    static void Main(string[] args)
    {
        dynamic proxy = new Proxy(new Target());
        int i = 10;
        int j = 20;
        proxy.Wrap(ref i, ref j);
        Console.WriteLine(i + ":" + j); // Print "10:20" while expect "20:10"
    }
}

class Proxy : DynamicObject
{
    private readonly Target target;

    public Proxy(Target target)
    {
        this.target = target;
    }

    public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
    {
        int i = (int) args[0];
        int j = (int) args[1];
        target.Swap(ref i, ref j);
        args[0] = i;
        args[1] = j;
        result = null;
        return true;
    }
}

class Target
{
    public void Swap(ref int i, ref int j)
    {
        int tmp = i;
        i = j;
        j = tmp;
    }
}

更新于7/15: 微软声称已经在下一个.NET版本中解决了这个问题。http://connect.microsoft.com/VisualStudio/feedback/details/543101/net-4-0s-dynamicobject-doesn-t-set-ref-out-arguments

更新于9/8/2012: 使用VS.NET 2012测试了.NET 4.0和4.5,确认已经修复。


C++中动态调用的参数传递语义如何确定? - Gabe
@gabe:实际上我看到了那个问题,但那个问题是关于是否可以确定参数是否可以被知道为按引用/out传递的,这与我在这里所问的完全不同。在我的情况下,我对此不感兴趣,因为我可以通过对“Target”类进行一点反射来解决。 - Buu
尽管你的问题不同,但那个问题的答案包含了你的问题的答案:DynamicObject是“按值调用” - Gabe
2
@gabe:我仍然不明白为什么会建议将完全不同的问题作为“精确重复”推荐给另一个问题,而后者恰好有一个您认为可以用于两个问题的答案 :). - Buu
我在VS.Net 2010上尝试了使用.Net 4.0,但它无法工作。 - user276648
3个回答

5

看起来这可能是一个错误 - 可能在DynamicObject中。如果您像这样向Proxy添加一个Wrap方法:

public void Wrap(ref int x, ref int y)
{
    target.Swap(ref x, ref y);
}

即使这仍然被称为动态(即Main中的代码保持不变),代码仍然可以工作......因此至少一般的“动态对象如何工作”的层支持按引用传递。
我怀疑如果这确实是DLR中的一个错误,可能为时已晚,无法在.NET 4中修复——但是仍然值得在Connect上报告,以便可以在服务包中修复。或者,如果这是故意设置的限制/限制,则应在MSDN中清楚地记录(就我所见,目前并没有)。

有趣的解决方法,谢谢。这很好知道,但如果我必须实现每个转发方法,显然我不会使用DynamicObject :)。根据您的建议,我已向Connect提交了错误报告。 - Buu
我认为这不可能是一个 bug,因为它本来就没有办法运作。 TryInvokeMember 接受一个对象数组作为参数。如果要将整数放入该数组中,则需要进行装箱操作,而这会复制它们。即使存在动态绑定器从原始参数数组中复制 ref 参数的情况,这种方法也只能半奏效,因为这只是模拟 ref 参数的行为。 - Gabe
1
@gabe:这就是使用反射调用具有 ref 参数的方法的工作原理 - 正如我所说,当提供静态方法时,即使它仍然以动态方式被“调用”,它也可以正常工作。换句话说,它在 DynamicMetaObject 级别上工作。当然,与“真正”的传递引用相比,它会有一些问题(例如,在方法调用本身期间变量将被分离),但对于99%的情况,我确信这并不重要。这感觉比完全忽略它们是 ref 参数更可取,就像现在一样。 - Jon Skeet
Jon: 你怎么使用 ref 参数调用 InvokeMethod?你是说如果我调用 Target.InvokeMember("Swap", 0, null, proxy, new object[] { i, j }),它会按预期工作吗? - Gabe
微软声称已经在下一个.NET版本中修复了这个问题。http://connect.microsoft.com/VisualStudio/feedback/details/543101/net-4-0s-dynamicobject-doesn-t-set-ref-out-arguments - Buu
显示剩余2条评论

5

这不是一个bug。正如在此处已经说过的,DynamicObject在TryInvokeMember中不支持ref和out参数。传递给该方法的所有内容都被视为"按值传递"。简而言之,TryInvokeMember方法会忽略这些关键字,这就是你的方法无法工作的原因。

如果你遵循Jon Skeet的建议,并创建一个从DynamicObject继承的类中的Wrap方法,那么情况会有所不同。流程如下:当DynamicObject有一个方法调用时,C#运行时绑定器首先在该类本身中查找该方法。如果找到了一个方法,它将调用这个方法。此时,"ref"和"out"参数的信息仍然保留。如果找不到这样的方法,它将调用TryInvokeMember方法,并且简单地丢弃有关"ref"和"out"关键字的信息,并开始将每一项都视为"按值传递"。

请记住,DynamicObject必须支持与其他语言的互操作性,这些语言可能没有所有的C#功能。

确实,"ref"和"out"的信息现在已经从文档中消失了。我将在下一次文档更新中添加它。


好的信息,谢谢!然而,虽然DynamicObject不应该为互操作性而担心ref/out,但是TryInvokeMember的调用者(根据您的解释,这是C#绑定器)应该关注并尝试根据在TryInvokeMember中设置的args数组设置out/ref。它不能吗?它不应该吗? - Buu

2
简单来说,DynamicObject不支持引用传递,因此你想要做的事情不是直接可能的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接