เหตุใดเราจึงต้องใช้คีย์เวิร์ด "เหตุการณ์" ในขณะที่กำหนดเหตุการณ์


109

ฉันไม่เข้าใจว่าทำไมเราต้องใช้คีย์เวิร์ด "เหตุการณ์" ในขณะที่กำหนดเหตุการณ์เมื่อเราสามารถทำสิ่งเดียวกันโดยไม่ต้องใช้คีย์เวิร์ด "เหตุการณ์" เพียงแค่ใช้ผู้รับมอบสิทธิ์

เช่น

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

ที่นี่ถ้าฉันลบคีย์เวิร์ด "เหตุการณ์" ออกจากบรรทัดที่สองฉันก็สามารถเพิ่มเหตุการณ์ได้โดยการเรียกผู้แทน ใครช่วยบอกหน่อยได้ไหมว่าทำไมต้องใช้คีย์เวิร์ดเหตุการณ์นี้


ตกลงถ้าคุณไม่ใช้คีย์เวิร์ดเหตุการณ์ใครก็ตามที่สามารถเข้าถึงเหตุการณ์นั้นโดยใช้คลาสอ็อบเจ็กต์ตั้งค่าเป็น NULL เช่น objClass.SelectedIndexChanged = null สิ่งนี้จะทำให้รหัสอ้างอิงของคุณขัดข้อง คำสำคัญเหตุการณ์บังคับให้ผู้ใช้กำหนดสิ่งที่คล้ายกับการมอบหมายโดยใช้ + =
Sumit Kapadia

คำตอบ:


142

เหตุการณ์ที่เหมือนฟิลด์และฟิลด์สาธารณะของประเภทผู้ร่วมประชุมมีลักษณะคล้ายกัน แต่จริงๆแล้วแตกต่างกันมาก

โดยพื้นฐานแล้วเหตุการณ์ก็เหมือนกับคุณสมบัติ - เป็นวิธีการเพิ่ม / ลบคู่หนึ่ง (แทนที่จะเป็น get / set of a property) เมื่อคุณประกาศเหตุการณ์ที่เหมือนฟิลด์ (เช่นเหตุการณ์ที่คุณไม่ได้ระบุการเพิ่ม / ลบบิตด้วยตัวเอง) เหตุการณ์สาธารณะจะถูกสร้างขึ้นและฟิลด์สำรองส่วนตัว วิธีนี้ช่วยให้คุณเพิ่มกิจกรรมแบบส่วนตัว แต่อนุญาตให้สมัครแบบสาธารณะได้ ด้วยเขตข้อมูลตัวแทนสาธารณะทุกคนสามารถลบตัวจัดการเหตุการณ์ของคนอื่นยกเหตุการณ์ขึ้นเองได้ ฯลฯ - มันเป็นหายนะของการห่อหุ้ม

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับกิจกรรม (และผู้ได้รับมอบหมาย) อ่านบทความของฉันในหัวข้อนี้ (ในบางจุดฉันจำเป็นต้องอัปเดตสิ่งนี้สำหรับ C # 4 ซึ่งเปลี่ยนเหตุการณ์ที่เหมือนฟิลด์เล็กน้อยมากส่วนสำคัญของมันยังคงถูกต้องอยู่)


18
นี่ดีกว่าคำอธิบายบรรทัดเดียวอย่างเป็นทางการของ MSDN มากกว่าพันเท่า: 'คำสำคัญของเหตุการณ์ใช้เพื่อประกาศเหตุการณ์ในชั้นเรียนผู้เผยแพร่'
cowlinator

37

คีย์เวิร์ดของเหตุการณ์ทำ 3 สิ่งที่แตกต่างกัน:

  1. คุณสามารถกำหนดเหตุการณ์ในอินเทอร์เฟซแม้ว่าคุณจะไม่สามารถกำหนดฟิลด์ปกติในอินเทอร์เฟซได้
  2. มันเปลี่ยนการเปิดเผยของ=และ()ตัวดำเนินการ (การกำหนดและการเรียกใช้) เป็นส่วนตัวเพื่อให้มีเพียงคลาสที่มีเท่านั้นที่สามารถเรียกใช้เหตุการณ์หรือแทนที่วิธีการทั้งหมดที่มีอยู่ในนั้น -=และ+=ผู้ประกอบการยังสามารถเรียกใช้ในเหตุการณ์จากนอกชั้นเรียนกำหนดมัน (พวกเขาได้รับการเข้าถึงปรับปรุงคุณเขียนติดกับเหตุการณ์)
  3. คุณยังสามารถลบล้างวิธีการ-=และ+=ปฏิบัติตามเหตุการณ์ต่างๆ

2
คุณ> MSDN ขอบคุณ.
M. Azyoksul

26

คำตอบอื่น ๆ ก็ดี ฉันแค่อยากจะเพิ่มอย่างอื่นเพื่อพิจารณา

คำถามของคุณคือ "ทำไมเราต้องจัดกิจกรรมในเมื่อเรามีช่องประเภทผู้รับมอบสิทธิ์" ฉันจะขยายคำถามว่าทำไมคุณถึงต้องการเมธอดคุณสมบัติเหตุการณ์ตัวสร้างอินสแตนซ์หรือตัวสุดท้ายหากคุณมีฟิลด์ประเภทผู้รับมอบสิทธิ์ เหตุใดคุณจึงต้องการสิ่งอื่นนอกเหนือจากเขตข้อมูลที่มีค่าและผู้รับมอบสิทธิ์ในประเภท ทำไมไม่พูด

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

เหรอ?

คุณไม่จำเป็นต้องมีวิธีการคุณสมบัติหรือเหตุการณ์ เราให้สิ่งนั้นแก่คุณเนื่องจากวิธีการคุณสมบัติและรูปแบบการออกแบบเหตุการณ์มีความสำคัญและมีประโยชน์และสมควรที่จะมีวิธีที่เป็นมาตรฐานมีเอกสารและชัดเจนในการนำไปใช้ในภาษา


4
ว้าว! มันเตือนว่าทำไมฉันถึงรัก c #! ในทุกภาษาที่ฉันใช้งานมีความสมดุลที่เหมาะสมของความกะทัดรัดความยืดหยุ่นและความหมายที่อ่านได้ ภาษาเดียวที่เทียบเคียงได้คือ Object Pascal
ATL_DEV

1
@ATL_DEV: มันมีเหตุผล สถาปนิกของภาษา C # ชื่อ Anders Hejlsberg เคยเป็นสถาปนิกของ Delphi ซึ่งเป็นภาษาที่ใช้ Object Pascal
Eric Lippert

9

ส่วนหนึ่งจำเป็นเพราะถ้าคุณไม่ใส่eventคีย์เวิร์ดจะทำให้การห่อหุ้มไม่สมบูรณ์ หากเป็นเพียงผู้รับมอบสิทธิ์แบบหลายผู้รับสาธารณะทุกคนสามารถเรียกใช้ตั้งค่าเป็นโมฆะหรือยุ่งเกี่ยวได้ ถ้าชั้นที่เรียกว่าMailNotifierมีอยู่และก็มีเหตุการณ์ที่เรียกว่าMailReceivedมันทำให้รู้สึกประเภทอื่น ๆ เพื่อให้สามารถที่จะยิงผ่านเหตุการณ์ที่เรียกไม่มีmailNotifier.MailReceived();

ในทางกลับกันคุณสามารถแทรกแซงและเรียกใช้เหตุการณ์ 'field like' จากประเภทที่กำหนดไว้เท่านั้น

หากคุณต้องการเก็บคำเชิญเข้าร่วมกิจกรรมไว้เป็นส่วนตัวไม่มีอะไรจะหยุดคุณทำสิ่งนี้ได้:

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(...);
   }       
}

... แต่นั่นเป็นการโหลดโค้ดทั้งหมดเพื่อ (มากหรือน้อย) ทำสิ่งที่เหตุการณ์เหมือนฟิลด์ให้เราอยู่แล้ว


มันจะยากกว่าสำหรับสิ่งต่างๆเช่นนักออกแบบที่จะใช้ ... โดยพื้นฐานแล้วคุณจะต้องอาศัยหลักการตั้งชื่อสำหรับวิธีการแทนที่จะมีข้อมูลเมตาสาธารณะที่บอกว่า "นี่คือเหตุการณ์"
Jon Skeet

3

เหตุการณ์มีข้อได้เปรียบที่แตกต่างกันเมื่อเทียบกับเขตข้อมูลที่ได้รับมอบหมาย เหตุการณ์สามารถกำหนดได้ในอินเทอร์เฟซในทางตรงกันข้ามกับฟิลด์การเพิ่มสิ่งที่เป็นนามธรรมให้กับโค้ดและที่สำคัญยิ่งไปกว่านั้น: สามารถเรียกเหตุการณ์ได้จากภายในคลาสที่กำหนดเท่านั้น ในกรณีของคุณใครก็ตามสามารถเรียกเหตุการณ์นี้ได้ซึ่งอาจทำลายรหัสของคุณ

ดูโพสต์บล็อกนี้สำหรับข้อมูลเพิ่มเติม


2
คุณไม่ควรจริงๆจะเปรียบเทียบเหตุการณ์และผู้ได้รับมอบหมาย - เหตุการณ์การเปรียบเทียบและสาขาของประชาชนที่มีประเภทตัวแทน และไม่กรอบงานไม่ต้องการให้เหตุการณ์มีลายเซ็นนั้น คุณสามารถสร้างกิจกรรมของผู้รับมอบสิทธิ์ประเภทใดก็ได้ที่คุณต้องการ
Jon Skeet

3

delegateเป็นประเภทอ้างอิง มันสืบทอดMulticastDelegate เหตุการณ์เป็นตัวปรับเปลี่ยนเหตุการณ์เป็นตัวปรับแต่งพิเศษสำหรับผู้รับมอบสิทธิ์ มันปรับเปลี่ยนความสามารถในการเข้าถึงฟังก์ชัน / วิธีการบางอย่างเช่น Invoke method หลังจากแก้ไขโดยเหตุการณ์โมดิฟายเออร์อินสแตนซ์ที่มอบสิทธิ์จะกลายเป็นแนวคิดใหม่ "เหตุการณ์" ดังนั้น Event จึงเป็นเพียงตัวแทนที่ได้รับการแก้ไข คุณไม่สามารถเปลี่ยนการอ้างอิงโดยตรงหรือเรียกใช้เหตุการณ์ภายนอกคลาสที่มีการกำหนด Event ได้ แต่คุณสามารถเปลี่ยนการอ้างอิงหรือเรียกใช้อินสแตนซ์ผู้รับมอบสิทธิ์ตามปกติได้ เหตุการณ์ให้การป้องกันเพิ่มเติมเพื่อให้เหตุการณ์มีคุณสมบัติด้านความปลอดภัยมากขึ้น เมื่อคุณอยู่นอกชั้นเรียนที่มีการกำหนดเหตุการณ์ไว้คุณจะได้รับอนุญาตให้ดำเนินการสองประเภทกับกิจกรรม "+ =" และ "- =" แต่คุณสามารถเข้าถึงฟิลด์สาธารณะคุณสมบัติวิธีการและอื่น ๆ ทั้งหมดของอินสแตนซ์ผู้รับมอบสิทธิ์ปกติ นี่คือตัวอย่างหนึ่ง:

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
        }
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.