Durante le indagini su this question mi sono incuriosito su come le nuove funzionalità di covarianza/controvarianza in C# 4.0 influiranno su di esso.Event and delegate controvariance in .NET 4.0 e C# 4.0
In Beta 1, C# sembra in disaccordo con il CLR. Eseguire in C# 3.0, se si ha:
public event EventHandler<ClickEventArgs> Click;
... e poi altrove si ha:
button.Click += new EventHandler<EventArgs>(button_Click);
... il compilatore sarebbe barf perché sono tipi delegato incompatibili. Ma in C# 4.0, si compila bene, perché in CLR 4.0 il parametro type è ora contrassegnato come in
, quindi è controverso, e quindi il compilatore presuppone che il delegato multicast +=
funzionerà.
Ecco la mia prova:
public class ClickEventArgs : EventArgs { }
public class Button
{
public event EventHandler<ClickEventArgs> Click;
public void MouseDown()
{
Click(this, new ClickEventArgs());
}
}
class Program
{
static void Main(string[] args)
{
Button button = new Button();
button.Click += new EventHandler<ClickEventArgs>(button_Click);
button.Click += new EventHandler<EventArgs>(button_Click);
button.MouseDown();
}
static void button_Click(object s, EventArgs e)
{
Console.WriteLine("Button was clicked");
}
}
Ma anche se si compila, non funziona in fase di esecuzione (ArgumentException
: I delegati devono essere dello stesso tipo).
Va bene se si aggiunge solo uno dei due tipi di delegato. Ma la combinazione di due diversi tipi in un multicast provoca l'eccezione quando viene aggiunto il secondo.
Immagino che questo sia un bug nel CLR in beta 1 (il comportamento del compilatore sembra sperare bene).
Aggiornamento per Release Candidate:
Il codice di cui sopra non è più compila. Deve essere che la controvarianza di TEventArgs
nel tipo di delegato EventHandler<TEventArgs>
è stata ripristinata, quindi ora che il delegato ha la stessa definizione di .NET 3.5.
Cioè, la versione beta ho guardato deve aver avuto:
public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e);
Ora è di nuovo a:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
Ma il parametro Action<T>
delegato T
è ancora controvariante:
public delegate void Action<in T>(T obj);
Lo stesso vale per di T
essendo covariante.
Questo compromesso ha molto senso, a patto che si presuma che l'uso principale dei delegati multicast sia nel contesto degli eventi. Personalmente ho scoperto che non uso mai delegati multicast, tranne come eventi.
Quindi suppongo che gli standard di codifica C# possano ora adottare una nuova regola: non creare delegati multicast da più tipi di delegati correlati tramite covarianza/controvarianza. E se non sai cosa significa, basta evitare di usare Action
per gli eventi sul lato sicuro.
Naturalmente, questa conclusione ha implicazioni per the original question that this one grew from ...
Quanto vuoi interessante è la domanda? –
Sono sorpreso che questo buco sia sfuggito all'attenzione del team C#, dovrebbe essere una delle prime cose che avrebbero testato dopo aver introdotto la varianza per i delegati generici vero? Mostra anche C# 5 (la versione clr è la stessa). – nawfal