サーチ…


備考

概要

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

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

System名前空間が含まれているAction<...> Predicate<T>Func<...,TResult> 「...」0および16ジェネリック型パラメータの間を表すデリゲートを、(0パラメータのため、 Action非ですジェネリック)。

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

Predicateはブール型の戻り型を持つメソッドを表し、Tは入力パラメータです。

カスタムデリゲートの種類

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

デリゲートの呼び出し

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

代理人に割り当てる

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

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

デリゲートの結合

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

名前付きメソッドの代理人の基礎となる参照

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

  • それらはクラスの同じインスタンス上の同じインスタンスメソッドです

  • それらはクラス上で同じ静的メソッドです

    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
    

デリゲート型の宣言

次の構文は、作成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キーワードを使用する)。デリゲート型とそのパラメータは、カスタム属性を持つことができます。

ザ・ファンク 、アクション述語デリゲート型

System名前空間が含まれているFunc<..., TResult>型を返す、0〜15の一般的なパラメータでデリゲート型をTResult

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<...>デリゲート型も含まれています。これはFunc<T1, .., Tn>と似ていますが、常にvoid返し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>しか受け入れません。

デリゲートに名前付きメソッドを割り当てる

名前付きのメソッドは、シグネチャが一致するデリゲートに割り当てることができます。

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直接割り当てることができます。

代議員の平等

.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

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

ラムダによる代理人への割り当て

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

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

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

加算+および減算-演算を使用してデリゲートインスタンスを結合できます。デリゲートには、割り当てられたデリゲートのリストが含まれています。

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

この例では、 d3d1d2デリゲートの組み合わせであるため、呼び出されるとプログラムは1System.Int32両方の文字列を出力します。


デリゲートを非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だけが実行されます。

デリゲート内のクロージャ

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

基本的に、クロージャは後で実行できるコードブロックですが、最初に作成された環境を維持します。つまり、それを作成したメソッドのローカル変数などを使用することができます。メソッドの実行が終了しました。 - Jon Skeet

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

}

.NETのClosureから取られた例

関数の変換をカプセル化する

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


Modified text is an extract of the original Stack Overflow Documentation
ライセンスを受けた CC BY-SA 3.0
所属していない Stack Overflow