Sự khác biệt giữa các đại biểu và một sự kiện là gì? Không cả hai giữ tham chiếu đến các chức năng có thể được thực thi?
Sự khác biệt giữa các đại biểu và một sự kiện là gì? Không cả hai giữ tham chiếu đến các chức năng có thể được thực thi?
Câu trả lời:
Một tuyên bố sự kiện thêm một lớp trừu tượng và bảo vệ trên cá thể đại biểu . Sự bảo vệ này ngăn khách hàng của đại biểu đặt lại đại biểu và danh sách gọi của nó và chỉ cho phép thêm hoặc xóa các mục tiêu khỏi danh sách gọi.
Để hiểu sự khác biệt bạn có thể nhìn vào 2 ví dụ này
Ví dụ với Đại biểu (trong trường hợp này là Hành động - đó là một loại đại biểu không trả về giá trị)
public class Animal
{
public Action Run {get; set;}
public void RaiseEvent()
{
if (Run != null)
{
Run();
}
}
}
Để sử dụng đại biểu, bạn nên làm một cái gì đó như thế này:
Animal animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();
Mã này hoạt động tốt nhưng bạn có thể có một số điểm yếu.
Ví dụ, nếu tôi viết điều này:
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;
với dòng mã cuối cùng, tôi đã ghi đè các hành vi trước đó chỉ với một hành vi bị thiếu +
(tôi đã sử dụng =
thay vì +=
)
Một điểm yếu khác là mọi lớp sử dụng Animal
lớp của bạn có thể tăng RaiseEvent
chỉ bằng cách gọi nó animal.RaiseEvent()
.
Để tránh những điểm yếu này, bạn có thể sử dụng events
trong c #.
Lớp Thú của bạn sẽ thay đổi theo cách này:
public class ArgsSpecial : EventArgs
{
public ArgsSpecial (string val)
{
Operation=val;
}
public string Operation {get; set;}
}
public class Animal
{
// Empty delegate. In this way you are sure that value is always != null
// because no one outside of the class can change it.
public event EventHandler<ArgsSpecial> Run = delegate{}
public void RaiseEvent()
{
Run(this, new ArgsSpecial("Run faster"));
}
}
để gọi các sự kiện
Animal animal= new Animal();
animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
animal.RaiseEvent();
Sự khác biệt:
Ghi chú:
EventHandler được tuyên bố là đại biểu sau:
public delegate void EventHandler (object sender, EventArgs e)
nó cần một người gửi (thuộc loại Object) và các đối số sự kiện. Người gửi là null nếu nó đến từ các phương thức tĩnh.
Ví dụ này, sử dụng EventHandler<ArgsSpecial>
, cũng có thể được viết bằng cách sử dụngEventHandler
thay thế.
Tham khảo tại đây để có tài liệu về EventHandler
RaiseEvent
miễn là một phương thức gọi có quyền truy cập vào một thể hiện animal
trong mã sử dụng sự kiện?
animal.Run(this, new ArgsSpecial("Run faster");
?
Ngoài các thuộc tính cú pháp và hoạt động, còn có một sự khác biệt về ngữ nghĩa.
Các đại biểu, về mặt khái niệm, các mẫu chức năng; nghĩa là, họ thể hiện một hợp đồng mà một chức năng phải tuân thủ để được coi là "loại" của đại biểu.
Sự kiện đại diện cho ... tốt, sự kiện. Họ dự định cảnh báo ai đó khi có chuyện gì xảy ra và vâng, họ tuân thủ định nghĩa đại biểu nhưng họ không giống nhau.
Ngay cả khi chúng giống hệt nhau (về mặt cú pháp và mã IL) vẫn sẽ có sự khác biệt về ngữ nghĩa. Nói chung, tôi thích có hai tên khác nhau cho hai khái niệm khác nhau, ngay cả khi chúng được thực hiện theo cùng một cách (điều đó không có nghĩa là tôi muốn có cùng một mã hai lần).
Đây là một liên kết tốt để tham khảo. http://csharpindepth.com/Articles/Ch CHƯƠNG2 / Events.aspx
Tóm lại, lấy đi từ bài viết - Sự kiện được gói gọn trong các đại biểu.
Trích dẫn từ bài viết:
Giả sử các sự kiện không tồn tại như một khái niệm trong C # /. NET. Làm thế nào một lớp khác đăng ký một sự kiện? Ba lựa chọn:
Một biến đại biểu công khai
Một biến đại biểu được hỗ trợ bởi một thuộc tính
Một biến đại biểu với các phương thức AddXXXHandler và RemoveXXXHandler
Tùy chọn 1 rõ ràng là khủng khiếp, vì tất cả các lý do bình thường, chúng tôi ghê tởm các biến công khai.
Tùy chọn 2 tốt hơn một chút, nhưng cho phép các thuê bao ghi đè lên nhau một cách hiệu quả - sẽ rất dễ dàng để viết someInstance.MyEvent = eventHandler; mà sẽ thay thế bất kỳ trình xử lý sự kiện hiện có thay vì thêm một trình xử lý mới. Ngoài ra, bạn vẫn cần phải viết các thuộc tính.
Tùy chọn 3 về cơ bản là những gì các sự kiện mang lại cho bạn, nhưng với một quy ước được bảo đảm (được tạo bởi trình biên dịch và được hỗ trợ bởi các cờ bổ sung trong IL) và triển khai "miễn phí" nếu bạn hài lòng với ngữ nghĩa mà các sự kiện giống như trường cung cấp cho bạn. Đăng ký và hủy đăng ký khỏi các sự kiện được gói gọn mà không cho phép truy cập tùy ý vào danh sách các trình xử lý sự kiện và ngôn ngữ có thể làm mọi thứ đơn giản hơn bằng cách cung cấp cú pháp cho cả khai báo và đăng ký.
public Delegate
biến sẽ phơi bày "dữ liệu", nhưng theo hiểu biết tốt nhất của tôi thì OOP không bao giờ đề cập đến bất kỳ khái niệm nào giống như Delegate
(đó không phải là "đối tượng" hay "thông điệp") và .NET thực sự hầu như không đối xử với các đại biểu như dữ liệu.
AddXXXHandler
phương thức của riêng bạn với một private Delegate
biến có thể là một lựa chọn tốt. Trong trường hợp này, bạn có thể kiểm tra xem liệu trình xử lý đã được đặt chưa và phản ứng thích hợp. Đây cũng có thể là một thiết lập tốt nếu bạn cần đối tượng đang giữ Delegate
để có thể xóa tất cả các trình xử lý ( event
không cung cấp cho bạn bất kỳ cách nào để làm điều này).
LƯU Ý: Nếu bạn có quyền truy cập vào C # 5.0 Unleashed , hãy đọc phần "Hạn chế đối với việc sử dụng đại biểu" trong Chương 18 có tiêu đề "Sự kiện" để hiểu rõ hơn về sự khác biệt giữa hai điều này.
Nó luôn giúp tôi có một ví dụ đơn giản, cụ thể. Vì vậy, đây là một cho cộng đồng. Đầu tiên tôi chỉ ra cách bạn có thể sử dụng các đại biểu một mình để làm những gì Sự kiện làm cho chúng tôi. Sau đó, tôi chỉ ra cách giải pháp tương tự sẽ hoạt động với một thể hiện của EventHandler
. Và sau đó tôi giải thích lý do tại sao chúng ta KHÔNG muốn làm những gì tôi giải thích trong ví dụ đầu tiên. Bài viết này được lấy cảm hứng từ một bài viết của John Skeet.
Ví dụ 1: Sử dụng đại biểu công cộng
Giả sử tôi có một ứng dụng WinForms với một hộp thả xuống duy nhất. Việc thả xuống bị ràng buộc với một List<Person>
. Trường hợp Person có các thuộc tính của Id, Name, NickName, HairColor. Trên biểu mẫu chính là điều khiển người dùng tùy chỉnh hiển thị các thuộc tính của người đó. Khi ai đó chọn một người trong trình đơn thả xuống, nhãn trong bản cập nhật kiểm soát người dùng sẽ hiển thị các thuộc tính của người được chọn.
Đây là cách nó hoạt động. Chúng tôi có ba tệp giúp chúng tôi kết hợp chúng lại với nhau:
Đây là mã có liên quan cho mỗi lớp:
class Mediator
{
public delegate void PersonChangedDelegate(Person p); //delegate type definition
public static PersonChangedDelegate PersonChangedDel; //delegate instance. Detail view will "subscribe" to this.
public static void OnPersonChanged(Person p) //Form1 will call this when the drop-down changes.
{
if (PersonChangedDel != null)
{
PersonChangedDel(p);
}
}
}
Đây là kiểm soát người dùng của chúng tôi:
public partial class DetailView : UserControl
{
public DetailView()
{
InitializeComponent();
Mediator.PersonChangedDel += DetailView_PersonChanged;
}
void DetailView_PersonChanged(Person p)
{
BindData(p);
}
public void BindData(Person p)
{
lblPersonHairColor.Text = p.HairColor;
lblPersonId.Text = p.IdPerson.ToString();
lblPersonName.Text = p.Name;
lblPersonNickName.Text = p.NickName;
}
}
Cuối cùng, chúng ta có đoạn mã sau trong Form1.cs. Ở đây chúng tôi đang gọi OnPersonChanged, gọi bất kỳ mã nào được đăng ký cho đại biểu.
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Mediator.OnPersonChanged((Person)comboBox1.SelectedItem); //Call the mediator's OnPersonChanged method. This will in turn call all the methods assigned (i.e. subscribed to) to the delegate -- in this case `DetailView_PersonChanged`.
}
Đồng ý. Vì vậy, đó là cách bạn sẽ làm cho điều này hoạt động mà không cần sử dụng các sự kiện và chỉ sử dụng các đại biểu . Chúng tôi chỉ cần đưa một đại biểu công khai vào một lớp - bạn có thể làm cho nó tĩnh hoặc đơn lẻ, hoặc bất cứ điều gì. Tuyệt quá.
NHƯNG, NHƯNG, NHƯNG, chúng tôi không muốn làm những gì tôi vừa mô tả ở trên. Bởi vì các lĩnh vực công cộng là xấu cho nhiều, nhiều lý do. Vậy lựa chọn của chúng ta là gì? Như John Skeet mô tả, đây là các lựa chọn của chúng tôi:
PersonChangedDel = null
, xóa sạch tất cả các đăng ký khác. vấn đề khác vẫn còn ở đây là vì người dùng có quyền truy cập vào đại biểu, họ có thể gọi các mục tiêu trong danh sách gọi - chúng tôi không muốn người dùng bên ngoài có quyền truy cập khi nào sẽ tăng sự kiện của chúng tôi.Tùy chọn thứ ba này về cơ bản là những gì một sự kiện mang lại cho chúng ta. Khi chúng tôi khai báo EventHandler, nó cho phép chúng tôi truy cập vào một đại biểu - không công khai, không phải là một tài sản, nhưng vì điều này chúng tôi gọi là một sự kiện vừa thêm / xóa người truy cập.
Chúng ta hãy xem cùng một chương trình trông như thế nào, nhưng bây giờ sử dụng Sự kiện thay vì đại biểu công khai (Tôi cũng đã thay đổi Người hòa giải của chúng tôi thành người độc thân):
Ví dụ 2: Với EventHandler thay vì đại biểu công khai
Người hòa giải:
class Mediator
{
private static readonly Mediator _Instance = new Mediator();
private Mediator() { }
public static Mediator GetInstance()
{
return _Instance;
}
public event EventHandler<PersonChangedEventArgs> PersonChanged; //this is just a property we expose to add items to the delegate.
public void OnPersonChanged(object sender, Person p)
{
var personChangedDelegate = PersonChanged as EventHandler<PersonChangedEventArgs>;
if (personChangedDelegate != null)
{
personChangedDelegate(sender, new PersonChangedEventArgs() { Person = p });
}
}
}
Lưu ý rằng nếu bạn F12 trên EventHandler, nó sẽ hiển thị cho bạn định nghĩa chỉ là một đại biểu có ý nghĩa chung với đối tượng "người gửi" bổ sung:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
Kiểm soát người dùng:
public partial class DetailView : UserControl
{
public DetailView()
{
InitializeComponent();
Mediator.GetInstance().PersonChanged += DetailView_PersonChanged;
}
void DetailView_PersonChanged(object sender, PersonChangedEventArgs e)
{
BindData(e.Person);
}
public void BindData(Person p)
{
lblPersonHairColor.Text = p.HairColor;
lblPersonId.Text = p.IdPerson.ToString();
lblPersonName.Text = p.Name;
lblPersonNickName.Text = p.NickName;
}
}
Cuối cùng, đây là mã Form1.cs:
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
}
Vì EventHandler muốn và EventArss làm tham số, tôi đã tạo lớp này chỉ với một thuộc tính duy nhất trong đó:
class PersonChangedEventArgs
{
public Person Person { get; set; }
}
Hy vọng rằng điều đó cho bạn thấy một chút về lý do tại sao chúng tôi có các sự kiện và chúng khác nhau như thế nào - nhưng về mặt chức năng giống nhau - như các đại biểu.
The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events
. Trong phiên bản mới nhất của Mediator
, bạn vẫn có thể gọi OnPersonChange
bất cứ khi nào bạn có một tham chiếu đến singleton. Có lẽ bạn nên đề cập rằng Mediator
phương pháp này không ngăn chặn hành vi cụ thể đó và gần với xe buýt sự kiện hơn.
Bạn cũng có thể sử dụng các sự kiện trong khai báo giao diện, không phải như vậy đối với các đại biểu.
Action a { get; set; }
một định nghĩa giao diện.
Thật là một sự hiểu lầm lớn giữa các sự kiện và đại biểu !!! Một đại biểu chỉ định một LOẠI (chẳng hạn như một class
hoặc một interface
), trong khi một sự kiện chỉ là một loại MEMBER (chẳng hạn như các trường, thuộc tính, v.v.). Và, giống như bất kỳ loại thành viên nào khác, một sự kiện cũng có một loại. Tuy nhiên, trong trường hợp của một sự kiện, loại sự kiện phải được chỉ định bởi một đại biểu. Chẳng hạn, bạn KHÔNG THỂ khai báo một sự kiện thuộc loại được xác định bởi giao diện.
Kết luận, chúng ta có thể thực hiện Quan sát sau : loại sự kiện PHẢI được xác định bởi một đại biểu . Đây là mối quan hệ chính giữa một sự kiện và đại biểu và được mô tả trong phần II.18 Xác định các sự kiện của ECMA-335 (CLI) Phân vùng I đến VI :
Trong cách sử dụng thông thường, TypeSpec (nếu có) xác định một đại biểu có chữ ký khớp với các đối số được truyền cho phương thức chữa cháy của sự kiện.
Tuy nhiên, thực tế này KHÔNG ngụ ý rằng một sự kiện sử dụng trường đại biểu ủng hộ . Trong thực tế, một sự kiện có thể sử dụng trường sao lưu của bất kỳ loại cấu trúc dữ liệu khác nhau mà bạn chọn. Nếu bạn triển khai một sự kiện một cách rõ ràng trong C #, bạn có thể tự do chọn cách bạn lưu trữ các trình xử lý sự kiện (lưu ý rằng các trình xử lý sự kiện là các thể hiện của loại sự kiện , do đó bắt buộc là một loại đại biểu --- từ Quan sát trước đó ). Nhưng, bạn có thể lưu trữ các trình xử lý sự kiện đó (là các thể hiện ủy nhiệm) trong cấu trúc dữ liệu, chẳng hạn như List
một Dictionary
hoặc bất kỳ thứ gì khác, hoặc thậm chí trong trường đại biểu dự phòng. Nhưng đừng quên rằng bạn KHÔNG bắt buộc phải sử dụng trường đại biểu.
Một sự kiện trong .net là sự kết hợp được chỉ định của phương thức Thêm và phương thức Xóa, cả hai đều mong đợi một số loại đại biểu cụ thể. Cả C # và vb.net đều có thể tự động tạo mã cho các phương thức thêm và xóa sẽ xác định một đại biểu để giữ các đăng ký sự kiện và thêm / xóa thông qua ủy nhiệm cho / từ đại biểu đăng ký đó. VB.net cũng sẽ tự động tạo mã (với câu lệnh RaiseEvent) để gọi danh sách đăng ký khi và chỉ khi nó không trống; vì một số lý do, C # không tạo ra cái sau.
Lưu ý rằng mặc dù thông thường để quản lý đăng ký sự kiện bằng cách sử dụng đại biểu phát đa hướng, đó không phải là phương tiện duy nhất để làm như vậy. Từ góc độ công khai, người đăng ký sự kiện sẽ cần biết cách để cho đối tượng biết họ muốn nhận sự kiện, nhưng không cần biết nhà xuất bản sẽ sử dụng cơ chế nào để nâng cao sự kiện. Cũng lưu ý rằng trong khi bất kỳ ai xác định cấu trúc dữ liệu sự kiện trong .net dường như nghĩ rằng nên có một phương tiện công khai để nâng cao chúng, thì cả C # và vb.net đều không sử dụng tính năng đó.
Để xác định về sự kiện theo cách đơn giản:
Sự kiện là một TÀI LIỆU THAM KHẢO cho một đại biểu với hai hạn chế
Trên hai là những điểm yếu cho các đại biểu và nó được giải quyết trong sự kiện. Mẫu mã hoàn chỉnh để hiển thị sự khác biệt trong fiddler tại đây https://dotnetfiddle.net/5iR3fB .
Chuyển đổi nhận xét giữa Sự kiện và Đại biểu và mã máy khách gọi / gán giá trị cho đại biểu để hiểu sự khác biệt
Đây là mã nội tuyến.
/*
This is working program in Visual Studio. It is not running in fiddler because of infinite loop in code.
This code demonstrates the difference between event and delegate
Event is an delegate reference with two restrictions for increased protection
1. Cannot be invoked directly
2. Cannot assign value to delegate reference directly
Toggle between Event vs Delegate in the code by commenting/un commenting the relevant lines
*/
public class RoomTemperatureController
{
private int _roomTemperature = 25;//Default/Starting room Temperature
private bool _isAirConditionTurnedOn = false;//Default AC is Off
private bool _isHeatTurnedOn = false;//Default Heat is Off
private bool _tempSimulator = false;
public delegate void OnRoomTemperatureChange(int roomTemperature); //OnRoomTemperatureChange is a type of Delegate (Check next line for proof)
// public OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above),
public event OnRoomTemperatureChange WhenRoomTemperatureChange;// { get; set; }//Exposing the delegate to outside world, cannot directly expose the delegate (line above),
public RoomTemperatureController()
{
WhenRoomTemperatureChange += InternalRoomTemperatuerHandler;
}
private void InternalRoomTemperatuerHandler(int roomTemp)
{
System.Console.WriteLine("Internal Room Temperature Handler - Mandatory to handle/ Should not be removed by external consumer of ths class: Note, if it is delegate this can be removed, if event cannot be removed");
}
//User cannot directly asign values to delegate (e.g. roomTempControllerObj.OnRoomTemperatureChange = delegateMethod (System will throw error)
public bool TurnRoomTeperatureSimulator
{
set
{
_tempSimulator = value;
if (value)
{
SimulateRoomTemperature(); //Turn on Simulator
}
}
get { return _tempSimulator; }
}
public void TurnAirCondition(bool val)
{
_isAirConditionTurnedOn = val;
_isHeatTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
System.Console.WriteLine("Heat :" + _isHeatTurnedOn);
}
public void TurnHeat(bool val)
{
_isHeatTurnedOn = val;
_isAirConditionTurnedOn = !val;//Binary switch If Heat is ON - AC will turned off automatically (binary)
System.Console.WriteLine("Aircondition :" + _isAirConditionTurnedOn);
System.Console.WriteLine("Heat :" + _isHeatTurnedOn);
}
public async void SimulateRoomTemperature()
{
while (_tempSimulator)
{
if (_isAirConditionTurnedOn)
_roomTemperature--;//Decrease Room Temperature if AC is turned On
if (_isHeatTurnedOn)
_roomTemperature++;//Decrease Room Temperature if AC is turned On
System.Console.WriteLine("Temperature :" + _roomTemperature);
if (WhenRoomTemperatureChange != null)
WhenRoomTemperatureChange(_roomTemperature);
System.Threading.Thread.Sleep(500);//Every second Temperature changes based on AC/Heat Status
}
}
}
public class MySweetHome
{
RoomTemperatureController roomController = null;
public MySweetHome()
{
roomController = new RoomTemperatureController();
roomController.WhenRoomTemperatureChange += TurnHeatOrACBasedOnTemp;
//roomController.WhenRoomTemperatureChange = null; //Setting NULL to delegate reference is possible where as for Event it is not possible.
//roomController.WhenRoomTemperatureChange.DynamicInvoke();//Dynamic Invoke is possible for Delgate and not possible with Event
roomController.SimulateRoomTemperature();
System.Threading.Thread.Sleep(5000);
roomController.TurnAirCondition (true);
roomController.TurnRoomTeperatureSimulator = true;
}
public void TurnHeatOrACBasedOnTemp(int temp)
{
if (temp >= 30)
roomController.TurnAirCondition(true);
if (temp <= 15)
roomController.TurnHeat(true);
}
public static void Main(string []args)
{
MySweetHome home = new MySweetHome();
}
}
Delegate là một con trỏ hàm an toàn kiểu. Sự kiện là việc triển khai mẫu thiết kế nhà xuất bản-thuê bao sử dụng đại biểu.
Nếu bạn kiểm tra Ngôn ngữ trung gian, bạn sẽ biết trình biên dịch .net chuyển đổi ủy nhiệm thành một lớp kín trong IL với một số hàm dựng sẵn, như invoke, BeginInvoke, endInvoke và lớp ủy nhiệm được kế thừa từ một lớp khác, có thể được gọi là "SystemMulticast". Tôi đoán Sự kiện là một lớp con của Đại biểu với một số thuộc tính bổ sung.
Sự khác biệt giữa thể hiện của sự kiện và đại biểu là, bạn không thể chạy sự kiện bên ngoài khai báo. Nếu bạn khai báo một sự kiện trong lớp A, bạn chỉ có thể chạy sự kiện này trong lớp A. Nếu bạn khai báo một đại biểu trong Lớp A, bạn có thể sử dụng đại biểu này ở bất cứ đâu. Tôi nghĩ rằng đây là một sự khác biệt chính giữa họ