ฉันตระหนักดีว่าสิ่งที่ฉันเสนอไม่เป็นไปตามหลักเกณฑ์ของ. 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.")]
ฉันหวังว่าแนวทางนี้จะกลายเป็นมาตรฐานในอนาคต มันใช้งานได้ดีมาก
ขอบคุณสำหรับทุกความคิดเห็นของคุณฉันซาบซึ้งจริงๆ ...
ไมค์