IronPython: Provide (or not) Argument for by-ref Parameter

Python function may return multiple objects as a tuple. The .NET method can only return one object as the result of a call; in order to return more than one objects, by-ref parameters are needed. They are decorated with the parameter modifier (ref, out) in C#.

Dictionary.TryGetValue(TKey key, out TValue value) has an output parameter "value". The API returns true if the dictionary contains an element with the specified key (the element value itself is returned to the parameter "value"), otherwise it returns false. When making calls to such methods in IronPython, we may not pass in argument for the output parameter. Instead, the result of such .NET method call in IronPython will likely be a tuple (unless the .NET method's return type is void and the method has only one by-ref parameter), which contains the value of the output parameter (see the example below).

import System
d = System.Collections.Generic.Dictionary[int, str]()
d[1], d[2] = 'One', 'Two'
 
print d.TryGetValue(1)         # (True, 'One')
print d.TryGetValue(2)[1]      # Two 
print d.TryGetValue(3)         # (False, None)

We may also pass a clr.Reference object for the output parameter. The clr.Reference object has only one member "Value", which will carry the output parameter value after the call. "clr.Reference" object is of the type identical to System.Runtime.CompilerServices.StrongBox<T> in the new .NET Framework 3.5. When encountering this type of argument, IronPython codegen/runtime does something special to update the "Value".

x = clr.Reference[str]()             # like "new StrongBox<string>()" in C#
print d.TryGetValue(1, x), x.Value   # True One
print d.TryGetValue(3, x), x.Value   # False None

For reference parameter, we are required to pass in something. If the argument is not clr.Reference object, such reference argument value will also be part of the returned tuple; otherwise, the by-ref change is tracked inside clr.Reference. The following C# code is my twisted way to find the maximum number; the IronPython snippet shows the behaviors of calling that method not using and using clr.Reference.

public class C {
  public bool M(int current, ref int max) {
    if (current > max) {
      max = current; return true;
    } else return false;
  }
}

import C
o = C()
 
print o.M(10, 20)             # (False, 20)
print o.M(30, 20)             # (True, 30)
 
x = clr.Reference[int](20)
print o.M(10, x), x            # False 20
print o.M(30, x), x            # True 30
 
o.M(1)                        # TypeError: M() takes exactly 2 arguments (1 given)

Note if the .NET method contains more than one by-ref parameters, IronPython expects the user to provide proper clr.Reference objects for either ALL or NONE of them. A mix of clr.Reference object and normal argument/omission (for out parameter) will cause error (as illustrated below).

public int M2(ref int x, ref int y) {...}

o.M2(clr.Reference[int](1), 2) # TypeError