Tại sao chúng ta cần từ khóa "sự kiện" khi xác định sự kiện?


108

Tôi không hiểu tại sao chúng ta cần từ khóa "event" trong khi xác định sự kiện, khi chúng ta có thể làm điều tương tự mà không cần sử dụng từ khóa "event", chỉ bằng cách sử dụng các đại diện.

ví dụ

public delegate void CustomEventHandler(int a, string b);
public event CustomEventHandler customEvent;
customEvent += new CustomEventHandler(customEventHandler);
customEvent(1,"a"); // Raising the event

Ở đây nếu tôi xóa từ khóa "sự kiện" khỏi dòng thứ hai, thì tôi cũng có thể nâng cao sự kiện bằng cách gọi đại biểu. Ai có thể vui lòng cho tôi biết tại sao lại cần từ khóa sự kiện này không?


ok nếu bạn không sử dụng từ khóa sự kiện, bất kỳ ai có thể truy cập sự kiện đó bằng đối tượng lớp, hãy đặt nó thành NULL như objClass.SelectedIndexChanged = null. điều này sẽ làm hỏng mã cơ bản của bạn. từ khóa sự kiện buộc người dùng chỉ định một cái gì đó tương tự như ủy quyền bằng cách sử dụng + =.
Sumit Kapadia vào

Câu trả lời:


141

Các sự kiện giống trường và các trường công khai của các loại đại biểu trông giống nhau, nhưng thực tế rất khác nhau.

Một sự kiện về cơ bản giống như một thuộc tính - nó là một cặp phương thức thêm / xóa (thay vì get / set của một thuộc tính). Khi bạn khai báo một sự kiện giống trường (tức là một sự kiện mà bạn không tự chỉ định thêm / bớt bit) thì một sự kiện công khai sẽ được tạo và một trường hỗ trợ riêng tư. Điều này cho phép bạn nâng cao sự kiện một cách riêng tư, nhưng cho phép đăng ký công khai. Với trường đại biểu công khai, bất kỳ ai cũng có thể xóa trình xử lý sự kiện của người khác, tự nâng cao sự kiện, v.v. - đó là một thảm họa đóng gói.

Để biết thêm về các sự kiện (và các đại biểu), hãy đọc bài viết của tôi về chủ đề này . (Tại một số thời điểm, tôi cần cập nhật điều này cho C # 4, điều này sẽ thay đổi các sự kiện giống trường rất nhẹ. Tuy nhiên, ý chính của nó vẫn chính xác.)


17
Điều này tốt hơn một nghìn lần so với lời giải thích một dòng chính thức của MSDN: 'Từ khóa sự kiện được sử dụng để khai báo một sự kiện trong lớp nhà xuất bản.'
cowlinator

37

Từ khóa sự kiện thực hiện 3 điều khác nhau:

  1. Bạn có thể xác định một sự kiện trong giao diện, mặc dù bạn không thể xác định các trường thông thường trong giao diện.
  2. Nó thay đổi khả năng hiển thị của toán tử =and ()(gán và gọi) thành private, do đó chỉ lớp chứa mới có thể gọi sự kiện hoặc ghi đè tất cả các phương thức chứa trong nó. Các toán tử -=+=vẫn có thể được gọi trên một sự kiện từ bên ngoài lớp xác định nó (chúng nhận được công cụ sửa đổi truy cập mà bạn đã viết bên cạnh sự kiện).
  3. Bạn cũng có thể ghi đè cách thức -=+=hành vi trên các sự kiện.

2
bạn> MSDN. Cảm ơn.
M. Azyoksul

26

Các câu trả lời khác là tốt; Tôi chỉ muốn thêm một cái gì đó khác để suy nghĩ về.

Câu hỏi của bạn là "tại sao chúng ta cần sự kiện khi chúng ta có các trường kiểu đại biểu?" Tôi sẽ mở rộng câu hỏi đó: tại sao bạn cần các phương thức, thuộc tính, sự kiện, hàm tạo cá thể hoặc trình hoàn thiện nếu bạn có các trường kiểu đại biểu? Tại sao bạn cần bất cứ thứ gì khác ngoài các trường chứa giá trị và đại biểu trong một kiểu? Tại sao không chỉ nói

class C
{
    private int z;
    public readonly Func<int, int> M = (int x)=>{ return x+z; }
    // ... and so on
}

?

Bạn không cần phương thức, thuộc tính hoặc sự kiện. Chúng tôi cung cấp cho bạn nội dung đó vì phương pháp, thuộc tính và các mẫu thiết kế sự kiện là quan trọng và hữu ích, đồng thời xứng đáng có một cách chuẩn, được lập thành văn bản, rõ ràng để triển khai chúng bằng ngôn ngữ.


4
Chà! Nó nhắc nhở tại sao tôi yêu c #! Trong số tất cả các ngôn ngữ tôi đã làm việc, nó có sự cân bằng phù hợp giữa tính nhỏ gọn, tính linh hoạt và ngữ nghĩa dễ đọc. Ngôn ngữ có thể so sánh duy nhất là Object Pascal.
ATL_DEV

1
@ATL_DEV: Có lý do cho điều đó. Kiến trúc sư của ngôn ngữ C #, Anders Hejlsberg, trước đây là kiến ​​trúc sư của Delphi, một ngôn ngữ dựa trên Object Pascal.
Eric Lippert

9

Nó cần một phần vì nếu bạn bỏ qua eventtừ khóa, nó sẽ phá vỡ tính đóng gói. Nếu nó chỉ là một đại biểu phát đa hướng công khai, bất kỳ ai cũng có thể gọi nó, đặt nó thành null hoặc giả mạo nó. Nếu một lớp được gọi MailNotifiertồn tại và nó có một sự kiện được gọi MailReceived, thì các kiểu khác không thể kích hoạt sự kiện đó thông qua việc gọi mailNotifier.MailReceived();

Mặt khác, bạn chỉ có thể can thiệp và gọi các sự kiện 'trường giống như' từ loại đã xác định nó.

Nếu bạn muốn giữ lời gọi sự kiện của mình ở chế độ riêng tư, không có gì ngăn bạn làm điều gì đó như sau:

public class MyClassWithNonFieldLikeEvent
{
   private CustomEventHandler m_delegate;

   public void Subscribe(CustomEventHandler handler) 
   {
      m_delegate += handler;        
   }

   public void Unsubscribe(CustomEventHandler handler)
   {          
      m_delegate -= handler;
   }

   private void DoSomethingThatRaisesEvent()
   {
      m_delegate.Invoke(...);
   }       
}

... nhưng đó là cả một đống mã chỉ để (ít nhiều) thực hiện những gì các sự kiện giống trường đã cung cấp cho chúng ta.


Nó cũng sẽ khó hơn đối với những thứ như nhà thiết kế sử dụng ... về cơ bản bạn sẽ dựa vào các quy ước đặt tên cho các phương thức, thay vì có siêu dữ liệu công khai nói rằng "đây là một sự kiện".
Jon Skeet

3

Sự kiện có lợi thế khác biệt so với trường đại biểu. Các sự kiện có thể được định nghĩa trong các giao diện ngược lại với các trường, thêm tính trừu tượng vào mã và quan trọng hơn: Sự kiện chỉ có thể được gọi từ bên trong lớp xác định. Trong trường hợp của bạn, bất kỳ ai cũng có thể gọi sự kiện, có thể phá hủy mã của bạn.

Xem bài đăng trên blog này để biết thêm thông tin.


2
Bạn không thực sự nên so sánh các sự kiện và các đại biểu - so sánh các sự kiện và các trường công khai với một loại đại biểu . Và không, khuôn khổ không yêu cầu các sự kiện phải có chữ ký đó. Bạn có thể tạo sự kiện thuộc bất kỳ kiểu đại biểu nào mà bạn thích.
Jon Skeet

3

ủy nhiệm là một loại tham chiếu. Nó kế thừa MulticastDelegate . sự kiện là một Modifier. biến cốlà một bổ ngữ đặc biệt cho đại biểu. Nó sửa đổi một số khả năng truy cập chức năng / phương thức, ví dụ như phương thức Gọi. Sau khi được sửa đổi bởi sự kiện bổ trợ, một cá thể đại biểu trở thành một khái niệm mới "Sự kiện". Vì vậy, Sự kiện chỉ là một đại biểu được sửa đổi. Bạn không thể trực tiếp thay đổi tham chiếu hoặc gọi một Sự kiện bên ngoài lớp mà Sự kiện đã được định nghĩa, nhưng bạn có thể thay đổi tham chiếu hoặc gọi một cá thể đại biểu bình thường. Sự kiện cung cấp biện pháp bảo vệ bổ sung, để Sự kiện có nhiều tính năng an toàn hơn. Khi bạn ở bên ngoài lớp nơi sự kiện được xác định, bạn được phép thực hiện hai loại hoạt động đối với Sự kiện, "+ =" và "- =". Nhưng bạn có thể truy cập vào tất cả trường công khai, thuộc tính, phương thức, v.v. của một cá thể đại biểu bình thường. Đây là một ví dụ:

namespace DelegateEvent
{
    //the following line behave as a class. It is indeed a reference type
    public delegate void MyDelegate(string inputs);

    //The following line is illegal. It can only be an instance. so it cannot be directly under namespace
    //public event MyDelegate MyEvent;


    public class MyClassA
    {
        public event MyDelegate MyEventA;
        public MyDelegate MyDelegateA;


        System.Threading.ManualResetEvent MyResetEvent = new System.Threading.ManualResetEvent(false);
        public void TryToDoSomethingOnMyDelegateA()
        {
            if (MyDelegateA != null)
            {
                //User can assecc all the public methods.
                MyDelegateA("I can invoke detegate in classA");         //invoke delegate
                MyDelegateA.Invoke("I can invoke detegate in classA");  //invoke delegate
                IAsyncResult result = MyDelegateA.BeginInvoke("I can invoke detegate in classA", MyAsyncCallback, MyResetEvent);    //Async invoke
                //user can check the public properties and fields of delegate instance
                System.Reflection.MethodInfo delegateAMethodInfo = MyDelegateA.Method;

                MyDelegateA = testMethod;                   //reset reference
                MyDelegateA = new MyDelegate(testMethod);   //reset reference
                MyDelegateA = null;                         //reset reference


                MyDelegateA += testMethod;                  //Add delegate
                MyDelegateA += new MyDelegate(testMethod);  //Add delegate
                MyDelegateA -= testMethod;                  //Remove delegate
                MyDelegateA -= new MyDelegate(testMethod);  //Remove delegate
            }
        }

        public void TryToDoSomethingOnMyEventA()
        {
            if (MyEventA != null)
            {
                MyEventA("I can invoke Event in classA");           //invoke Event
                MyEventA.Invoke("I can invoke Event in classA");    //invoke Event
                IAsyncResult result = MyEventA.BeginInvoke("I can invoke Event in classA", MyAsyncCallback, MyResetEvent);      //Async invoke
                //user can check the public properties and fields of MyEventA
                System.Reflection.MethodInfo delegateAMethodInfo = MyEventA.Method;


                MyEventA = testMethod;                   //reset reference
                MyEventA = new MyDelegate(testMethod);   //reset reference
                MyEventA = null;                         //reset reference


                MyEventA += testMethod;                  //Add delegate
                MyEventA += new MyDelegate(testMethod);  //Add delegate
                MyEventA -= testMethod;                  //Remove delegate
                MyEventA -= new MyDelegate(testMethod);  //Remove delegate
            }
        }

        private void MyAsyncCallback(System.IAsyncResult result)
        {
            //user may do something here
        }
        private void testMethod(string inputs)
        {
            //do something
        }

    }
    public class MyClassB
    {
        public MyClassB()
        {
            classA = new MyClassA();
        }
        public MyClassA classA;
        public string ReturnTheSameString(string inputString)
        {
            return inputString;
        }


        public void TryToDoSomethingOnMyDelegateA()
        {
            if (classA.MyDelegateA != null)
            {
                //The following two lines do the same job --> invoke the delegate instance
                classA.MyDelegateA("I can invoke delegate which defined in class A in ClassB");
                classA.MyDelegateA.Invoke("I can invoke delegate which defined in class A in ClassB");
                //Async invoke is also allowed

                //user can check the public properties and fields of delegate instance
                System.Reflection.MethodInfo delegateAMethodInfo = classA.MyDelegateA.Method;

                classA.MyDelegateA = testMethod;                   //reset reference
                classA.MyDelegateA = new MyDelegate(testMethod);   //reset reference
                classA.MyDelegateA = null;                         //reset reference


                classA.MyDelegateA += testMethod;                  //Add delegate
                classA.MyDelegateA += new MyDelegate(testMethod);  //Add delegate
                classA.MyDelegateA -= testMethod;                  //Remove delegate
                classA.MyDelegateA -= new MyDelegate(testMethod);  //Remove delegate

            }

        }
        public void TryToDoSomeThingMyEventA()
        {
            //check whether classA.MyEventA is null or not is not allowed
            //Invoke classA.MyEventA is not allowed
            //Check properties and fields of classA.MyEventA is not allowed
            //reset classA.MyEventA reference is not allowed

            classA.MyEventA += testMethod;                  //Add delegate
            classA.MyEventA += new MyDelegate(testMethod);  //Add delegate
            classA.MyEventA -= testMethod;                  //Remove delegate
            classA.MyEventA -= new MyDelegate(testMethod);  //Remove delegate
        }

        private void testMethod(string inputs)
        {
            //do something here
        }
    }
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.