2009-02-26 27 views
15

e grazie per qualsiasi assistenza.ritorno sconosciuto Elenco generico <T>

Come dovrei tornare da un metodo di un tipo Generic.List sconosciuto.

public void Main() 
{ 
    List<A> a= GetData("A"); 
} 

public List<T> GetData(string listType) 
{ 
    if(listType == "A") 
    { 
    List<A> a= new List<A>() 
    ... 
    return a; 
    } 
    else 
    { 
    List<B> b = new List<B>() 
    return b; 

    } 
} 

Nell'esempio che segue ricevo un errore simile a: Impossibile Convertire List<A> a List<T>

è possibile? L'errore si verifica sul 'return a;' linea di codice.
Inoltre, cosa dovrò fare per assicurarsi che un errore non si verifica sulla linea:

List<A> a= GetData("A"); 

Grazie, Steven

risposta

20

Uso IList invece di List<T>.

+0

Concordato che dovrebbe essere l'obiettivo finale, ma in realtà non lo aiuta a risolvere il problema immediato. –

4

EDIT per ogni risposta di Orione di seguito, contraint che AnthonyWJones suggerito

probabilmente dovrebbe avere una classe di interfaccia/astratta che A e B sono eredita da

public interface IMyInterface { } 
    public class A : IMyInterface { } 
    public class B : IMyInterface { } 

    public List<IMyInterface> GetData<T>() where T : IMyInterface 
    { 
     List<IMyInterface> myList = new List<IMyInterface>(); 
     if (typeof(T) == typeof(A)) 
     { 
      myList.Add(new A()); 
     } 
     if (typeof(T) == typeof(B)) 
     { 
      myList.Add(new B()); 
     } 
     return myList; 
    } 
+0

Hai dimenticato il parametro type - lo hai aggiunto per te. –

+0

if (typeof (T) == typeof (A)) rende panda triste :-( –

+0

@Orion Come potrei renderlo migliore? Sono d'accordo che sembra brutto, ma la mia idea è stata compiuta –

8

Non si può tornare direttamente a List<T> aggiunto come questo.

Perché? Fondamentalmente perché List<A> e List<B> (o List<string> vs List<int> che è la stessa cosa) sono considerati 2 classi indipendenti totalmente separate.
Così come non si può restituire un string da una funzione che è dichiarato per tornare int, non è possibile restituire un elenco di stringhe da una funzione che è dichiarata per restituire un elenco di int. Il <T> qui è un po 'di un'aringa rossa. Non è possibile scrivere un metodo generico che restituisca sia stringhe che int ...

Vedere here per ulteriori informazioni su questo tipo di cose.

Quindi quello che dovete fare è restituire qualcosa che entrambi i tipi derivano da (quello che "hanno in comune".)
Come John Rasch says, si potrebbe tornare IList, (notare il generico non, quindi è solo un elenco di object s) o semplicemente restituirlo come object. Sfortunatamente non c'è modo di preservare il tipo della lista.

1

Se non si conosce il tipo che si desidera fino a run-time, quindi farmaci generici sono probabilmente lo strumento sbagliato per il lavoro.

Se la funzione cambia in modo significativo il comportamento (come cambiare tipo di ritorno) sulla base di un argomento, allora probabilmente dovrebbe essere due funzioni.

Sembra che questa funzione non deve essere generico, e dovrebbe essere effettivamente due funzioni.

public void Main() { 
    List<A> a = GetDataA(); 
} 

public List<A> GetDataA() { 
    List<A> a= new List<A>() 
    ... 
    return a; 
} 
public List<B> GetDataB() { 
    List<B> b= new List<B>() 
    ... 
    return b; 
} 
10

Un'alternativa limitarsi a restituire un elenco di oggetti sarebbe né garantire che A e B derivare da un tipo di base comune o implementare un'interfaccia comune, restituisce un elenco di che tipo di base o interfaccia.Includere un vincolo sul metodo generico in tal senso: -

List<ICommon> GetData<T>() where T: ICommon 
{ 

} 
+0

nifty, non sapeva del vincolo 'dove T: ICommon' +1 –

7

A meno che non ci sia un motivo specifico che non è possibile specificare il tipo effettivo prima del tempo, si può solo fare il metodo stesso generico:

public void Main() { 
    List<A> a = GetData<A>(); 
} 

public List<TType> GetData<TType>() { 
    List<TType> list= new List<TType>(); 
    ... 
    return list; 
} 
2

Ho dovuto risolvere un problema simile recentemente dove nessuna delle soluzioni proposte era soddisfacente; limitare il parametro type non era pratico. Invece, I consente agli utenti del metodo di decidere come eseguire il munge dei dati. Ad esempio, puoi scrivere una versione generica di String.Split() che restituisce un Elenco fortemente tipizzato, a patto che tu dica come convertire sottostringhe in T.

Una volta che si è disposti a spostare la responsabilità sullo stack delle chiamate (e ottenere agevolmente il passaggio dei lambda in giro), è possibile generalizzare questo modello arbitrariamente. Ad esempio, se il modo in cui si ottiene GetData() varia (come presumibilmente suppongono alcune risposte), è possibile issare tale funzione anche nell'ambito del chiamante.

Demo:

static void Main(string[] args) 
{ 
    var parseMe = "Hello world! 1, 2, 3, DEADBEEF"; 

    // Don't need to write a fully generic Process() method just to parse strings -- you could 
    // combine the Split & Convert into one method and eliminate 2/3 of the type parameters 
    List<string> sentences = parseMe.Split('!', str => str); 
    List<int> numbers = sentences[1].Split(',', str => Int32.Parse(str, NumberStyles.AllowHexSpecifier | NumberStyles.AllowLeadingWhite)); 

    // Something a little more interesting 
    var lettersPerSentence = Process(sentences, 
            sList => from s in sList select s.ToCharArray(), 
            chars => chars.Count(c => Char.IsLetter(c))); 
} 

static List<T> Split<T>(this string str, char separator, Func<string, T> Convert) 
{  
    return Process(str, s => s.Split(separator), Convert).ToList(); 
} 

static IEnumerable<TOutput> Process<TInput, TData, TOutput>(TInput input, Func<TInput, IEnumerable<TData>> GetData, Func<TData, TOutput> Convert) 
{ 
    return from datum in GetData(input) 
      select Convert(datum); 
} 

guru di programmazione funzionale probabilmente sbadiglio in questa esplorazione: "si sta solo componendo Map un paio di volte" Anche i ragazzi di C++ potrebbero affermare che è un esempio in cui le tecniche di template (cioè i trasformatori STL() + i funtori) richiedono meno lavoro dei generici. Ma come qualcuno che fa principalmente C# è stato bello trovare una soluzione che preservasse sia l'uso del linguaggio di sicurezza sia quello idiomatico.

0

So che è troppo tardi ma sono venuto qui con lo stesso problema e questo è il modo in cui ho funzionato usando le interfacce. Ho pensato di pubblicarlo a beneficio degli altri

public interface IEntity 
    { 
     int ID 
     { 
      get; 
      set; 
     } 
    } 

public class Entity2:IEntity 
    { 
     public string Property2; 

     public int ID 
     { 
      get 
      { 
       throw new NotImplementedException(); 
      } 
      set 
      { 
       throw new NotImplementedException(); 
      } 
     } 
    } 

Analogamente per Entity1.

Ora nella mia classe (il mio strato di business) Ho questo metodo

public List<IEntity> GetEntities(Common.EntityType entityType) 
      { 
       List<IEntity> entities = new List<IEntity>(); 

       switch (entityType) 
       { 
        case Common.EntityType.Accounts: 
         Entity1 entity1 = new Entity1(); 
         entity1.Property1 = "AA"; 
         entities.Add(entity1); 

         break; 
        case Common.EntityType.Brands: 
         Entity2 entity2 = new Entity2(); 
         entity2.Property2 = "AA"; 
         entities.Add(entity2); 

         break; 
        default: 
         break; 
       } 

return entities; 
     } 

Da l'interfaccia utente, la definirei come questo

BusinessClass b = new BusinessClass(); 
     List<IEntity> a = b.GetEntities(Common.EntityType.Accounts); 

Spero che questo aiuti

0

Una soluzione è incapsulare i dati in un contenitore che funzionerà come client nello Visitor Pattern.

Prima dell'interfacciamento corrispondente al criterio:

/// <summary> 
/// The Client 
/// </summary> 
interface IDataContainer 
{ 
    void AcceptDataProcessor(IDataProcessor dataProcessor); 
} 

/// <summary> 
/// The Visitor. 
/// </summary> 
interface IDataProcessor 
{ 
    void WorkOn<TData>(List<TData> data); 
} 

Poi un'implementazione di ciascuno:

class DataContainer<TData> : IDataContainer 
{ 
    readonly List<TData> list; 

    public DataContainer(List<TData> list) 
    { 
     this.list = list; 
    } 

    public void AcceptDataProcessor(IDataProcessor dataProcessor) 
    { 
     dataProcessor.WorkOn(list); // Here the type is known. 
    } 
} 

class PrintDataProcessor : IDataProcessor 
{ 
    public void WorkOn<TData>(List<TData> data) 
    { 
     // print typed data. 
    } 
} 

Poi un uso di esso:

public void Main() 
{ 
    var aContainer = GetData("A"); 
    var bContainer = GetData("B"); 

    var printProccessor = new PrintDataProcessor(); 

    aContainer.AcceptDataProcessor(printProccessor); // Will print A data 
    bContainer.AcceptDataProcessor(printProccessor); // Will print B data 
} 


public IDataContainer GetData(string listType) 
{ 
    if (listType == "A") 
     return new DataContainer<A>(new List<A>()); 
    if (listType == "B") 
     return new DataContainer<B>(new List<B>()); 
    throw new InvalidOperationException(); 
} 

L'idea è che DataContainer so il tipo sottostante ma non lo espone.

  • Non lo espone così GetData può contenere qualsiasi tipo di dati (ma è nascosto).
  • DataContainer conoscere il tipo di fondo, si è incaricato di chiamare il buon metodo dattiloscritta del lavoratore: dataProcessor.WorkOn(list);

E 'un modello potente, ma costano molto in termini di codice.

Problemi correlati