2013-07-30 20 views
8

Contesto: .NET 4.0, C#C# utilizzando farmaci generici e l'interfaccia di implementazione

Sto creando una serie di interfacce e una serie di clases che li implementano per fornire un certo servizio. I client utilizzano le clases concrete ma chiamano metodi dichiarati utilizzando le interfacce come tipi di parametri.

Un esempio semplificato è questa:

namespace TestGenerics 
{ 
    // Interface, of fields 
    interface IField 
    { 
    } 

    // Interface: Forms (contains fields) 
    interface IForm<T> where T : IField 
    { 

    } 

    // CONCRETE CLASES 
    class Field : IField 
    { 
    } 

    class Form <T> : IForm<T> where T : IField 
    { 
    } 

    // TEST PROGRAM 
    class Program 
    { 
     // THIS IS THE SIGNATURE OF THE METHOD I WANT TO CALL 
     // parameters are causing the error. 
     public static void TestMethod(IForm<IField> form) 
     { 
      int i = 1; 
      i = i * 5; 
     } 

     static void Main(string[] args) 
     { 
      Form<Field> b = new Form<Field>(); 
      Program.TestMethod(b); 
     } 
    } 
} 

Il codice ha senso per me, ma ho l'errore del compilatore:

Argument 1: cannot convert from ' TestGenerics.Form<TestGenerics.Field> ' to ' TestGenerics.IForm<TestGenerics.IField> ' TestGenerics

io non sono sicuro di quello che sto facendo male , Ho letto molte pagine su internet ma nessuno ha risolto il mio problema.

Esiste una soluzione che non modificare più di tanto l'architettura di quello che sto cercando di costruire:

Edit: ho progettato le interfacce in modo tale da dover essere indipendenti clases concreti che li implementano . Le clases concrete potrebbero essere caricate da una DLL, ma la maggior parte delle applicazioni funziona con le interfacce. In alcuni casi ho bisogno di usare clases concreti, specialmente quando si usano clases che devono essere serializzati.

Grazie in anticipo.

Alejandro

+5

Questo sarebbe caduta sotto il regno di covarianza. In particolare, se aggiungi il modificatore generico '' out' '(http://msdn.microsoft.com/en-us/library/dd469487.aspx) all'interfaccia 'firma IForm dove T: IField' funzionerà .Ma questo aggiunge altre limitazioni/considerazioni quindi non posso commentare se è applicabile al tuo attuale design/utilizzo. –

+2

Sono curioso di sapere cosa stai cercando di realizzare con questo design. – sidesinger

+0

Grazie mille per entrambi i guardiani, erano davvero timidi. Ho aggiunto una modifica che spiega perché ho desingato la soluzione in questo modo, ma entrambi i ricercatori possono risolvere il mio problema. Saluti. – Sugar

risposta

13

Il problema è che Form<Field> implementa IForm<Field> ma non IForm<IField>. Non è possibile utilizzare una classe (o un'interfaccia) ereditata come parametro generico a meno che non sia contrassegnata come covariante con l'identificatore out. Tuttavia, contrassegnare la tua interfaccia come covariante ne limiterà significativamente l'utilizzo (fondamentalmente facendo in un'interfaccia "solo output" come IEnumerable), quindi potrebbe non funzionare per te.

Un modo per farlo funzionare è quello di rendere TestMethod generico così:

public static void TestMethod<T>(IForm<T> form) where T:IField 
{ 
    int i = 1; 
    i = i * 5; 
} 
10

È possibile utilizzare covarianza, in questo modo:

interface IForm<out T> where T : IField 
{ 

} 

più su covarianza e controvarianza here.

+1

Covarianza quando si consente di utilizzare una classe derivata come parametro di tipo generico utilizzando la parola chiave "out". Nel link che ho postato puoi vedere questa frase: "perché il parametro di tipo dell'interfaccia IEnumerable è covariante, puoi assegnare un'istanza di IEnumerable (IEnumerable (Of Derived) in Visual Basic) a una variabile di tipo IEnumerable " –

7

Altri hanno indicato il ragionamento dietro il messaggio di errore, ma esaminiamo il progetto del codice di esempio per un momento. Forse stai usando un generico dove nessuno è necessario.

Hai già detto che stai utilizzando metodi dichiarati nell'interfaccia IField, quindi potrebbe non essere necessario rendere generica la classe di IForm: è sufficiente che memorizzi i riferimenti a IField, anziché l'argomento generico 'T' (che è già garantito essere un IField comunque).

Ad esempio, utilizzare:

public interface IForm 
{ 
    IEnumerable<IField> Fields { get; set; } 
} 

anziché

public interface IForm<T> where T : IField 
{ 
    IEnumerable<T> Fields { get; set; } 
} 
+0

concordato. Un'altra indicazione è che il codice consumer ha istanziato questo come modulo IForm e Form , suggerendo che IField è sufficiente. Il vero test è che il tipo concreto T deve essere o meno parte dell'API rivolta verso l'esterno. – Tormod

Problemi correlati