C#中的委託

一码事發表於2024-04-05

C# 委託是.NET Framework 使用的一種型別安全的函式指標。 委託通常用於實現回撥和事件偵聽器。 委託無需瞭解其使用的方法類的任何知識。

委託是引用型別。 但是委託不是引用物件,而是引用方法。

在以下情況下使用代理:

  • 事件處理程式
  • 回呼
  • LINQ
  • 設計模式的實施

委託沒有什麼可以用常規方法完成的。 之所以使用代表,是因為它們帶來了許多優點。 它們提高了應用和程式碼重用的靈活性。 像介面一樣,委託使我們能夠解耦和泛化我們的程式碼。 委託還允許將方法作為引數傳遞。 當我們需要確定在執行時呼叫哪種方法時,可以使用委託。 最後,委託人提供了一種無需對子類進行子類化就可以對它的行為進行專門化的方法。 類可能具有複雜的泛型行為,但仍應專門化。 類是透過繼承或透過委託來專用的。

C# 使用委託

我們將有一些簡單的示例顯示如何使用委託。

Program.
using System;

namespace SimpleDelegate
{
    delegate void MyDelegate();

    class Program
    {
        static void Main(string[] args)
        {
            var md = new MyDelegate(MyCallback);
            md();
        }

        static void MyCallback()
        {
            Console.WriteLine("Calling callback");
        }
    }
}

我們宣告一個委託,建立該委託的例項並呼叫它。

delegate void MyDelegate();

這是我們的委託人宣告。 它不返回任何值,不接受任何引數。

var md = new MyDelegate(MyCallback);

我們建立委託的例項。 呼叫時,委託將呼叫靜態Callback()方法。

md();

我們打電話給代表。


$ dotnet run
Calling callback

這是輸出。

我們可以使用不同的語法來建立和使用委託。

Program.
using System;

namespace SimpleDelegate2
{
    delegate void MyDelegate();

    class Program
    {
        static void Main(string[] args)
        {
            MyDelegate del = MyCallback;
            del();
        }

        static void MyCallback()
        {
            Console.WriteLine("Calling callback");
        }
    }
}

建立委託的例項時,我們可以儲存一些型別。

MyDelegate del = MyCallback;

這是建立委託的另一種方法。 我們直接指向方法名稱。

C# 委託指向不同的方法

委託可以隨著時間指向不同的方法。


Program.
using System;

namespace DifferentMethods
{
    public delegate void NameDelegate(string msg);

    public class Person
    {
        public string firstName;
        public string secondName;

        public Person(string firstName, string secondName)
        {
            this.firstName = firstName;
            this.secondName = secondName;
        }

        public void ShowFirstName(string msg)
        {
            Console.WriteLine(msg + this.firstName);
        }

        public void ShowSecondName(string msg)
        {
            Console.WriteLine(msg + this.secondName);
        }
    }

    class Program
    {
        public static void Main()
        {
            var per = new Person("Fabius", "Maximus");

            var nDelegate = new NameDelegate(per.ShowFirstName);
            nDelegate("Call 1: ");

            nDelegate = new NameDelegate(per.ShowSecondName);
            nDelegate("Call 2: ");
        }
    }
}

在此示例中,我們只有一名代表。 該委託用於指向Person類的兩個方法。 方法與委託一起呼叫。

public delegate void NameDelegate(string msg);

使用delegate關鍵字建立委託。 委託簽名必須與委託呼叫的方法的簽名匹配。

1
2
var nDelegate = new NameDelegate(per.ShowFirstName);
nDelegate("Call 1: ");

我們建立一個新委託的例項,該例項指向ShowFirstName()方法。 稍後我們透過委託呼叫該方法。


$ dotnet run
Call 1: Fabius
Call 2: Maximus

這兩個名稱都是透過代理列印的。

C# 多播委託

多播委託是一個擁有對多個方法的引用的委託。 多播委託必須僅包含返回 void 的方法,否則將存在執行時異常。


Program.
using System;

namespace MulticastDelegate
{
    delegate void MyDelegate(int x, int y);

    public class Oper
    {
        public static void Add(int x, int y)
        {
            Console.WriteLine("{0} + {1} = {2}", x, y, x + y);
        }

        public static void Sub(int x, int y)
        {
            Console.WriteLine("{0} - {1} = {2}", x, y, x - y);
        }
    }

    class Program
    {
        static void Main()
        {
            var del = new MyDelegate(Oper.Add);

            del += new MyDelegate(Oper.Sub);
            del(6, 4);

            del -= new MyDelegate(Oper.Sub);
            del(2, 8);
        }
    }
}

這是一個多播委託的示例。

delegate void MyDelegate(int x, int y);

我們的代表接受兩個引數。 我們有一個Oper類,它具有兩個靜態方法。 一個將兩個值相加,另一個將兩個值相減。

var del = new MyDelegate(Oper.Add);

我們建立委託的例項。 委託指向Oper類的靜態Add()方法。

1
2
del += new MyDelegate(Oper.Sub);
del(6, 4);

我們將另一個方法插入到現有的委託例項中。 委託的第一次呼叫將呼叫兩個方法。

1
2
del -= new MyDelegate(Oper.Sub);
del(2, 8);

我們從委託中刪除一種方法。 委託的第二次呼叫僅呼叫一種方法。


$ dotnet run
6 + 4 = 10
6 - 4 = 2
2 + 8 = 10

這是程式的輸出。

C# 匿名方法

可以對委託使用匿名方法。


Program.
using System;

namespace Anonymous
{
    delegate void MyDelegate();

    class Program
    {
        static void Main(string[] args)
        {
            MyDelegate del = delegate
            {
                Console.WriteLine("Anonymous method");
            };

            del();
        }
    }
}

當將匿名方法與委託一起使用時,我們可以省略方法宣告。 該方法沒有名稱,只能透過委託來呼叫。


MyDelegate del = delegate
{
    Console.WriteLine("Anonymous method");
};

在這裡,我們建立一個指向匿名方法的委託。 匿名方法的主體用{}字元括起來,但是沒有名稱。

C# 委託作為方法引數

委託可以用作方法引數。


Program.
using System;

namespace MethodParameters
{
    delegate int Arithm(int x, int y);

    class Program
    {
        static void Main()
        {
            DoOperation(10, 2, Multiply);
            DoOperation(10, 2, Divide);
        }

        static void DoOperation(int x, int y, Arithm del)
        {
            int z = del(x, y);
            Console.WriteLine(z);
        }

        static int Multiply(int x, int y)
        {
            return x * y;
        }

        static int Divide(int x, int y)
        {
            return x / y;
        }
    }
}

我們有一個DoOperation()方法,該方法將一個委託作為引數。

delegate int Arithm(int x, int y);

這是一個委託宣告。


static void DoOperation(int x, int y, Arithm del)
{
    int z = del(x, y);
    Console.WriteLine(z);
}

這是DoOperation()方法的實現。 第三個引數是委託。 DoOperation()方法呼叫一個方法,該方法作為第三個引數傳遞給它。

1
2
DoOperation(10, 2, Multiply);
DoOperation(10, 2, Divide);

我們稱為DoOperation()方法。 我們傳遞兩個值和一個方法給它。 我們對這兩個值的處理方式取決於我們透過的方法。 這就是使用委託所帶來的靈活性。


$ dotnet run
20
5

C# 事件

事件是由某些操作觸發的訊息。 單擊按鈕或滴答滴答即是這種動作。 觸發事件的物件稱為傳送者,而接收事件的物件稱為接收者。

按照約定,.NET Framework 中的事件委託具有兩個引數:引發事件的源和事件的資料。


Program.
using System;

namespace SimpleEvent
{
    public delegate void OnFiveHandler(object sender, EventArgs e);

    class FEvent
    {
        public event OnFiveHandler FiveEvent;

        public void OnFiveEvent()
        {
            if (FiveEvent != null)
            {
                FiveEvent(this, EventArgs.Empty);
            }
        }
    }

    class Program
    {
        static void Main()
        {
            var fe = new FEvent();
            fe.FiveEvent += new OnFiveHandler(Callback);

            var random = new Random();

            for (int i = 0; i < 10; i++)
            {
                int rn = random.Next(6);

                Console.WriteLine(rn);

                if (rn == 5)
                {
                    fe.OnFiveEvent();
                }
            }
        }

        public static void Callback(object sender, EventArgs e)
        {
            Console.WriteLine("Five Event occurred");
        }
    }
}

我們有一個簡單的示例,可以在其中建立和啟動事件。 生成一個隨機數。 如果數字等於 5,則會生成FiveEvent事件。

public event OnFiveHandler FiveEvent;

使用event關鍵字宣告事件。

fe.FiveEvent += new OnFiveHandler(Callback);

在這裡,我們將名為FiveEvent的事件插入到Callback()方法中。 換句話說,如果觸發了ValueFive事件,則將執行Callback()方法。


public void OnFiveEvent()
{
    if(FiveEvent != null)
    {
        FiveEvent(this, EventArgs.Empty);
    }
}

當隨機數等於 5 時,我們呼叫OnFiveEvent()方法。 在這種方法中,我們引發了FiveEvent事件。 此事件不包含任何引數。


$ dotnet run
1
1
5
Five Event occurred
Five Event occurred

這是一個示例輸出。

C# 複雜事件示例

接下來,我們有一個更復雜的示例。 這次,我們將透過生成的事件傳送一些資料。


Program.
using System;

namespace ComplexEvent
{
    public delegate void OnFiveHandler(object sender, FiveEventArgs e);

    public class FiveEventArgs : EventArgs
    {
        public int count;
        public DateTime time;

        public FiveEventArgs(int count, DateTime time)
        {
            this.count = count;
            this.time = time;
        }
    }

    public class FEvent
    {
        public event OnFiveHandler FiveEvent;

        public void OnFiveEvent(FiveEventArgs e)
        {
            FiveEvent(this, e);
        }
    }

    public class RandomEventGenerator
    {
        public void Generate()
        {
            int count = 0;
            FiveEventArgs args;

            var fe = new FEvent();
            fe.FiveEvent += new OnFiveHandler(Callback);

            var random = new Random();

            for (int i = 0; i < 10; i++)
            {
                int rn = random.Next(6);

                Console.WriteLine(rn);

                if (rn == 5)
                {
                    count++;
                    args = new FiveEventArgs(count, DateTime.Now);
                    fe.OnFiveEvent(args);
                }
            }
        }

        public void Callback(object sender, FiveEventArgs e)
        {
            Console.WriteLine("Five event {0} occurred at {1}",
                e.count, e.time);
        }
    }

    class Program
    {
        static void Main()
        {
            var reg = new RandomEventGenerator();
            reg.Generate();
        }
    }
}

我們有四個班。 FiveEventArgs帶有事件物件的一些資料。 FEvent類封裝了事件物件。 RandomEventGenerator類負責生成隨機數。 它是事件傳送者。 最後,ComplexEvent是主要的應用類。

1
2
3
4
5
public class FiveEventArgs : EventArgs
{
    public int count;
    public DateTime time;
...

FiveEventArgs在事件物件內部傳送資料。 它繼承自EventArgs基類。 計數和時間成員是將被初始化並隨事件一起攜帶的資料。


if (rn == 5)
{
    count++;
    args = new FiveEventArgs(count, DateTime.Now);
    fe.OnFiveEvent(args);
}

如果生成的隨機數等於 5,我們用當前計數和DateTime值例項化FiveEventArgs類。 count變數對生成此事件的次數進行計數。 DateTime值儲存事件生成的時間。


$ dotnet run

Five event 1 occurred at 10/22/2019 2:13:10 PM
3
0

這是程式的示例輸出。

C# 預定義的委託

.NET 框架具有多個內建的委託,這些委託減少了所需的輸入並簡化了開發人員的程式設計工作。

C# 動作委託

操作委託封裝了沒有引數且不返回值的方法。


Program.
using System;

namespace ActionDelegate
{
    class Program
    {
        static void Main()
        {
            Action act = ShowMessage;
            act();
        }

        static void ShowMessage()
        {
            Console.WriteLine("C# language");
        }
    }
}

使用預定義的委託可以進一步簡化程式設計。 我們不需要宣告委託型別。


Action act = ShowMessage;
act();

我們例項化一個動作委託。 委託指向ShowMessage()方法。 呼叫委託時,將執行ShowMessage()方法。

有多種型別的動作委託。 例如,Action<T>委託封裝了一個採用單個引數且不返回值的方法。


Program.
using System;

namespace ActionDelegate2
{
    class Program
    {
        static void Main()
        {
            Action<string> act = ShowMessage;
            act("C# language");
        }

        static void ShowMessage(string message)
        {
            Console.WriteLine(message);
        }
    }
}

我們修改前面的示例,以使用帶有一個引數的動作委託。

1
2
Action<string> act = ShowMessage;
act("C# language");

我們建立動作< T >委託的例項,並使用一個引數對其進行呼叫。

C# 謂詞委託

謂詞是一種返回 true 或 false 的方法。 謂詞委託是對謂詞的引用。 謂詞對於過濾值列表非常有用。


Program.
using System;
using System.Collections.Generic;

namespace PredicateDelegate
{
    class Program
    {
        static void Main()
        {
            List<int> vals = new List<int> { 4, 2, 3, 0, 6, 7, 1, 9 };

            Predicate<int> myPred = greaterThanThree;

            List<int> vals2 = vals.FindAll(myPred);

            foreach (int i in vals2)
            {
                Console.WriteLine(i);
            }
        }

        static bool greaterThanThree(int x)
        {
            return x > 3;
        }
    }
}

我們有一個整數值列表。 我們要過濾所有大於三的數字。 為此,我們使用謂詞委託。

List<int> vals = new List<int> { 4, 2, 3, 0, 6, 7, 1, 9 };

這是整數值的一般列表。

Predicate<int> myPred = greaterThanThree;

我們建立一個謂詞委託的例項。 委託指向謂詞,這是一種返回 true 或 false 的特殊方法。

List<int> vals2 = vals.FindAll(myPred);

FindAll()方法檢索與指定謂詞定義的條件匹配的所有元素。


static bool greaterThanThree(int x)
{
    return x > 3;
}

對於大於三個的所有值,謂詞返回 true。

相關文章