質問

デリゲートとイベントの違いは何ですか?どちらも実行可能な関数への参照を保持していませんか?

役に立ちましたか?

解決

アン イベント 宣言により、抽象化と保護の層が追加されます。 代表者 実例。この保護により、デリゲートのクライアントがデリゲートとその呼び出しリストをリセットすることができなくなり、呼び出しリストへのターゲットの追加または削除のみが許可されます。

他のヒント

構文上および操作上の特性に加えて、意味上の違いもあります。

デリゲートは概念的には関数テンプレートです。つまり、関数がデリゲートの「型」と見なされるために従わなければならない規約を表します。

イベントが表すのは...さて、イベント。これらは、何かが起こったときに誰かに警告することを目的としており、確かにデリゲートの定義に従っていますが、同じものではありません。

たとえそれらが (構文的にも IL コードにおいても) まったく同じものであったとしても、意味的な違いは依然として残ります。一般に、私は、たとえそれらが同じ方法で実装されるとしても、2 つの異なる概念に対して 2 つの異なる名前を付けることを好みます (これは、同じコードを 2 回使用することを好むという意味ではありません)。

違いを理解するには、この 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") ;

コードの最後の行で、前の動作を 1 つだけ欠落させてオーバーライドしました。 + (利用した = の代わりに +=)

もう 1 つの弱点は、 Animal クラスは上げることができます RaiseEvent ただ呼んでいるだけ animal.RaiseEvent().

これらの弱点を回避するには、以下を使用できます events C#で。

Animal クラスは次のように変更されます。

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)

送信者 (オブジェクト型) とイベント引数を受け取ります。送信者が静的メソッドからのものである場合、送信者は null になります。

この例では、 EventHandler<ArgsSpecial>, を使用して書くこともできます EventHandler その代わり。

参照する ここ EventHandler に関するドキュメント

ここに別の優れたリンクがありますので、参照してください。http://csharpin Depth.com/Articles/Chapter2/Events.aspx

この記事の要点を簡単に言うと、イベントはデリゲートをカプセル化したものです。

記事からの引用:

C#/.NET にはイベントが概念として存在しなかったとします。別のクラスがイベントをサブスクライブするにはどうすればよいでしょうか?3 つのオプション:

  1. パブリックデリゲート変数

  2. プロパティに基づいたデリゲート変数

  3. AddXXXHandler メソッドと RemoveXXXHandler メソッドを備えたデリゲート変数

オプション 1 は明らかにひどいものです。私たちがパブリック変数を嫌うのは当然の理由からです。

オプション 2 の方がわずかに優れていますが、サブスクライバが効果的に相互にオーバーライドできます。someInstance.MyEvent =eventHandler; と記述するのは非常に簡単です。これにより、新しいイベント ハンドラーを追加するのではなく、既存のイベント ハンドラーが置き換えられます。さらに、プロパティを記述する必要があります。

オプション 3 は基本的にイベントが提供するものですが、保証された規則 (コンパイラーによって生成され、IL 内の追加のフラグによってサポートされる) と、フィールドのようなイベントが提供するセマンティクスに満足している場合は「無料」実装が含まれます。イベントのサブスクライブとサブスクライブ解除は、イベント ハンドラーのリストへの任意のアクセスを許可せずにカプセル化され、言語は宣言とサブスクリプションの両方の構文を提供することで処理を簡素化できます。

注記:にアクセスできる場合は、 C# 5.0 の解放, 、第 18 章「イベント」の「デリゲートの単純な使用の制限」を読んで、2 つの違いをよりよく理解してください。


シンプルで具体的な例があるといつも助かります。そこで、コミュニティ向けにこれを紹介します。最初に、デリゲートだけを使用してイベントが行うことを行う方法を示します。次に、同じソリューションが次のインスタンスでどのように機能するかを示します。 EventHandler. 。そして、最初の例で説明したことを実行したくない理由を説明します。この投稿のインスピレーションとなったのは、 記事 ジョン・スキート著。

例 1:パブリックデリゲートの使用

ドロップダウン ボックスが 1 つある WinForms アプリがあるとします。ドロップダウンは、 List<Person>. 。ここで、 Person には、Id、Name、NickName、HairColor のプロパティがあります。メイン フォームには、その人のプロパティを表示するカスタム ユーザー コントロールがあります。誰かがドロップダウンで人を選択すると、ユーザー コントロールのラベルが更新されて、選択された人のプロパティが表示されます。

enter image description here

その仕組みは次のとおりです。これをまとめるのに役立つ 3 つのファイルがあります。

  • 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. get/set を使用してデリゲートをプロパティに配置します (ここでの問題は、サブスクライバーが相互にオーバーライドできることです。そのため、多数のメソッドをデリゲートにサブスクライブし、その後、誤って次のようにしてしまう可能性があります) PersonChangedDel = null, 、他のすべてのサブスクリプションを消去します。ここで残るもう 1 つの問題は、ユーザーがデリゲートにアクセスできるため、呼び出しリスト内のターゲットを呼び出すことができることです。外部ユーザーがイベントをいつ発生させるかにアクセスできるようにしたくないのです。
  3. AddXXXHandler メソッドと RemoveXXXHandler メソッドを備えたデリゲート変数

この 3 番目のオプションは、基本的にイベントが提供するものです。EventHandler を宣言すると、パブリックにプロパティとしてではなく、アクセサーを追加/削除するだけのイベントとして、デリゲートへのアクセスが与えられます。

同じプログラムがどのようなものかを見てみましょう。ただし、パブリック デリゲートの代わりにイベントを使用しています (メディエーターもシングルトンに変更しました)。

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

EventHandler で F12 キーを押すと、その定義が追加の「送信者」オブジェクトを持つジェネリック化されたデリゲートであることが表示されることに注意してください。

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 を必要とするため、プロパティを 1 つだけ含むこのクラスを作成しました。

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

これで、イベントが存在する理由と、イベントがデリゲートとどのように異なるのか (しかし機能的には同じであるのか) について少しお分かりいただけたでしょうか。

インターフェイス宣言ではイベントを使用することもできますが、デリゲートの場合は使用できません。

イベントと参加者の間にはなんと大きな誤解があることでしょう!デリゲートは TYPE ( class, 、または interface します)。一方、イベントは単なるメンバー (フィールド、プロパティなど) の一種です。また、他の種類のメンバーと同様に、イベントにもタイプがあります。ただし、イベントの場合は、イベントのタイプをデリゲートが指定する必要があります。たとえば、インターフェイスによって定義されたタイプのイベントを宣言することはできません。

結論として、次のようにすることができます 観察:イベントのタイプはデリゲートによって定義されなければなりません. 。これはイベントとデリゲートの間の主な関係であり、セクションで説明されています。 II.18 イベントの定義ECMA-335 (CLI) パーティション I ~ VI:

通常の使用法では、TypeSpec (存在する場合) 代表者を特定します その署名は、イベントの fire メソッドに渡される引数と一致します。

しかし、 この事実は、イベントがバッキングデリゲートフィールドを使用することを意味するものではありません。. 。実際、イベントは、選択した任意の異なるデータ構造タイプのバッキング フィールドを使用できます。C# でイベントを明示的に実装する場合、イベントを保存する方法を自由に選択できます。 イベントハンドラ (ご了承ください イベントハンドラ の例です イベントの種類, 、これは強制的に デリゲートタイプ---前回より 観察)。ただし、これらのイベント ハンドラー (デリゲート インスタンス) を次のようなデータ構造に保存できます。 List または Dictionary または他のフィールド、または後援デリゲートフィールドでも。ただし、デリゲート フィールドの使用は必須ではないことを忘れないでください。

.net のイベントは、Add メソッドと Remove メソッドの指定された組み合わせであり、どちらも特定の種類のデリゲートを必要とします。C# と vb.net は両方とも、イベント サブスクリプションを保持するデリゲートを定義する追加メソッドと削除メソッドのコードを自動生成し、渡されたデリゲートをそのサブスクリプション デリゲートに追加または削除できます。また、VB.net は、サブスクリプション リストが空でない場合に限り、サブスクリプション リストを呼び出すコード (RaiseEvent ステートメントを使用) を自動生成します。何らかの理由で、C# は後者を生成しません。

マルチキャスト デリゲートを使用してイベント サブスクリプションを管理するのが一般的ですが、それが唯一の手段ではないことに注意してください。パブリックな観点から見ると、イベント サブスクライバーになる予定のユーザーは、イベントを受信したいことをオブジェクトに知らせる方法を知る必要がありますが、パブリッシャーがイベントを発生させるためにどのようなメカニズムを使用するかを知る必要はありません。また、.net でイベント データ構造を定義した人は、イベント データ構造を生成するための公開手段があるべきだと考えていたようですが、C# も vb.net もその機能を利用していないことにも注意してください。

イベントについて簡単に定義するには:

イベントというのは、 参照 2 つの制限のあるデリゲートに

  1. 直接呼び出すことはできません
  2. 値を直接割り当てることはできません (例:eventObj = delegateMethod)

上記の 2 つはデリゲートの弱点であり、イベントで対処されます。fiddler の違いを示す完全なコードサンプルはここにあります 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();
    }


}

Covariance そして Contravariance デリゲート オブジェクトにさらなる柔軟性を提供します。一方、イベントにはそのような概念はありません。

  • Covariance メソッドのリターンタイプがデリゲートの返品タイプを指定するクラスから派生したクラスであるクラスである場合、メソッドを代表者に割り当てることができます。
  • Contravariance メソッドのパラメータータイプがデリゲートのパラメーターとして指定されているクラスのベースクラスである場合、メソッドをデリゲートに割り当てることができます。

デリゲートはタイプセーフな関数ポインターです。イベントは、デリゲートを使用したパブリッシャー/サブスクライバー設計パターンの実装です。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top