2009-02-26 14 views
15

i dziękuję za wszelką pomoc.return unknown Lista ogólna <T>

Jak powrócić z metody nieznany typ Generic.List.

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; 

    } 
} 

W poniższym przykładzie I odbieranie błąd podobny do następującego: Nie można konwertować List<A> do List<T>

to jest możliwe? Błąd występuje na "return a;" linia kodu.
Również Co muszę zrobić, aby upewnić się, że błąd nie występuje na linii:

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

Dzięki Steven

Odpowiedz

20

Zastosowanie IList zamiast List<T>.

+0

Uzgodniono, że powinien to być ostateczny cel, ale tak naprawdę nie pomoże mu rozwiązać natychmiastowego problemu. –

4

EDIT za odpowiedź Oriona poniżej dodał contraint że AnthonyWJones sugerowaną

prawdopodobnie powinny mieć klasę interfejsu/streszczenie, że A i B są dziedziczenie z

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

Zapomniałeś parametru typu - dodał go dla ciebie. –

+0

jeśli (typeof (T) == typeof (A)) robi smutną pandę :-( –

+0

@Orion Jak mogłem zrobić to lepiej? Zgadzam się, że wygląda brzydko, ale mój pomysł został zrealizowany –

8

Nie można bezpośrednio zwrócić List<T> lubię to.

Dlaczego? Zasadniczo ponieważ List<A> i List<B> (lub List<string> vs List<int>, które jest tym samym) są uważane za 2 całkowicie oddzielne niepowiązane klasy.
Tak jak nie można zwrócić string z funkcji, która została zadeklarowana jako zwracająca int, nie można zwrócić listy ciągów z funkcji, która jest zadeklarowana, aby zwrócić listę elementów typu int. <T> tutaj jest trochę czerwonego śledzia. Nie można napisać ogólnej metody, która zwróci obie ciągi i znaki albo ...

Aby uzyskać więcej informacji na temat tego typu, zobacz artykuł here.

Więc co trzeba zrobić, to coś, że oba typy czerpać z powrotu (co oni „mają wspólnego”).
Jak John Rasch says, można powrócić IList (zwróć uwagę na NON rodzajowy, więc jest to tylko lista object s) lub po prostu zwróć jako object. Niestety nie ma sposobu, aby zachować typ listy.

1

Jeśli nie wiesz, jakiego typu chcesz do czasu wykonywania, generics są prawdopodobnie niewłaściwe narzędzie do pracy.

Jeśli twoja funkcja znacząco zmienia zachowanie (np. Zmieniając typ powrotu) na podstawie argumentu, to prawdopodobnie powinny to być dwie funkcje.

Wygląda na to, że ta funkcja nie powinna być ogólna i powinna w rzeczywistości być dwiema funkcjami.

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

Alternatywę ograniczania powrotu listę obiektów byłoby też zapewnić, że A i B wywodzą się od wspólnego typu podstawowego lub zastosować wspólny interfejs, a następnie powrócić do listy tego rodzaju podstawy i interfejs.Zawierać ograniczenia dotyczące metody rodzajowe do tego efektu: -

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

} 
+0

fajny, nie wiedziałem o ograniczeniu "gdzie T: ICommon" +1 –

7

ile nie istnieje konkretny powód, że nie można określić rzeczywisty typ wyprzedzeniem, można po prostu zrobić sama metoda ogólna:

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

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

Musiałem rozwiązać podobny problem ostatnio, gdy żadne z proponowanych rozwiązań nie było zadowalające; ograniczenie parametru typu nie było praktyczne. Zamiast tego, I pozwól konsumentom metody zdecydować, jak usunąć dane. Na przykład możesz napisać ogólną wersję String.Split(), która zwraca listę mocno napisaną, o ile powiesz mu, jak konwertować podciągów na T.

Gdy chcesz przenieść odpowiedzialność za stos wywoławczy (i poczuć się komfortowo, przechodząc wokół niego), możesz uogólnić ten schemat dowolnie. Na przykład, jeśli sposób, w jaki GetData() jest różny (jak niektórzy najwyraźniej przypuszczają), możesz również wciągnąć tę funkcję do zakresu dzwoniącego.

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

funkcjonalne guru programowania będzie prawdopodobnie ziewając przy tym: „eksploracji. Jesteś po prostu komponowania mapie kilka razy” Nawet użytkownicy C++ mogą twierdzić, że jest to przykład, w którym techniki szablonowe (tj. Transformacja STL() + funktory) wymagają mniej pracy niż generyczne. Ale jako ktoś, kto przede wszystkim robi C#, było miło znaleźć rozwiązanie, które zachowało zarówno bezpieczeństwo typu i użycie idiomatycznego języka.

0

Wiem, że jest za późno, ale przyjechałem tutaj z tym samym problemem i tak to zrobiłem, korzystając z interfejsów. Pomyślałem, że opublikuję go dla dobra innych.

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

Podobnie dla Entity1.

Teraz w mojej klasy (moim warstwy biznesowej) Mam tę metodę

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

od interfejsu użytkownika, nazwałbym go jak ten

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

nadzieję, że to pomaga

0

Roztwór to enkapsulacja danych w kontenerze, który będzie działać jako klient w Visitor Pattern.

Pierwsza część interfejsu dopasowania wzoru:

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

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

Następnie implementacja każda:

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. 
    } 
} 

Wtedy korzystanie z niej:

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

Chodzi o to, że DataContainer wiedzieć typ bazowy, ale nie ujawnia go.

  • Nie wystawia go, więc GetData może zawierać jakiekolwiek dane (ale jest ukryty).
  • DataContainer znać typ podstawowy, to jest za dobre, aby wywołać metodę wpisywanych pracownika: dataProcessor.WorkOn(list);

Jest to mocny wzór ale kosztują dużo pod względem kodu.