Conosco eventi e delegati in C #. Li trovo incredibilmente utili per i sottosistemi basati sugli eventi. Una cosa che non capisco, tuttavia, è il motivo per cui tutta la documentazione .NET per gli eventi utilizza un modello molto specifico per i delegati:
public class Foo
{
public class CustomEventArgs : System.EventArgs
{
...
}
public delegate void CustomEventHandler(object sender, EventArgs e);
public event CustomEventHandler CustomEvent;
public void OnCustomEvent(int x, float y, bool z)
{
if (CustomEvent != null)
CustomEvent(this, new CustomEventArgs(x, y, z));
}
}
Trovo questo metodo di dichiarazione e utilizzo dei delegati incredibilmente insicuro e goffo rispetto all'utilizzo di delegati personalizzati. Ad esempio:
public class Foo
{
public delegate void CustomEventHandler(Foo foo, int x, float y, bool z);
public event CustomEventHandler CustomEvent;
public void OnCustomEvent(int x, int y, int z)
{
if (CustomEvent != null)
CustomEvent(this, x, y, z);
}
}
Oltre all'ovvio vantaggio di "È il modello più familiare ai programmatori .NET", non riesco a vedere nessun altro vantaggio pratico nell'utilizzare i delegati con le firme void CustomEventHandler(object sender, CustomEventArgs e)
anziché con i delegati personalizzati. Principalmente, hai i seguenti vantaggi con i delegati personalizzati:
- Puoi garantire che il mittente sia di un tipo specifico.
- Non hai bisogno di una classe completamente nuova solo per passare i dati dell'evento, che porta al codice di messaggistica e al nome dell'inquinamento
Sarei interessato a sapere se ci sono altri vantaggi nell'usare il modello .NET per eventi e delegati che potrei mancare.
Modifica
Principalmente come risposta a @MainMa, volevo dare un esempio più concreto. L'esempio è una semplice rappresentazione di un personaggio con un concetto di salute e morte. Il personaggio lancia eventi quando è ferito o muore. Considera lo scenario seguente, utilizzando i delegati personalizzati:
public class Character
{
public delegate void DeathEventHandler(Character character);
public delegate void HurtEventHandler(Character character, float damage);
public event DeathEventHandler DeathEvent;
public event HurtEventHandler HurtEvent;
public bool IsDead { ... }
public void ApplyDamage(float damage)
{
...
OnHurt(damage);
...
if (IsDead)
OnDeath();
}
public void OnHurt(float damage)
{
if (HurtEvent != null)
HurtEvent(this, damage);
}
public void OnDeath()
{
if (DeathEvent != null)
DeathEvent(this);
}
}
Per me, questo è semplice. Separa le preoccupazioni e impone l'incapsulamento. Un conduttore che è preoccupato solo per la morte di un personaggio non ha bisogno di sapere quanti danni sono stati applicati al personaggio prima della morte. Così avanti e così via.
Ora confronta questo modello .NET.
public class Character
{
public class CharacterHurtEventArgs : EventArgs
{
public float Damage { ... }
public bool Dead { ... }
}
public event EventHandler<CharacterHurtEventArgs> HurtEvent;
public event EventHandler<EventArgs> DeathEvent; // No args ...?
// ... The rest is the same story as above more or less ...
}
Usando questo, ho appena aggiunto una classe extra. Il conduttore ha ancora bisogno di delegati diversi per questi eventi, e non vi è alcuna garanzia che l'oggetto morto sia un personaggio. Qualunque cosa potrebbe lanciarlo.
Ovviamente, seguendo il suggerimento di @ MainMa, potremmo modificarlo in:
public class Character
{
public class HealthConditionEventArgs : EventArgs
{
public float Damage { ... }
public bool Dead { ... }
public float Hitpoints { ... }
...
}
public event EventHandler<HealthConditionEventArgs> HealthConditionChangedEvent;
public void ApplyDamage(float damage)
{
...
HealthConditionChangedEventArgs e = new HealthConditionChangedEventArgs();
...
OnHealthConditionChanged(e);
...
}
public void OnHealthConditionChanged(HealthConditionEventArgs e)
{
if (HealthConditionChangedEvent != null)
HealthConditionChangedEvent(e);
}
}
Questo è leggermente più ordinato, ma elimina la separazione delle preoccupazioni. Ora se il conduttore si preoccupa solo della morte, dovrà ricevere tutti gli eventi feriti. Naturalmente, potremmo fare in modo che eventi separati contengano lo stesso argomento degli eventi, ma faresti comunque circolare l'intera condizione di salute dei personaggi come parte dell'evento. Di nuovo, se ti importa solo della morte, non è necessario che il conduttore sappia più del fatto che il personaggio è morto.
A mio parere, il primo esempio, tuttavia, risolve tutti questi problemi di incapsulamento. Delegati discreti per eventi discreti, con solo le informazioni necessarie passate in ogni evento. Neanche nuove classi.