筆記 C# 從 Delegate 到 Func & Action 的出現,到最後的 Lambda 對於語法使用上帶來什麼樣的差異。
說明
- 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 | 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:
- 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>
.
- 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).
- 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.
- 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.
- 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.
- 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 進行比較,而多播的特性以及