Delegeler ve olaylar arasındaki farklar nelerdir? Her ikisi de çalıştırılabilen işlevlere referansları tutmuyor mu?
Delegeler ve olaylar arasındaki farklar nelerdir? Her ikisi de çalıştırılabilen işlevlere referansları tutmuyor mu?
Yanıtlar:
Bir olay bildirimi üzerine soyutlama ve koruma katmanı ekler temsilci örneği. Bu koruma, delege istemcilerinin delege ve çağırma listesini sıfırlamasını önler ve yalnızca çağırma listesine hedef eklemeye veya listeden hedef kaldırmaya izin verir.
Farklılıkları anlamak için bu 2 örneğe bakabilirsiniz
Delegelerle örnek (bu durumda bir Eylem - bir değer döndürmeyen bir tür delege)
public class Animal
{
public Action Run {get; set;}
public void RaiseEvent()
{
if (Run != null)
{
Run();
}
}
}
Temsilciyi kullanmak için şöyle bir şey yapmalısınız:
Animal animal= new Animal();
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running") ;
animal.RaiseEvent();
Bu kod iyi çalışıyor, ancak bazı zayıf noktalarınız olabilir.
Örneğin, bunu yazarsam:
animal.Run += () => Console.WriteLine("I'm running");
animal.Run += () => Console.WriteLine("I'm still running");
animal.Run = () => Console.WriteLine("I'm sleeping") ;
kodun son satırı ile, sadece bir eksik önceki davranışları geçersiz kıldı +
( =
yerine kullandım +=
)
Başka bir zayıf nokta da kullanan her sınıf olmasıdır Animal
sınıfını yükseltebilirsiniz RaiseEvent
çağırarak sadece animal.RaiseEvent()
.
Bu zayıf noktaları önlemek için events
c # ' da kullanabilirsiniz .
Hayvan sınıfınız şu şekilde değişecektir:
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"));
}
}
olayları aramak
Animal animal= new Animal();
animal.Run += (sender, e) => Console.WriteLine("I'm running. My value is {0}", e.Operation);
animal.RaiseEvent();
farklılıklar:
Notlar:
EventHandler aşağıdaki temsilci olarak ilan edildi:
public delegate void EventHandler (object sender, EventArgs e)
bir gönderen (Nesne türü) ve olay bağımsız değişkenleri alır. Statik yöntemlerden geliyorsa gönderen boştur.
Bu örnek, bunun yerine EventHandler<ArgsSpecial>
kullanılarak da yazılabilir EventHandler
.
EventHandler ile ilgili belgeler için buraya bakın
RaiseEvent
Bir arama yönteminin, animal
olayı kullanan koddaki bir örneğine erişimi olduğu sürece kimse arama yapamaz mı?
animal.Run(this, new ArgsSpecial("Run faster");
?
Sözdizimsel ve operasyonel özelliklere ek olarak, anlamsal bir fark da vardır.
Delegeler, kavramsal olarak işlev şablonlarıdır; yani, delegenin "türü" olarak değerlendirilmek için bir işlevin uyması gereken bir sözleşmeyi ifade ederler.
Olaylar ... olayları temsil eder. Bir şey olduğunda birini uyarmak için tasarlanmıştır ve evet, bir delege tanımına bağlı kalırlar, ancak aynı şey değildirler.
Tam olarak aynı şey olsalar bile (sözdizimsel olarak ve IL kodunda) anlamsal farklılık olmaya devam edecektir. Genel olarak, aynı şekilde uygulansalar bile, iki farklı kavram için iki farklı isme sahip olmayı tercih ederim (bu, aynı koda iki kez sahip olmayı sevdiğim anlamına gelmez).
İşte başvurmak için iyi bir bağlantı. http://csharpindepth.com/Articles/Chapter2/Events.aspx
Kısaca, makaleden alınması - Olaylar delegeler üzerinde kapsüllenir.
Makaleden alıntı:
Olayların C # /. NET'te bir kavram olarak var olmadığını varsayalım. Başka bir sınıf bir etkinliğe nasıl abone olur? Üç seçenek:
Genel delege değişkeni
Bir özellik tarafından desteklenen bir temsilci değişken
AddXXXHandler ve RemoveXXXHandler yöntemlerine sahip bir temsilci değişken
Seçenek 1 açıkça korkunçtur, tüm normal nedenlerle genel değişkenlerden nefret ediyoruz.
Seçenek 2 biraz daha iyidir, ancak abonelerin birbirlerini etkili bir şekilde geçersiz kılmalarına izin verir - someInstance.MyEvent = eventHandler; yeni bir olay eklemek yerine mevcut olay işleyicilerinin yerini alacaktır. Ayrıca, hala özellikleri yazmanız gerekir.
Seçenek 3 temelde olayların size verdiği şeydir, ancak garantili bir kural (derleyici tarafından oluşturulur ve IL'de ekstra bayraklarla desteklenir) ve alan benzeri olayların size verdiği anlamlardan memnunsanız "ücretsiz" bir uygulama ile. Olaylara abone olma ve olaylardan aboneliği iptal etme, olay işleyicileri listesine rasgele erişime izin verilmeden kapsüllenir ve diller, hem bildirim hem de abonelik için sözdizimi sağlayarak işleri basitleştirebilir.
public Delegate
değişken "veri" yi açığa çıkaracağını söylüyorsa, ancak bilgim dahilinde OOP hiçbir zaman Delegate
bir "nesne" veya "mesaj" gibi kavramlardan hiç bahsetmedi. ve .NET zaten veri gibi delegelere zar zor davranıyor.
AddXXXHandler
yöntemlerinizi yapmak private Delegate
iyi bir seçenek olabilir. Bu durumda, bir işleyicinin önceden ayarlanmış olup olmadığını kontrol edebilir ve uygun şekilde tepki verebilirsiniz. Delegate
Tüm işleyicileri temizleyebilmek için nesneyi tutan nesneye ihtiyacınız varsa bu da iyi bir kurulum olabilir ( event
bunu yapmak için herhangi bir yol vermez).
NOT: C # 5.0 Unleashed'e erişiminiz varsa , ikisi arasındaki farkları daha iyi anlamak için Bölüm 18'deki "Olaylar" başlıklı "Delegelerin Düz Kullanımına İlişkin Sınırlamalar" bölümünü okuyun.
Her zaman basit, somut bir örnek almamda bana yardımcı olur. İşte topluluk için bir tane. Öncelikle, Etkinliklerin bizim için yaptıklarını yapmak için delegeleri nasıl kullanabileceğinizi göstereceğim. Sonra aynı çözümün bir örneğiyle nasıl çalışacağını göstereceğim EventHandler
. Ve sonra ilk örnekte açıkladıklarımı neden yapmak istemediğimizi açıklıyorum. Bu yazı John Skeet'in bir makalesinden esinlenmiştir .
Örnek 1: Genel temsilci kullanma
Tek bir açılır kutu içeren bir WinForms uygulamam olduğunu varsayalım. Açılır menü bir List<Person>
. Kişi Id, Name, NickName, HairColor özelliklerine sahiptir. Ana formda, o kişinin özelliklerini gösteren özel bir kullanıcı kontrolü bulunur. Birisi, seçilen kişinin özelliklerini göstermek için kullanıcı kontrol güncellemesindeki etiketleri açılır listeden bir kişi seçtiğinde.
İşte böyle çalışır. Bunu bir araya getirmemize yardımcı olan üç dosyamız var:
Sınıfların her biri için ilgili kod:
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);
}
}
}
İşte kullanıcı kontrolümüz:
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;
}
}
Son olarak Form1.cs dosyamızda aşağıdaki kod bulunmaktadır. Burada, temsilciye abone olan herhangi bir kodu çağıran OnPersonChanged'i çağırıyoruz.
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`.
}
Tamam. Yani olayları ve sadece delegeleri kullanmadan bu şekilde çalışacaksınız . Bir sınıfa sadece bir delege koyarız - bunu statik veya tek birton ya da her neyse yapabilirsiniz. Harika.
AMA, AMA, AMA, yukarıda açıkladığım şeyi yapmak istemiyoruz. Çünkü kamusal alanlar birçok nedenden dolayı kötüdür . Peki seçeneklerimiz neler? John Skeet'in açıkladığı gibi, seçeneklerimiz şunlardır:
PersonChangedDel = null
diğer tüm abonelikleri silerek söyleyebiliriz . burada kalan diğer bir sorun da kullanıcılar delege erişimine sahip oldukları için çağırma listesindeki hedefleri çağırabiliyorlar - harici kullanıcıların olaylarımızı ne zaman yükselteceklerine erişmelerini istemiyoruz.Bu üçüncü seçenek aslında bir etkinliğin bize verdiği şeydir. Bir EventHandler bildirdiğimizde, bize bir mülk olarak değil, herkese açık olarak değil, bir delege erişimi sağlıyor, ancak bu şey olarak, yalnızca erişimcileri ekleyen / kaldıran bir etkinlik diyoruz.
Aynı programın nasıl göründüğüne bakalım, ancak şimdi kamu delegesi yerine bir Etkinlik kullanıyor (Arabulucumuzu bir singleton olarak da değiştirdim):
Örnek 2: Herkese açık bir temsilci yerine EventHandler ile
arabulucu:
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'da F12 yaparsanız, tanımın yalnızca ekstra "gönderen" nesnesine sahip genel bir ified delegesi olduğunu gösterecektir:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
Kullanıcı Kontrolü:
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;
}
}
Son olarak, Form1.cs kodu şöyledir:
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Mediator.GetInstance().OnPersonChanged(this, (Person)comboBox1.SelectedItem);
}
EventHandler istiyor ve EventArgs bir parametre olarak, bu sınıf içinde sadece tek bir özellik ile oluşturdum:
class PersonChangedEventArgs
{
public Person Person { get; set; }
}
Umarım bu size neden olaylarımız olduğunu ve delegelerle nasıl farklı olduklarını - ancak işlevsel olarak aynı olduklarını - gösterir.
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
. Öğesinin en son sürümünde , singleton'a bir başvurunuz olduğunda Mediator
aramaya devam edebilirsiniz OnPersonChange
. Belki de Mediator
yaklaşımın bu belirli davranışı engellemediğini ve bir olay veriyoluna daha yakın olduğunu belirtmelisiniz.
Ayrıca olayları temsilciler için değil, arayüz bildirimlerinde kullanabilirsiniz.
Action a { get; set; }
Bir arayüz tanımına sahip olabilirsiniz .
Etkinlikler ve delegeler arasında ne büyük bir yanlış anlama !!! Bir temsilci bir TYPE (a class
veya bir interface
does gibi) belirtirken , bir olay yalnızca bir tür ÜYE'dir (alanlar, özellikler vb.). Ve diğer herhangi bir üye gibi, bir etkinliğin de bir türü vardır. Ancak, bir olay olması durumunda, olayın türü bir temsilci tarafından belirtilmelidir. Örneğin, bir arabirim tarafından tanımlanan türde bir olay bildiremezsiniz.
Sonuç olarak, aşağıdaki Gözlemi yapabiliriz: bir olayın türü bir delege tarafından tanımlanmalıdır ZORUNLU . Bu, olay ve bir temsilci arasındaki temel ilişki ve bölümünde anlatılan II.18 tanımlama olaylar arasında , ECMA-335 (CLI) bölme I VI :
Tipik kullanımda, TypeSpec (varsa) imzası olayın fire yöntemine iletilen bağımsız değişkenlerle eşleşen bir temsilci tanımlar .
Ancak bu gerçek, bir olayın destek delege alanı kullandığını ima ETMEZ . Gerçekte, bir olay seçtiğiniz herhangi bir farklı veri yapısı tipinde bir destek alanı kullanabilir. Bir olayı C # 'da açıkça uygularsanız, olay işleyicilerini saklama biçiminizi seçebilirsiniz ( olay işleyicilerin , olay türünün örnekleri olduğunu unutmayın; bu da zorunlu olarak bir delege türüdür ; önceki Gözlemden) ). Ancak, bu olay işleyicilerini (delege örnekleri olan) a List
veya a Dictionary
veya başka herhangi bir veri yapısında veya hatta destek delege alanında saklayabilirsiniz . Ancak bir delege alanı kullanmanın zorunlu OLMADIĞINI unutmayın.
.Net dosyasındaki bir olay, her ikisi de belirli türde bir temsilci bekleyen bir Add yöntemi ile Remove yönteminin belirlenmiş bir birleşimidir. Hem C # hem de vb.net, olay aboneliklerini tutmak için bir temsilci tanımlayacak ve bu abonelik temsilcisine gönderilen / gönderilen temsilci ekleyecek / kaldıracak ekleme ve kaldırma yöntemleri için kod otomatik olarak oluşturabilir. VB.net ayrıca, yalnızca boş değilse abonelik listesini çağırmak için kodu otomatik olarak oluşturur (RaiseEvent deyimi ile); bazı nedenlerden dolayı, C # ikincisini üretmez.
Çok noktaya yayın delegesi kullanarak olay aboneliklerini yönetmek yaygın olsa da, bunu yapmanın tek yolu olmadığını unutmayın. Kamusal bir bakış açısıyla, bir olay abonesi bir nesneye olayları almak istediğini nasıl bildireceğini bilmelidir, ancak yayıncının olayları yükseltmek için hangi mekanizmayı kullanacağını bilmesine gerek yoktur. Ayrıca, .net'teki olay verisi yapısını kim tanımlasa da, onları yükseltmenin genel bir yolu olması gerektiğini düşünürken, ne C # ne de vb.net bu özelliği kullanmaz.
Etkinlik hakkında basit bir şekilde tanımlamak için:
Etkinlik, iki kısıtlamaya sahip bir temsilciye REFERANS
İkisinin üstünde delegeler için zayıf noktalar var ve etkinlikte ele alındı. Kemancıdaki farkı göstermek için tam kod örneği burada https://dotnetfiddle.net/5iR3fB .
Olayı ve Temsilciyi ve farkı anlamak için temsilciye değer atanan / atayan istemci kodu arasında açıklamayı değiştirin
İşte satır içi kod.
/*
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();
}
}
Temsilci, tür güvenli bir işlev işaretçisidir. Etkinlik, temsilci kullanan yayıncı-abone tasarım modelinin bir uygulamasıdır.
Ara Dil'i işaretlerseniz, .net derleyicisinin delegate'i, invoke, beginInvoke, endInvoke ve delegate sınıfı gibi başka bir sınıftan miras alınan "BuildMulticast" gibi bazı yerleşik işlevlerle IL'de kapalı bir sınıfa dönüştürdüğünü bileceksiniz. Event, bazı ek özelliklere sahip bir Delege alt sınıfıdır.
Olay örneği ile temsilci arasındaki fark, olayı bildirimin dışında çalıştıramazsınız. A sınıfında bir olay bildirirseniz, bu olayı yalnızca A sınıfında çalıştırabilirsiniz. A Sınıfında bir temsilci bildirirseniz, bu temsilciyi her yerde kullanabilirsiniz. Bence bu aralarındaki temel fark