1. ホーム
  2. c#

[解決済み] .NETのイベント署名 -- Strong Typedの'Sender'を使用?[クローズド]

2022-10-18 15:58:20

質問

私が提案していることは、.NETのガイドラインに従っていないこと、したがって、この理由だけではおそらく良くない考えであることは十分に理解しています。しかし、私は2つの可能な観点からこれを検討したいと思います。

(1) 100%内部目的である私自身の開発作業にこれを使用することを考慮すべきかどうか。

(2) これはフレームワークの設計者が変更または更新を検討できる概念ですか?

私は、現在の.NETデザインパターンである「オブジェクト」としてタイプする代わりに、強いタイプされた「送信者」を利用するイベント署名を使用することについて考えています。すなわち、次のような標準的なイベント署名を使用する代わりにです。

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

以下のように、強い型の'sender'パラメータを利用したイベントシグネチャを使用することを検討しています。

まず、"StrongTypedEventHandler"を定義します。

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

これは、Action<TSender, TEventArgs>と大差ないのですが、このAction<TSender, TEventArgs>を利用することで StrongTypedEventHandler を使用することで、TEventArgsが次のような派生をすることを強制しています。 System.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のイベント処理パターンを壊していることを十分に理解しています。しかし、contravarianceによって、サブスクライバが希望すれば、従来のイベント処理署名を使用できることを心に留めておいてください。

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

つまり、イベントハンドラが異なる (あるいは未知の) オブジェクトタイプからのイベントを購読する必要がある場合、ハンドラは 'sender' パラメータを 'object' とタイプして、潜在的な送信者オブジェクトの全範囲を処理することができます。

慣習を破ること (これは、私を信じて軽く考えないことです) 以外には、これに対するマイナス面を思いつきません。

ここでは、いくつかの CLS 準拠の問題があるかもしれません。これは Visual Basic .NET 2008 では 100% 問題なく実行できますが (テスト済み)、2005 までの古いバージョンの Visual Basic .NET では、代表団の共変動と共変動がないものと思われます。 [編集: その後テストしたところ、確認できました。VB.NET 2005以下はこれを処理できませんが、VB.NET 2008は100%問題ありません。以下の "Edit #2" を参照してください]。 他の.NET言語でも問題があるかもしれませんが、確証はありません。

しかし、私は C# または Visual Basic .NET 以外の言語で開発することは考えていませんし、.NET Framework 3.0 以上の C# と VB.NET に制限することに抵抗はありません(.NET Framework 3.0 以降に戻ることは考えられません)。(正直なところ、この時点で 2.0 に戻ることは考えられません)。

誰かこれの問題点を思いつく人はいますか?それとも、これは単に、人々の胃を回転させるほど慣習を破っているのでしょうか?

以下は、私が見つけた関連リンクです。

(1) イベント設計ガイドライン [MSDN 3.5]

(2) C# simple Event Raising - using "sender" vs. custom EventArgs [StackOverflow 2009].

(3) .netのイベントシグネチャパターン [StackOverflow 2008].

私はこれについての誰か、そして皆の意見に興味があります...。

よろしくお願いします。

マイク

1を編集します。 これは トミー・カルリエの投稿 :

ここに、強い型のイベントハンドラと「オブジェクト送信者」パラメータを使用する現在の標準的なイベントハンドラの両方が、このアプローチと共存できることを示す完全な動作例を示します。コードをコピーペーストして、実行することができます。

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# 言語のデリゲートには長い間、共変と反変があり、それは本質的なものだと感じていますが、そうではありません。しかし、Visual Basic .NETでは、.NET Framework 3.0 (VB.NET 2008) になるまで、デリゲートに共変および共変量機能が搭載されませんでした。そしてその結果、.NET 2.0 以下の Visual Basic.NET では、このアプローチを利用することができないでしょう。

例えば、上記の例を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(sender As Object, e As vbGenericEventHandling.GenericEventHandling.PublisherEventArgs)' です。 と同じシグネチャを持ちません。 delegate 'デリゲートサブ StrongTypedEventHandler(Of TSender, TEventArgs As System.EventArgs)(sender As Publisher, e As PublisherEventArgs)'

基本的に、デリゲートはVB.NETのバージョン2005以下では不変のものです。実はこのアイデアは数年前に考えたのですが、VB.NETがこれに対応できないことが気になっていました...。でも、今はしっかりC#に移行して、VB.NETでも扱えるようになったので、まあ、この投稿になったわけです。

編集:アップデートその3

OK、私はしばらくの間、これをかなりうまく使っています。本当にいいシステムだと思います。StrongTypedEventHandler"をGenericEventHandler"と名付け、以下のように定義することにしました。

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

この名前の変更以外は、上述と全く同じように実装しました。

これは、FxCop 規則 CA1009 に抵触します。

慣例により、.NET イベントには、イベントを指定する 2 つのパラメータがあります。 イベントの送信者とイベント データを指定する 2 つのパラメータがあります。 送信者とイベント データを指定します。イベントハンドラ のシグネチャは、この形式に従わなければなりません。 void MyEventHandler( object sender, EventArgs e)。sender' パラメータ は常に 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.")]

将来的には、この方法が標準になることを願っています。本当にうまく機能するのです。

皆さん、たくさんのご意見ありがとうございました。

マイク

どのように解決するのですか?

Microsoft がこれに気づいたようで、同様の例が MSDN に掲載されています。

一般的なデリゲート