C# Delegate, Func, Action & Lambda

2023-03-06

筆記 C# 從 Delegate 到 Func & Action 的出現,到最後的 Lambda 對於語法使用上帶來什麼樣的差異。

logo

說明

Delegate
C# 1.0 就存在的元老,將方法作為參數傳遞的型別,定義方法的簽名和回傳值型別,可以用來實現事件處理機制。
Func
C# 3.0 出現,泛型 Delegate,可以表示具有一個或多個輸入參數和回傳值的方法
Action
C# 3.0 出現,泛型 Delegate,與 Func 的差別在於無回傳值。
Lambda Expression
C# 3.0 出現,簡潔的方法定義方式,可以用於創造匿名方法,並且搭配 Linq 查詢使用。

Delegate

Why using Delegate?

Delegates essentially act as type-safe function pointers, Allow you to treat methods as objects, it is useful for event handling and asynchronous programming, and it provide compile-time type checking for method signatures.

MathOperation compute1 = new MathOperation(Add);

compute1.Invoke(10, 5);

MathOperation compute2 = Add;
compute2(10, 5);


void Add(int i, int j) => Console.WriteLine(i + j);
void Subtract(int i, int j) => Console.WriteLine(i - j);
void Multiply(int i, int j) => Console.WriteLine(i * j);
void Divide(int i, int j) => Console.WriteLine(i / j);

// Must be declared outside of the method
delegate void MathOperation(int i, int j);

Enable Class View in Visual Studio: Ctrl + W, C

Class View and Object Browser icons

Class View and Object Browser icons | Learn.microsoft

Generic Delegate

  • Action: void without return value
  • Func: with return value
  • Predicate: with boolean return type
Action<int, int>? compute1 = new Action<int, int>(Add);

compute1 += Subtract;
compute1 += Multiply;
compute1 += Divide;

compute1(10, 5);

compute1 -= Subtract;
compute1 -= Multiply;
compute1 -= Divide;
compute1 -= Add;

// Unhandled exception. System.NullReferenceException: Object reference not set to an instance of an object.
// compute1.Invoke(10, 5);

compute1?.Invoke(10, 5);


void Add(int i, int j) => Console.WriteLine(i + j);
void Subtract(int i, int j) => Console.WriteLine(i - j);
void Multiply(int i, int j) => Console.WriteLine(i * j);
void Divide(int i, int j) => Console.WriteLine(i / j);

What is += and -= Operator

In C#, the += and -= operators are used with delegates to add or remove methods from the invocation list of a delegate instance, respectively.

What is ?. Operator

The ?. operator in C# is known as the null-conditional operator. It allows you to access members (methods, properties, and indexers) of an object only when that object is not null, preventing a NullReferenceException. If the object is null, the expression evaluates to null instead of throwing an exception.

if(compute1 != null)
{
    compute1(10, 5);
}

// ✨ equivalent to
compute1?.Invoke(10, 5);

How to using Func Delegate, the difference between Func and Action is that Func returns a value.

Func<DateTime> func = GetDateTime;
Func<string, string> func2 = Greeting;

Console.WriteLine(func());
Console.WriteLine(func2("Webber"));

DateTime GetDateTime() => DateTime.Now;
string Greeting(string name) => $"Hello, {name}!";

How to using Predicate Delegate, We can use Func Delegate instead of Predicate Delegate 😊

using System.Text.RegularExpressions;

Predicate<string> IsFourDigit = Check;
Console.WriteLine(IsFourDigit("1234"));
Console.WriteLine(IsFourDigit("12345"));
Console.WriteLine(IsFourDigit("12ab"));

Func<string, bool> IsFourDigitFuncVersion = Check;
Console.WriteLine(IsFourDigitFuncVersion("1234"));

bool Check(string input) => new Regex(@"^\d{4}$").IsMatch(input);

Example Code about Func<int, bool> in Linq:

int[] numbers = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

var result = numbers.Where(Check);

foreach (var item in result)
{
    Console.Write($"{item} ");
}

bool Check(int i) => i % 2 == 0;

By the way, We using Collection Expression in example code.

int[] numbers = [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];

// instead of 
int[] numbers = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

Events

using System.ComponentModel;

Account account = new();

// 3. Subscribe Event
account.ProgressChanged += Account_ProgressChanged;

// 4. Implement Event Handler
void Account_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
    Console.WriteLine($"{e.ProgressPercentage}%");
}

Console.WriteLine("Transaction Printing Started");
account.PrintTransactions();

class Account
{
    // 1. Declare a event
    public event EventHandler<ProgressChangedEventArgs>? ProgressChanged;

    public void PrintTransactions()
    {
        for (int i = 1; i <= 10; i++)
        {
            Thread.Sleep(300);

            // 2. Invoke Event
            ProgressChanged?.Invoke(this, new ProgressChangedEventArgs(i * 10, null));
        }
        Console.WriteLine("Transaction Printing Completed!");
    }
}

This code demonstrates how to use events in C# to monitor progress while printing transactions in an Account class. Let's break down the code step by step:

  1. Event Declaration (Account class):
public event EventHandler<ProgressChangedEventArgs>? ProgressChanged;

Here, ProgressChanged is an event declared in the Account class. It is of type EventHandler<ProgressChangedEventArgs>, which means it can be subscribed to by methods that match the signature of EventHandler<ProgressChangedEventArgs>.

  1. Invoking the Event (PrintTransactions method):
ProgressChanged?.Invoke(this, new ProgressChangedEventArgs(i * 10, null));

Inside the PrintTransactions method, the ProgressChanged event is invoked. This line triggers the event, notifying any subscribers (like the Account_ProgressChanged method) about the progress of the transaction printing. It passes this as the sender and creates a new ProgressChangedEventArgs object containing information about the progress (in this case, i * 10 represents the percentage progress).

  1. Subscribing to the Event (Main method):
account.ProgressChanged += Account_ProgressChanged;

In the Main method or wherever account is used, the Account_ProgressChanged method is subscribed to the ProgressChanged event of the account object. This means whenever ProgressChanged is invoked within the Account class, Account_ProgressChanged will be called.

  1. Event Handler Implementation (Account_ProgressChanged method):
void Account_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
    Console.WriteLine($"{e.ProgressPercentage}%");
}

This method handles the ProgressChanged event. It simply prints out the progress percentage (e.ProgressPercentage) to the console whenever the event is fired.

  1. Executing the Transaction Printing (Main method):
Console.WriteLine("交易明細列印開始");
account.PrintTransactions();

Finally, in the Main method, after subscribing to the event and defining the event handler, the program starts printing transaction details by calling PrintTransactions on the account object. This method iterates through a loop (simulating the printing of 10 transactions), invoking the ProgressChanged event each time to report progress.

  1. Output:

As a result, when you run this code, it will print the progress of the transaction printing process to the console, using the Account_ProgressChanged method as the event handler.

In summary, this code demonstrates how events and event handlers work together in C#. The Account class uses an event to notify subscribers about the progress of a task (PrintTransactions), and the Main method subscribes to this event to monitor and react to progress updates.


Using Annoymous Methods instead of Implement new Event Handler

🐢 Before

3. Subscribe Event
account.ProgressChanged += Account_ProgressChanged;

// 4. Implement Event Handler
void Account_ProgressChanged(object? sender, ProgressChangedEventArgs e)
{
    Console.WriteLine($"{e.ProgressPercentage}%");
}

✨ After

account.ProgressChanged += delegate (object? sender, ProgressChangedEventArgs e)
{
    Console.WriteLine($"{e.ProgressPercentage}%");
};

Using Lambda Expression instead of Annoymous Methods

🐢 Before

account.ProgressChanged += delegate (object? sender, ProgressChangedEventArgs e)
{
    Console.WriteLine($"{e.ProgressPercentage}%");
};

✨ After

account.ProgressChanged += (sender, e) => Console.WriteLine($"{e.ProgressPercentage}%");

原始筆記內容

// 定義 Delegate Type
delegate int CalcDelegate(int a, int b);

// 實作 Add 方法的邏輯
static int Add(int a, int b)
{
	return a + b;
}

void Main()
{
	int result;

	// 使用 Delegate 搭配 Add 方法進行運算
	CalcDelegate calc = Add;
	result = calc(10, 5);

	// 使用 Func 搭配 Add 方法進行運算
	Func<int, int, int> calcFunc = Add;
	result = calc(10, 5);

	// 使用 Lambda Expression 定義 Add 方法
	Func<int, int, int> add = (a, b) => a + b;
	result = add(10, 5);
}

.NET BCL Series | Delegate, Events & Lambda

delegate int Delegator (int x);

static void Main()
{
	Delegator d = Square;
	int result = d(3); // 9
}

static int Square (int x) => x * x;

也可以使用 Invoke 的方式使用 Delegate:

Delegator d = new Delegator(Square);
d.Invoke(3);

Delegate 可以在 Runtime 重新指向符合 Signature (Return & Parameters Type) 的方法:

d = Cube;
int reulst d(3); // 27

static int Cube(int x) => x * x * x;

Multicatst

翻譯是多播,使用上可以在 Delegate 變數註冊多個方法,Delegate 會依序執行被註冊的方法。

delegate void Delegator();

static void Main()
{
  Delegator d = null;
  d += SayHi();
  d += SayBye();
  d();
}

static void SayHi() { Console.WriteLine("Hello World."); }
static void SayBye() { Console.WriteLine("Bye Bye."); }

Delegate 經常與 Interface 的 Polymorphism 進行比較,而多播的特性以及