Tôi nhận ra rằng câu hỏi này đã hơn 10 tuổi, nhưng dường như tôi không chỉ có câu trả lời rõ ràng nhất mà còn không thực sự rõ ràng từ câu hỏi về sự hiểu biết tốt về những gì diễn ra dưới vỏ bọc. Ngoài ra, có những câu hỏi khác về ràng buộc muộn và điều đó có nghĩa gì đối với các đại biểu và lambdas (nhiều hơn về điều đó sau).
Đầu tiên để giải quyết con voi / khỉ đột 800 lb trong phòng, khi nào nên chọn event
vs Action<T>
/ Func<T>
:
- Sử dụng lambda để thực thi một câu lệnh hoặc phương thức. Sử dụng
event
khi bạn muốn nhiều hơn một mô hình pub / sub với nhiều câu lệnh / lambdas / hàm sẽ thực thi (đây là một sự
khác biệt lớn ngay lập tức).
- Sử dụng lambda khi bạn muốn biên dịch các câu lệnh / hàm để biểu thị cây. Sử dụng các đại biểu / sự kiện khi bạn muốn tham gia vào ràng buộc muộn truyền thống hơn, chẳng hạn như được sử dụng trong phản chiếu và xen kẽ COM.
Ví dụ về một sự kiện, cho phép kết nối một tập hợp các sự kiện đơn giản và 'tiêu chuẩn' bằng ứng dụng bảng điều khiển nhỏ như sau:
public delegate void FireEvent(int num);
public delegate void FireNiceEvent(object sender, SomeStandardArgs args);
public class SomeStandardArgs : EventArgs
{
public SomeStandardArgs(string id)
{
ID = id;
}
public string ID { get; set; }
}
class Program
{
public static event FireEvent OnFireEvent;
public static event FireNiceEvent OnFireNiceEvent;
static void Main(string[] args)
{
OnFireEvent += SomeSimpleEvent1;
OnFireEvent += SomeSimpleEvent2;
OnFireNiceEvent += SomeStandardEvent1;
OnFireNiceEvent += SomeStandardEvent2;
Console.WriteLine("Firing events.....");
OnFireEvent?.Invoke(3);
OnFireNiceEvent?.Invoke(null, new SomeStandardArgs("Fred"));
//Console.WriteLine($"{HeightSensorTypes.Keyence_IL030}:{(int)HeightSensorTypes.Keyence_IL030}");
Console.ReadLine();
}
private static void SomeSimpleEvent1(int num)
{
Console.WriteLine($"{nameof(SomeSimpleEvent1)}:{num}");
}
private static void SomeSimpleEvent2(int num)
{
Console.WriteLine($"{nameof(SomeSimpleEvent2)}:{num}");
}
private static void SomeStandardEvent1(object sender, SomeStandardArgs args)
{
Console.WriteLine($"{nameof(SomeStandardEvent1)}:{args.ID}");
}
private static void SomeStandardEvent2(object sender, SomeStandardArgs args)
{
Console.WriteLine($"{nameof(SomeStandardEvent2)}:{args.ID}");
}
}
Đầu ra sẽ như sau:

Nếu bạn đã làm tương tự với Action<int>
hoặc Action<object, SomeStandardArgs>
, bạn sẽ chỉ nhìn thấy SomeSimpleEvent2
và SomeStandardEvent2
.
Vì vậy, những gì đang xảy ra bên trong event
?
Nếu chúng tôi mở rộng ra FireNiceEvent
, trình biên dịch thực sự tạo ra những điều sau đây (tôi đã bỏ qua một số chi tiết liên quan đến đồng bộ hóa luồng không liên quan đến cuộc thảo luận này):
private EventHandler<SomeStandardArgs> _OnFireNiceEvent;
public void add_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
{
Delegate.Combine(_OnFireNiceEvent, handler);
}
public void remove_OnFireNiceEvent(EventHandler<SomeStandardArgs> handler)
{
Delegate.Remove(_OnFireNiceEvent, handler);
}
public event EventHandler<SomeStandardArgs> OnFireNiceEvent
{
add
{
add_OnFireNiceEvent(value)
}
remove
{
remove_OnFireNiceEvent(value)
}
}
Trình biên dịch tạo ra một biến đại biểu riêng không thể nhìn thấy đối với không gian tên lớp mà nó được tạo. Đại biểu đó là những gì được sử dụng để quản lý đăng ký và tham gia ràng buộc muộn và giao diện công khai là giao diện quen thuộc +=
và các -=
nhà khai thác mà tất cả chúng ta đều biết và yêu thích :)
Bạn có thể tùy chỉnh mã cho trình xử lý thêm / xóa bằng cách thay đổi phạm vi của FireNiceEvent
đại biểu thành được bảo vệ. Điều này hiện cho phép các nhà phát triển thêm các móc tùy chỉnh vào các móc, chẳng hạn như ghi nhật ký hoặc móc bảo mật. Điều này thực sự làm cho một số tính năng rất mạnh mẽ hiện cho phép tùy chỉnh khả năng truy cập để đăng ký dựa trên vai trò của người dùng, v.v. Bạn có thể làm điều đó với lambdas không? (Trên thực tế bạn có thể bằng cách biên dịch cây biểu thức tùy chỉnh, nhưng điều đó nằm ngoài phạm vi của phản hồi này).
Để giải quyết một vài điểm từ một số câu trả lời ở đây:
Thực sự không có sự khác biệt về 'độ giòn' giữa việc thay đổi danh sách đối số Action<T>
và thay đổi các thuộc tính trong một lớp có nguồn gốc từ EventArgs
. Cả hai sẽ không chỉ yêu cầu thay đổi biên dịch, cả hai sẽ thay đổi giao diện công cộng và sẽ yêu cầu phiên bản. Không khác nhau.
Đối với tiêu chuẩn ngành nào, điều đó phụ thuộc vào việc sử dụng cái này ở đâu và tại sao. Action<T>
và như vậy thường được sử dụng trong IoC và DI, và event
thường được sử dụng trong định tuyến thư như khung kiểu GUI và MQ. Lưu ý rằng tôi nói thường xuyên , không phải lúc nào .
Các đại biểu có tuổi thọ khác với lambdas. Người ta cũng phải nhận thức được việc bắt giữ ... không chỉ với việc đóng cửa, mà còn với khái niệm 'nhìn những gì con mèo kéo vào'. Điều này không ảnh hưởng đến dung lượng bộ nhớ / tuổi thọ cũng như quản lý hay rò rỉ.
Một điều nữa, một cái gì đó tôi đã tham khảo trước đó ... khái niệm ràng buộc muộn. Bạn sẽ thường thấy điều này khi sử dụng khung như LINQ, liên quan đến thời điểm lambda trở thành 'sống'. Điều đó rất khác so với ràng buộc muộn của một đại biểu, điều này có thể xảy ra nhiều hơn một lần (tức là lambda luôn ở đó, nhưng ràng buộc xảy ra theo yêu cầu thường xuyên như cần thiết), trái ngược với lambda, một khi nó xảy ra, nó đã được thực hiện - ma thuật đã biến mất và phương thức / tài sản (ies) sẽ luôn luôn ràng buộc. Một cái gì đó để giữ trong tâm trí.