C# Day 6: Delegates, Func, Action, Lambda, Closure và Events
Ngày 6 tập trung vào các concept quan trọng:
Delegate
Func
Action
Predicate
Lambda
Closure
Multicast Delegate
Event
Publisher / Subscriber
EventHandler<TEventArgs>
Đây là nền tảng để hiểu các API như:
Where(...)
Select(...)
Any(...)
Task.Run(...)
button.Click += ...
Mục tiêu
Cuối ngày cần trả lời được:
Delegate là gì?
Func và Action khác nhau thế nào?
Lambda là gì?
Closure là gì?
Multicast delegate hoạt động ra sao?
Event khác delegate ở đâu?
Publisher / Subscriber là gì?
1. Delegate là gì?
Delegate là một type-safe reference tới method.
public delegate int Calculate(int a, int b);
Method tương thích:
static int Add(int a, int b)
{
return a + b;
}
Gán method:
Calculate calculate = Add;
int result = calculate(2, 3);
Mental model:
Delegate
→ reference tới method
→ method phải match signature
2. Signature Matching
Delegate:
public delegate int Calculate(int a, int b);
thì method phải có:
2 parameter kiểu int
+
return int
Đây là lý do delegate là type-safe.
3. Delegate cho phép truyền Behavior như Data
static int Add(int a, int b)
{
return a + b;
}
static int Multiply(int a, int b)
{
return a * b;
}
Delegate:
public delegate int Operation(int a, int b);
Method nhận delegate:
static int Execute(
int a,
int b,
Operation operation)
{
return operation(a, b);
}
Dùng:
var sum = Execute(2, 3, Add);
var product = Execute(2, 3, Multiply);
Mental model:
Data
→ 2, 3
Behavior
→ Add hoặc Multiply
4. Method Group
static bool IsAdult(User user)
{
return user.Age >= 18;
}
Có thể:
Func<User, bool> predicate = IsAdult;
IsAdult ở đây là method group.
5. Func<>
Func<> là generic delegate có return value.
Func<int, int, int> add =
(a, b) => a + b;
Cách đọc:
Func<int, int, int>
↑ ↑ ↑
input input return
Type cuối cùng là return type.
Ví dụ:
Func<User, string> getName =
user => user.Name;
6. Một số dạng Func<>
Không có input:
Func<DateTime> getNow =
() => DateTime.UtcNow;
Một input:
Func<int, bool> isAdult =
age => age >= 18;
Hai input:
Func<int, int, int> add =
(a, b) => a + b;
7. Action<>
Action<> là delegate trả về void.
Action<string> print =
message => Console.WriteLine(message);
Không parameter:
Action sayHello =
() => Console.WriteLine("Hello");
Mental model:
Func
→ có return value
Action
→ void
8. Predicate<T>
Predicate<T> có shape:
T
→ bool
Ví dụ:
Predicate<int> isAdult =
age => age >= 18;
Nó tương tự về shape với:
Func<int, bool>
LINQ thường dùng Func<T, bool>.
9. Lambda Expression
Lambda là cách viết anonymous function.
x => x * 2
Ví dụ:
Func<int, int> doubleValue =
x => x * 2;
Nhiều parameter:
Func<int, int, int> add =
(a, b) => a + b;
Nhiều statement:
Func<int, int> calculate =
x =>
{
var doubled = x * 2;
return doubled + 10;
};
Mental model:
lambda
→ unnamed function
10. Lambda không phải lúc nào cũng là Delegate
Lambda có thể được convert thành delegate:
Func<User, bool> predicate =
user => user.IsActive;
Nhưng trong một số context, nó có thể thành expression tree:
Expression<Func<User, bool>>
Hiện tại chỉ cần nhớ:
Lambda
→ syntax
Delegate
→ runtime callable type
11. Callback
Callback là pattern:
Truyền một function cho code khác để code đó gọi lại khi cần.
static void Process(Action onCompleted)
{
Console.WriteLine("Processing...");
onCompleted();
}
Dùng:
Process(
() => Console.WriteLine("Done")
);
12. Closure
Ví dụ:
var minimumAge = 18;
Func<User, bool> filter =
user => user.Age >= minimumAge;
Lambda sử dụng biến bên ngoài:
minimumAge
Đây là captured variable.
Mental model:
Closure
→ function + captured surrounding state
13. Captured Variable không đơn giản là Copy Value
var minimumAge = 18;
Func<int, bool> check =
age => age >= minimumAge;
minimumAge = 21;
Console.WriteLine(check(20));
Kết quả:
False
Vì lambda capture biến minimumAge, không đơn giản copy giá trị 18.
14. Closure trong Loop
var actions = new List<Action>();
for (var i = 0; i < 3; i++)
{
var current = i;
actions.Add(
() => Console.WriteLine(current)
);
}
Tạo current giúp mỗi callback giữ giá trị riêng theo iteration.
Closure rất tiện nhưng cần hiểu captured-variable semantics.
15. Multicast Delegate
Delegate có thể có nhiều method trong invocation list.
Action action = MethodA;
action += MethodB;
action += MethodC;
action();
Conceptually:
MethodA()
↓
MethodB()
↓
MethodC()
16. += và -=
action += MethodB;
thêm handler.
action -= MethodB;
remove handler.
Mental model:
+=
→ add vào invocation list
-=
→ remove khỏi invocation list
17. Multicast Delegate có Return Value
Func<int> func = GetA;
func += GetB;
var result = func();
Các handler có thể đều được gọi, nhưng giá trị nhận trực tiếp từ invocation thông thường là return value của handler cuối.
Vì vậy multicast delegate thường dễ hiểu hơn với notification/Action.
18. Exception trong Multicast Delegate
Nếu:
Handler A
→ OK
Handler B
→ throw
Handler C
thì khi invoke thông thường, Handler C có thể không được gọi.
Nếu cần isolation giữa handlers thì phải tự thiết kế xử lý.
19. event là gì?
event là notification mechanism được xây trên delegate.
public class OrderService
{
public event Action? OrderCompleted;
public void Complete()
{
OrderCompleted?.Invoke();
}
}
Subscribe:
orderService.OrderCompleted
+= OnOrderCompleted;
Handler:
static void OnOrderCompleted()
{
Console.WriteLine("Order completed");
}
20. Publisher / Subscriber
Mental model:
Publisher
→ sở hữu / raise event
Subscriber
→ subscribe / xử lý event
Ví dụ:
OrderService
→ publisher
EmailNotifier
AuditLogger
→ subscribers
21. Delegate vs Event
Delegate field:
public Action? Completed;
Caller có thể assign lại:
service.Completed = MyHandler;
Event:
public event Action? Completed;
Consumer chủ yếu được:
+=
-=
Còn publisher kiểm soát:
Completed?.Invoke();
Mental model:
delegate
→ callable behavior reference
event
→ controlled notification mechanism
22. ?.Invoke()
OrderCompleted?.Invoke();
Conceptually:
if (OrderCompleted is not null)
{
OrderCompleted.Invoke();
}
23. EventHandler<TEventArgs>
.NET thường dùng event pattern:
public event EventHandler<OrderEventArgs>?
OrderCompleted;
Event args:
public sealed class OrderEventArgs : EventArgs
{
public Guid OrderId { get; }
public OrderEventArgs(Guid orderId)
{
OrderId = orderId;
}
}
Raise:
OrderCompleted?.Invoke(
this,
new OrderEventArgs(orderId)
);
Mental model:
sender
→ ai phát event
event args
→ dữ liệu event
24. Subscribe và Unsubscribe
Subscribe:
service.OrderCompleted
+= OnOrderCompleted;
Unsubscribe:
service.OrderCompleted
-= OnOrderCompleted;
Nếu publisher sống lâu hơn subscriber, không unsubscribe trong một số scenario có thể khiến publisher giữ reference tới subscriber lâu hơn mong muốn.
25. Lambda và Unsubscribe
Không nên viết:
service.Completed +=
() => Console.WriteLine("Done");
service.Completed -=
() => Console.WriteLine("Done");
và kỳ vọng hai lambda là cùng handler instance.
Nếu cần unsubscribe:
Action handler =
() => Console.WriteLine("Done");
service.Completed += handler;
// later
service.Completed -= handler;
26. Delegate trong LINQ
Ví dụ:
users.Where(
user => user.IsActive
);
Where với LINQ to Objects cơ bản nhận predicate có shape:
Func<User, bool>
Flow:
User
↓
predicate
↓
true / false
27. Delegate trong Select
users.Select(
user => user.Name
);
Lambda có shape:
User
→ string
Tương tự:
Func<User, string>
28. Delegate trong Sort
users.Sort(
(a, b) =>
a.Name.CompareTo(b.Name)
);
Bạn truyền comparison behavior vào sorting algorithm.
Mental model:
Algorithm
+
behavior được inject
29. Delegate vs Interface Callback
Interface:
public interface ICallback
{
void OnCompleted();
}
Delegate:
Action onCompleted
Delegate phù hợp khi:
callback nhỏ
một behavior
muốn dùng lambda
Interface phù hợp khi:
contract nhiều method
state/lifecycle rõ ràng
domain abstraction
object dependency
Mental model:
Delegate
→ function contract
Interface
→ object capability contract
30. C# Event khác Distributed Event
C# event:
in-process
same application memory
delegate-based
Distributed event/message:
cross-process
cross-service
network
durability/retry có thể cần
Ví dụ:
Kafka
RabbitMQ
Azure Service Bus
Không nên xem C# event là message broker.
31. Common Mistake — Dùng Event cho Request/Response
Nếu caller cần:
"Tính và trả kết quả cho tôi"
thì method/function thường phù hợp hơn.
Event phù hợp với:
"Điều gì đó vừa xảy ra"
Ví dụ:
OrderCompleted
UserRegistered
ButtonClicked
32. Common Mistake — Capture quá nhiều State
var hugeObject = LoadHugeObject();
Action action =
() => Console.WriteLine(
hugeObject.Name
);
Closure có thể giữ hugeObject sống lâu hơn dự kiến.
Closure không xấu, nhưng cần hiểu lifetime.
33. Production Example — Validation Strategy
static bool ValidateUser(
User user,
Func<User, bool> rule)
{
return rule(user);
}
Dùng:
var valid =
ValidateUser(
user,
x => x.Age >= 18
);
Hoặc:
var valid =
ValidateUser(
user,
x => x.IsActive
);
Một algorithm, nhiều behavior.
34. Production Example — Event Notification
public sealed class OrderService
{
public event EventHandler<OrderEventArgs>?
OrderCompleted;
public void Complete(Guid orderId)
{
OrderCompleted?.Invoke(
this,
new OrderEventArgs(orderId)
);
}
}
Subscriber:
orderService.OrderCompleted
+= OnOrderCompleted;
35. Mental Model Tổng Kết
Delegate
→ type-safe reference tới method
Func<>
→ delegate có return value
Action<>
→ delegate trả void
Predicate<T>
→ T vào, bool ra
Lambda
→ anonymous function syntax
Closure
→ function + captured state
Multicast Delegate
→ nhiều handlers
Event
→ controlled notification mechanism
Publisher
→ raise event
Subscriber
→ listen event
36. Bảng So Sánh
| Concept | Input | Output | Use case |
|---|---|---|---|
Action |
0+ | void |
execute behavior |
Func<TResult> |
0+ | value | calculation/transformation |
Predicate<T> |
1 | bool |
condition |
| Delegate | tùy signature | tùy signature | function contract |
| Event | delegate-based | notification | publisher/subscriber |
37. Câu Hỏi Ôn Tập
1. Delegate là gì?
Một type-safe reference tới method có signature tương thích.
2. Delegate giúp gì?
Cho phép truyền behavior vào code khác.
3. Func<int, string> đọc thế nào?
Nhận int, trả string.
4. Func<int, int, bool> đọc thế nào?
Nhận hai int, trả bool.
5. Action<User> nghĩa là gì?
Nhận User, trả void.
6. Predicate<User> nghĩa là gì?
Nhận User, trả bool.
7. Lambda là gì?
Syntax để biểu diễn anonymous function.
8. Lambda có luôn là delegate không?
Không. Nó có thể được convert sang delegate hoặc expression tree tùy context.
9. Closure là gì?
Function kết hợp với state được capture từ surrounding scope.
10. Captured variable có luôn copy giá trị lúc tạo lambda không?
Không.
11. Multicast delegate là gì?
Delegate có invocation list chứa nhiều method.
12. += làm gì?
Thêm handler.
13. -= làm gì?
Remove handler.
14. Event khác delegate field thế nào?
Event giới hạn quyền consumer chủ yếu ở subscribe/unsubscribe; publisher kiểm soát raise.
15. Publisher là gì?
Object/type phát event.
16. Subscriber là gì?
Code đăng ký xử lý event.
17. EventHandler<TEventArgs> truyền gì?
sender
+
event args
18. Vì sao cần unsubscribe?
Để tránh callback không mong muốn và tránh publisher giữ subscriber lâu hơn cần thiết trong một số lifecycle.
19. Vì sao LINQ cần delegate?
Vì LINQ nhận behavior như predicate hoặc selector.
20. Where(x => ...) có shape gì?
Với LINQ to Objects cơ bản:
Func<T, bool>
38. Bài Tập Ngày 6
Bài 1 — Custom Delegate
public delegate decimal CalculatePrice(
decimal price,
decimal discount
);
Tạo:
PercentageDiscount
FixedDiscount
và truyền từng method vào processor.
Bài 2 — Func
Tạo:
Func<User, string>
để convert:
User
→ DisplayName
Bài 3 — Action
Tạo:
Action<string>
để log message.
Bài 4 — Predicate
Tạo:
Predicate<User>
kiểm tra:
Age >= 18
Bài 5 — Closure
var minAge = 18;
Tạo:
Func<User, bool>
capture minAge.
Sau đó đổi:
minAge = 21;
và giải thích kết quả.
Bài 6 — Multicast Delegate
Tạo:
Action notification;
gắn:
SendEmail
WriteAuditLog
UpdateCache
và quan sát invocation order.
Bài 7 — Event
Tạo OrderService có:
OrderCompleted
với hai subscriber:
EmailNotifier
AuditLogger
39. Production Exercise
Tự viết:
public static IEnumerable<User> FilterUsers(
IEnumerable<User> users,
Func<User, bool> predicate)
{
foreach (var user in users)
{
if (predicate(user))
{
yield return user;
}
}
}
Dùng:
var activeUsers =
FilterUsers(
users,
x => x.IsActive
);
và:
var adults =
FilterUsers(
users,
x => x.Age >= 18
);
Tự giải thích:
Algorithm
→ FilterUsers
Behavior
→ predicate
Delegate
→ inject behavior
Đây chính là tư duy phía sau nhiều API LINQ.
Key Takeaways
Delegate
→ function contract
Func
→ function có return value
Action
→ function trả void
Predicate
→ condition function
Lambda
→ anonymous-function syntax
Closure
→ lambda/function capture surrounding state
Multicast Delegate
→ nhiều function trong invocation list
Event
→ notification có kiểm soát
Điểm quan trọng nhất:
Delegate cho phép behavior được truyền và compose giống một value.
Ngày tiếp theo:
Day 7
→ LINQ cơ bản
với:
Where
Select
Any
All
First
Single
OrderBy
GroupBy
SelectMany
Deferred Execution