デリゲートとイベントの違いは何ですか?


317

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



2
これは例で説明していますunitygeek.com/delegates-events-unity
Rahul Lalit

回答:


283

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


44
もちろん、この保護層は、「クライアント」(定義するクラス/構造体の外のコード)がデリゲートを呼び出したり、イベントの「背後」にあるデリゲートオブジェクトを取得したりすることを防ぎます。
Jeppe Stig Nielsen

7
完全に真実ではありません。バックエンドデリゲートインスタンスなしでイベントを宣言できます。C#では、イベントを明示的に実装し、選択した別のバックエンドデータ構造を使用できます。
ミゲルガンボア2015

3
@mmcdoleあなたは彼を説明する例を提供できますか?
vivek nuna

103

違いを理解するには、この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#で使用できます。

あなたの動物クラスはこのように変化します:

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に関するドキュメントについては、こちらを参照してください


7
「クラス外の誰もイベントを発生させることはできません」に遭遇するまで、すべてがすばらしく見えました。どういう意味ですか?RaiseEvent呼び出しメソッドがanimalイベントを使用するコードののインスタンスにアクセスできる限り、誰も呼び出すことはできませんか?
dance2die 2014

11
@Sungイベントは、クラス内からのみ発生させることができます。説明が明確ではない可能性があります。イベントを使用すると、イベントを発生させる関数(カプセル化)を呼び出すことができますが、それを定義するクラス内からのみ発生させることができます。よくわからない場合はお知らせください。
faby 2014

1
「イベントを直接割り当てることはできません。」私があなたの間違いを理解していない限り、これは真実ではありません。次に例を示します。gist.github.com
Chiel92

2
@faby、イベントがパブリックとして宣言されていても、私はまだできないということanimal.Run(this, new ArgsSpecial("Run faster");ですか?
Pap

1
@ChieltenBrinkeもちろん、イベントはクラスのメンバー内で割り当てることができますが、それ以外の場合はできません。
ジムバルター2017年

94

構文と操作のプロパティに加えて、意味上の違いもあります。

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

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

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


8
デリゲートの優れた説明。
サンプソン

1
では、イベントは「特別な」タイプのデリゲートと言えるでしょうか。
Pap

デリゲートを使用して、「何かが発生したときに誰かに警告する」ことができます。多分あなたはそれをしないだろうが、それは可能であり、したがってそれはイベントの固有の特性ではない。
スティーブ

@JorgeCórdobaのデリゲートとイベントの例デリゲートは新聞の所有者とイベント(購読または購読解除)で、一部の人々は新聞を購入し、一部の人々は新聞を購入しないことを意味します。正しいか間違っていますか?
Rahul_Patil

37

参照する別の良いリンクがあります。 http://csharpindepth.com/Articles/Chapter2/Events.aspx

簡単に言うと、記事からの取説-イベントはデリゲートをカプセル化したものです。

記事からの引用:

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

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

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

  3. AddXXXHandlerメソッドとRemoveXXXHandlerメソッドを持つデリゲート変数

オプション1は、私たちがパブリック変数を嫌う通常の理由すべてのために、明らかに恐ろしいです。

オプション2の方が少し優れていますが、サブスクライバーが互いに効果的にオーバーライドできるようになっています。新しいイベントハンドラを追加するのではなく、既存のイベントハンドラを置き換えます。さらに、プロパティを書き込む必要があります。

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


素敵で簡潔な説明。Thanx
Pap

これは何よりも理論的な懸念事項ですが、FWIWでは常に「オプション1はパブリック変数が好きではないので悪い」という議論は、もう少し明確にする必要があるかもしれません。「悪いOOPプラクティス」であるため、技術的public Delegate変数は「データ」を公開するだろうと彼が言っている場合、私の知る限り、OOPはDelegate(オブジェクトでもメッセージでもない)のような概念については言及していませ ん。 、そして.NETはとにかくデリゲートをデータのようにほとんど扱いません。
jrh

さらに実用的なアドバイスもしたいと思いますが、ハンドラーが1つだけであることを確認したい場合はAddXXXHandlerprivate Delegate変数を使用して独自のメソッドを作成することをお勧めします。この場合、ハンドラがすでに設定されているかどうかを確認し、適切に対応できます。これは、Delegateすべてのハンドラーをクリアできるようにを保持しているオブジェクトが必要な場合にも適切な設定です(eventこれを行う方法はありません)。
jrh

7

注:C#5.0 Unleashedにアクセスできる場合は、18章の「イベント」というタイトルの「デリゲートのプレーンな使用に関する制限」を読んで、2つの違いをよく理解してください。


簡単で具体的な例を示すことは常に助けになります。これがコミュニティ向けのものです。最初に、イベントを使用してデリゲートを単独で使用する方法を示します。次に、同じソリューションがのインスタンスでどのように機能するかを示しますEventHandler。そして、最初の例で説明したことをしたくない理由を説明します。この投稿は、ジョン・スキートの記事に触発されまし

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

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

ここに画像の説明を入力してください

これがどのように機能するかです。これをまとめるのに役立つ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`.
}

OK。あなたはこの作業を取得する方法をそのですので、イベントを使用せずばかりデリゲートを使用して。パブリックデリゲートをクラスに配置するだけで、静的またはシングルトンなどにできます。すごい。

しかし、しかし、私たちは、上で述べたようなことをしたくありません。ので、公共のフィールドが悪いです、多くの、多くの理由のために。それで私たちのオプションは何ですか?ジョン・スキートが説明するように、ここに私たちのオプションがあります:

  1. パブリックデリゲート変数(これは上記で実行したことです。これを実行しないでください。これはなぜ悪いのかを上記で説明しました)
  2. デリゲートをget / setを使用してプロパティに配置します(ここでの問題は、サブスクライバーがお互いをオーバーライドできることです-そのため、デリゲートに多数のメソッドをサブスクライブし、誤ってと言ってPersonChangedDel = null、他のすべてのサブスクリプションを削除してしまう可能性があります。ここで残っている他の問題は、ユーザーがデリゲートにアクセスできるため、呼び出しリストのターゲットを呼び出すことができるということです-外部ユーザーがイベントを発生させるときにアクセスできないようにする必要があります。
  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を押すと、定義が追加の "sender"オブジェクトを含む一般化されたデリゲートであることがわかります。

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

うまくいけば、イベントがある理由と、それらがデリゲートとどのように異なっているか(ただし機能的には同じか)について少しお分かりいただけると思います。


私はこの投稿のすべての良い仕事に感謝し、そのほとんどを読んで楽しんでいましたが、まだ1つの問題が解決されていないと感じています- The other problem that remains here is that since the users have access to the delegate, they can invoke the targets in the invocation list -- we don't want external users having access to when to raise our events。の最新バージョンではMediatorOnPersonChangeシングルトンへの参照があればいつでもを呼び出すことができます。たぶん、このMediatorアプローチはその特定の動作を妨げるものではなく、イベントバスに近いということを言及する必要があります。
Ivaylo Slavov、2015年

6

デリゲートではなく、インターフェイス宣言でイベン​​トを使用することもできます。


2
@surfenインターフェイスにはイベントを含めることができますが、デリゲートを含めることはできません。
Alexandr Nikitin 2013

1
どういう意味ですか?Action a { get; set; }インターフェース定義内に含めることができます。
Chiel ten Brinke、2015

6

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

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

一般的な使用法では、TypeSpec(存在する場合)は、シグネチャがイベントのfireメソッドに渡された引数と一致するデリゲート識別します。

ただし、この事実は、イベントがバッキングデリゲートフィールドを使用することを意味するものではありません。実際、イベントでは、選択したさまざまなデータ構造タイプのバッキングフィールドを使用できます。C#でイベントを明示的に実装する場合、イベントハンドラーを格納する方法を自由に選択できますイベントハンドラーイベントタイプのインスタンスであり、これは強制的にデリゲートタイプであることに注意してください---前の観察から))。ただし、これらのイベントハンドラー(デリゲートインスタンス)は、a Listやa などのデータ構造Dictionary、またはバッキングデリゲートフィールドに格納することもできます。ただし、デリゲートフィールドの使用は必須ではないことを忘れないでください。


4

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

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


3

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

イベントは、2つの制限があるデリゲートへの参照です

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

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


}

2

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


0

Intermediate Languageをチェックすると、.netコンパイラがデリゲートをILのシールされたクラスに変換することを理解できます。EventはDelegateの子クラスで、いくつかの追加プロパティがあると思います。

イベントのインスタンスとデリゲートの違いは、宣言の外でイベントを実行できないことです。クラスAでイベントを宣言した場合、このイベントはクラスAでのみ実行できます。クラスAでデリゲートを宣言した場合、このデリゲートはどこでも使用できます。これが主な違いだと思います

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.