C#学習23日目

目次

C# デリゲート

代表団の概要

デリゲートを使用する

名前付きメソッドと匿名メソッドを持つデリゲート

デリゲート (マルチキャスト デリゲート) をマージする方法

デリゲートを宣言、インスタンス化、使用する方法

信頼性の高いプログラミング


C# デリゲート

Delegate は、特定のパラメータ リストと戻り値の型を持つメソッドへの参照を表す参照型です。デリゲートをインスタンス化するとき、そのインスタンスを、互換性のあるシグネチャと戻り値の型を持つ任意のメソッドに関連付けることができます。デリゲート インスタンスを通じてメソッドを呼び出すことができます。

デリゲートは、メソッドをパラメータとして他のメソッドに渡すために使用されます。イベント ハンドラーは、デリゲートを通じて呼び出されるメソッドです。特定のイベントが発生したときにクラス (Windows コントロールなど) が呼び出すことができるカスタム メソッドを作成できます。次の例は、デリゲート宣言を示しています。

public delegate int PerformCalculation(int x, int y);

デリゲートの型と一致するアクセス可能なクラスまたは構造体のメソッドは、デリゲートに割り当てることができます。メソッドは静的メソッドまたはインスタンス メソッドのいずれかです。この柔軟性は、メソッド呼び出しをプログラムで変更し、既存のクラスに新しいコードを挿入できることを意味します。

注:メソッドのオーバーロードのコンテキストでは、メソッドのシグネチャには戻り値が含まれません。ただし、デリゲートのコンテキストでは、署名には戻り値が含まれます。つまり、メソッドとデリゲートの戻り値の型は同じである必要があります。

メソッドをパラメーターとして参照できるため、デリゲートはコールバック メソッドを定義するのに最適です。アプリケーション内で 2 つのオブジェクトを比較するメソッドを作成できます。このメソッドは、並べ替えアルゴリズムのデリゲートで使用できます。比較コードがライブラリから分離されているため、ソート方法の方が一般的になる可能性があります。

呼び出し規約をより詳細に制御する必要がある同様のシナリオのために、関数ポインタ が C# 9 に追加されました。デリゲート型に追加された仮想メソッドを使用して、デリゲートに関連付けられたコードを呼び出します。関数ポインターを使用すると、さまざまな規則を指定できます。

代表団の概要

デリゲートには次のプロパティがあります。

  1. C++ 関数ポインターに似ていますが、完全にオブジェクト指向です。 C++ 関数ポインターとは異なり、デリゲートはオブジェクト インスタンスとメソッドの両方をカプセル化します。
  2. メソッドをパラメーターとして渡すことを許可すると、デリゲートが非常に柔軟になり、コールバック メソッドの定義に使用できます。
  3. 複数のデリゲートを連鎖させることができます。たとえば、イベントに対して複数のメソッドを呼び出して、マルチキャスト デリゲートの機能を実現できます。
  4. メソッドはデリゲートの型と正確に一致する必要はありません。つまり、さまざまなメソッド シグネチャに対応するためにバリアントを使用できます。詳細については、デリゲートでのバリエーションの使用を参照してください。
  5. ラムダ式を使用して、インライン コード ブロックをより簡潔に記述します。一部のコンテキストでは、ラムダ式をデリゲート型にコンパイルできるため、コードの記述がさらに簡素化されます。ラムダ式の詳細については、 ラムダ式をご覧ください。

一般に、委任は C# の重要な機能として、コールバック、イベント処理、動的メソッド呼び出しなどのシナリオを処理するための非常に柔軟で強力な方法を開発者に提供します。

デリゲートを使用する

デリゲート は、C や C++ の関数ポインタと同様に、メソッドを安全にカプセル化する型です。 C の関数ポインターとは異なり、デリゲートはオブジェクト指向で、タイプセーフで信頼性があります。デリゲートの種類は、デリゲートの名前によって決まります。次の例では、 Callback  という名前のデリゲートを宣言します。これは、 文字列 をパラメータとしてカプセル化し、  < a i=5>void メソッド:

public delegate void Callback(string message);

デリゲート オブジェクトは通常 2 つの方法で構築できます。1 つはデリゲートがカプセル化するメソッドの名前を指定する方法、もう 1 つは ラムダ式< a i= 2>。この方法でデリゲートがインスタンス化された後、それを呼び出すことができます。デリゲートを呼び出すと、デリゲート インスタンスにアタッチされたメソッドが呼び出されます。呼び出し元によってデリゲートに渡されたパラメーターはメソッドに渡され、デリゲートはメソッドの戻り値 (存在する場合) を呼び出し元に返します。例:

// 为委托创建方法
public static void DelegateMethod(string message)
{
    Console.WriteLine(message);
}
// 实例化委托。
Callback handler = DelegateMethod;

// 使用委托。
handler("Hello World");

デリゲート タイプは、.NET の Delegate クラスから派生します。デリゲート型はシールされているため、 デリゲートから派生したり、派生したりすることはできません。そこからカスタムクラスを作成します。インスタンス化されたデリゲートはオブジェクトであるため、引数として渡すことも、プロパティに割り当てることもできます。これにより、メソッドはデリゲートをパラメーターとして受け入れ、後でそのデリゲートを呼び出すことができます。これは非同期コールバックと呼ばれ、長いプロセスが完了したときに呼び出し元に通知する一般的な方法です。この方法でデリゲートを使用する場合、デリゲートを使用するコードは、どの実装メソッドを使用するかを認識する必要がありません。この機能は、カプセル化されたインターフェイスによって提供される機能と似ています。

コールバックのもう 1 つの一般的な使用法は、カスタム比較メソッドを定義し、そのデリゲートを short メソッドに渡すことです。これにより、呼び出し元のコードを並べ替えアルゴリズムの一部にすることができます。次のメソッド例では、Del 型をパラメータとして使用します。

public static void MethodWithCallback(int param1, int param2, Callback callback)
{
    callback("The number is: " + (param1 + param2).ToString());
}

次に、上で作成したデリゲートをメソッドに渡すことができます。

MethodWithCallback(1, 2, handler);

次の出力をコンソールに受け取ります。

The number is: 3

抽象的な方法でデリゲートを使用する場合、MethodWithCallback はコンソールを直接呼び出す必要はありません。コンソールを持つように設計する必要がないことに注意してください。 MethodWithCallback が行うことは、単純に文字列を準備して他のメソッドに渡すことです。デリゲートのメソッドは任意の数のパラメーターを受け取ることができるため、この機能は特に強力です。

インスタンス メソッドをカプセル化するようにデリゲートが構築されると、デリゲートはインスタンスとメソッドの両方を参照します。デリゲートは、カプセル化するメソッド以外のインスタンス型について何も知りません。そのため、デリゲートの署名と一致するオブジェクト上のメソッドが存在する限り、デリゲートは任意の型のオブジェクトを参照できます。静的メソッドをカプセル化するようにデリゲートが構築されている場合、デリゲートはメソッドを参照するだけです。次のステートメントを考えてみましょう。

public class MethodClass
{
    public void Method1(string message) { }
    public void Method2(string message) { }
}

前に示した静的な DelegateMethod と合わせて、Del インスタンスがカプセル化できるメソッドが 3 つあります。

呼び出されたとき、デリゲートは複数のメソッドを呼び出すことができます。これをマルチキャストと呼びます。デリゲートのメソッド リスト (呼び出しリスト) にメソッドを追加するには、加算演算子または加算代入演算子 (「+」または「+=」) を使用して 2 つのデリゲートを追加するだけです。例えば:

var obj = new MethodClass();
Callback d1 = obj.Method1;
Callback d2 = obj.Method2;
Callback d3 = DelegateMethod;

//这两种类型的分配都是有效的
Callback allMethodsDelegate = d1 + d2;
allMethodsDelegate += d3;

現時点では、allMethodsDelegate の呼び出しリストには、Method1、Method2、DelegateMethod という 3 つのメソッドが含まれています。元の 3 つのデリゲート (d1、d2、および d3) は変更されません。 allMethodsDelegate が呼び出されると、3 つのメソッドすべてが順番に呼び出されます。デリゲートが参照パラメーターを使用する場合、参照は 3 つのメソッドすべてに逆の順序で渡され、1 つのメソッドによって行われた変更は他のメソッドにも反映されます。メソッドがメソッド内でキャッチされない例外をスローした場合、その例外はデリゲートの呼び出し元に渡され、呼び出しリスト内の後続のメソッドは呼び出されません。デリゲートに戻り値や出力パラメーターがある場合、最後に呼び出されたメソッドの戻り値とパラメーターが返されます。呼び出しリストからメソッドを削除するには、減算演算子または減算代入演算子 (- または -=) を使用します。例:

//remove Method1
allMethodsDelegate -= d1;

// 删除 d2 时复制 AllMethodsDelegate
Callback oneMethodDelegate = allMethodsDelegate - d2;

デリゲート型は System.Delegate から派生するため、そのクラスによって定義されたメソッドとプロパティをデリゲートで呼び出すことができます。たとえば、デリゲート呼び出しのリスト内のメソッドの数をクエリするには、次のように記述できます。

int invocationCount = d1.GetInvocationList().GetLength(0);

呼び出しリスト内の複数のメソッドを持つデリゲートは、System.Delegate のサブクラスである  MulticastDelegate から派生します。どちらのクラスも GetInvocationList をサポートしているため、上記のコードは他の場合でも同様に機能します。

マルチキャスト デリゲートはイベント処理で広く使用されています。イベント ソース オブジェクトは、イベントを受信するように登録されているレシーバー オブジェクトにイベント通知を送信します。イベントに登録するには、受信者はイベントを処理するメソッドを作成し、そのメソッドのデリゲートを作成して、そのデリゲートをイベント ソースに渡す必要があります。イベントが発生すると、ソースはデリゲートを呼び出します。次に、デリゲートは受信側のイベント処理メソッドを呼び出し、イベント データを提供します。特定のイベントのデリゲートの種類は、イベント ソースによって決まります。詳細については、イベントをご覧ください。

コンパイル時に、異なる型の 2 つの割り当てられたデリゲートを比較すると、コンパイル エラーが発生します。デリゲート インスタンスが静的 System.Delegate 型の場合、比較は許可されますが、実行時に false が返されます。例えば:

delegate void Callback1();
delegate void Callback2();

static void method(Callback1 d, Callback2 e, System.Delegate f)
{
    // Compile-time error.
    //Console.WriteLine(d == e);

    // OK at compile-time. False if the run-time type of f
    // is not the same as that of d.
    Console.WriteLine(d == f);
}

名前付きメソッドと匿名メソッドを持つデリゲート

デリゲートは名前付きメソッドに関連付けることができます。名前付きメソッドを使用してデリゲートをインスタンス化する場合、メソッドはパラメータとして渡されます。例:

// 声明一个委托
delegate void WorkCallback(int x);

// 定义命名方法
void DoWork(int k) { /* ... */ }

// 使用方法作为参数实例化委托
WorkCallback d = obj.DoWork;

これは、名前付きメソッドを使用して呼び出されます。名前付きメソッドを使用して構築されたデリゲートは、静的メソッドまたはインスタンス メソッドをカプセル化できます。以前のバージョンの C# では、名前付きメソッドがデリゲートをインスタンス化する唯一の方法でした。ただし、新しいメソッドを作成すると不要なオーバーヘッドが発生する場合、C# ではデリゲートをインスタンス化し、デリゲートが呼び出されたときにデリゲートが処理するコード ブロックをすぐに指定できます。コード ブロックにはラムダ式または匿名メソッドを含めることができます。

デリゲート パラメーターとして渡されるメソッドには、デリゲート宣言と同じ署名が必要です。デリゲート インスタンスは、静的メソッドまたはインスタンス メソッドをカプセル化できます。

注:デリゲートは out パラメータを使用できますが、これを使用することはお勧めしません。マルチキャストを使用したデリゲート どのデリゲートが呼び出されるかを知る方法がないため、イベント デリゲートとともに使用されます。

C# 10 以降、単一のオーバーロードを含むメソッド グループは自然型を持ちます。これは、コンパイラがデリゲート型の戻り値の型とパラメーターの型を推論できることを意味します。

var read = Console.Read; // 只有一个过载;Func<int>推断
var write = Console.Write; // 错误:多个重载,无法选择

using System;

public delegate void MyDelegate(string message);

public class Program
{
    public static void Main()
    {
        // 使用命名方法创建委托实例
        MyDelegate delegate1 = new MyDelegate(Method1);
        delegate1("来自命名方法的Hello");

        // 使用匿名方法创建委托实例
        MyDelegate delegate2 = delegate (string message)
        {
            Console.WriteLine("匿名方法: " + message);
        };

        delegate2("来自匿名方法的Hello");
    }

    // 命名方法
    public static void Method1(string message)
    {
        Console.WriteLine("方法1: " + message);
    }
}

この例では、最初に MyDelegate という名前のデリゲート型を定義します。これは文字列パラメーターを受け入れ、値を返しません。次に、Main メソッドで、2 つのデリゲート インスタンス delegate1 と delegate2 を作成します。

このうち、delegate1 は名前付きメソッド Method1 を使用して初期化し、delegate1 を呼び出してデリゲートをトリガーすることで、関連付けられた名前付きメソッドを呼び出します。

delegate2 は、匿名メソッドを使用して初期化し、delegate2 を呼び出してデリゲートをトリガーすることにより、関連付けられた匿名メソッドを呼び出します。

デリゲート (マルチキャスト デリゲート) をマージする方法

+ 演算子を使用して複数のデリゲート オブジェクトをデリゲート インスタンスに割り当て、それによってマルチキャスト デリゲートを作成します。このマルチキャスト デリゲートには、割り当てられたデリゲートのリストが含まれています。マルチキャスト デリゲートが呼び出されると、リスト内のデリゲートが順番に呼び出されます。

+ 演算子を使用してマージできるのは、同じ型のデリゲート インスタンスのみです。それ以外の場合は、コンパイル エラーが発生します。

さらに、 - 演算子を使用して、マルチキャスト デリゲートからコンポーネント デリゲートを削除することもできます。たとえば、2 つのデリゲート オブジェクト delegate1 と delegate2 を含むマルチキャスト デリゲート delegateChain がある場合、- 演算子を使用してマルチキャスト デリゲートからデリゲート オブジェクトの 1 つを削除できます。

delegateChain = delegateChain - delegate1; // 从多播委托中删除 delegate1

これにより、delegateChain から delegate1 が削除され、delegateChain には delegate2 デリゲート オブジェクトのみが含まれるようになります。

using System;

public delegate void MyDelegate(string message);

class Program
{
    static void Main()
    {
        MyDelegate delegate1 = new MyDelegate(Method1);
        MyDelegate delegate2 = new MyDelegate(Method2);
        MyDelegate delegate3 = new MyDelegate(Method3);

        // 合并多个委托对象到一个多播委托
        MyDelegate delegateChain = delegate1 + delegate2 + delegate3;

        // 调用多播委托,将依次调用列表中的委托
        delegateChain("第一次依次调用列表中的委托");

        Console.WriteLine();

        // 从多播委托中删除一个委托对象
        delegateChain = delegateChain - delegate2;

        // 再次调用多播委托,将不再调用已删除的委托对象
        delegateChain("第二次依次调用列表中的委托");
    }

    static void Method1(string message)
    {
        Console.WriteLine("Method1: " + message);
    }

    static void Method2(string message)
    {
        Console.WriteLine("Method2: " + message);
    }

    static void Method3(string message)
    {
        Console.WriteLine("Method3: " + message);
    }
}

実行結果は次のとおりです。

Method1: 第一次依次调用列表中的委托
Method2: 第一次依次调用列表中的委托
Method3: 第一次依次调用列表中的委托

Method1: 第二次依次调用列表中的委托
Method3: 第二次依次调用列表中的委托

この例では、3 つの異なるデリゲート オブジェクト delegate1、delegate2、delegate3 を定義しており、すべて異なるメソッドを指しています。 + 演算子を使用してそれらをマルチキャスト デリゲート delegateChain に結合し、それを呼び出してリスト内のデリゲートを順番に呼び出します。

次に、- 演算子を使用してデリゲート オブジェクト delegate2 を delegateChain から削除しました。 delegateChain が再度呼び出されると、削除されたデリゲート オブジェクト delegate2 は呼び出されなくなります。

デリゲートを宣言、インスタンス化、使用する方法

デリゲートは、次のいずれかの方法を使用して宣言できます。

  • デリゲート型を宣言し、一致するシグネチャを持つメソッドを宣言します。
  • メソッド グループをデリゲート型に割り当てます。
  • 匿名メソッドを宣言します。
  • ラムダ式を使用します。詳細については、 ラムダ式をご覧ください。

以下は、さまざまなメソッドを使用してデリゲートを宣言するサンプル コードです。

using System;

// 1. 使用匹配签名声明委托类型并声明方法
public delegate void MyDelegate(string message);

class Program
{
    static void Main()
    {
        // 2. 将方法分配给委托类型
        MyDelegate delegate1 = new MyDelegate(Method1);
        delegate1("将方法分配给委托类型");

        // 3. 声明匿名方法并赋给委托
        MyDelegate delegate2 = delegate (string message) { Console.WriteLine("匿名方法: " + message); };
        delegate2("声明匿名方法并赋给委托");

        // 4. 使用 lambda 表达式
        MyDelegate delegate3 = (message) => { Console.WriteLine("Lambda表达式: " + message); };
        delegate3("使用 lambda 表达式");
    }

    static void Method1(string message)
    {
        Console.WriteLine("Method1: " + message);
    }
}

この例では、デリゲートを宣言してメソッドを割り当てる 4 つの異なる方法を示します。

  1. まず、一致するシグネチャを使用して MyDelegate 型のデリゲートを宣言し、そのシグネチャがデリゲート型と一致するメソッド Method1 を宣言します。
  2. 次に、名前付きメソッド Method1 を delegate1 デリゲート オブジェクトに割り当てて呼び出します。
  3. 次に、匿名メソッドを宣言し、それを delegate2 デリゲート オブジェクトに割り当てて呼び出します。
  4. 最後に、ラムダ式を使用してメソッドを宣言し、それを delegate3 デリゲート オブジェクトに割り当てて呼び出します。

以下は、C# でデリゲートを宣言、インスタンス化、使用する方法を示す簡単な例です。

using System;

// 声明一个委托类型,它接受两个整数参数并返回一个整数
public delegate int MyDelegate(int x, int y);

class Program
{
    static void Main()
    {
        // 实例化委托对象并将其分配给命名方法
        MyDelegate delegate1 = new MyDelegate(Method1);

        // 使用委托对象调用已分配的方法
        int result1 = delegate1(10, 5);
        Console.WriteLine("结果1: " + result1);

        // 重新分配委托对象到匿名方法
        MyDelegate delegate2 = delegate (int x, int y) { return x - y; };

        // 使用委托对象调用已分配的匿名方法
        int result2 = delegate2(10, 5);
        Console.WriteLine("结果2: " + result2);

        // 重新分配委托对象到 lambda 表达式
        MyDelegate delegate3 = (x, y) => x * y;

        // 使用委托对象调用已分配的 lambda 表达式
        int result3 = delegate3(10, 5);
        Console.WriteLine("结果3: " + result3);
    }

    // 命名方法,它与 MyDelegate 委托类型具有相同的签名
    static int Method1(int x, int y)
    {
        return x + y;
    }
}

この例では、最初に 2 つの整数パラメーターを受け入れ、整数を返すデリゲート型 MyDelegate を宣言します。次に、3 つのデリゲート オブジェクトをインスタンス化し、それらをさまざまなメソッド、匿名メソッド、ラムダ式に割り当てました。これらのデリゲート オブジェクトを使用して、割り当てられたメソッド、匿名メソッド、ラムダ式を呼び出し、結果を出力します。

信頼性の高いプログラミング

1. 委任タイプを宣言します。

public delegate void ProcessBookCallback(Book book);

各デリゲート型は、引数の数と型、およびカプセル化できるメソッドの戻り値の型を記述します。新しい引数の型または戻り値の型のセットが必要な場合は、新しいデリゲート型を宣言する必要があります。

2. 委任をインスタンス化します。

デリゲート型を宣言した後、デリゲート オブジェクトを作成し、それを特定のメソッドに関連付ける必要があります。上の例では、次の例に示すように、PrintTitle メソッドを ProcessPaperbackBooks メソッドに渡すことでこれを行います。

bookDB.ProcessPaperbackBooks(PrintTitle);

これにより、静的メソッド Test.PrintTitle に関連付けられた新しいデリゲート オブジェクトが作成されます。同様に、次の例に示すように、非静的メソッド AddBookToTotal をオブジェクト トータラーに渡します。

bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);

どちらの場合も、新しいデリゲート オブジェクトを ProcessPaperbackBooks メソッドに渡します。デリゲートが作成されると、それに関連付けられたメソッドは決して変更されず、デリゲート オブジェクトは不変です。

3. 代表者に電話をかける

デリゲート オブジェクトを作成した後、通常は、デリゲートを呼び出す他のコードにそのデリゲート オブジェクトを渡します。デリゲート オブジェクトは、デリゲート オブジェクトの名前と、その後にデリゲートに渡されるかっこ内の引数を使用して呼び出されます。以下はデリゲート呼び出しの例です。

processBook(b);

アプリケーションのニーズと設計に応じて、デリゲートを同期または非同期で呼び出すことができることに注意することが重要です。非同期呼び出しは、BeginInvoke メソッドと EndInvoke メソッドを使用して実現できます。

おすすめ

転載: blog.csdn.net/m0_74293254/article/details/134527521