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