2012-03-31 7 views
5

Ho cercato di creare un evento generico. In sostanza si dovrebbe assomigliare a questa: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 
    { 
    } 
} 

voglio consentire all'utente di passare qualsiasi delegato, che è secondo parametro è derivato da "ISomeInterface."

"in" specifica contro-varianza, giusto? Ciò significa che se l'API si aspetta qualcosa di più generale, puoi passarlo qualcosa di più specifico (nella mia base "ISomeInterface" sarebbe generale, e il mio "SomeDerivedClass" sarebbe specifico.) Mi viene comunque detto il mio compilatore che "nessun sovraccarico per il gestore del metodo corrisponde a DelegateTest.SomeClass.SomeEventDelegate."

Mi chiedo perché questo non funziona. Quali sono i problemi che sarebbero causati se lo fosse? O mi sto perdendo qualcosa per farlo funzionare?

Grazie in anticipo!

+0

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

+0

Penso che non si possa avere un singolo evento per gestire più tipi, è possibile avere solo delegati. Quello che puoi fare è cambiare il gestore per accettare invece un 'ISomeInterface' e quindi controllare il tipo di oggetto che ha inviato. O usare l'interfaccia stessa. http://msdn.microsoft.com/en-us/library/dd469484.aspx – BrunoLM

risposta

6

"in" specifica contro-varianza, giusto?

Sì.

Questo significa che se l'API si aspetta qualcosa di più generale, si può passare qualcosa di più specifico (in mia base "ISomeInterface" sarebbe in generale, e la mia "SomeDerivedClass" sarebbe specifico).

No. La contravarianza del delegato consente a un delegato di fare riferimento a un metodo con tipi di parametro meno derivati ​​rispetto al tipo delegato. Per esempio, supponiamo ISomeInterface aveva un'interfaccia di base:

interface ISomeBaseInterface 
{ 
} 

interface ISomeInterface : ISomeBaseInterface 
{ 
} 

e supponiamo handler preso ISomeBaseInterface invece di SomeDerivedClass:

static void handler(object sender, ISomeBaseInterface e) 

Poi new SomeClass().SomeEvent += handler avrebbe funzionato.

Ecco perché il codice originale non è di tipo sicuro: Quando SomeClass solleva SomeEvent, può potenzialmente passare nulla che implementa ISomeInterface come argomento data. Ad esempio, si potrebbe passare un'istanza di SomeDerivedClass, ma potrebbe anche passare un'istanza di

class SomeOtherDerivedClass : ISomeInterface 
{ 
} 

Se tu fossi in grado di registrare void handler(object sender, SomeDerivedClass e) con l'evento, che gestore sarebbe vento per essere invocato con SomeOtherDerivedClass, che doesn' lavoro.

In sintesi, è possibile registrare i gestori di eventi che sono più generale rispetto al tipo di evento, e non i gestori di eventi che sono più specifiche.

UPDATE: commentato:

Beh, io in realtà voglio scorrere la lista e controllare i tipi.Quindi, se l'evento dovesse essere attivato con un oggetto dati di tipo diciamo SomeOtherDerivedObject, il programma eseguirà l'iterazione attraverso l'elenco dei metodi sottoscritti all'evento finché non ne trova uno che corrisponda alla firma (oggetto, SomeOtherDerivedObject). Quindi l'evento stesso verrebbe utilizzato solo per memorizzare, non per chiamare effettivamente i delegati.

Non penso che C# consente di dichiarare un event che funziona con tipi di delegati arbitrari. Ecco come è possibile scrivere metodi che aggiungono gestori di eventi e invocarli:

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

Grazie. C'è un modo per farlo funzionare al contrario? Attualmente sto usando l'approccio Java-like (interfacce + addListener.) – haiyyu

+1

Se sei assolutamente certo che 'SomeClass' con solo mai passate istanze di' SomeDerivedClass' come argomento 'data', puoi aggiungere un cast esplicito : 'lol.SomeEvent + = (sender, e) => handler (sender, (SomeDerivedClass) e)'. Ma allora potreste anche dichiarare 'SomeEvent' come' SomeEventDelegate '. –

+0

Beh, in realtà voglio scorrere l'elenco e controllare i tipi. Quindi, se l'evento dovesse essere attivato con un oggetto dati di tipo diciamo SomeOtherDerivedObject, il programma eseguirà l'iterazione attraverso l'elenco dei metodi sottoscritti all'evento finché non ne trova uno che corrisponda alla firma (oggetto, SomeOtherDerivedObject). Quindi l'evento stesso verrebbe utilizzato solo per memorizzare, non per chiamare effettivamente i delegati. Spero che sia comprensibile. – haiyyu

2

Una seccatura importante con delega controvarianza è che, mentre un delegato di tipo ad esempio Action<Fruit> può essere passato a una routine che prevede un Action<Banana>, un tentativo di combinare due delegati i cui tipi effettivi sono Action<Fruit> e Action<Banana> non riuscirà * anche se entrambi i delegati hanno il tipo "compile-time" Action<Banana>. Per ovviare a questo, vi suggerirei di usare un metodo come il seguente:

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); 
    } 

Dato un delegato e un tipo delegato, questo metodo verifica se il tipo del delegato passato-in corrisponde precisamente il tipo specificato; se non lo fa, ma i metodi nel delegato originale hanno le firme appropriate per il tipo specificato, verrà creato un nuovo delegato con le caratteristiche necessarie. Notare che se in due occasioni separate questa funzione è passata a delegati che non sono del tipo corretto ma che si confrontano l'uno con l'altro, anche i delegati restituiti da questo metodo si confronteranno uguali tra loro. Quindi, se si ha un evento che dovrebbe accettare un delegato di tipo Action<string>, si potrebbe usare il metodo sopra per convertire ad es. uno Action<object> inoltrato in un "reale" Action<string> prima di aggiungerlo o rimuoverlo dall'evento.

Se uno sarà aggiungendo o sottraendo un delegato passata-in da un campo del corretto tipo delegato, inferenza e comportamento Intellisense può essere migliorata se si usano i seguenti metodi:

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); 
    } 

Questi metodi vengono visualizzati come metodi di estensione su tipi derivati ​​da Delegate e consentiranno l'aggiunta o la sottrazione di istanze di tali tipi da variabili o campi di tipi di delegati appropriati; tale addizione o sottrazione verrà eseguita in modalità thread-safe, quindi potrebbe essere possibile utilizzare questi metodi in metodi di aggiunta/rimozione di eventi senza blocco aggiuntivo.

+0

Molto utile, grazie! –