Passing keyword args to C# methods from IronPython

In Python one can call functions with either the argument itself or a named argument i.e f(3) or f(x=3). Python also provides syntax to absorb excess arguments for either style of argument passing with either * or **. So for example for this function f:

 def f(x, *args, **kwargs):
    pass
    
f(3,4,5,y=6,z=7)

The value passed to x is 3 and args then absorbs the extra positional arguments and is passed as a tuple (4,5). kwargs then takes up the keyword arguments and is passed as a dictionary {'y':6, 'z':7}.

Variable number of arguments in the CLS

Now if 'f' were a C# method would this still work? Passing the positional and named parameters from IronPython definitely works. The question is can C# accept variable number of parameters? The CLS clearly states that the only calling convention supported is the standard managed calling convention and variable length argument lists are not allowed. So how does C# support the params keyword then? The C# compiler applies an attribute (System.ParamArray) and passes the extra positional parameters as an array. Great, so can we then do the same thing for dictionary params as well?

ParamDictionaryAttribute

There is a ParamDictionaryAttribute in Microsoft.Scripting.dll that serves the purpose of the System.ParamArray for keyword arguments. But since the C# compiler doesn't know about it there is no syntactic sugar here and it needs to be manually applied. We can, therefore, define a function like this:

 using System;
using Microsoft.Scripting;

public class foo {
    public void bar(int x, [ParamDictionary] IAttributesCollection kwargs) {
        foreach (SymbolId key in kwargs.SymbolAttributes.Keys) {
            Console.WriteLine("key: {0}, value: {1} ", key, kwargs[key]);
        }
    }
}

The function bar now accepts a dictionary of excess keyword parameters when called from IronPython. IAttributesCollection is another interface defined in Microsoft.Scripting which represents a dictionary that can be accessed using SymbolIds and also arbitrary objects. The SymbolAttributes property returns just the SymbolIds. This can now be called from IronPython like so:

 import clr
clr.AddReference("test.dll")
import foo
foo().bar(2,y=4, z=6)

And you'll see y:4 and z:6 were indeed passed into the method as items in the dictionary.