web-dev-qa-db-ja.com

手動でのデリゲートの作成と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>

69
Elisabeth

利点は明快さです。型に明示的な名前を付けることにより、それが何をするかを読者にわかりやすくします。

また、コードを記述するときにも役立ちます。このようなエラー:

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

言うよりも役に立たない:

cannot convert from CreateListAction to UpdateListAction

また、2つの異なるデリゲートがあり、どちらも同じタイプのパラメーターを取るが、概念的には2つのまったく異なることを行う場合、コンパイラーは、意図した場所で誤って一方を使用しないようにすることができます。

52
Mark Byers

デリゲートのActionおよびFuncファミリーの登場により、カスタムデリゲートの使用が減りましたが、後者はまだ用途があります。カスタムデリゲートの利点は次のとおりです。

  1. 他の人が指摘したように、ジェネリックActionFuncとは異なり、意図を明確に伝えます( Patrik は、意味のあるパラメーター名に関して非常に優れています)。

  2. 他の2つの汎用デリゲートとは異なり、ref/outパラメーターを指定できます。たとえば、

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

    だがしかし

    Func<out string, ref int, double> ChangeListAction;
    
  3. また、カスタムデリゲートでは、コードベースのどこかでChangeListAction(つまり、定義)を1回だけ記述する必要があります。一方、定義しない場合は、どこでもFunc<string, int, double>を散らかす必要があります。後者の場合、署名を変更するのは面倒です-乾燥していないという悪いケース。

  4. オプションのパラメーターを持つことができます。

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

    だがしかし

    Func<string, int, double> ChangeListAction = (p1 = "haha", p2) => (double)p2; 
    
  5. メソッドのパラメーターにはparamsキーワードを使用できますが、Action/Funcでは使用できません。

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

    だがしかし

    Func<int, params string[], double> ChangeListAction;
    
  6. まあ、あなたが本当に運が悪くて、(今のところ)16を超えるパラメータが必要な場合:)


ActionFuncのメリットについて:

  1. 早くて汚いので、ずっと使っています。ユースケースが取るに足らない場合は、コードが短くなります(カスタムデリゲートが時代遅れになったため)。

  2. さらに重要なことに、そのタイプはドメイン間で互換性があります。 ActionFuncはフレームワークで定義されており、パラメーターの型が一致する限り、シームレスに動作します。 ChangeSomeActionChangeListActionを使用することはできません。 Linqは、この側面の優れた使用法を見つけます。

69
nawfal

デリゲートを明示的に宣言すると、いくつかの型チェックに役立ちます。コンパイラは、変数に割り当てられたデリゲートがChangeListActionとして使用されることを意図しており、シグネチャと互換性がある偶然のアクションではないことを確認できます。

ただし、独自のデリゲートを宣言する本当の価値は、セマンティックな意味を与えることです。コードを読む人は、デリゲートが何をしているかをその名前で知るでしょう。 3つのintフィールドを持つクラスがあり、代わりに3つのint要素の配列を宣言したとします。配列は同じことを行うことができますが、フィールドの名前は、開発者にとって役立つ意味情報をもたらします。

LINQのような汎用ライブラリを設計する場合は、Func、Predicate、およびActionデリゲートを使用する必要があります。この場合、デリゲートには、実行してアクションを実行する、または述語として使用されるという事実以外に、事前定義されたセマンティクスはありません。

余談ですが、タプルvs匿名型と独自のクラスの宣言では、同様のトレードオフの問題があります。タプルにすべてを貼り付けることができますが、プロパティは単にItem1、Item2であり、タイプの使用について何も通知しません。

11
Stilgar

いくつかの回答は、勝利が明確であることを述べているので、タイプに名前を付けると、APIのユーザーにとって理解しやすくなります。たいていの場合、パブリックAPIのデリゲート型を宣言しますが、内部でFunc<?,?>を使用しても問題ありません。

他の回答で言及されていないデリゲート型を宣言することの大きな利点の1つは、型に名前を付けて、実際にパラメーターに名前を付けることとは別に、これにより使いやすさが大幅に向上することです。

7
Patrik Hägne

デリゲートのみを使用できる特別なユースケースを見つけました。

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

Func/Actionを使用しても機能しません:'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);

次のコードはコンパイルされますが、System.Runtime.InteropServices.DllImportAttributeはジェネリック型のマーシャリングをサポートしていないため、実行時に例外がスローされます。

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

この例を提示して、すべての人に次のことを示します。そして、これはあなたの質問に対する合理的な答えですwhy not use Action<T>/Func<T> ?

6
Tyler Long

Func/Actionであまりにも多くのパラメーターを取得し始めたら、デリゲートを明示的に宣言します。そうしないと、「2番目のintはどういう意味ですか?」を振り返る必要があります。

5
Ana Betts

より良い、より複雑な回答については、@ nawfalをご覧ください。私はもっ​​と単純化しようとします。

クラスのメンバーを宣言しているので、デリゲートに固執する必要があります。 delegateの使用は、より記述的で構造的です。

Action/Func型は受け渡し用に作成されているため、パラメーターおよびローカル変数としてより多く使用する必要があります。

そして実際には、両方ともDelegateクラスを継承しています。 ActionとFuncはジェネリック型であり、さまざまなパラメーター型のデリゲートを簡単に作成できます。そしてdelegateキーワードは、実際には1つの宣言でDelegateから継承するまったく新しいクラスを作成します。

2
Bizniztime

[〜#〜] msdn [〜#〜] で述べたように、Func<>自体は事前定義されたDelegateです。初めて、私はこれについて混乱しました。実験後、私の理解はかなり明確になりました。通常、C#では、

TypeへのポインタとしてのInstance

同じコンセプトが適用されます

DelegateへのポインタとしてMethod

これらとDelegateの違いは、Inheritanceなど、OOPの概念を持たないことです。このことをより明確にするために、私は実験を行いました

public delegate string CustomDelegate(string a);

// Func<> is a delegate itself, BUILD-IN delegate
//==========
// Short Version Anonymous Function
//----------
Func<string, string> fShort = delegate(string a)
{
  return "ttt";
};
// Long Version Anonymous Function
//----------
Func<string, string> fLong = a => "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;

フレームワークの多くの組み込みメソッド(たとえば、LINQ)は、Func<>デリゲートのパラメーターを受け取ります。この方法でできることは

DeclareFunc<>タイプのデリゲートで、カスタムデリゲートDefineではなく関数に渡します。

たとえば、上のコードからさらにコードを追加します

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