デリゲートとイベントの違いは何ですか? どちらも実行可能な関数への参照を保持していませんか?
11 に答える
イベント宣言は、デリゲートインスタンスに抽象化と保護のレイヤーを追加します。この保護により、デリゲートのクライアントがデリゲートとその呼び出しリストをリセットするのを防ぎ、呼び出しリストからのターゲットの追加または削除のみを許可します。
違いを理解するには、この 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 つだけ欠落させてオーバーライドしました(代わりに+
を使用しました) 。=
+=
別の弱点は、クラスを使用するすべてのクラスがAnimal
デリゲートを直接呼び出すことができることです。たとえば、animal.Run()
oranimal.Run.Invoke()
は Animal クラスの外部で有効です。
これらの弱点を回避する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();
違い:
- パブリック プロパティではなくパブリック フィールドを使用しています (イベントを使用すると、コンパイラは不要なアクセスからフィールドを保護します)。
- イベントを直接割り当てることはできません。この場合、動作をオーバーライドして示した以前のエラーは発生しません。
- クラス外の誰もイベントを発生させたり呼び出したりすることはできません。たとえば、
animal.Run()
oranimal.Run.Invoke()
は Animal クラスの外では無効であり、コンパイラ エラーが発生します。 - イベントはインターフェイス宣言に含めることができますが、フィールドは含めることができません
ノート:
EventHandler は、次のデリゲートとして宣言されています。
public delegate void EventHandler (object sender, EventArgs e)
送信者 (オブジェクト型) とイベント引数を取ります。静的メソッドから送信された場合、送信者は null です。
を使用するこの例は、代わりにEventHandler<ArgsSpecial>
を使用して記述することもできますEventHandler
。
EventHandler に関するドキュメントについては、こちらを参照してください
構文上および操作上の特性に加えて、意味上の違いもあります。
デリゲートは、概念的には関数テンプレートです。つまり、デリゲートの「タイプ」と見なされるために関数が順守しなければならないコントラクトを表します。
イベントは...まあ、イベントを表します。それらは、何かが起こったときに誰かに警告することを目的としており、そうです、デリゲートの定義に従いますが、同じものではありません。
それらが(構文的にもILコードでも)まったく同じものであったとしても、意味上の違いは残ります。一般に、同じ方法で実装されている場合でも、2つの異なる概念に対して2つの異なる名前を付けることを好みます(これは、同じコードを2回持つことを意味するわけではありません)。
注: C# 5.0 Unleashedにアクセスできる場合は、18 章の「イベント」というタイトルの「デリゲートのプレーンな使用に関する制限」を読んで、2 つの違いをよりよく理解してください。
シンプルで具体的な例があると、いつも助かります。というわけで、ここにコミュニティ用の 1 つを示します。最初に、デリゲートを単独で使用して、イベントが行うことを行う方法を示します。次に、同じソリューションが のインスタンスでどのように機能するかを示しますEventHandler
。次に、最初の例で説明したことを実行したくない理由を説明します。この投稿は、John Skeetの記事に触発されました。
例 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。これが、 events を使用せずにdelegatesを使用するだけで、これを機能させる方法です。パブリック デリゲートをクラスに配置しただけです。これを静的にすることも、シングルトンにすることもできます。偉大な。
しかし、しかし、しかし、私たちは今上で説明したことをしたくありません。public フィールドは多くの理由で良くないからです。では、私たちの選択肢は何ですか?John Skeet が説明しているように、ここにオプションがあります。
- パブリック デリゲート変数 (これは私たちが上で行ったことです。これを行わないでください。上でなぜそれが悪いのかを説明しました)
- get/set を使用してデリゲートをプロパティに入れます (ここでの問題は、サブスクライバーが互いにオーバーライドできることです。そのため、一連のメソッドをデリゲートにサブスクライブし、誤って と言って
PersonChangedDel = null
、他のすべてのサブスクリプションを消去する可能性があります。ここで残るもう 1 つの問題は、ユーザーがデリゲートにアクセスできるため、呼び出しリスト内のターゲットを呼び出すことができるということです。外部ユーザーがイベントを発生させるタイミングにアクセスできないようにする必要があります。 - 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; }
}
これで、イベントが存在する理由と、デリゲートとの違い (機能的には同じ) について少しお分かりいただけたと思います。
デリゲートではなく、インターフェイス宣言でイベントを使用することもできます。
.net のイベントは、Add メソッドと Remove メソッドの指定された組み合わせであり、どちらも特定の種類のデリゲートを想定しています。C# と vb.net の両方で、イベント サブスクリプションを保持するデリゲートを定義する add メソッドと remove メソッドのコードを自動生成し、渡されたデリゲートをそのサブスクリプション デリゲートに追加/削除できます。VB.net は、(RaiseEvent ステートメントを使用して) コードを自動生成し、サブスクリプション リストが空でない場合にのみ、サブスクリプション リストを呼び出します。何らかの理由で、C# は後者を生成しません。
マルチキャスト デリゲートを使用してイベント サブスクリプションを管理することは一般的ですが、これが唯一の方法ではないことに注意してください。公共の観点からすると、イベント サブスクライバーになる予定のユーザーは、イベントを受信する必要があることをオブジェクトに知らせる方法を知る必要がありますが、パブリッシャーがイベントを発生させるためにどのメカニズムを使用するかを知る必要はありません。また、.net でイベント データ構造を定義した人は明らかに、イベント データ構造を生成する公開手段があるはずだと考えていましたが、C# も vb.net もその機能を利用していません。
簡単な方法でイベントについて定義するには:
イベントは、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();
}
}