views:

295

answers:

3

I'm experimenting with DynamicObject. One of the things I try to do is setting the values of ref/out arguments, as shown in the code below. However, I am not able to have the values of i and j in Main() set properly (even though they are set correctly in TryInvokeMember()). Does anyone know how to call a DynamicObject object with ref/out arguments and be able to retrieve the values set inside the method?

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

Update 7/15: Microsoft claims to have fixed the issue for the next release of .NET http://connect.microsoft.com/VisualStudio/feedback/details/543101/net-4-0s-dynamicobject-doesn-t-set-ref-out-arguments

+2  A: 

To make a long story short, DynamicObject doesn't support pass-by-reference, so what you want to do is not directly possible.

Gabe
+2  A: 

This looks like it could be a bug - probably in DynamicObject. If you add a Wrap method to Proxy like this:

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

Then even though this is still called dynamically (i.e. the code in Main stays the same) the code works... so at least the general "how does a dynamic object work" layer supports pass-by-reference.

I suspect if this is indeed a bug in the DLR, it may be too late to fix for .NET 4 - but it's worth reporting on Connect anyway so it can be fixed in a service pack. Alternatively, if this is a deliberate restriction/limitation, it should be clearly documented in MSDN (which it isn't at the moment, as far as I can see).

Jon Skeet
Interesting workaround, thanks. That's good to know but obviously I wouldn't resort to DynamicObject if I have to implement each forwarding method :). I've submitted a bug report to Connect as per your suggestion.
Buu Nguyen
I don't see how it could be a bug because there's no way it could work. `TryInvokeMember` takes an array of objects for the arguments. In order to put the ints into the array they have to be boxed, which copies them. The only way this could even half-work is if the dynamic binder copied ref args back from the original args array once the call completes, but that's just simulating ref args.
Gabe
@gabe: That's how it works when you call a method with ref parameters using reflection - and as I've said, it works when the method is provided statically even though it's still being *called* dynamically. In other words, it works at the DynamicMetaObject level. Sure, it would have some kinks compared with "true" pass by reference (e.g. the variables would be disassociated during the method call itself) but for 99% of cases I'm sure it wouldn't matter. That feels like it would be preferable to just ignoring the fact that they're ref parameters completely, as it is now.
Jon Skeet
Jon: How can you call InvokeMethod with a ref parameter? Are you saying that it will work as expected if I call `Target.InvokeMember("Swap", 0, null, proxy, new object[] { i, j })`?
Gabe
No, not like that: `object[] args = new object[] { i, j }; Target.InvokeMember("Swap", 0, null, proxy, args); i = (int) args[0]; j = (int) args[1];` In other words, the array ends up with the right values in afterwards, with reflection - it's not *exactly* identical behaviour, but pretty close.
Jon Skeet
Then this would work: `object[] args = new object[] { i, j }; proxy.Wrap(args); i = (int) args[0]; j = (int) args[1];` but that's what the poster is trying to *avoid*.
Gabe
Microsoft claims to have fixed the issue for the next release of .NET http://connect.microsoft.com/VisualStudio/feedback/details/543101/net-4-0s-dynamicobject-doesn-t-set-ref-out-arguments
Buu Nguyen
+2  A: 

This is not a bug. As it was already said here, DynamicObject doesn't support ref and out parameters in TryInvokeMember. Everything passed to this method is treated "by value". Shortly, TryInvokeMember method simply ignores these keywords, and that is why your method doesn't work.

If you follow Jon Skeet suggestion and create your own Wrap method within a class inherited from DynamicObject, this will be a little bit different scenario. The workflow looks like this: when there is a method call for DynamicObject, C# runtime binder first looks for the method in the class itself. If it can find one, it calls this method. At this point, information about "ref" and "out" parameters is still preserved. If it can't find such a method, it calls TryInvokeMember method and simply throws out information about "ref" and "out" keywords and starts treating everyting as "by value". Remember that DynamicObject has to suport interoperability with other language, which might not have all of the C# features.

True, information about "ref" and "out" is now missing from documentation. I will add it to the next documenation update.

Alexandra Rusina
Good info, thanks! However, while DynamicObject shouldn't bother about ref/out for interoperability, the caller of TryInvokeMember (which is the C# binder as I understand from your explanation), should bother and try to set the out/ref based on the args array set inside TryInvokeMember. Couldn't it? Shouldn't it?
Buu Nguyen