ลายเซ็นเหตุการณ์ใน. NET - การใช้ 'ผู้ส่ง' ที่พิมพ์อย่างรัดกุม? [ปิด]


107

ฉันตระหนักดีว่าสิ่งที่ฉันเสนอไม่เป็นไปตามหลักเกณฑ์ของ. NET ดังนั้นอาจเป็นความคิดที่ไม่ดีด้วยเหตุผลนี้เพียงอย่างเดียว อย่างไรก็ตามฉันต้องการพิจารณาสิ่งนี้จากสองมุมมองที่เป็นไปได้:

(1) ฉันควรพิจารณาใช้สิ่งนี้สำหรับงานพัฒนาของฉันเองซึ่ง 100% สำหรับวัตถุประสงค์ภายใน

(2) นี่เป็นแนวคิดที่นักออกแบบกรอบงานสามารถพิจารณาเปลี่ยนแปลงหรือปรับปรุงได้หรือไม่?

ฉันกำลังคิดเกี่ยวกับการใช้ลายเซ็นเหตุการณ์ที่ใช้ 'ผู้ส่ง' ที่พิมพ์หนักแน่นแทนที่จะพิมพ์เป็น 'วัตถุ' ซึ่งเป็นรูปแบบการออกแบบ. NET ในปัจจุบัน นั่นคือแทนที่จะใช้ลายเซ็นเหตุการณ์มาตรฐานที่มีลักษณะดังนี้:

class Publisher
{
    public event EventHandler<PublisherEventArgs> SomeEvent;
}

ฉันกำลังพิจารณาใช้ลายเซ็นเหตุการณ์ที่ใช้พารามิเตอร์ 'ผู้ส่ง' ที่พิมพ์แน่นดังต่อไปนี้:

ขั้นแรกกำหนด "StrongTypedEventHandler":

[SerializableAttribute]
public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
    TSender sender,
    TEventArgs e
)
where TEventArgs : EventArgs;

นี้ไม่ได้เป็นสิ่งที่แตกต่างจากการกระทำ <TSender, TEventArgs> แต่โดยการใช้ของเราบังคับใช้ที่เกิดขึ้นจากStrongTypedEventHandler TEventArgsSystem.EventArgs

ตัวอย่างถัดไปเราสามารถใช้ StrongTypedEventHandler ในคลาสการเผยแพร่ได้ดังนี้:

class Publisher
{
    public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;

    protected void OnSomeEvent()
    {
        if (SomeEvent != null)
        {
            SomeEvent(this, new PublisherEventArgs(...));
        }
    }
}

การจัดเตรียมข้างต้นจะช่วยให้สมาชิกสามารถใช้ตัวจัดการเหตุการณ์ที่พิมพ์ยากซึ่งไม่จำเป็นต้องแคสต์:

class Subscriber
{
    void SomeEventHandler(Publisher sender, PublisherEventArgs e)
    {           
        if (sender.Name == "John Smith")
        {
            // ...
        }
    }
}

ฉันตระหนักดีว่าสิ่งนี้ผิดปกติกับรูปแบบการจัดการเหตุการณ์. NET มาตรฐาน อย่างไรก็ตามโปรดทราบว่าการฝ่าฝืนจะทำให้สมาชิกใช้ลายเซ็นการจัดการเหตุการณ์แบบเดิมได้หากต้องการ:

class Subscriber
{
    void SomeEventHandler(object sender, PublisherEventArgs e)
    {           
        if (((Publisher)sender).Name == "John Smith")
        {
            // ...
        }
    }
}

นั่นคือถ้าตัวจัดการเหตุการณ์จำเป็นต้องสมัครสมาชิกเหตุการณ์จากประเภทอ็อบเจ็กต์ที่แตกต่างกัน (หรืออาจไม่เป็นที่รู้จัก) ตัวจัดการสามารถพิมพ์พารามิเตอร์ "ผู้ส่ง" เป็น "อ็อบเจ็กต์" เพื่อจัดการอ็อบเจ็กต์ผู้ส่งที่มีศักยภาพทั้งหมด

นอกเหนือจากการทำลายแบบแผน (ซึ่งเป็นสิ่งที่ฉันไม่ถือสาหรอกเชื่อฉันเถอะ) ฉันไม่สามารถคิดถึงข้อเสียของสิ่งนี้ได้

อาจมีปัญหาการปฏิบัติตามข้อกำหนด CLS ที่นี่ สิ่งนี้ทำงานใน Visual Basic .NET 2008 ได้ดี 100% (ฉันได้ทดสอบแล้ว) แต่ฉันเชื่อว่า Visual Basic .NET รุ่นเก่าจนถึงปี 2005 ไม่มีความแปรปรวนร่วมและความแปรปรวนของตัวแทน [แก้ไข: ฉันได้ทดสอบแล้วและได้รับการยืนยัน: VB.NET 2005 และต่ำกว่าไม่สามารถจัดการสิ่งนี้ได้ แต่ VB.NET 2008 นั้นใช้ได้ 100% ดู "แก้ไข # 2" ด้านล่าง]อาจมีภาษา. NET อื่น ๆ ที่มีปัญหาเช่นกันฉันไม่แน่ใจ

แต่ฉันไม่เห็นว่าตัวเองกำลังพัฒนาสำหรับภาษาอื่น ๆ นอกเหนือจาก C # หรือ Visual Basic .NET และฉันไม่รังเกียจที่จะ จำกัด ไว้ที่ C # และ VB.NET สำหรับ. NET Framework 3.0 ขึ้นไป (ฉันนึกไม่ออกว่าจะกลับไปที่ 2.0 ในตอนนี้พูดตามตรง)

ใครสามารถคิดว่าปัญหานี้? หรือสิ่งนี้ทำลายการประชุมใหญ่จนทำให้ท้องของผู้คนเปลี่ยนไป?

นี่คือลิงค์ที่เกี่ยวข้องที่ฉันพบ:

(1) แนวทางการออกแบบเหตุการณ์ [MSDN 3.5]

(2) C # การเพิ่มเหตุการณ์อย่างง่าย - โดยใช้ "ผู้ส่ง" กับ EventArgs ที่กำหนดเอง [StackOverflow 2009]

(3) รูปแบบลายเซ็นเหตุการณ์ใน. net [StackOverflow 2008]

ฉันสนใจความคิดเห็นของทุกคนและทุกคนเกี่ยวกับเรื่องนี้ ...

ขอบคุณล่วงหน้า,

ไมค์

แก้ไข # 1:นี่เป็นการตอบสนองต่อโพสต์ของ Tommy Carlier :

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

namespace csScrap.GenericEventHandling
{
    class PublisherEventArgs : EventArgs
    {
        // ...
    }

    [SerializableAttribute]
    public delegate void StrongTypedEventHandler<TSender, TEventArgs>(
        TSender sender,
        TEventArgs e
    )
    where TEventArgs : EventArgs;

    class Publisher
    {
        public event StrongTypedEventHandler<Publisher, PublisherEventArgs> SomeEvent;

        public void OnSomeEvent()
        {
            if (SomeEvent != null)
            {
                SomeEvent(this, new PublisherEventArgs());
            }
        }
    }

    class StrongTypedSubscriber
    {
        public void SomeEventHandler(Publisher sender, PublisherEventArgs e)
        {
            MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.");
        }
    }

    class TraditionalSubscriber
    {
        public void SomeEventHandler(object sender, PublisherEventArgs e)
        {
            MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.");
        }
    }

    class Tester
    {
        public static void Main()
        {
            Publisher publisher = new Publisher();

            StrongTypedSubscriber strongTypedSubscriber = new StrongTypedSubscriber();
            TraditionalSubscriber traditionalSubscriber = new TraditionalSubscriber();

            publisher.SomeEvent += strongTypedSubscriber.SomeEventHandler;
            publisher.SomeEvent += traditionalSubscriber.SomeEventHandler;

            publisher.OnSomeEvent();
        }
    }
}

แก้ไข # 2:นี่เป็นการตอบสนองต่อคำแถลงของ Andrew Hareเกี่ยวกับความแปรปรวนร่วมและความขัดแย้งและวิธีการใช้ที่นี่ ผู้ร่วมประชุมในภาษา C # มีความแปรปรวนร่วมและความแปรปรวนมานานจนรู้สึก "เป็นเนื้อแท้" แต่มันไม่ใช่ มันอาจเป็นสิ่งที่เปิดใช้งานใน CLR ฉันไม่รู้ แต่ Visual Basic .NET ไม่ได้รับความแปรปรวนร่วมและความสามารถในการผันแปรสำหรับผู้รับมอบสิทธิ์จนถึง. NET Framework 3.0 (VB.NET 2008) และเป็นผลให้ Visual Basic.NET สำหรับ. NET 2.0 และต่ำกว่าจะไม่สามารถใช้แนวทางนี้ได้

ตัวอย่างเช่นตัวอย่างข้างต้นสามารถแปลเป็น VB.NET ได้ดังนี้:

Namespace GenericEventHandling
    Class PublisherEventArgs
        Inherits EventArgs
        ' ...
        ' ...
    End Class

    <SerializableAttribute()> _
    Public Delegate Sub StrongTypedEventHandler(Of TSender, TEventArgs As EventArgs) _
        (ByVal sender As TSender, ByVal e As TEventArgs)

    Class Publisher
        Public Event SomeEvent As StrongTypedEventHandler(Of Publisher, PublisherEventArgs)

        Public Sub OnSomeEvent()
            RaiseEvent SomeEvent(Me, New PublisherEventArgs)
        End Sub
    End Class

    Class StrongTypedSubscriber
        Public Sub SomeEventHandler(ByVal sender As Publisher, ByVal e As PublisherEventArgs)
            MessageBox.Show("StrongTypedSubscriber.SomeEventHandler called.")
        End Sub
    End Class

    Class TraditionalSubscriber
        Public Sub SomeEventHandler(ByVal sender As Object, ByVal e As PublisherEventArgs)
            MessageBox.Show("TraditionalSubscriber.SomeEventHandler called.")
        End Sub
    End Class

    Class Tester
        Public Shared Sub Main()
            Dim publisher As Publisher = New Publisher

            Dim strongTypedSubscriber As StrongTypedSubscriber = New StrongTypedSubscriber
            Dim traditionalSubscriber As TraditionalSubscriber = New TraditionalSubscriber

            AddHandler publisher.SomeEvent, AddressOf strongTypedSubscriber.SomeEventHandler
            AddHandler publisher.SomeEvent, AddressOf traditionalSubscriber.SomeEventHandler

            publisher.OnSomeEvent()
        End Sub
    End Class
End Namespace

VB.NET 2008 ทำงานได้ดี 100% แต่ตอนนี้ฉันได้ทดสอบบน VB.NET 2005 เพื่อความแน่ใจและมันไม่ได้รวบรวมโดยระบุว่า:

เมธอด 'Public Sub SomeEventHandler (ผู้ส่งเป็น Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' ไม่มีลายเซ็นเดียวกับผู้รับมอบสิทธิ์ 'Delegate Sub StrongTypedEventHandler (ของ TSender, TEventArgs As System.EventArgs) (sender As PublisherA, eventArgs) '

โดยทั่วไปผู้รับมอบสิทธิ์จะไม่แปรผันใน VB.NET เวอร์ชัน 2005 และต่ำกว่า ฉันคิดถึงความคิดนี้เมื่อสองสามปีก่อน แต่ VB.NET ไม่สามารถจัดการกับสิ่งนี้ทำให้ฉันรำคาญ ... แต่ตอนนี้ฉันย้ายไปที่ C # อย่างมั่นคงแล้วและตอนนี้ VB.NET ก็สามารถจัดการได้แล้วดังนั้น โพสต์นี้

แก้ไข: อัปเดต # 3

โอเคฉันใช้มันค่อนข้างประสบความสำเร็จมาระยะหนึ่งแล้ว เป็นระบบที่ดีจริงๆ ฉันตัดสินใจตั้งชื่อ "StrongTypedEventHandler" ของฉันเป็น "GenericEventHandler" โดยกำหนดดังนี้:

[SerializableAttribute]
public delegate void GenericEventHandler<TSender, TEventArgs>(
    TSender sender,
    TEventArgs e
)
where TEventArgs : EventArgs;

นอกเหนือจากการเปลี่ยนชื่อนี้ฉันใช้มันตามที่กล่าวไว้ข้างต้นทุกประการ

มันเดินทางผ่านกฎ FxCop CA1009 ซึ่งระบุว่า:

"ตามแบบแผนเหตุการณ์. NET มีพารามิเตอร์สองตัวที่ระบุผู้ส่งเหตุการณ์และข้อมูลเหตุการณ์ลายเซ็นของตัวจัดการเหตุการณ์ควรเป็นไปตามแบบฟอร์มนี้: โมฆะ MyEventHandler (ผู้ส่งอ็อบเจ็กต์, EventArgs e) พารามิเตอร์ 'ผู้ส่ง' มักจะเป็นประเภท System.Object, แม้ว่าจะเป็นไปได้ที่จะใช้ประเภทที่เฉพาะเจาะจงมากขึ้นพารามิเตอร์ 'e' จะเป็นประเภท System.EventArgs เสมอเหตุการณ์ที่ไม่ให้ข้อมูลเหตุการณ์ควรใช้ชนิดผู้ร่วมประชุม System.EventHandler ตัวจัดการเหตุการณ์จะคืนค่าโมฆะเพื่อให้สามารถส่ง แต่ละเหตุการณ์ไปยังวิธีการเป้าหมายหลายวิธีค่าใด ๆ ที่ส่งคืนโดยเป้าหมายจะหายไปหลังจากการเรียกครั้งแรก "

แน่นอนเรารู้ทั้งหมดนี้และกำลังทำผิดกฎอยู่แล้ว (ตัวจัดการเหตุการณ์ทั้งหมดสามารถใช้ 'object Sender' มาตรฐานในลายเซ็นได้หากต้องการไม่ว่าในกรณีใด ๆ - นี่คือการเปลี่ยนแปลงที่ไม่ทำลาย)

ดังนั้นการใช้SuppressMessageAttributeเคล็ดลับไม่:

[SuppressMessage("Microsoft.Design", "CA1009:DeclareEventHandlersCorrectly",
    Justification = "Using strong-typed GenericEventHandler<TSender, TEventArgs> event handler pattern.")]

ฉันหวังว่าแนวทางนี้จะกลายเป็นมาตรฐานในอนาคต มันใช้งานได้ดีมาก

ขอบคุณสำหรับทุกความคิดเห็นของคุณฉันซาบซึ้งจริงๆ ...

ไมค์


6
ทำมัน. (อย่าคิดว่านี่เป็นเหตุผลให้คำตอบ)
Konrad Rudolph

1
ข้อโต้แย้งของฉันไม่ได้ชี้มาที่คุณจริงๆแน่นอนคุณควรทำสิ่งนี้ในโครงการของคุณเอง พวกเขามีข้อโต้แย้งว่าเหตุใดจึงอาจไม่ทำงานใน BCL
Tommy Carlier

3
ผู้ชายฉันหวังว่าโครงการของฉันจะทำสิ่งนี้ตั้งแต่เริ่มต้นฉันเกลียดการคัดเลือกผู้ส่ง
Matt H

7
ตอนนี้เป็นคำถาม เห็นไหมคน? ไม่ได้หนึ่งของทวีตขนาดเหล่านี้oh hi this my hom work solve it plz :code dump:คำถาม แต่คำถามที่เราเรียนรู้จาก
Camilo Martin

3
ข้อเสนอแนะก็เพียงแค่ชื่อมันกว่าEventHandler<,> BCL GenericEventHandler<,>มีอยู่แล้วEventHandler<>ซึ่งมีชื่อว่า EventHandler ดังนั้น EventHandler จึงเป็นชื่อที่ใช้กันทั่วไปและผู้รับมอบสิทธิ์สนับสนุนประเภทโอเวอร์โหลด
nawfal

คำตอบ:


25

ดูเหมือนว่า Microsoft ได้หยิบสิ่งนี้ขึ้นมาเป็นตัวอย่างที่คล้ายกันในตอนนี้บน MSDN:

ผู้รับมอบสิทธิ์ทั่วไป


2
+1 อ่ายอดเยี่ยม พวกเขาได้รับสิ่งนี้แน่นอน ดีจัง. แม้ว่าฉันหวังว่าพวกเขาจะทำให้รูปแบบนี้เป็นที่รู้จักใน VS IDE เนื่องจากในขณะนี้การใช้รูปแบบนี้ในแง่ของ IntelliSense เป็นเรื่องที่อึดอัดมากขึ้น
Mike Rosenblum

13

สิ่งที่คุณกำลังเสนอนั้นสมเหตุสมผลจริงๆและฉันแค่สงสัยว่านี่เป็นหนึ่งในสิ่งเหล่านั้นที่เป็นอย่างนั้นเพราะมันถูกออกแบบมาก่อนชื่อสามัญหรือว่ามีเหตุผลที่แท้จริงสำหรับสิ่งนี้


1
ฉันแน่ใจว่านี่คือเหตุผลที่แน่นอน อย่างไรก็ตามในตอนนี้ภาษาเวอร์ชันที่ใหม่กว่ามีความไม่ตรงกันในการจัดการสิ่งนี้ดูเหมือนว่าพวกเขาควรจะสามารถจัดการกับสิ่งนี้ในลักษณะที่เข้ากันได้แบบย้อนกลับ เครื่องจัดการก่อนหน้านี้ที่ใช้ 'วัตถุผู้ส่ง' จะไม่พัง แต่นี่ไม่เป็นความจริงสำหรับภาษาที่เก่ากว่าและอาจไม่เป็นความจริงสำหรับภาษา. NET ในปัจจุบันฉันไม่แน่ใจ
Mike Rosenblum

13

Windows Runtime (WinRT) แนะนำTypedEventHandler<TSender, TResult>ตัวแทนซึ่งทำในสิ่งที่คุณStrongTypedEventHandler<TSender, TResult>ทำ แต่เห็นได้ชัดว่าไม่มีข้อ จำกัด ในTResultพารามิเตอร์ type:

public delegate void TypedEventHandler<TSender, TResult>(TSender sender,
                                                         TResult args);

เอกสาร MSDN เป็นที่นี่


1
อาดีที่เห็นว่ามีความคืบหน้า ... ฉันสงสัยว่าทำไม TResult ถึงไม่ถูก จำกัด ให้สืบทอดจากคลาส 'EventArgs' คลาสฐาน 'EventArgs' ว่างเปล่าพื้นฐาน บางทีพวกเขาอาจจะถอยห่างจากข้อ จำกัด นี้?
Mike Rosenblum

อาจมีการควบคุมดูแลจากทีมออกแบบ ใครจะรู้.
Pierre Arnaud

เหตุการณ์ต่างๆทำงานได้ดีโดยไม่ต้องใช้EventArgsมันเป็นเพียงการประชุมเท่านั้น
Sebastian

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

1
ดูไม่เป็นการกำกับดูแล ข้อ จำกัด ถูกลบออกจากผู้ร่วมประชุม System.EventHandler <TEventArgs> เช่นกัน referencesource.microsoft.com/#mscorlib/system/…
colton7909

5

ฉันมีปัญหากับข้อความต่อไปนี้:

  • ฉันเชื่อว่า Visual Basic .NET เวอร์ชันเก่าถึงปี 2548 ไม่มีความแปรปรวนร่วมและความแปรปรวนของผู้รับมอบสิทธิ์
  • ฉันตระหนักดีว่านี่เป็นการหมิ่นประมาท

ก่อนอื่นคุณไม่ได้ทำอะไรที่นี่เกี่ยวข้องกับความแปรปรวนร่วมหรือความขัดแย้ง ( แก้ไข: คำสั่งก่อนหน้านี้ไม่ถูกต้องสำหรับข้อมูลเพิ่มเติมโปรดดูความแปรปรวนร่วมและความแตกต่างในตัวแทน ) โซลูชันนี้จะทำงานได้ดีใน CLR เวอร์ชัน 2.0 ขึ้นไปทั้งหมด (เห็นได้ชัดว่าสิ่งนี้จะใช้ไม่ได้กับแอปพลิเคชัน CLR 1.0 เนื่องจากใช้ generics)

ประการที่สองฉันไม่เห็นด้วยอย่างยิ่งที่ความคิดของคุณส่อไปในทาง "ดูหมิ่น" เนื่องจากเป็นความคิดที่ยอดเยี่ยม


2
สวัสดีแอนดรูขอบคุณที่ยกนิ้วให้! เมื่อพิจารณาถึงระดับชื่อเสียงของคุณแล้วสิ่งนี้มีความหมายมากสำหรับฉัน ... เกี่ยวกับปัญหาความแปรปรวนร่วม / ความขัดแย้ง: หากตัวแทนที่ได้รับจากผู้สมัครสมาชิกไม่ตรงกับลายเซ็นของงานของผู้จัดพิมพ์ทุกประการจะมีความแปรปรวนร่วมและความขัดแย้ง C # มีความแปรปรวนร่วมและความแปรปรวนจากการมอบหมายมาตลอดดังนั้นจึงรู้สึกเป็นเนื้อแท้ แต่ VB.NET ไม่มีความแปรปรวนร่วมและความแปรปรวนของผู้รับมอบสิทธิ์จนถึง. NET 3.0 ดังนั้น VB.NET สำหรับ. NET 2.0 และต่ำกว่าจะไม่สามารถใช้ระบบนี้ได้ (ดูตัวอย่างโค้ดที่ฉันเพิ่มใน "แก้ไข # 2" ด้านบน)
Mike Rosenblum

@ ไมค์ - ฉันขอโทษคุณถูกต้อง 100%! ฉันได้แก้ไขคำตอบของฉันเพื่อสะท้อนประเด็นของคุณ :)
Andrew Hare

4
อาน่าสนใจ! ดูเหมือนว่าความแปรปรวนร่วม / ความแปรปรวนของการมอบหมายเป็นส่วนหนึ่งของ CLR แต่ (ด้วยเหตุผลที่ฉันไม่รู้) VB.NET ไม่ได้เปิดเผยก่อนเวอร์ชันล่าสุด ที่นี่บทความโดยฟราน Balena ที่แสดงให้เห็นว่าผู้ร่วมประชุมแปรปรวนสามารถทำได้โดยใช้การสะท้อนหากไม่ได้เปิดใช้งานโดยภาษาตัวเอง: dotnet2themax.com/blogs/fbalena/...
Mike Rosenblum

1
@ ไมค์ - การเรียนรู้สิ่งต่าง ๆ ที่ CLR สนับสนุนยังไม่ได้รับการสนับสนุนในภาษา. NET นั้นเป็นเรื่องที่น่าสนใจเสมอ
Andrew Hare

5

ฉันได้ดูวิธีจัดการกับ WinRT ใหม่และจากความคิดเห็นอื่น ๆ ที่นี่และในที่สุดก็ตัดสินใจทำเช่นนี้:

[Serializable]
public delegate void TypedEventHandler<in TSender, in TEventArgs>(
    TSender sender,
    TEventArgs e
) where TEventArgs : EventArgs;

นี่เป็นวิธีที่ดีที่สุดในการพิจารณาการใช้ชื่อ TypedEventHandler ใน WinRT


ทำไมต้องเพิ่มข้อ จำกัด ทั่วไปใน TEventArgs มันถูกลบออกจาก EventHandler <> และ TypedEventHandler <,> เพราะมันไม่สมเหตุสมผล
Mike Marynowski

2

ฉันคิดว่ามันเป็นความคิดที่ดีและ MS ก็อาจไม่มีเวลาหรือความสนใจที่จะลงทุนเพื่อทำให้สิ่งนี้ดีขึ้นเช่นเมื่อพวกเขาย้ายจาก ArrayList ไปยังรายการตามทั่วไป


คุณอาจจะคิดถูก ... ในทางกลับกันฉันคิดว่ามันเป็นเพียง "มาตรฐาน" และอาจไม่ใช่ปัญหาทางเทคนิคเลย นั่นคือความสามารถนี้อาจมีอยู่ในภาษา. NET ปัจจุบันทั้งหมดฉันไม่รู้ ฉันรู้ว่า C # และ VB.NET สามารถจัดการสิ่งนี้ได้ อย่างไรก็ตามฉันไม่แน่ใจว่าสิ่งนี้ใช้ได้ในวงกว้างแค่ไหนในภาษา. NET ปัจจุบันทั้งหมด ... แต่เนื่องจากมันใช้งานได้ใน C # และ VB.NET และทุกคนที่นี่ให้การสนับสนุนฉันคิดว่าฉันมีแนวโน้มที่จะทำเช่นนี้ :-)
Mike Rosenblum

2

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

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

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


2

การมองย้อนกลับไปที่การดูหมิ่นเป็นเหตุผลเดียวในการทำให้ผู้ส่งเป็นประเภทวัตถุ (หากจะละเว้นปัญหาเกี่ยวกับความขัดแย้งในรหัส VB ​​2005 ซึ่งเป็นข้อผิดพลาด IMHO ของ Microsoft) ทุกคนสามารถแนะนำแรงจูงใจทางทฤษฎีอย่างน้อยที่สุดสำหรับการตอกย้ำอาร์กิวเมนต์ที่สองให้กับประเภท EventArgs ยิ่งไปกว่านั้นมีเหตุผลที่ดีที่จะปฏิบัติตามแนวทางและอนุสัญญาของ Microsoft ในกรณีนี้หรือไม่?

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

[ตัวอย่างที่ 1]

public delegate void ConnectionEventHandler(Server sender, Connection connection);

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, connection);
    }

    public event ConnectionEventHandler ClientConnected;
}

[ตัวอย่างที่ 2]

public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);

public class ConnectionEventArgs : EventArgs
{
    public Connection Connection { get; private set; }

    public ConnectionEventArgs(Connection connection)
    {
        this.Connection = connection;
    }
}

public partial class Server
{
    protected virtual void OnClientConnected(Connection connection)
    {
        if (ClientConnected != null) ClientConnected(this, new ConnectionEventArgs(connection));
    }

    public event ConnectionEventHandler ClientConnected;
}

2
ใช่การสร้างคลาสแยกต่างหากที่สืบทอดมาจาก System.EventArgs อาจดูเหมือนไม่ใช้งานง่ายและแสดงถึงงานพิเศษ แต่ก็มีเหตุผลที่ดีมาก หากคุณไม่จำเป็นต้องเปลี่ยนรหัสของคุณวิธีของคุณก็ใช้ได้ แต่ความจริงก็คือคุณอาจต้องเพิ่มฟังก์ชันการทำงานของเหตุการณ์ในเวอร์ชันอนาคตและเพิ่มคุณสมบัติให้กับอาร์กิวเมนต์เหตุการณ์ ในสถานการณ์ของคุณคุณจะต้องเพิ่มโอเวอร์โหลดพิเศษหรือพารามิเตอร์ที่เป็นทางเลือกให้กับลายเซ็นของตัวจัดการเหตุการณ์ นี่เป็นแนวทางที่ใช้ใน VBA และ VB 6.0 เดิมซึ่งใช้งานได้ แต่ในทางปฏิบัติค่อนข้างน่าเกลียด
Mike Rosenblum

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

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

1

ด้วยสถานการณ์ปัจจุบัน (ผู้ส่งเป็นวัตถุ) คุณสามารถแนบวิธีการกับหลายเหตุการณ์ได้อย่างง่ายดาย:

button.Click += ClickHandler;
label.Click += ClickHandler;

void ClickHandler(object sender, EventArgs e) { ... }

หากผู้ส่งเป็นแบบทั่วไปเป้าหมายของเหตุการณ์การคลิกจะไม่ใช่ประเภทปุ่มหรือป้ายกำกับ แต่เป็นประเภทการควบคุม (เนื่องจากเหตุการณ์ถูกกำหนดไว้ในการควบคุม) ดังนั้นเหตุการณ์บางอย่างใน Button-class จะมีเป้าหมายเป็นประเภท Control ส่วนเหตุการณ์อื่น ๆ จะมีประเภทเป้าหมายอื่น


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

ใช่ฉันยอมรับนี่เป็นสิ่งที่ดีเกี่ยวกับเหตุการณ์. NET มาตรฐานที่ได้รับการยอมรับ!
Lu4

1

ฉันไม่คิดว่ามีอะไรผิดปกติกับสิ่งที่คุณต้องการทำ ส่วนใหญ่ฉันสงสัยว่าobject senderพารามิเตอร์ยังคงอยู่เพื่อที่จะสนับสนุนโค้ดก่อน 2.0 ต่อไป

หากคุณต้องการทำการเปลี่ยนแปลงนี้สำหรับ API สาธารณะจริงๆคุณอาจต้องการพิจารณาสร้างคลาส EvenArgs พื้นฐานของคุณเอง สิ่งนี้:

public class DataEventArgs<TSender, TData> : EventArgs
{
    private readonly TSender sender, TData data;

    public DataEventArgs(TSender sender, TData data)
    {
        this.sender = sender;
        this.data = data;
    }

    public TSender Sender { get { return sender; } }
    public TData Data { get { return data; } }
}

จากนั้นคุณสามารถประกาศกิจกรรมของคุณเช่นนี้

public event EventHandler<DataEventArgs<MyClass, int>> SomeIndexSelected;

และวิธีการดังนี้

private void HandleSomething(object sender, EventArgs e)

จะยังคงสามารถติดตามได้

แก้ไข

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

ข้อดีอย่างหนึ่งของการยึดติดกับสิ่งDataEventArgsนี้คือคุณสามารถเชื่อมโยงเหตุการณ์เปลี่ยนผู้ส่ง (เพื่อแสดงถึงผู้ส่งคนสุดท้าย) ในขณะที่ EventArgs ยังคงรักษาผู้ส่งเดิมไว้


เฮ้ไมเคิลนี่เป็นทางเลือกที่ค่อนข้างเรียบร้อย ฉันชอบมัน. อย่างที่คุณพูดถึงมันซ้ำซ้อนที่จะมีการส่งผ่านพารามิเตอร์ 'ผู้ส่ง' สองครั้งอย่างมีประสิทธิภาพ มีการพูดถึงแนวทางที่คล้ายกันที่นี่: stackoverflow.com/questions/809609/…และฉันทามติดูเหมือนว่าจะไม่ได้มาตรฐานเกินไป นี่คือเหตุผลที่ฉันลังเลที่จะแนะนำแนวคิด "ผู้ส่ง" ที่พิมพ์ยากที่นี่ (ดูเหมือนจะได้รับการตอบรับที่ดี แต่ฉันก็พอใจแล้ว)
Mike Rosenblum

1

ไปเลย สำหรับโค้ดที่ไม่ใช่องค์ประกอบฉันมักจะทำให้ลายเซ็นเหตุการณ์ง่ายขึ้น

public event Action<MyEventType> EventName

ที่ไม่ได้รับมรดกจากMyEventType EventArgsจะรำคาญทำไมถ้าฉันไม่เคยตั้งใจจะใช้สมาชิกของ EventArgs เลย


1
ตกลง! ทำไมเราต้องรู้สึกว่าตัวเองเป็นลิง?
Lu4

1
+1-ed นี่คือสิ่งที่ฉันใช้ด้วย บางครั้งความเรียบง่ายก็ชนะ! หรือแม้กระทั่งevent Action<S, T>, event Action<R, S, T>ฯลฯ ฉันมีวิธีการขยายไปยังRaiseพวกเขาด้ายอย่างปลอดภัย :)
Nawfal
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.