手动创建委托与使用 Action/Func 委托

今天我想宣布:

private delegate double ChangeListAction(string param1, int number);

但为什么不用这个呢:

private Func<string, int, double> ChangeListAction;

或者如果 ChangeListAction没有返回值,我可以使用:

private Action<string,int> ChangeListAction;

那么,使用 delegate关键字声明委托的优势在哪里呢?

是不是因为.NET 1.1,.NET 2.0带来了 Action<T>,.NET 3.5带来了 Func<T>

18314 次浏览

The advantage is clarity. By giving the type an explicit name it is more clear to the reader what it does.

It will also help you when you are writing the code. An error like this:

cannot convert from Func<string, int, double> to Func<string, int, int, double>

is less helpful than one which says:

cannot convert from CreateListAction to UpdateListAction

It also means that if you have two different delegates, both of which take the same types of parameters but conceptually do two entirely different things, the compiler can ensure that you can't accidentally use one where you meant the other.

Declaring a delegate explicitly can help with some type checks. The compiler can make sure that the delegate assigned to the variable is intended to be used as ChangeListAction and not some random action that happens to be compatible with the signature.

However the real value of declaring your own delegate is that it gives it semantic meaning. A person reading the code will know what the delegate is doing by its name. Imagine if you had a class with three int fields but instead you declared an array of three int elements. The array can do the same thing but the names of the fields bring semantic information that is useful to the developers.

You should use Func, Predicate and Action delegates when you are designing a general purpose library like LINQ. In this case the delegates do not have a predefined semantics other than the fact that they will execute and action or be used as a predicate.

On a side note there is a similar tradeoff issue with Tuple vs anonymous type vs declaring your own class. You could just stick everything in a Tuple but then the properties are just Item1, Item2 which tells nothing about the use of the type.

Declare the delegate explicitly when you start to get too many parameters in the Func/Action, otherwise you keep having to look back, "What's the 2nd int mean again?"

As some answers mention the win is clarity, you name the type and so it will be easier to understand for a user of your api. I'd say - in most cases - declare delegate types for your public apis but it's quite alright to use Func<?,?> internally.

One huge benefit of declaring the delegate type that is not mentioned in the other answers is that aside from giving the type a name you actually get to name the parameters also, this will massively increase the usability.

The advent of Action and Func family of delegates has rendered custom delegates less used, but the latter still finds uses. Advantages of custom delegates include:

  1. As others have pointed, conveys intent clearly unlike generic Action and Func (Patrik has a very good point about meaningful parameter names).

  2. You can specify ref/out parameters unlike the other two generic delegates. For eg, you can have

    public delegate double ChangeListAction(out string p1, ref int p2);
    

    but not

    Func<out string, ref int, double> ChangeListAction;
    
  3. Also, with custom delegates you need to write ChangeListAction (I mean the definition) only once somewhere in your code base, whereas if you don't define one you will have to litter everywhere Func<string, int, double> all over. Changing the signature will be a hassle in the latter case - a bad case of not being dry.

  4. Can have optional parameters.

    public delegate double ChangeListAction(string p1 = "haha", int p2);
    

    but not

    Func<string, int, double> ChangeListAction = (p1 = "haha", p2) => (double)p2;
    
  5. You can have params keyword for parameters of a method, not so with Action/Func.

    public delegate double ChangeListAction(int p1, params string[] p2);
    

    but not

    Func<int, params string[], double> ChangeListAction;
    
  6. Well, if you're truly out of luck and need parameters more than 16 (for the moment) :)


As to merits of Action and Func:

  1. It's quick and dirty, and I use it all over. It makes code short if the use-case is trivial (custom delegates have gone out of fashion with me).

  2. More importantly, its type compatible across domains. Action and Func are framework defined, and they operates seamlessly as long as the parameter types match. You can't have ChangeSomeAction for ChangeListAction. Linq finds great use of this aspect.

I have found a special use case where you can only use delegate:

public delegate bool WndEnumProc(IntPtr hwnd, IntPtr lParam);
[DllImport("User32.dll")]
public static extern bool EnumWindows(WndEnumProc lpEnumFunc, IntPtr lParam);

Using Func/Action just does not work: 'Namespace.Class.WndEnumProc' is a 'field' but is used like a 'type':

public Func<IntPtr, IntPtr, bool> WndEnumProc;
[DllImport("User32.dll")]
public static extern bool EnumWindows(WndEnumProc lpEnumFunc, IntPtr lParam);

Following code does compile, but throws exception when running because System.Runtime.InteropServices.DllImportAttribute does not support marshaling of generic types:

[DllImport("User32.dll")]
public static extern bool EnumWindows(Func<IntPtr, IntPtr, bool> lpEnumFunc, IntPtr lParam);

I present this example to show to every one that: sometimes delegate is your only choice. And this is a reasonable answer to your question why not use Action<T>/Func<T> ?

As MSDN said, Func<> is itself pre-defined Delegate. For the first time, I confused about this stuff. After the experimental, my understanding was quite clearer. Normally, in C#, we can see

Type as a pointer to Instance.

The same concept is applied to

Delegate as a pointer to Method

The difference between these to things is Delegate do not possess the concept of OOP, for example, Inheritance. To make this things clearer, I did the experimental with

public delegate string CustomDelegate(string a);


// Func<> is a delegate itself, BUILD-IN delegate
//==========
// Short Version Anonymous Function
Func<string, string> fShort = a => "ttt";
//----------
// Long Version Anonymous Function
Func<string, string> fLong = delegate(string a)
{
return "ttt";
};
//----------
MyDelegate customDlg;
Func<string, string> fAssign;
// if we do the thing like this we get the compilation error!!
// because fAssign is not the same KIND as customDlg
//fAssign = customDlg;

Many build-in methods in framework (for example, LINQ), receive the parameter of Func<> delegate. The thing we can do with this method is

Declare the delegate of Func<> type and pass it to the function rather than Define the custom delegate.

For example, from the code above I add more code

string[] strList = { "abc", "abcd", "abcdef" };
strList.Select(fAssign); // is valid
//strList.Select(customDlg); // Compilation Error!!

For better and more elaborate answer look at @nawfal. I will try to be more simplistic.

You are declaring a member of a class so you should stick with delegate. Using delegate is more descriptive and structural.

Action/Func types are made for passing around so you should use them more as parameters and local variables.

And actually both of those are inheriting Delegate class. Action and Func are generic types and simplify creating delegates with different parameter types. And delegate keyword actually creates whole new class inheriting from Delegate in one declaration.