Using optional and named parameters with Action and Func delegates

As mentioned here -

Optional parameters are an attribute of a method or delegate parameter. When you call a signature (method or delegate) that has a known optional parameter at compile-time, the compiler will insert the optional parameter value at the callsite.

The runtime is not aware of optional parameters, so you can't make a delegate that inserts an optional parameter when it's called.

So, to use that you have to extract out concrete implementation(custom delegate) which is known at compile time and will replace the parameters at call site with optional parameters and named parameters can be used as well.

Declare custom delegate -

public delegate int FuncDelegate(int x, int y = 20);

Now you can use it in method body -

FuncDelegate sum = delegate(int x, int y) { return x + y; };
int result = sum (x : 20, y: 40 );
result = sum(20);

Also, only compile time constant can be used in default parameters list. But DateTime.Now is not a compile time constant so that cannot be used as well for specifying optional value to your parameter.

So for Action part this will work -

public delegate void ActionDelegate(string message,
                                    DateTime dateTime = default(DateTime));

Use delegate now here -

ActionDelegate print =
                delegate(string message, DateTime dateTime)
                { Console.WriteLine(dateTime.ToString()); };
print(dateTime: DateTime.Now, message: "SomeThing");

C# 7 now allows for 'local functions'. So instead of creating an Action<T> or Func<T> you can write a 'normal' method. This means that normal rules about default parameters apply.

So you can scope some piece of logic to be internal to a function without fighting with the delegate syntax.

It also works like a closure so you have access to local variables from the 'parent' method.

I added a pointless throwAnException optional parameter below to Microsoft's example.

public static IEnumerable<char> AlphabetSubset3(char start, char end)
{
    if (start < 'a' || start > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(start), message: "start must be a letter");
    if (end < 'a' || end > 'z')
        throw new ArgumentOutOfRangeException(paramName: nameof(end), message: "end must be a letter");

    if (end <= start)
        throw new ArgumentException($"{nameof(end)} must be greater than {nameof(start)}");

    return alphabetSubsetImplementation();

    IEnumerable<char> alphabetSubsetImplementation(bool throwAnException = false)
    {
        if (throwAnException) { throw Exception("You asked me to do this"); }

        for (var c = start; c < end; c++)
            yield return c;
    }
}

You have an answer for the optional parameter part. Regarding the named parameter, its entirely possible to provide names for arguments, but just that x and y are not the parameter names for Action/Func generic delegates. If you have a delegate declared like this:

delegate void D(int p);

//now
D x = a => { };

x(a: 1); //is illegal, since 'a' is not the name of the parameter but 'p'; so 
x(p: 1) //is legal

a really cant be that parameter name because a is just a part of the signature of the current method your delegate references to (ie the anonymous method). It is not really part of the signature of the original delegate. Think about this scenario:

D x = a => { };

//and somewhere else
x = b => { };

//and yet again
x = SomeMethod;

// now should it be x(a: 1) or x(b: 1) or x(parameterNameOfMethodSomeMethod: 1)?

Only p makes sense there.

In the case of Action/Func they are declared like:

public delegate void Action<in T1, in T2>(T1 arg1, T2 arg2);
public delegate TResult Func<in T1, in T2, out TResult>(T1 arg1, T2 arg2);

So you see the parameter naming right? So in this case:

Func<int, int, int> sum = delegate(int x, int y) { return x + y; };
Action<string, DateTime> print =
    delegate(string message, DateTime datetime) { Console.WriteLine("{0} {1}", message, datetime); };

//and you should be calling them like:

Console.WriteLine(sum(arg1: 20, arg2: 40));
print(arg2: DateTime.Now, arg1: "Hello"); //note the order change here

Of course its meaningless in this scenario since you're not using any optional parameter.

Tags:

C#

Delegates