Using optional and named parameters with Action and Func delegates
Asked Answered
C

3

12

Why it's not possible to do the following :

Func<int, int, int> sum = delegate(int x, int y = 20) { return x + y; };

Action<string, DateTime> print = 
    delegate(string message, DateTime datetime = DateTime.Now) 
    { 
        Console.WriteLine(message);
    };

sum(x: 20, y: 40);
print(datetime: DateTime.Now, message: "Hello");

case with only named parameters :

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

Console.WriteLine(sum(y: 20, x: 40));
print(datetime: DateTime.Now, message: "Hello");

case with only optional parameters:

Func<int, int, int> sum = delegate(int x, int y = 20) { return x + y; };

Action<string , DateTime> print = 
    delegate(string message, DateTime datetime = DateTime.Now)
    { 
        Console.WriteLine("{0} {1}",message, datetime);
    };

Console.WriteLine(sum(40));
print("Hello");
Contingency answered 3/11, 2013 at 10:11 Comment(4)
Please Explain What you want to achieve.Manhattan
@Sudhakar, the OP wants to define an Action or Func with optional parameters (i.e. parameters with default values).Tecu
possible duplicate of Parameter Action<T1, T2, T3> in which T3 can be optionalAbdominal
@SriramSakthivel Only one part of this q is a duplicate of the linked question. I think it's better we leave this open.Maiga
F
10

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");
Featured answered 3/11, 2013 at 10:43 Comment(3)
I'ma talking about Action and Func delegates.That didn't work with.Contingency
Yeah that what i mentioned that optional parameter can only be used on methods and delegates at compile time. Func and Actions are more of runtime and not at compile time.Featured
Slightly ironic because the definition of Action<T> is Encapsulates a method that has four parameters and does not return a value.Sailplane
M
4

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.

Maiga answered 22/12, 2013 at 0:32 Comment(0)
S
3

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;
    }
}
Sailplane answered 30/1, 2019 at 20:4 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.