델리게이트와 이벤트의 차이점은 무엇입니까? 둘 다 실행할 수있는 함수에 대한 참조를 보유하지 않습니까?
델리게이트와 이벤트의 차이점은 무엇입니까? 둘 다 실행할 수있는 함수에 대한 참조를 보유하지 않습니까?
답변:
이벤트 선언은에 추상화와 보호 계층이 추가 대리자 인스턴스를. 이 보호 기능은 대리자의 클라이언트가 대리자와 호출 목록을 재설정하지 못하도록하며 호출 목록에서 대상을 추가하거나 제거 할 수 있습니다.
차이점을 이해하려면이 두 가지 예를 살펴보십시오.
대리인의 예 (이 경우 Action-값을 반환하지 않는 일종의 대리인 임)
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") ;
마지막 코드 줄을 +
사용 하여 하나의 누락으로 이전 동작을 재정의했습니다 ( =
대신 사용 했습니다 +=
)
또 다른 약점은 당신의 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();
차이점 :
노트:
EventHandler는 다음 대리자로 선언됩니다.
public delegate void EventHandler (object sender, EventArgs e)
발신자 (객체 유형)와 이벤트 인수가 필요합니다. 정적 메소드에서 온 발신인은 널입니다.
을 사용하는이 예제는 대신 EventHandler<ArgsSpecial>
사용하여 작성할 수도 있습니다 EventHandler
.
RaiseEvent
호출 메소드가 animal
이벤트를 사용하는 코드 의 인스턴스에 액세스 할 수있는 한 아무도 호출 할 수 없습니까 ?
animal.Run(this, new ArgsSpecial("Run faster");
합니까?
구문 및 운영 속성 외에도 의미 상 차이가 있습니다.
대표자는 개념적으로 기능 템플릿입니다. 즉, 대리인의 "유형"으로 간주 되려면 기능이 준수해야하는 계약을 표현합니다.
이벤트는 ... 잘, 이벤트를 나타냅니다. 그들은 어떤 일이 발생할 때 누군가에게 경고하기위한 것이며, 예, 대리인 정의를 고수하지만 같은 것은 아닙니다.
그것들이 정확히 같은 것 (구문 적으로 그리고 IL 코드에서)이더라도 의미상의 차이는 여전히 남아 있습니다. 일반적으로 동일한 방식으로 구현 된 경우에도 두 개의 서로 다른 개념에 대해 서로 다른 두 개의 이름을 사용하는 것이 좋습니다 (동일한 코드를 두 번 사용하는 것을 의미하지는 않습니다).
여기에 또 다른 좋은 링크가 있습니다. http://csharpindepth.com/Articles/Chapter2/Events.aspx
간단히 말해서 기사에서 빼앗아-이벤트는 대리인을 캡슐화합니다.
기사에서 인용 :
이벤트가 C # /. NET에 개념으로 존재하지 않았다고 가정하십시오. 다른 클래스는 어떻게 이벤트를 구독합니까? 세 가지 옵션 :
공개 델리게이트 변수
속성으로 뒷받침되는 델리게이트 변수
AddXXXHandler 및 RemoveXXXHandler 메소드가있는 델리게이트 변수
옵션 1은 명백히 끔찍한 데, 우리가 공개 변수를 싫어하는 모든 일반적인 이유 때문입니다.
옵션 2가 약간 나아지지만 구독자가 서로를 대체 할 수 있습니다. someInstance를 작성하기가 너무 쉽습니다 .MyEvent = eventHandler; 새 이벤트 핸들러를 추가하지 않고 기존 이벤트 핸들러를 대체합니다. 또한 속성을 작성해야합니다.
옵션 3은 기본적으로 이벤트가 제공하는 것이지만 필드와 유사한 이벤트가 제공하는 의미에 만족하는 경우 보장 된 규칙 (컴파일러에서 생성하고 IL에서 추가 플래그로 지원)과 "무료"구현을 사용합니다. 이벤트 처리기 목록에 대한 임의의 액세스를 허용하지 않고 이벤트 구독 및 구독 취소가 캡슐화되며 언어는 선언 및 구독에 대한 구문을 제공하여 작업을 단순화 할 수 있습니다.
public Delegate
변수가 "데이터"를 노출 할 것이지만, 내가 아는 한, OOP는 Delegate
( "객체"도 "메시지"도 아님) 어떤 개념도 언급하지 않았다. .NET은 실제로 델리게이트를 데이터처럼 취급하지 않습니다.
AddXXXHandler
메소드를 작성 private Delegate
하는 것이 좋습니다. 이 경우 핸들러가 이미 설정되어 있는지 확인하고 적절하게 반응 할 수 있습니다. Delegate
모든 핸들러를 지울 수 있는 객체를 보유 해야하는 경우에도 좋은 설정 일 수 있습니다 ( event
이 방법을 제공하지는 않음).
참고 : C # 5.0 Unleashed에 액세스 할 수있는 경우 "이벤트"라는 제목의 18 장에서 "대리인의 일반 사용에 대한 제한 사항"을 읽고 두 항목의 차이점을 더 잘 이해하십시오.
항상 간단하고 구체적인 예를 갖는 데 도움이됩니다. 여기 커뮤니티를위한 것이 있습니다. 먼저 델리게이트 만 사용하여 이벤트가 우리를 위해하는 일을 수행하는 방법을 보여줍니다. 그런 다음 동일한 솔루션이의 인스턴스와 어떻게 작동하는지 보여줍니다 EventHandler
. 그런 다음 첫 번째 예에서 설명하지 않은 이유를 설명합니다. 이 게시물은 기사 에서 영감을 얻었습니다. John Skeet .
예 1 : 공개 대리인 사용
단일 드롭 다운 상자가있는 WinForms 앱이 있다고 가정합니다. 드롭 다운이에 바인딩됩니다 List<Person>
. 여기서 Person은 Id, Name, NickName, HairColor의 속성을 갖습니다. 기본 양식에는 해당 개인의 속성을 표시하는 사용자 지정 사용자 정의 컨트롤이 있습니다. 드롭 다운에서 사람을 선택하면 사용자 컨트롤의 레이블이 업데이트되어 선택한 사람의 속성이 표시됩니다.
작동 방식은 다음과 같습니다. 이를 정리하는 데 도움이되는 세 개의 파일이 있습니다.
각 클래스에 대한 관련 코드는 다음과 같습니다.
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이 설명했듯이 옵션은 다음과 같습니다.
PersonChangedDel = null
로``다른 구독을 모두 제거 있습니다. 여기에 남아있는 다른 문제는 사용자가 대리인에게 액세스 할 수 있기 때문에 호출 목록에서 대상을 호출 할 수 있다는 것입니다. 외부 사용자가 이벤트를 제기 할 때 액세스 할 수는 없습니다.이 세 번째 옵션은 본질적으로 이벤트가 우리에게 제공하는 것입니다. 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를 매개 변수로 사용하기 때문에 단일 속성 만으로이 클래스를 만들었습니다.
class PersonChangedEventArgs
{
public Person Person { get; set; }
}
우리에게 이벤트가 왜 있는지, 그리고 이벤트가 델리게이트와는 다르지만 기능적으로는 어떻게 다른지에 대해 조금 보여 주길 바랍니다.
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
. 의 최신 버전 에서는 싱글 톤에 대한 참조가있을 때마다를 Mediator
호출 할 수 있습니다 OnPersonChange
. 어쩌면 Mediator
접근 방식이 특정 동작을 방해하지 않으며 이벤트 버스에 더 가깝다는 것을 언급해야 할 것 입니다.
델리게이트가 아닌 인터페이스 선언에서 이벤트를 사용할 수도 있습니다.
Action a { get; set; }
인터페이스 정의를 가질 수 있습니다 .
이벤트와 대의원들 사이에 정말 큰 오해 !!! 델리게이트는 TYPE (예 : class
, 또는 do)을 지정 interface
하지만 이벤트는 일종의 MEMBER (예 : 필드, 속성 등)입니다. 그리고 다른 종류의 멤버와 마찬가지로 이벤트에도 유형이 있습니다. 그러나 이벤트의 경우 이벤트 유형은 대리인이 지정해야합니다. 예를 들어, 인터페이스에 의해 정의 된 유형의 이벤트를 선언 할 수 없습니다.
결론적으로, 우리는 다음과 같은 관찰을 할 수있다 : 이벤트의 타입은 반드시 delegate에 의해 정의되어야한다 . 이 이벤트와 대리인 간의 기본 관계이며 단원에서 설명 II.18 정의 이벤트 의 ECMA-335 (CLI)에 파티션 I VI :
일반적인 사용법에서 TypeSpec (있는 경우) 은 서명이 이벤트의 fire 메소드에 전달 된 인수와 일치 하는 대리자 를 식별합니다 .
그러나이 사실은 이벤트가 지원 위임 필드를 사용한다는 것을 의미하지는 않습니다 . 실제로, 이벤트는 선택한 다른 데이터 구조 유형의 백업 필드를 사용할 수 있습니다. C #에서 명시 적으로 이벤트를 구현하는 경우 이벤트 핸들러 를 저장하는 방법을 자유롭게 선택할 수 있습니다 ( 이벤트 핸들러 는 이벤트 유형의 인스턴스이며 , 이는 이전 관찰 의 위임 유형 임). ). 그러나 이러한 이벤트 처리기 (대리자 인스턴스)는 a 또는 a 와 같은 다른 데이터 구조 또는 백킹 대리자 필드에 저장할 수 있습니다 . 그러나 위임 필드를 반드시 사용해야하는 것은 아닙니다.List
Dictionary
.net의 이벤트는 Add 메서드와 Remove 메서드의 지정된 조합이며 둘 다 특정 유형의 대리자를 기대합니다. C #과 vb.net은 이벤트 구독을 유지하기 위해 대리자를 정의하고 해당 구독 대리자와 전달 된 위임을 추가 / 제거하는 add 및 remove 메서드에 대한 코드를 자동 생성 할 수 있습니다. VB.net은 또한 비어 있지 않은 경우에만 구독 목록을 호출하는 코드 (RaiseEvent 문 포함)를 자동 생성합니다. 어떤 이유로 C #은 후자를 생성하지 않습니다.
멀티 캐스트 델리게이트를 사용하여 이벤트 구독을 관리하는 것이 일반적이지만 그렇게하는 것이 유일한 방법은 아닙니다. 공개적인 관점에서 볼 때, 예정된 이벤트 가입자는 개체에게 이벤트 수신을 알리는 방법을 알아야하지만, 게시자가 이벤트를 발생시키기 위해 어떤 메커니즘을 사용할지는 알 필요가 없습니다. 또한 .net에서 이벤트 데이터 구조를 정의한 사람은 공개적으로이를 올리는 방법이 있어야한다고 생각했지만 C #이나 vb.net은이 기능을 사용하지 않습니다.
간단한 방법으로 이벤트에 대해 정의하려면 다음을 수행하십시오.
이벤트는 두 가지 제한이있는 델리게이트에 대한 참조입니다.
위의 두 가지는 대표단의 약점이며 이벤트에서 해결됩니다. 피들러의 차이점을 보여주는 완전한 코드 샘플은 https://dotnetfiddle.net/5iR3fB 입니다.
차이를 이해하기 위해 값을 호출 / 할당하는 Event와 Delegate 및 클라이언트 코드 간의 주석 전환
인라인 코드는 다음과 같습니다.
/*
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();
}
}
Intermediate Language를 확인하면 .net 컴파일러가 다른 클래스에서 상속 된 클래스 (invoke, beginInvoke, endInvoke 및 delegate)와 같은 내장 함수를 사용하여 IL에서 봉인 된 클래스로 델리게이트를 변환하여 "SystemMulticast"라고 할 수 있습니다. Event는 몇 가지 추가 속성이있는 Delegate의 자식 클래스입니다.
이벤트 인스턴스와 델리게이트의 차이점은 선언 외부에서 이벤트를 실행할 수 없다는 것입니다. 클래스 A에서 이벤트를 선언하면 클래스 A에서만이 이벤트를 실행할 수 있습니다. 클래스 A에서 대리자를 선언하면 어디서나이 대리자를 사용할 수 있습니다. 나는 이것이 그들 사이의 주요 차이점이라고 생각합니다