1

Trying to invoke a sample method taking a ref parameter:

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

Utilizing the 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

However, the code fails with RuntimeBinderException that it can't convert int to ref int. How to fix it?

I am trying to mimic the following code:

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

I have to use DLR instead of reflection, because the supplied object (prog) may be dynamic.

IS4
  • 11,945
  • 2
  • 47
  • 86
  • While http://stackoverflow.com/questions/3146317/create-expression-to-invoke-method-with-out-parameter is about `out` rather than `ref`, the same problem applies and the same answer solves, unless I'm missing a further difference. – Jon Hanna Apr 24 '15 at 16:39
  • @Jon Wrong. This uses the DLR, not reflection. Also setting the parameter type to `typeof(int).MakeByRefType()` does not help, if that's what you expected the answer. – IS4 Apr 24 '15 at 16:43
  • @IllidanS4 the other example uses Expression trees in the same way you do. Are you mixing up DLR with runtime creation of expressions? – Bas Apr 24 '15 at 17:00
  • @Bas Duh, I am - don't you see `Expression.Dynamic`? – IS4 Apr 24 '15 at 17:11
  • @IllidanS4 just making sure this is intended – Bas Apr 24 '15 at 17:13
  • There are projects like [Dynamitey](https://github.com/ekonbenefits/dynamitey) that make it a lot easier to work with the DLR. I'm admittedly not sure upfront that it will help you, but it helped me a while ago. – zneak Apr 24 '15 at 17:51
  • Ought to be covered by [this Q+A](http://stackoverflow.com/questions/14940171/how-do-i-call-methods-with-reference-variables-with-expression-trees). – Hans Passant Apr 24 '15 at 17:51

2 Answers2

1

I reproduced your problem and found a few potential issues with the code in question. To explain, I'll start with how I interpreted what you are actually trying to do, which is run the following code:

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

Note that the above code works fine and runs the method.

A few things to note here:

  • The program variable is a constant.
  • The method returns void
  • We should be able to pass in any integer by reference.

Some things in your question that do not align with this are:

  • Your Expression.Dynamic call tells the method to return object. Instead it should return typeof(void).
  • Your Expression.Lambda call specifies the delegate type as an Action<int>. The parameter should be of type ref int. To solve this there should be an Action that has ref parameters instead. delegate void RefAction<T>(ref T arg1)

This has brought me to the following code:

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

However, I get the same exception as you:

An unhandled exception of type 'Microsoft.CSharp.RuntimeBinder.RuntimeBinderException' occurred in System.Core.dll

Additional information: Cannot convert type 'int' to 'ref int'

This leads me to believe there is some framework bug that causes the RuntimeBinder to incorrectly handle ref parameters for dynamic invocation, or that the Expression.Dynamic code incorrectly assigns by value parameters where ref parameters should be used.

Note that I was able to reproduce the issue for out parameters as well, yielding the exact same result.

For you situation however, you already appear to know so much about the types you are going to invoke, and even create a strongly typed delegate to invoke. In that case I would go with the following:

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);
Bas
  • 26,772
  • 8
  • 53
  • 86
  • Thanks for you effort, but I need to use the DLR in case the object was dynamic (ref and out parameters are supported on dynamic objects). – IS4 Apr 24 '15 at 18:32
  • @IllidanS4 That's why I mentioned the likelyhood that you can't do it because of a runtime bug. I hope you solve it but it feels like something that cannot be solved. – Bas Apr 24 '15 at 19:33
0

If I'm not wrong, Dynamic doesn't support ref/out parameters. Refer Alexandra Rusina's answer in the linked thread and here also.

Try the following (which won't compile):

dynamic value = 5;
RefTest(ref value);

Simply dynamic don't support ref/out. So obviously trying the same with expressions also won't work.

Community
  • 1
  • 1
Sriram Sakthivel
  • 72,067
  • 7
  • 111
  • 189
  • How I read the question is that the parameter is strongly typed. It's just dynamic invocation that the OP is asking for... – Bas Apr 24 '15 at 17:47
  • @Bas You may be right. Not sure I'm missing the actual question. I believe this is a XY question though. After reading your answer I believe, ref and dynmaic doesn't work together well. – Sriram Sakthivel Apr 24 '15 at 17:58