デリゲート

# デリゲート型の宣言

次の構文は delegate を作成します NumberInOutDelegate という名前のタイプ int を取るメソッドを表す int を返します .

public delegate int NumberInOutDelegate(int input);

これは次のように使用できます:

public static class Program
{
    static void Main()
    {
        NumberInOutDelegate square = MathDelegates.Square;
        int answer1 = square(4); 
        Console.WriteLine(answer1); // Will output 16

        NumberInOutDelegate cube = MathDelegates.Cube;
        int answer2 = cube(4);
        Console.WriteLine(answer2); // Will output 64            
    }
}

public static class MathDelegates
{
    static int Square (int x)
    {
        return x*x;
    }

    static int Cube (int x)
    {
        return x*x*x;
    }
}

example デリゲート インスタンスは Square と同じ方法で実行されます 方法。デリゲート インスタンスは文字通り、呼び出し元のデリゲートとして機能します。呼び出し元がデリゲートを呼び出し、デリゲートがターゲット メソッドを呼び出します。この間接化により、呼び出し元がターゲット メソッドから切り離されます。

ジェネリックを宣言できます デリゲート型であり、その場合、型が共変であることを指定できます (out ) または反変 (in ) 型引数の一部。例:

public delegate TTo Converter<in TFrom, out TTo>(TFrom input);

他のジェネリック型と同様に、ジェネリック デリゲート型は where TFrom : struct, IConvertible where TTo : new() などの制約を持つことができます。 .

イベント ハンドラー型など、マルチキャスト デリゲートに使用することを意図したデリゲート型の共変性と反変性を回避します。これは、連結 (+ ) 実行時の型がコンパイル時の型と異なる場合、差異が原因で失敗する可能性があります。たとえば、次のことは避けてください:

public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e);

代わりに、不変のジェネリック型を使用してください:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

一部のパラメーターが ref によって変更されるデリゲートもサポートされています または out 、次のように:

public delegate bool TryParser<T>(string input, out T result);

(サンプル使用 TryParser<decimal> example = decimal.TryParse; )、または最後のパラメーターが params であるデリゲート 修飾子。デリゲート タイプには、オプションのパラメーターを指定できます (既定値を指定します)。デリゲート型は int* のようなポインター型を使用できます または char* 署名または戻り値の型で (unsafe を使用) キーワード)。デリゲート型とそのパラメーターは、カスタム属性を保持できます。

# Func、Action、および Predicate デリゲート型

System 名前空間に Func<..., TResult> が含まれています TResult 型を返す、0 ~ 15 個のジェネリック パラメーターを持つデリゲート型 .

private void UseFunc(Func<string> func)
{
    string output = func(); // Func with a single generic type parameter returns that type
    Console.WriteLine(output);
}

private void UseFunc(Func<int, int, string> func)
{
    string output = func(4, 2); // Func with multiple generic type parameters takes all but the first as parameters of that type
    Console.WriteLine(output);
}

System 名前空間には Action<...> も含まれています さまざまな数のジェネリック パラメーター (0 から 16) を持つデリゲート型。 Func<T1, .., Tn> に似ています 、しかし常に void を返します .

private void UseAction(Action action)
{
    action(); // The non-generic Action has no parameters
}

private void UseAction(Action<int, string> action)
{
    action(4, "two"); // The generic action is invoked with parameters matching its type arguments
}

Predicate<T> Func の形式でもあります ただし、常に bool を返します .述語は、カスタム基準を指定する方法です。入力の値と述語内で定義されたロジックに応じて、true のいずれかを返します。 または false . Predicate<T> したがって、Func<T, bool> と同じように動作します。 どちらも同じように初期化して使用できます。

Predicate<string> predicate = s => s.StartsWith("a");
Func<string, bool> func = s => s.StartsWith("a");

// Both of these return true
var predicateReturnsTrue = predicate("abc");
var funcReturnsTrue = func("abc");

// Both of these return false
var predicateReturnsFalse = predicate("xyz");
var funcReturnsFalse = func("xyz");

Predicate<T> を使用するかどうかの選択 または Func<T, bool> は本当に意見の問題です。 Predicate<T> Func<T, bool> は間違いなく作者の意図をより表現しています。 より多くの C# 開発者になじみがある可能性があります。

それに加えて、特に別の API と対話する場合、オプションの 1 つしか使用できない場合があります。例:List<T>Array<T> 通常、Predicate<T> を使用します ほとんどの LINQ 拡張機能は Func<T, bool> のみを受け入れますが、そのメソッドについては .

# デリゲートの結合 (マルチキャスト デリゲート)

追加 + および減算 - 操作を使用して、デリゲート インスタンスを組み合わせることができます。デリゲートには、割り当てられたデリゲートのリストが含まれています。

using System;
using System.Reflection;
using System.Reflection.Emit;

namespace DelegatesExample {
    class MainClass {
        private delegate void MyDelegate(int a);

        private static void PrintInt(int a) {
            Console.WriteLine(a);
        }

        private static void PrintType<T>(T a) {
            Console.WriteLine(a.GetType());
        }

        public static void Main (string[] args)
        {
            MyDelegate d1 = PrintInt;
            MyDelegate d2 = PrintType;

            // Output:
            // 1
            d1(1);

            // Output:
            // System.Int32
            d2(1);

            MyDelegate d3 = d1 + d2;
            // Output:
            // 1
            // System.Int32
            d3(1);

            MyDelegate d4 = d3 - d2;
            // Output:
            // 1
            d4(1);

            // Output:
            // True
            Console.WriteLine(d1 == d4);
        }
    }
}

この例では d3 d1 の組み合わせです と d2 デリゲートなので、呼び出されると、プログラムは両方の 1 を出力します と System.Int32

デリゲートを non void と組み合わせる 戻り型:

マルチキャスト デリゲートに nonvoid がある場合 戻り値の型の場合、呼び出し元は最後に呼び出されたメソッドから戻り値を受け取ります。上記のメソッドは引き続き呼び出されますが、戻り値は破棄されます。


   class Program
    {
        public delegate int Transformer(int x);

        static void Main(string[] args)
        {
            Transformer t = Square;
            t += Cube;
            Console.WriteLine(t(2));  // O/P 8 
        }

        static int Square(int x) { return x * x; }

        static int Cube(int x) { return x*x*x; }
    }

t(2) 最初に Square を呼び出します そして Cube . Square の戻り値は破棄され、最後のメソッドの戻り値、つまり Cube 保持されます。

# 安全なマルチキャスト デリゲートの呼び出し

マルチキャスト デリゲートを呼び出したいと思ったことはありますが、チェーン内のいずれかで例外が発生した場合でも呼び出しリスト全体を呼び出したいとします。運が良ければ、AggregateException をスローする拡張メソッドを作成しました。 リスト全体の実行が完了した後にのみ:

public static class DelegateExtensions
{
    public static void SafeInvoke(this Delegate del,params object[] args)
    {
        var exceptions = new List<Exception>();

        foreach (var handler in del.GetInvocationList())
        {
            try
            {
                handler.Method.Invoke(handler.Target, args);
            }
            catch (Exception ex)
            {
                exceptions.Add(ex);
            }
        }

        if(exceptions.Any())
        {
            throw new AggregateException(exceptions);
        }
    }
}

public class Test
{
    public delegate void SampleDelegate();

    public void Run()
    {
        SampleDelegate delegateInstance = this.Target2;
        delegateInstance += this.Target1;

        try
        {
            delegateInstance.SafeInvoke();
        } 
        catch(AggregateException ex)
        {
            // Do any exception handling here
        }
    }

    private void Target1()
    {
        Console.WriteLine("Target 1 executed");
    }

    private void Target2()
    {
        Console.WriteLine("Target 2 executed");
        throw new Exception();
    }
}

この出力:

Target 2 executed
Target 1 executed

SaveInvoke なしで直接呼び出す 、ターゲット 2 のみを実行します。

# デリゲートの平等

.Equals() に電話しています デリゲートでは、参照の等価性によって比較します:

Action action1 = () => Console.WriteLine("Hello delegates");
Action action2 = () => Console.WriteLine("Hello delegates");
Action action1Again = action1;

Console.WriteLine(action1.Equals(action1)) // True
Console.WriteLine(action1.Equals(action2)) // False
Console.WriteLine(action1Again.Equals(action1)) // True

これらのルールは += を実行するときにも適用されます または -= たとえば、イベントのサブスクライブおよびサブスクライブ解除時など、マルチキャスト デリゲートで。

# 名前付きメソッド デリゲートの基になる参照

名前付きメソッドをデリゲートに割り当てる場合、次の場合に同じ基になるオブジェクトを参照します:

  • クラスの同じインスタンスの同じインスタンス メソッドです
  • クラスの同じ静的メソッドです
    public class Greeter
    {
        public void WriteInstance()
        {
            Console.WriteLine("Instance");
        }
    
        public static void WriteStatic()
        {
            Console.WriteLine("Static");
        }
    }
    
    // ...
    
    Greeter greeter1 = new Greeter();
    Greeter greeter2 = new Greeter();
    
    Action instance1 = greeter1.WriteInstance;
    Action instance2 = greeter2.WriteInstance;
    Action instance1Again = greeter1.WriteInstance;
    
    Console.WriteLine(instance1.Equals(instance2)); // False
    Console.WriteLine(instance1.Equals(instance1Again)); // True
    
    Action @static = Greeter.WriteStatic;
    Action staticAgain = Greeter.WriteStatic;
    
    Console.WriteLine(@static.Equals(staticAgain)); // True
    
    
  • # 名前付きメソッドをデリゲートに割り当てる

    名前付きメソッドは、署名が一致するデリゲートに割り当てることができます:

    public static class Example
    {
        public static int AddOne(int input)
        {
            return input + 1;
        }
    }
    
    
    Func<int,int> addOne = Example.AddOne
    
    

    Example.AddOne int を取る int を返します 、その署名はデリゲート Func<int,int> と一致します . Example.AddOne addOne に直接割り当てることができます 署名が一致しているためです。

    # ラムダによるデリゲートへの割り当て

    ラムダを使用して、デリゲートに割り当てる匿名メソッドを作成できます:

    Func<int,int> addOne = x => x+1;
    
    

    この方法で変数を作成する場合は、型の明示的な宣言が必要であることに注意してください:

    var addOne = x => x+1; // Does not work
    
    

    # デリゲートをパラメーターとして渡す

    デリゲートは型付き関数ポインタとして使用できます:

    class FuncAsParameters
    {
      public void Run()
      {
        DoSomething(ErrorHandler1);
        DoSomething(ErrorHandler2);
      }
    
      public bool ErrorHandler1(string message)
      {
        Console.WriteLine(message);
        var shouldWeContinue = ...  
        return shouldWeContinue;
      }
    
      public bool ErrorHandler2(string message)
      {
        // ...Write message to file...
        var shouldWeContinue = ...  
        return shouldWeContinue;
      }
    
      public void DoSomething(Func<string, bool> errorHandler)
      {
        // In here, we don't care what handler we got passed!
        ...
        if (...error...)
        {
          if (!errorHandler("Some error occurred!"))
          {
            // The handler decided we can't continue
            return;
          }
        }
      }
    }
    
    

    # デリゲート内の閉鎖

    クロージャーは、Parent を使用できるインラインの匿名メソッドです。 親のスコープで定義されているメソッド変数およびその他の匿名メソッド。

    本質的に、クロージャーは後で実行できるコードのブロックですが、最初に作成された環境を維持します。つまり、クロージャーを作成したメソッドのローカル変数などを、そのメソッドが終了した後でも引き続き使用できます。実行中。**-- ジョン スキート**

    delegate int testDel();
    static void Main(string[] args)
    {
        int foo = 4;
        testDel myClosure = delegate()
        {
            return foo;
        };
        int bar = myClosure();
    
    }
    
    

    Closures in .NET からの例

    # funcs での変換のカプセル化

    public class MyObject{
        public DateTime? TestDate { get; set; }
    
        public Func<MyObject, bool> DateIsValid = myObject => myObject.TestDate.HasValue && myObject.TestDate > DateTime.Now;
    
        public void DoSomething(){
            //We can do this:
            if(this.TestDate.HasValue && this.TestDate > DateTime.Now){
                CallAnotherMethod();
            }
    
            //or this:
            if(DateIsValid(this)){
                CallAnotherMethod();
            }
        }
    }
    
    

    クリーン コーディングの精神で、上記のようなチェックと変換を Func としてカプセル化すると、コードが読みやすく理解しやすくなります。上記の例は非常に単純ですが、複数の DateTime プロパティがあり、それぞれに異なる検証規則があり、さまざまな組み合わせを確認したい場合はどうなるでしょうか?それぞれが戻りロジックを確立している単純な 1 行の Func は、読みやすく、コードの複雑さを軽減することができます。以下の Func 呼び出しを検討し、さらに多くのコードがメソッドを乱雑にすることを想像してください:

    public void CheckForIntegrity(){
        if(ShipDateIsValid(this) && TestResultsHaveBeenIssued(this) && !TestResultsFail(this)){
            SendPassingTestNotification();
        }
    }
    
    

    # コメント

    # まとめ

    デリゲート タイプ 特定のメソッド シグネチャを表す型です。この型のインスタンスは、署名が一致する特定のメソッドを参照します。メソッド パラメーターにはデリゲート型がある場合があるため、この 1 つのメソッドに別のメソッドへの参照を渡し、そのメソッドを呼び出すことができます

    # 組み込みデリゲート型:Action<...>Predicate<T>Func<...,TResult>

    System 名前空間には Action<...> が含まれています ,Predicate<T> そして Func<...,TResult> デリゲート。"..." は 0 ~ 16 個のジェネリック型パラメーターを表します (0 個のパラメーターの場合、Action は一般的ではありません)。

    Func TResult に一致する戻り値の型を持つメソッドを表します 、および Action 戻り値のないメソッド (void) を表します。どちらの場合も、追加のジェネリック型パラメーターはメソッド パラメーターと順番に一致します。

    Predicate 戻り値の型が boolean のメソッドを表し、T は入力パラメータです。

    # カスタム デリゲート タイプ

    名前付きデリゲート型は delegate を使用して宣言できます キーワード。

    # デリゲートの呼び出し

    デリゲートは、メソッドと同じ構文を使用して呼び出すことができます:デリゲート インスタンスの名前の後に、パラメーターを含む括弧が続きます。

    # デリゲートへの割り当て

    代理人は次の方法で割り当てることができます:

    • 名前付きメソッドの割り当て
    • ラムダを使用して匿名メソッドを割り当てる
    • delegate を使用して名前付きメソッドを割り当てる キーワード。

    # デリゲートの結合

    + を使用して、複数のデリゲート オブジェクトを 1 つのデリゲート インスタンスに割り当てることができます。 オペレーター。 - 演算子を使用して、別のデリゲートからコンポーネント デリゲートを削除できます。