2012-03-31 6 views
5

He intentado crear un evento genérico. Básicamente se debe tener este aspecto:EventHandlers and Covariance

namespace DelegateTest 
{ 
    class Program 
    { 
     static void Main(string[] args) 
     { 
      var lol = new SomeClass(); 
      lol.SomeEvent += handler; 
     } 

     static void handler(object sender, SomeDerivedClass e) 
     { 

     } 

    } 

    class SomeClass 
    { 

     public delegate void SomeEventDelegate<in T>(object sender, T data); 
     public event SomeEventDelegate<ISomeInterface> SomeEvent; 

    } 

    interface ISomeInterface 
    { 
    } 

    class SomeDerivedClass : ISomeInterface 
    { 
    } 
} 

Quiero que el usuario pueda pasar cualquier delegado, que es segundo parámetro se deriva de "ISomeInterface."

"in" especifica contra-varianza, ¿verdad? Eso significa que si la API espera algo más general, puede pasarle algo más específico (en mi base "ISomeInterface" sería general, y mi "SomeDerivedClass" sería específica). Me están diciendo que soy el compilador que "ninguna sobrecarga para el manejador de métodos coincide con DelegateTest.SomeClass.SomeEventDelegate".

Me pregunto por qué esto no está funcionando. ¿Cuáles son los problemas que se causarían si fuera así? ¿O me estoy perdiendo algo para que funcione?

¡Gracias de antemano!

+0

Ver http://stackoverflow.com/questions/129453/net-eventhandlers-generic-or-no –

+0

Creo que no puede haber un solo evento para manejar varios tipos, solo puede tener delegados. Lo que puede hacer es cambiar su manejador para aceptar un 'ISomeInterface' en su lugar, y luego verificar el tipo de objeto que envió. O usa la interfaz en sí. http://msdn.microsoft.com/en-us/library/dd469484.aspx – BrunoLM

Respuesta

6

"in" especifica contra-varianza, ¿no?

Sí.

Eso significa que si la API se esperaba algo más general, se puede pasar algo más específico (en mi base "ISomeInterface" sería en general, y mi "SomeDerivedClass" sería específica).

No. Delegate contravariance permite a un delegado hacer referencia a un método con tipos de parámetros que son menos derivados que en el tipo de delegado. Por ejemplo, supongamos que ISomeInterface tenían un interfaz base:

interface ISomeBaseInterface 
{ 
} 

interface ISomeInterface : ISomeBaseInterface 
{ 
} 

Y supongamos handler tomó ISomeBaseInterface en lugar de SomeDerivedClass:

static void handler(object sender, ISomeBaseInterface e) 

Entonces new SomeClass().SomeEvent += handler funcionaría.

He aquí por qué el código original no es de tipo seguro: Cuando SomeClass plantea SomeEvent, que potencialmente puede pasar nada que implementa ISomeInterface como argumento data. Por ejemplo, podría pasar una instancia de SomeDerivedClass, pero también podría pasar una instancia de

class SomeOtherDerivedClass : ISomeInterface 
{ 
} 

Si fueron capaces de registrar void handler(object sender, SomeDerivedClass e) con el evento, el manejador del terminaría siendo invocado con SomeOtherDerivedClass, que doesn' t trabajo.

En resumen, se puede registrar controladores de eventos que son más general que el tipo de evento, no controladores de eventos que son más específicos .

ACTUALIZACIÓN: Ha comentado:

Bueno, en realidad yo quiero para recorrer la lista y comprobar los tipos.Entonces, si el evento fuera disparado con un objeto de datos de tipo digamos SomeOtherDerivedObject, entonces el programa iteraría a través de la lista de métodos suscritos al evento hasta que encuentre uno que coincida con la firma (objeto, SomeOtherDerivedObject). Por lo tanto, el evento en sí mismo solo se usaría para almacenar, no para llamar realmente a los delegados.

No creo que C# le permita declarar un event que funciona con tipos de delegados arbitrarios. He aquí cómo usted puede escribir métodos que añaden controladores de eventos y invocarlos:

class SomeClass 
{ 
    private Delegate handlers; 

    public delegate void SomeEventDelegate<in T>(object sender, T data); 

    public void AddSomeEventHandler<T>(SomeEventDelegate<T> handler) 
    { 
     this.handlers = Delegate.Combine(this.handlers, handler); 
    } 

    protected void OnSomeEvent<T>(T data) 
    { 
     if (this.handlers != null) 
     { 
      foreach (SomeEventDelegate<T> handler in 
       this.handlers.GetInvocationList().OfType<SomeEventDelegate<T>>()) 
      { 
       handler(this, data); 
      } 
     } 
    } 
} 
+0

Gracias. ¿Hay alguna forma de hacerlo funcionar al revés? Actualmente estoy usando el enfoque tipo Java (interfaces + addListener). – haiyyu

+1

Si está absolutamente seguro de que 'SomeClass' solo pasa instancias de' SomeDerivedClass' como el argumento 'data', entonces puede agregar un molde explícito : 'lol.SomeEvent + = (sender, e) => handler (emisor, (SomeDerivedClass) e)'. Pero también podría declarar 'SomeEvent' como' SomeEventDelegate '. –

+0

Bueno, en realidad quiero repetir la lista y verificar los tipos. Entonces, si el evento fuera disparado con un objeto de datos de tipo digamos SomeOtherDerivedObject, entonces el programa iteraría a través de la lista de métodos suscritos al evento hasta que encuentre uno que coincida con la firma (objeto, SomeOtherDerivedObject). Por lo tanto, el evento en sí mismo solo se usaría para almacenar, no para llamar realmente a los delegados. Espero que sea comprensible. – haiyyu

2

Una molestia importante con contravarianza delegado es que mientras que un delegado de tipo p Action<Fruit> se puede pasar a una rutina esperando un Action<Banana>, un intento de combinar dos delegados cuyos tipos reales son Action<Fruit> y Action<Banana> fallará * incluso si ambos delegados tienen el tipo de "tiempo de compilación" Action<Banana>. Para evitar esto, se recomienda usar un método como el siguiente:

static T As<T>(this Delegate del) where T : class 
    { 
     if (del == null || del.GetType() == typeof(T)) return (T)(Object)del; 
     Delegate[] invList = ((Delegate)(Object)del).GetInvocationList(); 
     for (int i = 0; i < invList.Length; i++) 
      if (invList[i].GetType() != typeof(T)) 
       invList[i] = Delegate.CreateDelegate(typeof(T), invList[i].Target, invList[i].Method); 
     return (T)(Object)Delegate.Combine(invList); 
    } 

Dado un delegado y un tipo de delegado, este método comprobará si el tipo del delegado pasado en coincide exactamente con el tipo especificado; si no lo hace, pero los métodos en el delegado original tienen las firmas adecuadas para el tipo especificado, se creará un nuevo delegado con las características necesarias. Tenga en cuenta que si en dos ocasiones distintas se pasa esta función a los delegados que no son del tipo apropiado pero que se comparan entre sí, los delegados devueltos por este método también se compararán entre sí. Por lo tanto, si se tiene un evento que se supone que debe aceptar un delegado de tipo Action<string>, se podría usar el método anterior para convertir, p. un Action<object> pasado en un Action<string> "real" antes de agregarlo o quitarlo del evento.

Si uno va a añadir o restar un delegado pasado a partir de un campo del tipo de delegado adecuada, la inferencia de tipos y el comportamiento Intellisense pueden mejorarse si uno utiliza los métodos siguientes:

static void AppendTo<T>(this Delegate newDel, ref T baseDel) where T : class 
    { 
     newDel = (Delegate)(Object)newDel.As<T>(); 
     T oldBaseDel, newBaseDel; 
     do 
     { 
      oldBaseDel = baseDel; 
      newBaseDel = (T)(Object)Delegate.Combine((Delegate)(object)oldBaseDel, newDel); 
     } while (System.Threading.Interlocked.CompareExchange(ref baseDel, newBaseDel, oldBaseDel) != oldBaseDel); 
    } 

    static void SubtractFrom<T>(this Delegate newDel, ref T baseDel) where T : class 
    { 
     newDel = (Delegate)(Object)newDel.As<T>(); 
     T oldBaseDel, newBaseDel; 
     do 
     { 
      oldBaseDel = baseDel; 
      newBaseDel = (T)(Object)Delegate.Remove((Delegate)(object)oldBaseDel, newDel); 
     } while (System.Threading.Interlocked.CompareExchange(ref baseDel, newBaseDel, oldBaseDel) != oldBaseDel); 
    } 

Estos métodos aparecen como métodos de extensión en los tipos derivados de Delegate, y permitirán que las instancias de dichos tipos se agreguen o sustraigan de variables o campos de tipos de delegados adecuados; dicha adición o sustracción se realizará de forma segura para la ejecución de subprocesos, por lo que es posible utilizar estos métodos en caso de que se agreguen o eliminen métodos sin bloqueo adicional.

+0

Muy útil, gracias! –