2012-06-29 3 views
9

Ho il sospetto che la risposta è no, ma voglio sapere se è possibile fare qualcosa di simile:È possibile vincolare un parametro del tipo di metodo generico C# come "assegnabile dal parametro di tipo della classe contenente"?

public class MyGenericClass<TSomeClass> { 
    public void MyGenericMethod<TSomeInterface>() 
     // This doesn't compile. 
     where TSomeClass : TSomeInterface 
    { 
     //... 
    } 
} 

Quello che voglio dire per indicare nell'esempio di cui sopra (non funzionante) è quello di limitare TSomeInterface tale che può essere qualsiasi classe base, interfaccia implementata o conversione implicita (se si vuole veramente ottenere) di MyGenericClass.

NOTA: ho il sospetto che la ragione per cui questo non è mai stato implementato in C# è che i vincoli generici non sono davvero vuole essere contratti di codice, che è come sto cercando di usare qui. Non mi interessa davvero quale sia il tipo TSomeInterface, purché sia ​​implementato da TSomeClass.

Finora, ho hacked questo insieme:

public class MyGenericClass<TSomeClass> { 
    public void MyGenericMethod<TIntermediateType, TSomeInterface>() 
     where TIntermediateType : TSomeClass, TSomeInterface 
    { 
     //... 
    } 
} 

Questo più o meno fa rispettare il vincolo che voglio (che TSomeClass deve ereditare da, o, nel caso di un'interfaccia, implementare, TSomeInterface), ma chiamando è molto goffo, perché devo specificare TIntermediateType (anche se voglio davvero di valutare contro TSomeClass):

var myGenericInstance = new MyGenericClass<TSomeClass>(); 
myGenericInstance.MyGenericMethod(TSomeClass, TSomeInterface); 

Inoltre, l'hack di cui sopra è rotto perché il chiamante potrebbe in oy specifica una sottoclasse di TSomeClass come primo parametro di tipo, dove solo la sottoclasse implementa TSomeInterface.

Il motivo che voglio fare questo è che sto scrivendo un modello di fabbrica fluente per un servizio WCF e vorrei come per evitare che il chiamante (al momento della compilazione) dal tentativo di creare un endpoint con un contratto che la classe di servizio non implementa. Posso ovviamente controllarlo a runtime (WCF in effetti fa questo per me), ma sono un grande fan del controllo in fase di compilazione.

Esiste un modo migliore/più elegante per ottenere ciò che sono dopo?

+1

Questo è praticamente impossibile dal momento che stai cercando di mescolare i generici con il runtime. – Polity

+0

No, non lo sono. Sto solo cercando di applicare un vincolo generico che (per quanto posso dire) il linguaggio C# non consente (vincolando un parametro di tipo tale che sia una superclasse o l'interfaccia implementata di un parametro di tipo nella classe contenitore). –

+0

Per quanto vedo, si tenta di vincolare la classe generica in base ai dati di tipo che si alimentano con un metodo.Se si desidera limitare la classe generica, è necessario farlo a livello di classe (logicamente) altrimenti non è possibile per il compilatore JIT creare una classe concreta. – Polity

risposta

3

Il modo in cui sono stato in grado di avvolgere la mia testa intorno al motivo per cui questo non viene compilato è la seguente:

considerare questo programma viene compilato:

class Program { 
    class Class1 { } 
    class Class2 { } 
    public class MyGenericClass<TSomeClass> { 
     public void MyGenericMethod<TSomeInterface>() where TSomeClass : TSomeInterface { 
     } 
    } 
    static void Main(string[] args) { 
     var inst = new MyGenericClass<Class1>(); 
    } 
} 

Tutto è buono. Il compilatore è felice. Ora considero a cambiare il metodo di Main:

static void Main(string[] args) { 
    var inst = new MyGenericClass<Class1>(); 
    inst.MyGenericMethod<Class2>(); 
} 

Il compilatore si lamenta che Class1 non implementa Class2. Ma quale riga è errata? Il vincolo è sulla chiamata a MyGenericMethod, ma la riga di codice incriminata è la creazione di MyGenericClass.

In altre parole, quale si ottiene la linea rossa ondulata?

+1

In il mio mondo ideale sarebbe ovviamente il call del metodo, proprio come sarebbe se si provasse a fare questo: 'var x = new List (); s.Add (5);'. Il compilatore presume che la dichiarazione sia corretta e la chiamata al metodo sta usando un argomento non valido. +1 per pensare comunque alla sintassi. –

3

Come discusso in this linked question, non è possibile utilizzare un parametro di tipo diverso dalla dichiarazione corrente, sul lato sinistro di una clausola where.

Così come suggerito da w0lf in che altra domanda, che cosa si può fare è di fornire entrambi i tipi nella vostra interfaccia (piuttosto che il metodo) dichiarazione:

public class MyGenericClass<TSomeClass, TSomeInterface> { 
    where TSomeClass : TSomeInterface 
    public void MyGenericMethod() // not so generic anymore :( 
    { 
     //... 
    } 
} 

Che, tuttavia, limita notevolmente la vostra MyGenericMethod e costringe il classe per dichiarare in anticipo quale interfaccia di base con cui consentire.

Così un'altra opzione è quello di utilizzare un metodo statico con più parametri di tipo:

public class MyGenericClass<TSomeClass> { 
    public static void MyGenericMethod<TSomeClass, TSomeInterface> 
             (MyGenericClass<TSomeClass> that) 
     where TSomeClass : TSomeInterface 
    { 
     // use "that" instead of this 
    } 
} 

Forse si potrebbe rendere un metodo di estensione per farlo apparire all'utente come un metodo effettivo.

Nessuno di questi è esattamente quello che volevi, ma probabilmente meglio della soluzione di tipo intermedio.

Per quanto riguarda il motivo per perché no?, la mia ipotesi è che complicherebbe il compilatore senza aggiungere abbastanza valore. Ecco uno discussion by Angelika Langer of the same subject but about Java. Anche se ci sono differenze significative tra C# e Java, credo che il suo conclusione potrebbe applicare anche qui:

La linea di fondo è che l'utilità di limiti inferiori del tipo parametri è un po 'discutibile. Sarebbero confusi e forse anche fuorvianti se usati come parametri di tipo di una classe generica. Su d'altra parte, i metodi generici trarrebbero occasionalmente da un parametro di tipo con un limite inferiore. Per i metodi, è spesso possibile trovare un rimedio per la mancanza di un parametro di tipo con limite inferiore . Tale work-around prevede in genere un metodo statico generico o un carattere jolly associato inferiore.

Offre anche un caso di utilizzo piacevole, vedere il link sopra.

1

Un metodo di estensione fornisce la soluzione migliore, sebbene non risolva completamente tutte le vostre preoccupazioni.

public class MyGenericClass<TSomeClass> 
{ 
} 

public static class MyGenericClassExtensions 
{ 
    public static void MyGenericMethod<TSomeClass, TSomeInterface>(this MyGenericClass<TSomeClass> self) 
     where TSomeClass : TSomeInterface 
    { 
     //... 
    } 
} 

E 'ancora necessario specificare entrambi i tipi quando si chiama MyGenericMethod, ma impedisce al chiamante di specificare un tipo non corretto per TSomeClass come è possibile con l'approccio che si avvicinò con. Con questo approccio, il metodo può essere chiamato in questo modo:

var myGenericInstance = new MyGenericClass<TSomeClass>(); 
myGenericInstance.MyGenericMethod<TSomeClass, TSomeInterface>(); 

Sarà un errore di compilazione se il parametro di tipo MyGenericClass viene dichiarato con non corrisponde al primo parametro di tipo a MyGenericMethod.

Poiché il primo parametro di tipo può essere dedotto dall'argomento this, è spesso possibile che il compilatore deduca entrambi i parametri di tipo se i loro parametri aggiuntivi al metodo.