2015-05-11 9 views
8

Abbiamo un metodo di estensione generico che funziona solo per gli oggetti che supporta sia l'INotifyCollectionChanged e le interfacce IEnumerable. E 'scritto in questo modo:Come si può espressamente lanciare una variabile di tipo 'oggetto' per soddisfare un vincolo generico a più caratteri?

public static class SomeExtensions 
{ 
    public static void DoSomething<T>(this T item) 
    where T : INotifyCollectionChanged, IEnumerable 
    { 
     // Do something 
    } 
} 

Di seguito compila bene dal ObservableCollection<string> implementa entrambe le interfacce e grazie alla parola chiave 'var', KnownType è fortemente tipizzato:

var knownType = new ObservableCollection<string>(); 
knownType.DoSomething(); 

Tuttavia, stiamo cercando di chiamare questo da un ValueConverter. Il problema è che il valore in entrata è di tipo object e anche se possiamo verificare che l'oggetto passato implementa entrambe le interfacce, non possiamo capire come scriverlo esplicitamente in modo da poter chiamare quel metodo di estensione generica. Questo non funziona (ovviamente) ...

object unknownType = new ObservableCollection<string>(); 
((INotifyCollectionChanged,IEnumerable)unknownType).DoSomething(); 

Così come si può lanciare un oggetto a più interfacce in modo da poter chiamare il generico? Non sono nemmeno sicuro che sia possibile.

+1

Questa è una domanda fantastica. Ovviamente puoi usare 'dynamic' ma dubito che sia quello che cerchi. – usr

+0

È possibile utilizzare la riflessione. – mageos

+1

Evitando le dinamiche, gli unici modi per farlo coinvolgeranno i tipi che soddisfano entrambe le interfacce. È possibile utilizzare una classe o un'interfaccia che implementa entrambe le interfacce. Ma, come hai capito, non c'è modo in C# di rappresentare il tipo di una variabile che soddisfa due interfacce indipendenti. – recursive

risposta

2

Il modo più pulito che posso pensare di fare questo sarebbe dynamic. Tuttavia, poiché i metodi di estensione non funzionano correttamente con dynamic (vedere più avanti), è necessario fare riferimento al metodo di estensione in modo esplicito.

Extensions.DoSomething(unknownType as dynamic); 

EDIT Come un avvertimento aginst il Gotcha mi sono imbattuto in questa sede, si noti che la chiamata al metodo con esplicito dyanmic come argomento di tipo (via DoSomething<dynamic>) sarà non lavoro - che provoca errori di compilazione quando si cerca di corrisponde a più vincoli. Inoltre, quando non si utilizza più vincoli, questo si traduce nel dynamic la risoluzione del problema in base al tipo in fase di compilazione della variabile passata, non il tipo di runtime.

Questo comporterà una chiamata a Extensions.UnknownType<dynamic>, il cui numero dynamic verrà risolto in fase di esecuzione, ovvero utilizzerà il tipo derivato completo del parametro specificato. Finché questo parametro implementa le interfacce desiderate, si parte.

essere preoccupati perché, come il codice molto dynamic, questo potrebbe incontrare problemi che non saranno visibili fino al runtime. Usa con parsimonia!

Se si effettua più chiamate con gli stessi paremeters generici, si potrebbe essere meglio l'aggiunta di un metodo di supporto generico nel vostro convertitore e quindi chiamando che utilizzando value as dynamic

Addendum:

Quando si utilizza dynamic, qualsiasi cosa chiamata contro l'oggetto dynamic tenterà di risolvere come membro del tipo specificato in fase di esecuzione, ma si non guardare in alto Metodi di estensione, dal momento che esistono intrinsecamente in modo del tutto dif classe feroce, spesso un'assemblea diversa.

+1

Non sono sicuro di seguire il ragionamento qui perché, come hai detto, le dinamiche non chiamano i metodi di estensione. E se andrete comunque in questo modo, perché non estendere semplicemente "Object" e testare esplicitamente le due interfacce, lanciando una ArgumentException se necessario? Ovviamente consiglierei di estendere 'Object' così andando su quella strada, probabilmente mi limito a rilasciare 'this' e renderlo comunque un metodo statico regolare per i motivi sopra esposti. – MarqueIV

+1

I metodi di estensione, quando chiamati esplicitamente, possono essere usati con oggetti 'dinamici'. Anche se più dettagliato, questo permette di chiamare metodi che hanno più vincoli di interfaccia – David

+0

Penso che otterrete il voto perché abbiamo finito con questo, anche se come mi aspettavo, non c'è modo di fare un cast duro. Detto questo, penso che la tua seconda soluzione (dopo "o") sia sbagliata. Questo non è stato compilato per noi. Hai preso quella per lavorare? Se sì, puoi fornire un esempio? In caso contrario, rimuovilo e segnerò il tuo come risposta. – MarqueIV

0

Si può cercare di recuperare un delegato per il metodo giusto utilizzando MethodInfo e delegati.

La soluzione seguente non si uso dinamico, ma un po 'di Riflessione. Fondamentalmente il metodo fa le seguenti operazioni:

  1. Recupera un delegato del metodo che si desidera chiamare, ma per un altro, di tipo noto.
  2. Recupera da esso la definizione del metodo generico.
  3. Quindi recupera lo MethodInfo per il tipo di oggetto.
  4. Chiama il metodo di estensione invocando l'metodo finale.

Il _Placeholder classe è del tipo noto utilizzato per andare a prendere il delegato iniziale, ma si può usare qualsiasi altro tipo che soddisfano i vincoli generici.

In questo modo si avrà anche il controllo in fase di compilazione del nome del metodo di estensione e dei suoi vincoli.

Un buon approccio dovrebbe anche memorizzare nella cache il MethodInfo (o Delegato) finale, per evitare di ripetere chiamate costose.


private sealed class _Placeholder : INotifyCollectionChanged, IEnumerable 
{ 
    public event NotifyCollectionChangedEventHandler CollectionChanged; 

    public IEnumerator GetEnumerator() { throw new NotImplementedException(); } 
} 

static void Test(object obj) 
{ 
    if ((obj is INotifyCollectionChanged) && (obj is IEnumerable)) 
    { 
     var typeObject = obj.GetType(); 

     // Retrieve the delegate for another type 
     Action<_Placeholder> _DoSomething = SomeExtensions.DoSomething<_Placeholder>; 
     // Retrieve the generic definition 
     var infoTemp = _DoSomething.Method.GetGenericMethodDefinition(); 
     // Retrieve the MethodInfo for our object's type 
     var method = infoTemp.MakeGenericMethod(typeObject); 

     // Call the extension method by invoking 
     method.Invoke(null, new[] { obj }); 

     //If you can return the delegate, you can try the following: 
     //var typeDelegate = typeof(Action<>).MakeGenericType(typeObject); 
     //var action = Delegate.CreateDelegate(typeDelegate, method); 
     //((Action<_Test>)action)(obj as _Test); 
    } 
}