อะไรคือความแตกต่างระหว่างผู้ได้รับมอบหมายและเหตุการณ์?


317

อะไรคือความแตกต่างระหว่างผู้ได้รับมอบหมายและเหตุการณ์? ทั้งคู่ไม่เก็บการอ้างอิงถึงฟังก์ชันที่สามารถเรียกใช้งานได้หรือ



2
นี้อธิบายด้วยตัวอย่างมีลักษณะunitygeek.com/delegates-events-unity
Rahul Lalit

คำตอบ:


283

การประกาศเหตุการณ์จะเพิ่มเลเยอร์ของนามธรรมและการป้องกันในอินสแตนซ์ของผู้รับมอบสิทธิ์ การป้องกันนี้จะป้องกันไม่ให้ไคลเอนต์ของผู้รับมอบสิทธิ์ตั้งค่าผู้รับมอบสิทธิ์และรายการการเรียกใช้และอนุญาตให้เพิ่มหรือลบเป้าหมายออกจากรายการการเรียกใช้


44
ถ้าแน่นอนเลเยอร์การป้องกันนี้จะป้องกัน "ไคลเอนต์" (รหัสภายนอกคลาส / โครงสร้างที่กำหนด) จากการเรียกใช้ผู้รับมอบสิทธิ์และจากการได้รับวัตถุผู้แทน "หลัง" เหตุการณ์
Jeppe Stig Nielsen

7
ไม่จริงทั้งหมด คุณสามารถประกาศกิจกรรมโดยไม่มีอินสแตนซ์ของผู้มอบหมายแบ็กเอนด์ ใน c # คุณสามารถใช้เหตุการณ์อย่างชัดเจนและใช้โครงสร้างข้อมูลแบ็คเอนด์ที่คุณเลือก
Miguel Gamboa

3
@mmcdole คุณสามารถยกตัวอย่างเพื่ออธิบายเขาได้อย่างไร
vivek nuna

103

เพื่อทำความเข้าใจความแตกต่างคุณสามารถดูตัวอย่าง 2 นี้

ตัวอย่างกับผู้ได้รับมอบหมาย (ในกรณีนี้คือการกระทำ - นั่นคือประเภทของตัวแทนที่ไม่ส่งคืนค่า)

public class Animal
{
    public Action Run {get; set;}

    public void RaiseEvent()
    {
        if (Run != null)
        {
            Run();
        }
    }
}

หากต้องการใช้ผู้รับมอบสิทธิ์คุณควรทำสิ่งนี้:

Animal animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();

รหัสนี้ทำงานได้ดี แต่คุณอาจมีจุดอ่อน

ตัวอย่างเช่นถ้าฉันเขียนสิ่งนี้:

animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;

ด้วยรหัสบรรทัดสุดท้ายฉันได้แทนที่พฤติกรรมก่อนหน้านี้เพียงแค่ขาดหายไปหนึ่งรายการ+(ฉันใช้=แทน+=)

อีกจุดที่อ่อนแอคือว่าชั้นซึ่งใช้ของคุณทุกAnimalระดับสามารถเพิ่มเพียงแค่เรียกมันว่าRaiseEventanimal.RaiseEvent()

เพื่อหลีกเลี่ยงจุดอ่อนเหล่านี้คุณสามารถใช้eventsใน c #

คลาสสัตว์ของคุณจะเปลี่ยนแปลงด้วยวิธีนี้:

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"));
    }
}

เพื่อโทรเหตุการณ์

 Animal animal= new Animal();
 animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
 animal.RaiseEvent();

แตกต่าง:

  1. คุณไม่ได้ใช้ทรัพย์สินสาธารณะ แต่เป็นเขตข้อมูลสาธารณะ (ใช้กิจกรรมคอมไพเลอร์ปกป้องเขตข้อมูลของคุณจากการเข้าถึงที่ไม่ต้องการ)
  2. ไม่สามารถกำหนดกิจกรรมได้โดยตรง ในกรณีนี้มันจะไม่ก่อให้เกิดข้อผิดพลาดก่อนหน้านี้ที่ฉันได้แสดงให้เห็นด้วยการเอาชนะพฤติกรรม
  3. ไม่มีใครอยู่นอกชั้นเรียนของคุณที่สามารถเพิ่มเหตุการณ์
  4. เหตุการณ์สามารถรวมอยู่ในการประกาศอินเตอร์เฟสในขณะที่ฟิลด์ไม่สามารถ

หมายเหตุ:

EventHandler ได้รับการประกาศให้เป็นตัวแทนต่อไปนี้:

public delegate void EventHandler (object sender, EventArgs e)

มันต้องใช้ผู้ส่ง (ของประเภทวัตถุ) และข้อโต้แย้งเหตุการณ์ ผู้ส่งเป็นโมฆะหากมาจากวิธีการคงที่

ตัวอย่างนี้ซึ่งใช้EventHandler<ArgsSpecial>สามารถเขียนได้โดยใช้EventHandlerแทน

อ้างอิงที่นี่สำหรับเอกสารเกี่ยวกับ EventHandler


7
ทุกอย่างดูดีจนกระทั่งฉันวิ่งเข้าไปใน "ไม่มีใครนอกห้องเรียนของคุณที่สามารถยกเหตุการณ์" นั่นหมายความว่าอย่างไร? ทุกคนไม่สามารถโทรRaiseEventตราบใดที่วิธีการโทรมีการเข้าถึงอินสแตนซ์ของanimalในรหัสที่ใช้เหตุการณ์หรือไม่
dance2die

11
@ เหตุการณ์ที่เกิดขึ้นสามารถเพิ่มขึ้นได้จากภายในชั้นเรียนบางทีฉันอาจจะอธิบายไม่ชัดเจน ด้วยเหตุการณ์ที่คุณสามารถเรียกใช้ฟังก์ชั่นที่เพิ่มเหตุการณ์ (encapsulation) แต่มันสามารถเพิ่มขึ้นจากภายในชั้นเรียนที่กำหนด แจ้งให้เราทราบหากฉันไม่ชัดเจน
Faby

1
"ไม่สามารถกำหนดกิจกรรมได้โดยตรง" ถ้าฉันไม่เข้าใจคุณผิดนี่ก็ไม่เป็นความจริง นี่คือตัวอย่าง: gist.github.com/Chiel92/36bb3a2d2ac7dd511b96
Chiel ten Brinke

2
@faby คุณหมายถึงว่าแม้ว่าจะมีการประกาศกิจกรรมเป็นสาธารณะ แต่ฉันก็ยังทำไม่ได้animal.Run(this, new ArgsSpecial("Run faster");?
Pap

1
@ChieltenBrinke แน่นอนว่ากิจกรรมสามารถกำหนดได้ภายในสมาชิกของคลาส ... แต่ไม่ใช่อย่างอื่น
Jim Balter

94

นอกจากคุณสมบัติทางด้านวากยสัมพันธ์และการปฏิบัติการแล้วยังมีความแตกต่างทางอรรถศาสตร์

เทมเพลตของฟังก์ชันผู้ได้รับมอบหมาย นั่นคือพวกเขาแสดงสัญญาฟังก์ชั่นจะต้องปฏิบัติตามเพื่อให้ได้รับการพิจารณา "ประเภท" ของผู้ได้รับมอบหมาย

เหตุการณ์เป็นตัวแทน ... ดีกิจกรรม พวกเขาตั้งใจที่จะเตือนผู้อื่นเมื่อมีอะไรเกิดขึ้นและใช่พวกเขาปฏิบัติตามคำจำกัดความของผู้ร่วมประชุม แต่พวกเขาไม่เหมือนกัน

แม้ว่าพวกเขาจะเป็นสิ่งเดียวกัน (syntactically และในรหัส IL) จะยังคงมีความแตกต่างทางความหมาย โดยทั่วไปฉันต้องการชื่อที่แตกต่างกันสองชื่อสำหรับแนวคิดที่แตกต่างกันสองรายการแม้ว่าจะมีการใช้งานในลักษณะเดียวกัน (ซึ่งไม่ได้หมายความว่าฉันต้องการมีรหัสเดียวกันสองครั้ง)


8
คำอธิบายที่ยอดเยี่ยมของผู้ได้รับมอบหมาย
Sampson

1
ดังนั้นเราสามารถพูดได้หรือไม่ว่าเหตุการณ์เป็นประเภท "ตัวแทน" พิเศษ?
Pap

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

@Jorge Córdobaตัวอย่างของผู้ได้รับมอบหมายและผู้แทนกิจกรรมเป็นเจ้าของหนังสือพิมพ์และกิจกรรม (สมัครสมาชิกหรือยกเลิกการสมัคร) และบางคนซื้อหนังสือพิมพ์และบางคนไม่ซื้อหนังสือพิมพ์หมายความว่าเจ้าของหนังสือพิมพ์ไม่สามารถบังคับให้ทุกคนซื้อหนังสือพิมพ์ของฉัน ถูกหรือผิด?
Rahul_Patil

37

นี่เป็นอีกลิงค์ที่ดีในการอ้างอิง http://csharpindepth.com/Articles/Chapter2/Events.aspx

สั้น ๆ นำออกไปจากบทความ - เหตุการณ์คือการห่อหุ้มของผู้ได้รับมอบหมาย

อ้างอิงจากบทความ:

สมมติว่าเหตุการณ์ไม่มีอยู่ในแนวคิดใน C # /. NET ชั้นเรียนอื่นจะสมัครเข้าร่วมกิจกรรมได้อย่างไร สามตัวเลือก:

  1. ตัวแปรผู้แทนสาธารณะ

  2. ตัวแปรผู้แทนได้รับการสนับสนุนโดยพร็อพเพอร์ตี้

  3. ตัวแปรผู้รับมอบสิทธิ์ด้วยวิธี AddXXXHandler และ RemoveXXXHandler

ตัวเลือกที่ 1 น่ากลัวอย่างเห็นได้ชัดด้วยเหตุผลปกติทั้งหมดที่เราเกลียดตัวแปรสาธารณะ

ตัวเลือกที่ 2 นั้นดีกว่าเล็กน้อย แต่ช่วยให้สมาชิกสามารถแทนที่ซึ่งกันและกันได้อย่างมีประสิทธิภาพ - มันจะง่ายเกินไปที่จะเขียน someInstance.MyEvent = eventHandler; ซึ่งจะแทนที่ตัวจัดการเหตุการณ์ที่มีอยู่แทนที่จะเพิ่มตัวจัดการเหตุการณ์ใหม่ นอกจากนี้คุณยังต้องเขียนคุณสมบัติ

ตัวเลือกที่ 3 นั้นเป็นสิ่งที่เหตุการณ์ให้คุณ แต่มีการรับประกันแบบแผน (สร้างโดยคอมไพเลอร์และสำรองโดยการตั้งค่าสถานะพิเศษใน IL) และการใช้งาน "ฟรี" ถ้าคุณพอใจกับความหมายที่เหตุการณ์คล้ายสนามให้คุณ การสมัครและยกเลิกการสมัครจากเหตุการณ์ถูกห่อหุ้มโดยไม่อนุญาตให้เข้าถึงรายการตัวจัดการเหตุการณ์โดยไม่ตั้งใจและภาษาสามารถทำให้สิ่งต่าง ๆ ง่ายขึ้นโดยจัดเตรียมไวยากรณ์สำหรับทั้งการประกาศและการสมัครสมาชิก


คำอธิบายที่ดีและรัดกุม Thanx
Pap

นี่เป็นข้อกังวลเชิงทฤษฎีมากกว่าสิ่งใด ๆ แต่ FWIW ฉันมักจะรู้สึกว่าอาร์กิวเมนต์ "ตัวเลือกที่ 1 ไม่ดีเพราะเราไม่ชอบตัวแปรสาธารณะ" สามารถใช้การชี้แจงเพิ่มเติมได้เล็กน้อย ถ้าเขาบอกว่าเพราะมันเป็น "การปฏิบัติ OOP เลว" ในทางเทคนิคpublic Delegateตัวแปรจะถูกเปิดเผย "ข้อมูล" แต่ที่ดีที่สุดของ OOP ความรู้ของฉันไม่เคยกล่าวถึงแนวความคิดใด ๆ ค่อนข้างเหมือน Delegate(มันไม่เป็น "วัตถุ" หรือเป็น "ข้อความ") และ. NET จะปฏิบัติต่อผู้ได้รับมอบหมายเช่นเดียวกับข้อมูลจริงๆ
jrh

แม้ว่าฉันอยากจะให้คำแนะนำที่เป็นประโยชน์มากขึ้นถ้าคุณอยู่ในสถานการณ์ที่คุณต้องการให้แน่ใจว่ามีผู้จัดการเพียงคนเดียวการทำAddXXXHandlerวิธีการของคุณเองด้วยprivate Delegateตัวแปรอาจเป็นตัวเลือกที่ดี ในกรณีนี้คุณสามารถตรวจสอบเพื่อดูว่ามีการตั้งค่าตัวจัดการแล้วและตอบสนองอย่างเหมาะสม นี่อาจเป็นการตั้งค่าที่ดีหากคุณต้องการให้วัตถุที่ถืออยู่Delegateนั้นสามารถลบตัวจัดการทั้งหมดได้ ( eventไม่ได้ให้วิธีการใด ๆ กับคุณ)
jrh

7

หมายเหตุ: หากคุณเข้าถึงC # 5.0 Unleashedให้อ่าน "ข้อ จำกัด ในการใช้งานของผู้ได้รับมอบหมาย" ในบทที่ 18 เรื่อง "กิจกรรม" เพื่อทำความเข้าใจความแตกต่างระหว่างทั้งสองให้ดีขึ้น


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

ตัวอย่างที่ 1: การใช้ตัวแทนสาธารณะ

สมมติว่าฉันมีแอพ WinForms พร้อมกล่องแบบเลื่อนลงกล่องเดียว List<Person>หล่นลงถูกผูกไว้กับผู้ โดยที่ Person มีคุณสมบัติของ Id, ชื่อ, ชื่อเล่น, HairColor ในรูปแบบหลักคือการควบคุมผู้ใช้ที่กำหนดเองที่แสดงคุณสมบัติของบุคคลนั้น เมื่อมีคนเลือกบุคคลในรายการแบบหล่นลงป้ายกำกับในการปรับปรุงการควบคุมผู้ใช้เพื่อแสดงคุณสมบัติของบุคคลที่เลือก

ป้อนคำอธิบายรูปภาพที่นี่

นี่คือวิธีการทำงาน เรามีสามไฟล์ที่ช่วยให้เรารวบรวมสิ่งนี้เข้าด้วยกัน:

  • Mediator.cs - คลาสแบบคงที่มีผู้รับมอบสิทธิ์
  • Form1.cs - แบบฟอร์มหลัก
  • DetailView.cs - การควบคุมผู้ใช้จะแสดงรายละเอียดทั้งหมด

นี่คือรหัสที่เกี่ยวข้องสำหรับแต่ละคลาส:

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

นี่คือการควบคุมผู้ใช้ของเรา:

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;

    }
}

ในที่สุดเราก็มีรหัสต่อไปนี้ใน Form1.cs ของเรา ที่นี่เรากำลังโทรหา OnPersonChanged ซึ่งจะเรียกรหัสใด ๆ ที่สมัครเป็นผู้รับมอบสิทธิ์

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`.
}

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

แต่ฉันไม่ต้องการทำสิ่งที่ฉันอธิบายไว้ข้างต้น เนื่องจากเขตข้อมูลสาธารณะไม่ดีด้วยเหตุผลหลายประการ ตัวเลือกของเราคืออะไร? ตามที่ John Skeet อธิบายนี่คือตัวเลือกของเรา:

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

ตัวเลือกที่สามนี้เป็นสิ่งสำคัญที่เหตุการณ์ให้เรา เมื่อเราประกาศ EventHandler มันทำให้เราสามารถเข้าถึงผู้ได้รับมอบหมาย - ไม่ใช่สาธารณะไม่ใช่ทรัพย์สิน แต่เป็นสิ่งนี้เราเรียกเหตุการณ์ที่เพิ่งเพิ่ม / ลบ accessors

มาดูกันว่าโปรแกรมนี้หน้าตาเป็นอย่างไร แต่ตอนนี้ใช้กิจกรรมแทนผู้แทนสาธารณะ (ฉันเปลี่ยน Mediator ของเราเป็นซิงเกิลตัน):

ตัวอย่างที่ 2: ด้วย EventHandler แทนที่จะเป็นตัวแทนสาธารณะ

คนกลาง:

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

โปรดสังเกตว่าถ้าคุณ F12 บน EventHandler มันจะแสดงให้คุณเห็นว่าคำจำกัดความเป็นเพียงตัวแทนทั่วไปที่ได้รับการกล่าวขวัญพร้อมกับวัตถุ "ผู้ส่ง" เพิ่มเติม:

public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);

การควบคุมผู้ใช้:

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;

    }
}

สุดท้ายนี่คือรหัส Form1.cs:

private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
        Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
}

เนื่องจาก EventHandler ต้องการและ EventArgs เป็นพารามิเตอร์ฉันสร้างคลาสนี้มีเพียงคุณสมบัติเดียวในนั้น:

class PersonChangedEventArgs
{
    public Person Person { get; set; }
}

หวังว่าจะแสดงให้คุณเห็นเล็กน้อยเกี่ยวกับสาเหตุที่เรามีเหตุการณ์และความแตกต่าง - แต่ใช้งานได้เหมือนกัน - ในฐานะผู้ได้รับมอบหมาย


ในขณะที่ฉันขอขอบคุณทุกการทำงานที่ดีในโพสต์นี้และผมมีความสุขกับการอ่านมากที่สุดของมัน, ฉันยังคงรู้สึกหนึ่งปัญหาไม่ได้อยู่ 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- ในเวอร์ชันล่าสุดของMediatorคุณยังสามารถโทรหาOnPersonChangeเมื่อใดก็ตามที่คุณมีการอ้างอิงถึงซิงเกิล บางทีคุณควรพูดถึงว่าMediatorวิธีการนี้ไม่ได้ป้องกันพฤติกรรมเฉพาะนั้นและอยู่ใกล้กับบัสเหตุการณ์
Ivaylo Slavov

6

คุณยังสามารถใช้เหตุการณ์ในการประกาศอินเทอร์เฟซไม่ใช่สำหรับผู้รับมอบสิทธิ์


2
@surfen Interface สามารถมีเหตุการณ์ได้ แต่ไม่ใช่ตัวแทน
Alexandr Nikitin

1
คุณหมายถึงอะไรกันแน่? คุณสามารถมีAction a { get; set; }นิยามภายในอินเตอร์เฟส
Chiel ten Brinke

6

ช่างเป็นความเข้าใจผิดที่ยอดเยี่ยมระหว่างเหตุการณ์และตัวแทน !!! ผู้รับมอบสิทธิ์จะระบุ TYPE (เช่น a classหรือ a interfacedo) ในขณะที่เหตุการณ์เป็นเพียงประเภทของสมาชิก (เช่นเขตข้อมูลคุณสมบัติ ฯลฯ ) และเช่นเดียวกับสมาชิกประเภทอื่น ๆ กิจกรรมก็มีประเภทเช่นกัน แต่ในกรณีของเหตุการณ์ประเภทของเหตุการณ์จะต้องระบุโดยผู้รับมอบสิทธิ์ ตัวอย่างเช่นคุณไม่สามารถประกาศเหตุการณ์ประเภทที่กำหนดโดยอินเทอร์เฟซ

สรุปเราสามารถทำให้ต่อไปนี้สังเกต: ชนิดของเหตุการณ์ที่จะต้องกำหนดโดยผู้แทน นี่คือความสัมพันธ์หลักระหว่างเหตุการณ์และผู้รับมอบสิทธิ์และมีการอธิบายไว้ในส่วนII.18 การกำหนดเหตุการณ์ของพาร์ติชัน ECMA-335 (CLI) I ถึง VI :

ในการใช้งานทั่วไป TypeSpec (ถ้ามี) ระบุตัวแทนที่มีลายเซ็นตรงกับข้อโต้แย้งที่ส่งผ่านไปยังวิธีการดับเพลิงของเหตุการณ์

แต่ความเป็นจริงนี้ไม่ได้หมายความว่าเหตุการณ์จะใช้ข้อมูลการสนับสนุนของผู้ร่วมประชุม ในความเป็นจริงเหตุการณ์อาจใช้เขตข้อมูลสำรองของประเภทโครงสร้างข้อมูลที่แตกต่างกันที่คุณเลือก หากคุณใช้เหตุการณ์อย่างชัดเจนใน C # คุณมีอิสระที่จะเลือกวิธีที่คุณจัดเก็บจัดการเหตุการณ์ (ทราบว่าตัวจัดการเหตุการณ์กรณีของประเภทของเหตุการณ์ที่เกิดขึ้นซึ่งจะเป็น mandatorily ประเภทผู้รับมอบสิทธิ์ --- จากก่อนหน้านี้การสังเกต ) แต่คุณสามารถจัดเก็บตัวจัดการเหตุการณ์เหล่านั้น (ซึ่งเป็นอินสแตนซ์ของผู้รับมอบสิทธิ์) ในโครงสร้างข้อมูลเช่น a Listหรือ a Dictionaryหรืออื่น ๆ หรือแม้กระทั่งในเขตข้อมูลสำรองของผู้รับมอบสิทธิ์ แต่อย่าลืมว่าคุณไม่จำเป็นต้องใช้เขตข้อมูลตัวแทน


4

เหตุการณ์ใน. net คือชุดค่าผสมที่กำหนดไว้ของวิธีการเพิ่มและวิธีการลบซึ่งทั้งคู่คาดว่าผู้รับมอบสิทธิ์บางประเภทโดยเฉพาะ ทั้ง C # และ vb.net สามารถสร้างรหัสโดยอัตโนมัติสำหรับวิธีการเพิ่มและลบซึ่งจะกำหนดผู้รับมอบสิทธิ์เพื่อเก็บการสมัครสมาชิกของเหตุการณ์และเพิ่ม / ลบรหัสผ่านที่ส่งผ่านใน delegagte ไปยัง / จากผู้รับมอบสิทธิ์การสมัครสมาชิกนั้น VB.net จะสร้างรหัสอัตโนมัติ (พร้อมกับคำสั่ง RaiseEvent) เพื่อเรียกใช้รายการการสมัครหากหากไม่ว่างเปล่า; ด้วยเหตุผลบางอย่าง C # ไม่ได้สร้างสิ่งหลัง

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


3

ในการกำหนดเกี่ยวกับเหตุการณ์ด้วยวิธีง่ายๆ:

เหตุการณ์คือการอ้างอิงถึงตัวแทนที่มีข้อ จำกัด สองประการ

  1. ไม่สามารถเรียกใช้โดยตรง
  2. ไม่สามารถกำหนดค่าได้โดยตรง (เช่น eventObj = delegateMethod)

สองข้อข้างต้นเป็นจุดอ่อนสำหรับผู้ได้รับมอบหมายและได้รับการแก้ไขในเหตุการณ์ ตัวอย่างโค้ดที่สมบูรณ์เพื่อแสดงให้เห็นความแตกต่างในไวโอลินอยู่ที่นี่https://dotnetfiddle.net/5iR3fB

สลับความคิดเห็นระหว่างกิจกรรมและผู้รับมอบสิทธิ์และรหัสลูกค้าที่เรียกใช้ / กำหนดค่าเพื่อมอบหมายให้เข้าใจถึงความแตกต่าง

นี่คือรหัสแบบอินไลน์

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


}

2

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


0

ถ้าคุณตรวจสอบ Intermediate Language คุณจะรู้ว่า. net compiler แปลงผู้รับมอบสิทธิ์เป็นคลาสที่ปิดผนึกใน IL ด้วยฟังก์ชั่น build-in เช่น invoke, startInvoke, endInvoke และมอบหมายคลาสที่สืบทอดจากคลาสอื่นซึ่งอาจเรียกว่า "SystemMulticast" ฉันเดาว่าอีเวนต์เป็นคลาสย่อยของผู้ได้รับมอบหมายพร้อมคุณสมบัติเพิ่มเติมบางอย่าง

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

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