【问题标题】:Use DLR to invoke a method with ref parameter使用 DLR 调用带有 ref 参数的方法
【发布时间】:2015-04-25 02:36:50
【问题描述】:

尝试调用带有 ref 参数的示例方法:

public void RefTest(ref int i)
{
    Console.WriteLine(i);
    i = 18;
}

利用 DLR:

var prog = new Program();
var binder = Microsoft.CSharp.RuntimeBinder.Binder.InvokeMember(
    CSharpBinderFlags.ResultDiscarded,
    "RefTest", null, typeof(Program),
    new CSharpArgumentInfo[]{
        CSharpArgumentInfo.Create(0,null),
        CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsRef,null)
    }
);
ParameterExpression p = Expression.Parameter(typeof(int));
Expression dyn = Expression.Dynamic(binder, typeof(object), Expression.Constant(prog), p);
var lam = Expression.Lambda<Action<int>>(dyn, p).Compile();
lam(9); //RuntimeBinderException

但是,代码失败并出现 RuntimeBinderException,它无法将 int 转换为 ref int。如何解决?

我正在尝试模仿以下代码:

dynamic prog = new Program();
Action<int> lam = i => prog.RefTest(ref i);
lam(9);

我必须使用 DLR 而不是反射,因为提供的对象 (prog) 可能是动态的。

【问题讨论】:

  • 虽然 stackoverflow.com/questions/3146317/… 大约是 out 而不是 ref,但同样的问题适用并且相同的答案解决了,除非我错过了进一步的区别。
  • @Jon 错了。这使用 DLR,而不是反射。如果这是您所期望的答案,那么将参数类型设置为 typeof(int).MakeByRefType() 也无济于事。
  • @IllidanS4 另一个示例以与您相同的方式使用表达式树。您是否将 DLR 与表达式的运行时创建混为一谈?
  • @Bas Duh,我 - 你没看到Expression.Dynamic吗?
  • @IllidanS4 只是确保这是有意的

标签: c# dynamic


【解决方案1】:

我重现了您的问题,并发现相关代码存在一些潜在问题。为了解释,我将从我如何解释您实际尝试做的事情开始,即运行以下代码:

dynamic program = ...;
program.RefTest(ref myInt);

请注意,上面的代码可以正常运行并运行该方法。

这里有几点需要注意:

  • program 变量是一个常量。
  • 方法返回void
  • 我们应该能够通过引用传入任何整数。

您的问题中与此不一致的一些内容是:

  • 您的Expression.Dynamic 调用告诉该方法返回object。相反,它应该返回 typeof(void)
  • 您的Expression.Lambda 调用将委托类型指定为Action&lt;int&gt;。该参数的类型应为ref int。为了解决这个问题,应该有一个Action,它有ref 参数。 delegate void RefAction&lt;T&gt;(ref T arg1)

这让我想到了以下代码:

Expression programExpr = Expression.Constant(program);
var binder = Binder.InvokeMember(CSharpBinderFlags.ResultDiscarded | CSharpBinderFlags.InvokeSimpleName, "Bla", null, typeof (Program),
    new[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null), 
            CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.IsRef, null) });
var intParam = Expression.Parameter(typeof (int).MakeByRefType(), "x");
Expression methodCall = Expression.Dynamic(binder, typeof (void), programExpr, intParam);
var expr = Expression.Lambda<RefAction<int>>(methodCall, intParam).Compile();
expr(ref myInt);

但是,我遇到了和你一样的异常:

“Microsoft.CSharp.RuntimeBinder.RuntimeBinderException”类型的未处理异常发生在 System.Core.dll

附加信息:无法将类型“int”转换为“ref int”

这使我相信存在一些框架错误,导致 RuntimeBinder 错误地处理动态调用的 ref 参数,或者 Expression.Dynamic 代码错误地按值参数分配应该使用 ref 参数的位置.

请注意,我也能够重现 out 参数的问题,产生完全相同的结果。

但是,对于您的情况,您似乎已经非常了解要调用的类型,甚至创建了一个强类型的委托来调用。在这种情况下,我会选择以下内容:

var programExpr = Expression.Constant(program);
var intParam = Expression.Parameter(typeof(int).MakeByRefType(), "x");
var methodCall = Expression.Call(programExpr, typeof (Program)
    .GetMethod("RefTest", BindingFlags.NonPublic | BindingFlags.Instance), intParam);
var expr = Expression.Lambda<RefAction<int>>(methodCall, intParam).Compile();
expr(ref myInt);

【讨论】:

  • 感谢您的努力,但如果对象是动态的,我需要使用 DLR(动态对象支持 ref 和 out 参数)。
  • @IllidanS4 这就是为什么我提到由于运行时错误而可能无法执行此操作的原因。我希望你能解决它,但感觉就像无法解决的事情。
【解决方案2】:

如果我没记错的话,Dynamic doesn't support ref/out parameters。参考 Alexandra Rusina 在链接线程和here also 中的回答。

尝试以下(不会编译):

dynamic value = 5;
RefTest(ref value);

Simply dynamic 不支持 ref/out。所以很明显,用表达式尝试同样的方法也行不通。

【讨论】:

  • 我如何阅读这个问题是该参数是强类型的。这只是 OP 要求的动态调用......
  • @Bas 你可能是对的。不确定我是否错过了实际问题。我相信这是一个XY问题。阅读您的答案后,我相信 ref 和 dynmaic 不能很好地协同工作。
猜你喜欢
  • 2019-05-07
  • 2015-09-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-06
相关资源
最近更新 更多