Evento y delegación de contravariancia en.NET 4.0 y C#4.0
events delegates (3)
¿Estás obteniendo la ArgumentException de ambos? Si la excepción es lanzada solo por el nuevo controlador, entonces pensaría que es compatible con versiones anteriores.
Por cierto, creo que tienes tus comentarios mezclados. En C # 3.0 esto:
button.Click += new EventHandler<EventArgs>(button_Click); // old
no se habría ejecutado. Eso es c # 4.0
Mientras investigaba esta pregunta , sentí curiosidad acerca de cómo las nuevas características de covarianza / contravarianza en C # 4.0 lo afectarán.
En Beta 1, C # parece estar en desacuerdo con el CLR. De vuelta en C # 3.0, si tuvieras:
public event EventHandler<ClickEventArgs> Click;
... y luego en otro lugar que tenías:
button.Click += new EventHandler<EventArgs>(button_Click);
... el compilador descartaría porque son tipos de delegados incompatibles. Pero en C # 4.0, compila bien, porque en CLR 4.0 el parámetro de tipo ahora está marcado como in
, por lo que es contravariante, por lo que el compilador supone que el delegado de multidifusión +=
funcionará.
Aquí está mi prueba:
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");
}
}
Pero aunque se compila, no funciona en tiempo de ejecución ( ArgumentException
: los delegados deben ser del mismo tipo).
Está bien si solo agrega uno de los dos tipos de delegados. Pero la combinación de dos tipos diferentes en una multidifusión causa la excepción cuando se agrega la segunda.
Supongo que esto es un error en el CLR en la versión beta 1 (el comportamiento del compilador parece afortunado).
Actualización para Release Candidate:
El código anterior ya no se compila. Debe ser que la contravariancia de TEventArgs
en el tipo de delegado EventHandler<TEventArgs>
se ha retrotraído, por lo que ahora ese delegado tiene la misma definición que en .NET 3.5.
Es decir, la beta que miré debe haber tenido:
public delegate void EventHandler<in TEventArgs>(object sender, TEventArgs e);
Ahora está de vuelta a:
public delegate void EventHandler<TEventArgs>(object sender, TEventArgs e);
Pero el parámetro T
delegado de Action<T>
sigue siendo contravariante:
public delegate void Action<in T>(T obj);
Lo mismo ocurre con la Func<T>
de Func<T>
es covariante.
Este compromiso tiene mucho sentido, siempre y cuando supongamos que el uso principal de los delegados de multidifusión se encuentra en el contexto de los eventos. Personalmente, descubrí que nunca uso delegados de multidifusión, excepto como eventos.
Así que supongo que los estándares de codificación C # ahora pueden adoptar una nueva regla: no formar delegados de multidifusión de múltiples tipos de delegados relacionados a través de la covarianza / contravarianza. Y si no sabes lo que eso significa, simplemente evita usar Action
para que los eventos sean seguros.
Por supuesto, esa conclusión tiene implicaciones para la pregunta original de la que creció ...
Muy interesante. No necesita usar eventos para ver que esto ocurra, y de hecho, me resulta más sencillo usar delegados simples.
Considere Func<string>
y Func<object>
. En C # 4.0 puede convertir implícitamente una Func<string>
a Func<object>
porque siempre puede usar una referencia de cadena como referencia de objeto. Sin embargo, las cosas van mal cuando intenta combinarlas. Aquí hay un programa breve pero completo que demuestra el problema de dos maneras diferentes:
using System;
class Program
{
static void Main(string[] args)
{
Func<string> stringFactory = () => "hello";
Func<object> objectFactory = () => new object();
Func<object> multi1 = stringFactory;
multi1 += objectFactory;
Func<object> multi2 = objectFactory;
multi2 += stringFactory;
}
}
Esto compila bien, pero las dos llamadas Combine
(ocultas por el + = azúcar sintáctico) arrojan excepciones. (Comente el primero para ver el segundo).
Esto es definitivamente un problema, aunque no estoy exactamente seguro de cuál debería ser la solución. Es posible que, en el momento de la ejecución, el código de delegado necesite encontrar el tipo más apropiado para usar en función de los tipos de delegados implicados. Eso es un poco desagradable. Sería bastante agradable tener una llamada Delegate.Combine
genérica, pero no podría realmente expresar los tipos relevantes de una manera significativa.
Una cosa que vale la pena señalar es que la conversión covariante es una conversión de referencia; en lo anterior, multi1
y stringFactory
refieren al mismo objeto: no es lo mismo que escribir
Func<object> multi1 = new Func<object>(stringFactory);
(En ese punto, se ejecutará la siguiente línea sin excepción). En el momento de la ejecución, el BCL realmente tiene que ocuparse de combinar una Func<string>
y un Func<object>
; no tiene otra información para continuar.
Es desagradable, y espero seriamente que se arregle de alguna manera. Avisaré a Mads y Eric sobre esta pregunta para que podamos obtener un comentario más informado.
Solo tuve que arreglar esto en mi aplicación. Hice lo siguiente:
// variant delegate with variant event args
MyEventHandler<<in T>(object sender, IMyEventArgs<T> a)
// class implementing variant interface
class FiresEvents<T> : IFiresEvents<T>
{
// list instead of event
private readonly List<MyEventHandler<T>> happened = new List<MyEventHandler<T>>();
// custom event implementation
public event MyEventHandler<T> Happened
{
add
{
happened.Add(value);
}
remove
{
happened.Remove(value);
}
}
public void Foo()
{
happened.ForEach(x => x.Invoke(this, new MyEventArgs<T>(t));
}
}
No sé si existen diferencias relevantes para los eventos regulares de lanzamiento múltiple. Por lo que lo usé, funciona ...
Por cierto: nunca me gustaron los eventos en C # . No entiendo por qué hay una función de idioma, cuando no proporciona ninguna ventaja.