Chữ ký sự kiện trong .NET - Sử dụng 'Người gửi' được đánh máy mạnh?


106

Tôi hoàn toàn nhận ra rằng những gì tôi đang đề xuất không tuân theo các nguyên tắc .NET, và do đó, có lẽ là một ý tưởng tồi chỉ vì lý do này. Tuy nhiên, tôi muốn xem xét điều này từ hai khía cạnh có thể có:

(1) Tôi có nên cân nhắc sử dụng cái này cho công việc phát triển của bản thân không, 100% cho mục đích nội bộ.

(2) Đây có phải là một khái niệm mà các nhà thiết kế khung có thể xem xét thay đổi hoặc cập nhật không?

Tôi đang suy nghĩ về việc sử dụng chữ ký sự kiện sử dụng 'người gửi' được nhập mạnh, thay vì nhập nó là 'đối tượng', đây là mẫu thiết kế .NET hiện tại. Đó là, thay vì sử dụng chữ ký sự kiện tiêu chuẩn trông như thế này:

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

Tôi đang xem xét sử dụng chữ ký sự kiện sử dụng tham số 'người gửi' được đánh mạnh, như sau:

Đầu tiên, xác định một "StrongTypedEventHandler":

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

Đây không phải là tất cả những gì khác biệt với một Hành động <TSender, TEventArgs>, nhưng bằng cách sử dụng StrongTypedEventHandler, chúng tôi thực thi mà TEventArgs có được từ đó System.EventArgs.

Tiếp theo, làm ví dụ, chúng ta có thể sử dụng StrongTypedEventHandler trong một lớp xuất bản như sau:

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

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

Sự sắp xếp trên sẽ cho phép người đăng ký sử dụng trình xử lý sự kiện kiểu mạnh mà không yêu cầu truyền:

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

Tôi hoàn toàn nhận ra rằng điều này không đúng với mẫu xử lý sự kiện .NET tiêu chuẩn; tuy nhiên, hãy nhớ rằng sự trái ngược sẽ cho phép người đăng ký sử dụng chữ ký xử lý sự kiện truyền thống nếu muốn:

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

Nghĩa là, nếu một trình xử lý sự kiện cần đăng ký các sự kiện từ các loại đối tượng khác nhau (hoặc có thể không xác định), trình xử lý có thể nhập tham số 'sender' là 'object' để xử lý toàn bộ các đối tượng người gửi tiềm năng.

Ngoài việc phá vỡ quy ước (đó là điều mà tôi không coi thường, tin tôi đi), tôi không thể nghĩ ra bất kỳ mặt trái nào của điều này.

Có thể có một số vấn đề tuân thủ CLS ở đây. Điều này chạy trong Visual Basic .NET 2008 100% tốt (tôi đã thử nghiệm), nhưng tôi tin rằng các phiên bản cũ hơn của Visual Basic .NET đến 2005 không có hiệp phương sai đại biểu và phương sai. [Chỉnh sửa: Tôi đã thử nghiệm điều này kể từ đó và được xác nhận: VB.NET 2005 trở xuống không thể xử lý điều này, nhưng VB.NET 2008 là tốt 100%. Xem "Chỉnh sửa # 2" bên dưới.] Có thể có các ngôn ngữ .NET khác cũng gặp sự cố với vấn đề này, tôi không thể chắc chắn.

Nhưng tôi không thấy mình đang phát triển cho bất kỳ ngôn ngữ nào ngoài C # hoặc Visual Basic .NET và tôi không ngại giới hạn ngôn ngữ đó đối với C # và VB.NET cho .NET Framework 3.0 trở lên. (Thành thật mà nói, tôi không thể tưởng tượng sẽ quay trở lại 2.0 vào thời điểm này.)

Bất cứ ai khác có thể nghĩ ra một vấn đề với điều này? Hay điều này chỉ đơn giản là phá vỡ quy ước đến mức khiến bao tử của mọi người quay cuồng?

Dưới đây là một số liên kết liên quan mà tôi đã tìm thấy:

(1) Hướng dẫn thiết kế sự kiện [MSDN 3.5]

(2) Nâng cao sự kiện đơn giản trong C # - sử dụng “người gửi” so với EventArgs tùy chỉnh [StackOverflow 2009]

(3) Mẫu chữ ký sự kiện trong .net [StackOverflow 2008]

Tôi quan tâm đến ý kiến ​​của bất kỳ ai và của mọi người về điều này ...

Cảm ơn trước,

Mike

Chỉnh sửa # 1: Đây là phản hồi cho bài đăng của Tommy Carlier :

Dưới đây là một ví dụ hoạt động đầy đủ cho thấy rằng cả trình xử lý sự kiện kiểu mạnh và trình xử lý sự kiện tiêu chuẩn hiện tại sử dụng tham số 'object sender' đều có thể cùng tồn tại với phương pháp này. Bạn có thể sao chép-dán mã và chạy mã:

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

Chỉnh sửa # 2: Đây là câu trả lời cho tuyên bố của Andrew Hare về hiệp phương sai và phương sai và cách nó áp dụng ở đây. Các đại biểu trong ngôn ngữ C # đã có hiệp phương sai và phương sai trong thời gian dài đến mức nó chỉ cảm thấy "nội tại", nhưng không phải vậy. Nó thậm chí có thể là một cái gì đó được kích hoạt trong CLR, tôi không biết, nhưng Visual Basic .NET đã không nhận được khả năng hiệp phương sai và tương phản cho các đại biểu của nó cho đến .NET Framework 3.0 (VB.NET 2008). Và kết quả là Visual Basic.NET cho .NET 2.0 trở xuống sẽ không thể sử dụng phương pháp này.

Ví dụ, ví dụ trên có thể được dịch sang VB.NET như sau:

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 có thể chạy tốt 100%. Nhưng bây giờ tôi đã thử nghiệm nó trên VB.NET 2005, chỉ để chắc chắn, và nó không biên dịch, nói rõ:

Phương thức 'Public Sub SomeEventHandler (sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' không có chữ ký giống như đại diện 'Delegate Sub StrongTypedEventHandler (Of TSender, TEventArgs As System.EventAventArgs) (Người gửi As Publisher, eventArgs) '

Về cơ bản, các đại biểu là bất biến trong VB.NET phiên bản 2005 trở xuống. Tôi thực sự đã nghĩ ra ý tưởng này cách đây vài năm, nhưng việc VB.NET không có khả năng giải quyết vấn đề này đã làm phiền tôi ... Nhưng bây giờ tôi đã chuyển hẳn sang C # và VB.NET hiện có thể xử lý nó, vì vậy, do đó, bài này.

Chỉnh sửa: Cập nhật # 3

Ok, tôi đã sử dụng cái này khá thành công trong một thời gian. Nó thực sự là một hệ thống tốt đẹp. Tôi quyết định đặt tên "StrongTypedEventHandler" của mình là "GenericEventHandler", được định nghĩa như sau:

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

Ngoài việc đổi tên này, tôi đã thực hiện nó chính xác như đã thảo luận ở trên.

Nó vượt qua quy tắc FxCop CA1009, trong đó nêu rõ:

"Theo quy ước, các sự kiện .NET có hai tham số chỉ định người gửi sự kiện và dữ liệu sự kiện. Chữ ký của trình xử lý sự kiện phải theo mẫu sau: void MyEventHandler (object sender, EventArgs e). Tham số 'sender' luôn thuộc loại System.Object, ngay cả khi có thể sử dụng một loại cụ thể hơn. Tham số 'e' luôn thuộc loại System.EventArgs. Các sự kiện không cung cấp dữ liệu sự kiện nên sử dụng loại ủy quyền System.EventHandler. Các trình xử lý sự kiện trả về void để chúng có thể gửi mỗi sự kiện cho nhiều phương thức đích. Bất kỳ giá trị nào được trả về bởi một mục tiêu sẽ bị mất sau lần gọi đầu tiên. "

Tất nhiên, chúng tôi biết tất cả những điều này, và dù sao cũng đang vi phạm các quy tắc. (Tất cả các trình xử lý sự kiện có thể sử dụng 'Người gửi đối tượng' tiêu chuẩn trong chữ ký của họ nếu được ưu tiên trong mọi trường hợp - đây là một thay đổi không vi phạm.)

Vì vậy, việc sử dụng một SuppressMessageAttributethực hiện mẹo:

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

Tôi hy vọng rằng cách tiếp cận này sẽ trở thành tiêu chuẩn vào một thời điểm nào đó trong tương lai. Nó thực sự hoạt động rất độc đáo.

Cảm ơn tất cả các ý kiến ​​của các bạn, tôi thực sự đánh giá cao nó ...

Mike


6
Làm đi. (Đừng nghĩ biện minh này một câu trả lời.)
Konrad Rudolph

1
Lập luận của tôi không thực sự nhắm vào bạn: tất nhiên bạn nên làm điều này trong các dự án của riêng bạn. Họ đã lập luận tại sao nó có thể không hoạt động trong BCL.
Tommy Carlier

3
Trời đất, tôi ước gì dự án của tôi đã làm được điều này ngay từ đầu, tôi ghét bỏ người gửi.
Matt H

7
Bây giờ ĐÂY là một câu hỏi. Thấy chưa, mọi người? Không phải là một trong những oh hi this my hom work solve it plz :code dump:câu hỏi cỡ tweet này , mà là một câu hỏi mà chúng tôi rút kinh nghiệm .
Camilo Martin

3
Một gợi ý khác, chỉ cần đặt tên cho nó EventHandler<,>hơn GenericEventHandler<,>. Đã có chung chung EventHandler<>trong BCL được đặt tên chỉ là EventHandler. Vì vậy, EventHandler là một cái tên phổ biến hơn và các đại biểu ủng hộ kiểu quá tải
nawfal

Câu trả lời:


25

Có vẻ như Microsoft đã chọn điều này vì một ví dụ tương tự hiện đã có trên MSDN:

Đại biểu Chung


2
+1 Ah, xuất sắc. Họ đã nhận ra điều này thực sự. Điều này là tốt. Tuy nhiên, tôi hy vọng rằng họ làm cho mô hình này được công nhận trong VS IDE, bởi vì, hiện tại, việc sử dụng mô hình này đối với IntelliSense, v.v.
Mike Rosenblum

13

Những gì bạn đang đề xuất thực sự có rất nhiều ý nghĩa và tôi chỉ tự hỏi liệu đây có phải là một trong những điều đơn giản là như vậy bởi vì nó ban đầu được thiết kế trước generic hay có lý do thực sự cho điều này.


1
Tôi chắc chắn rằng đây chính xác là lý do. Tuy nhiên, bây giờ các phiên bản mới hơn của ngôn ngữ có sự tương phản để xử lý điều này, có vẻ như chúng sẽ có thể xử lý điều này theo cách tương thích ngược. Các trình xử lý trước sử dụng 'đối tượng người gửi' sẽ không bị hỏng. Nhưng điều này không đúng với các ngôn ngữ cũ hơn và có thể không đúng với một số ngôn ngữ .NET hiện tại, tôi không chắc.
Mike Rosenblum

13

Windows Runtime (WinRT) giới thiệu một TypedEventHandler<TSender, TResult>đại biểu, thực hiện chính xác những gì bạn StrongTypedEventHandler<TSender, TResult>làm, nhưng dường như không có ràng buộc về TResulttham số kiểu:

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

Tài liệu MSDN ở đây .


1
Ah, thật tốt khi thấy rằng có sự tiến bộ ... Tôi tự hỏi tại sao TResult không bị hạn chế kế thừa từ lớp 'EventArgs'. Lớp cơ sở 'EventArgs' trống về cơ bản; có thể họ đang rời bỏ hạn chế này?
Mike Rosenblum

Đó có thể là sự giám sát của nhóm thiết kế; ai biết.
Pierre Arnaud,

tốt, sự kiện hoạt động tốt mà không sử dụng EventArgs, nó chỉ là một điều ước
Sebastian

3
Nó nói rõ trong tài liệu TypedEventHandler rằng argssẽ là nullnếu không có dữ liệu sự kiện, vì vậy có vẻ như họ đang tránh sử dụng một đối tượng trống về cơ bản theo mặc định. Tôi đoán rằng ý tưởng ban đầu là một phương thức có tham số thứ hai của kiểu EventArgscó thể xử lý bất kỳ sự kiện nào vì các kiểu sẽ luôn tương thích. Họ có thể nhận ra rằng việc có thể xử lý nhiều sự kiện khác nhau bằng một phương pháp không phải là tất cả những gì quan trọng.
jmcilhinney

1
Nó không giống như một giám sát. Ràng buộc cũng bị xóa khỏi đại biểu System.EventHandler <TEventArgs>. referencesource.microsoft.com/#mscorlib/system/...
colton7909

5

Tôi gặp vấn đề với các tuyên bố sau:

  • Tôi tin rằng các phiên bản cũ hơn của Visual Basic .NET đến năm 2005 không có hiệp phương sai và phương sai đại biểu.
  • Tôi hoàn toàn nhận ra rằng điều này có nghĩa là phạm thượng.

Trước hết, không có gì bạn đã làm ở đây có liên quan đến hiệp phương sai hoặc tương phản. ( Chỉnh sửa: Tuyên bố trước là sai, để biết thêm thông tin, vui lòng xem Covariance và Contravariance trong Delegariance ) Giải pháp này sẽ hoạt động tốt trong tất cả các phiên bản CLR 2.0 trở lên (rõ ràng điều này sẽ không hoạt động trong ứng dụng CLR 1.0 vì nó sử dụng generics).

Thứ hai, tôi hoàn toàn không đồng ý rằng ý tưởng của bạn có nghĩa là "báng bổ" vì đây là một ý tưởng tuyệt vời.


2
Xin chào Andrew, cảm ơn bạn đã ủng hộ! Xem xét mức độ danh tiếng của bạn, điều này thực sự có ý nghĩa rất lớn đối với tôi ... Về vấn đề hiệp phương sai / tương phản: nếu đại diện do người đăng ký cung cấp không khớp chính xác với chữ ký của sự kiện của nhà xuất bản, thì hiệp phương sai và phương sai có liên quan. C # đã có hiệp phương sai đại biểu và phương sai từ mãi mãi, vì vậy nó có cảm giác bản chất, nhưng VB.NET không có hiệp phương sai và tương phản đại biểu cho đến .NET 3.0. Do đó, VB.NET cho .NET 2.0 trở xuống sẽ không thể sử dụng hệ thống này. (Xem ví dụ mã tôi đã thêm vào "Chỉnh sửa # 2", ở trên.)
Mike Rosenblum

@Mike - Tôi xin lỗi, bạn đúng 100%! Tôi đã chỉnh sửa câu trả lời của tôi để phản ánh quan điểm của bạn :)
Andrew Hare

4
Ah, thú vị! Có vẻ như hiệp phương sai / tương phản đại biểu là một phần của CLR, nhưng (vì lý do tôi không biết) nó không được VB.NET tiết lộ trước phiên bản gần đây nhất. Đây là bài viết của Francesco Balena cho thấy cách đạt được phương sai đại biểu bằng cách sử dụng Phản chiếu, nếu không được bật bởi chính ngôn ngữ: dotnet2themax.com/blogs/fbalena/… .
Mike Rosenblum

1
@Mike - Luôn luôn thú vị khi tìm hiểu những thứ mà CLR hỗ trợ chưa được hỗ trợ trong bất kỳ ngôn ngữ .NET nào.
Andrew Hare

5

Tôi đã xem qua cách điều này được xử lý với WinRT mới và dựa trên các ý kiến ​​khác ở đây, và cuối cùng quyết định thực hiện như thế này:

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

Đây dường như là cách tốt nhất để xem xét việc sử dụng tên TypedEventHandler trong WinRT.


Tại sao lại thêm hạn chế chung vào TEventArgs? Nó đã bị xóa khỏi EventHandler <> và TypedEventHandler <,> vì nó không thực sự có ý nghĩa.
Mike Marynowski

2

Tôi nghĩ đó là một ý tưởng tuyệt vời và MS có thể đơn giản là không có thời gian hoặc hứng thú để đầu tư vào việc làm cho điều này tốt hơn, chẳng hạn như khi họ chuyển từ ArrayList sang danh sách dựa trên chung.


Bạn có thể đúng ... Mặt khác, tôi nghĩ đó chỉ là một "tiêu chuẩn", và có thể không phải là một vấn đề kỹ thuật nào cả. Đó là, khả năng này có thể có trong tất cả các ngôn ngữ .NET hiện tại, tôi không biết. Tôi biết rằng C # và VB.NET có thể xử lý điều này. Tuy nhiên, tôi không chắc điều này hoạt động rộng rãi như thế nào trong tất cả các ngôn ngữ .NET hiện tại ... Nhưng vì nó hoạt động trong C # và VB.NET, và mọi người ở đây đều rất ủng hộ, tôi nghĩ tôi rất có thể làm được điều này. :-)
Mike Rosenblum

2

Theo những gì tôi hiểu, trường "Người gửi" luôn phải tham chiếu đến đối tượng chứa đăng ký sự kiện. Nếu tôi có bộ ghi chép của mình, thì cũng sẽ có một trường chứa thông tin đủ để hủy đăng ký một sự kiện nếu nó trở nên cần thiết (*) (ví dụ: hãy xem xét một trình ghi thay đổi đăng ký các sự kiện 'tập hợp đã thay đổi'; nó chứa hai phần , một trong số đó thực hiện công việc thực tế và lưu giữ dữ liệu thực tế, và một trong số đó cung cấp trình bao bọc giao diện công khai, phần chính có thể chứa một tham chiếu yếu đến phần trình bao bọc. Nếu phần trình bao bọc được thu thập rác, điều đó có nghĩa là không còn ai quan tâm đến dữ liệu đang được thu thập và trình ghi thay đổi nên hủy đăng ký khỏi bất kỳ sự kiện nào mà nó nhận được).

Vì có thể một đối tượng có thể gửi các sự kiện thay mặt cho một đối tượng khác, tôi có thể thấy một số khả năng hữu ích khi có trường "người gửi" thuộc loại Đối tượng và để có trường dẫn xuất từ ​​EventArgs chứa tham chiếu đến đối tượng nên được hành động. Tuy nhiên, tính hữu ích của trường "người gửi" có lẽ bị hạn chế bởi thực tế là không có cách nào rõ ràng để một đối tượng hủy đăng ký khỏi một người gửi không xác định.

(*) Trên thực tế, một cách tốt hơn để xử lý việc hủy đăng ký là có kiểu ủy quyền phát đa hướng cho các hàm trả về Boolean; nếu một hàm được gọi bởi một đại biểu như vậy trả về giá trị True, đại biểu sẽ được vá để loại bỏ đối tượng đó. Điều này có nghĩa là các đại biểu sẽ không còn thực sự bất biến nữa, nhưng có thể thực hiện thay đổi đó theo cách an toàn cho luồng (ví dụ: bằng cách hủy tham chiếu đối tượng và để mã ủy nhiệm đa hướng bỏ qua mọi tham chiếu đối tượng null được nhúng). Theo kịch bản này, nỗ lực xuất bản và sự kiện đối với một đối tượng được xử lý có thể được xử lý rất sạch sẽ, bất kể sự kiện đến từ đâu.


2

Nhìn lại sự báng bổ là lý do duy nhất khiến người gửi trở thành một kiểu đối tượng (nếu bỏ qua các vấn đề với sự tương phản trong mã VB 2005, đó là một lỗi IMHO của Microsoft), bất cứ ai có thể đề xuất ít nhất động cơ lý thuyết để đóng đối số thứ hai thành kiểu EventArgs. Đi xa hơn nữa, có lý do chính đáng để tuân thủ các nguyên tắc và quy ước của Microsoft trong trường hợp cụ thể này không?

Việc cần phải phát triển một trình bao bọc EventArgs khác cho một dữ liệu khác mà chúng ta muốn chuyển vào bên trong trình xử lý sự kiện có vẻ kỳ lạ, tại sao không thể chuyển thẳng dữ liệu đó đến đó. Xem xét các phần mã sau

[Ví dụ 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;
}

[Ví dụ 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
Có, việc tạo một lớp riêng biệt kế thừa từ System.EventArgs có vẻ không trực quan và đại diện cho công việc bổ sung, nhưng có một lý do rất chính đáng cho việc đó. Nếu bạn không bao giờ cần thay đổi mã của mình, thì cách tiếp cận của bạn là tốt. Nhưng thực tế là bạn có thể cần phải tăng chức năng của sự kiện trong phiên bản tương lai và thêm thuộc tính vào các nhóm sự kiện. Trong kịch bản của bạn, bạn sẽ phải thêm quá tải bổ sung hoặc các tham số tùy chọn vào chữ ký của trình xử lý sự kiện. Đây là một cách tiếp cận được sử dụng trong VBA và VB 6.0 kế thừa, có thể thực hiện được, nhưng hơi xấu trong thực tế.
Mike Rosenblum

1
Tuy nhiên, bằng cách kế thừa từ EventArgs, một phiên bản trong tương lai có thể kế thừa từ lớp đối số sự kiện cũ hơn của bạn và tăng cường nó. Tất cả các trình gọi cũ hơn vẫn có thể hoạt động chính xác như cũ, bằng cách hoạt động dựa trên lớp cơ sở của lớp đối số sự kiện mới của bạn. Rất sạch sẽ. Nhiều công việc hơn cho bạn, nhưng gọn gàng hơn cho bất kỳ người gọi nào phụ thuộc vào thư viện của bạn.
Mike Rosenblum

Nó thậm chí không cần kế thừa nó, bạn chỉ có thể thêm chức năng bổ sung trực tiếp vào lớp event args của mình và nó sẽ tiếp tục hoạt động tốt. Điều đó nói rằng, hạn chế ghim các args thành các eventargs đã bị loại bỏ vì nó không có ý nghĩa gì đối với nhiều tình huống, tức là. khi bạn biết rằng bạn sẽ không bao giờ cần mở rộng chức năng của một sự kiện cụ thể hoặc khi tất cả những gì bạn cần là một kiểu giá trị đối trong các ứng dụng rất nhạy cảm về hiệu suất.
Mike Marynowski 11/09/18

1

Với tình hình hiện tại (người gửi là đối tượng), bạn có thể dễ dàng đính kèm một phương thức vào nhiều sự kiện:

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

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

Nếu người gửi là chung, mục tiêu của sự kiện nhấp chuột sẽ không thuộc loại Nút hoặc Nhãn, mà thuộc loại Điều khiển (vì sự kiện được xác định trên Điều khiển). Vì vậy, một số sự kiện trên lớp Nút sẽ có mục tiêu thuộc kiểu Điều khiển, những sự kiện khác sẽ có kiểu mục tiêu khác.


2
Tommy, bạn có thể làm chính xác điều này với hệ thống mà tôi đang đề xuất. Bạn vẫn có thể sử dụng trình xử lý sự kiện tiêu chuẩn có tham số 'đối tượng gửi' để xử lý các sự kiện kiểu mạnh này. (Xem ví dụ mã mà bây giờ tôi đã thêm vào các bài bản gốc.)
Mike Rosenblum

Vâng, tôi đồng ý, đây là điều tốt về các sự kiện .NET tiêu chuẩn, được chấp nhận!
Lu4

1

Tôi không nghĩ rằng có gì sai với những gì bạn muốn làm. Đối với hầu hết các phần, tôi nghi ngờ rằng object sendertham số vẫn còn để tiếp tục hỗ trợ mã trước 2.0.

Nếu bạn thực sự muốn thực hiện thay đổi này cho một API công khai, bạn có thể muốn xem xét việc tạo lớp EvenArgs cơ sở của riêng mình. Một cái gì đó như thế này:

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

Sau đó, bạn có thể khai báo các sự kiện của mình như thế này

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

Và các phương pháp như thế này:

private void HandleSomething(object sender, EventArgs e)

sẽ vẫn có thể đăng ký.

BIÊN TẬP

Dòng cuối cùng đó khiến tôi suy nghĩ một chút ... Bạn thực sự có thể triển khai những gì bạn đề xuất mà không phá vỡ bất kỳ chức năng bên ngoài nào vì thời gian chạy không có vấn đề gì với các thông số dự báo. Tôi vẫn sẽ nghiêng về DataEventArgsgiải pháp (cá nhân). Tôi sẽ làm như vậy, tuy nhiên biết rằng nó là dư thừa, vì người gửi được lưu trữ trong tham số đầu tiên và như một thuộc tính của các chuỗi sự kiện.

Một lợi ích của việc gắn bó với DataEventArgslà bạn có thể xâu chuỗi các sự kiện, thay đổi người gửi (đại diện cho người gửi cuối cùng) trong khi EventArgs vẫn giữ nguyên người gửi ban đầu.


Này Michael, đây là một sự thay thế khá gọn gàng. Tôi thích nó. Tuy nhiên, như bạn đã đề cập, việc truyền tham số 'sender' một cách hiệu quả trong hai lần là thừa. Một cách tiếp cận tương tự được thảo luận ở đây: stackoverflow.com/questions/809609/… và sự đồng thuận dường như là nó quá không chuẩn. Đây là lý do tại sao tôi do dự khi đề xuất ý tưởng 'người gửi' được đánh máy mạnh ở đây. (Có vẻ để được đón nhận mặc dù, vì vậy tôi hài lòng.)
Mike Rosenblum

1

Cứ liều thử đi. Đối với mã không dựa trên thành phần, tôi thường đơn giản hóa chữ ký Sự kiện để đơn giản

public event Action<MyEventType> EventName

nơi MyEventTypekhông kế thừa từ EventArgs. Tại sao phải bận tâm, nếu tôi không bao giờ có ý định sử dụng bất kỳ thành viên nào của EventArgs.


1
Đồng ý! Tại sao chúng ta nên cảm thấy mình là khỉ?
Lu4

1
+ 1-ed, đây là những gì tôi cũng sử dụng. Đôi khi sự đơn giản lại chiến thắng! Hoặc thậm chí event Action<S, T>, event Action<R, S, T>v.v. Tôi có một phương thức mở rộng cho Raisehọ theo chuỗi một cách an toàn :)
nawfal
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.