вернуть неизвестный общий список <T>

и спасибо за любую помощь.

Как мне вернуть из метода неизвестный тип 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;

   }
}

В приведенном ниже примере я получаю сообщение об ошибке, подобное: Не удается преобразовать List<A> в List<T>

Это возможно? Ошибка возникает при "return a;" строка кода.
Кроме того, что мне нужно сделать, чтобы убедиться, что в строке не возникает ошибка:

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

Спасибо Стивен

Ответов (10)

Решение

Используйте IList вместо List<T> .

Альтернативой ограничению возврата списка объектов может быть либо гарантия того, что A и B являются производными от общего базового типа, либо реализация общего интерфейса, а затем возврат списка этого базового типа или интерфейса. Включите ограничение для универсального метода с этой целью: -

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

}

Решение состоит в том, чтобы инкапсулировать данные в контейнер, который будет работать как клиент в шаблоне посетителя .

Сначала какой-нибудь интерфейс, соответствующий шаблону:

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

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

Затем реализация каждого:

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

Тогда использование этого:

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

Идея состоит в том, что вы DataContainer знаете базовый тип, но не раскрываете его.

  • Он не раскрывает его, поэтому GetDataможет содержать любые данные (но он скрыт).
  • DataContainer знать базовый тип, он отвечает за вызов хорошего типизированного метода рабочего: dataProcessor.WorkOn(list);

Это мощный шаблон, но он стоит дорого с точки зрения кода.

Вы можете сделать что-то вроде:

public void Main()
{
    List<int> a = GetData<int>();
    List<string> b = GetData<string>();
}

public List<T> GetData<T>()
{
    var type = typeof(T);
    if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>))
    {
        type = type.GenericTypeArguments[0];
    }

    if (type == typeof(int))
    {
        var a = new List<int> { 1, 2, 3 };
        return a.Select(v => v != null ? (T)Convert.ChangeType(v, type) : default(T)).ToList();
    }
    else if (type == typeof(string))
    {
        var b = new List<string> { "a", "b", "c" };
        return b.Select(v => v != null ? (T)Convert.ChangeType(v, type) : default(T)).ToList();
    }
}

Возможно, вы сможете изменить это в соответствии со своими потребностями.

Вы не можете напрямую вернуть List<T> подобный.

Почему? В основном потому, что List<A> and List<B> (или List<string> vs, List<int> что одно и то же) рассматриваются как 2 совершенно разных несвязанных класса.
Точно так же, как вы не можете вернуть string из функции, которая объявлена ​​как return int, вы не можете вернуть список строк из функции, которая, как объявлено, возвращает список целых чисел. Это <T> немного отвлекающий маневр. Вы также не можете написать общий метод, который возвращал бы как строки, так и целые числа ...

См. Здесь для получения дополнительной информации об этом.

Итак, что вам нужно сделать, так это вернуть то, что оба типа являются производными (что у них «общего».)
Как говорит Джон Раш , вы можете вернуть IList (обратите внимание на общий NON, так что это просто список object s) или просто вернуть это как object . К сожалению, сохранить тип списка невозможно.

Если нет конкретной причины, по которой вы не можете указать фактический тип заранее, вы можете просто сделать сам метод универсальным:

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

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

ИЗМЕНИТЬ согласно ответу Ориона ниже, добавлено ограничение, которое предложил AnthonyWJones

у вас, вероятно, должен быть интерфейс / абстрактный класс, который A и B наследуют от

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

Если вы не знаете, какой тип вам нужен до момента выполнения, то универсальные шаблоны, вероятно, не подходят для этой работы.

Если ваша функция значительно изменяет поведение (например, изменение возвращаемого типа) на основе аргумента, то, вероятно, это должны быть две функции.

Похоже, эта функция не должна быть универсальной, а на самом деле должна состоять из двух функций.

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

Недавно мне пришлось решить аналогичную проблему, где ни одно из предложенных решений не было удовлетворительным; ограничение параметра типа было непрактичным. Вместо этого я позволяю потребителям метода решать, как изменять данные . Например, вы можете написать общую версию String.Split (), которая возвращает строго типизированный список, если вы укажете ему, как преобразовать подстроки в T.

Как только вы захотите переложить ответственность вверх по стеку вызовов (и освоите передачу лямбда-выражений), вы можете произвольно обобщить этот шаблон. Например, если способ GetData () меняется (как, по-видимому, предполагают некоторые ответы), вы также можете перенести эту функцию в область действия вызывающего.

Демо:

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

Гуру функционального программирования, вероятно, зевнут на это исследование: «вы просто составляете Map несколько раз». Даже разработчики C++ могут заявить, что это пример, в котором методы шаблонов (например, STL transform () + функторы) требуют меньше работы, чем дженерики. Но как человеку, который в первую очередь занимается C#, было приятно найти решение, которое сохранило как безопасность типов, так и идиоматическое использование языка.

Я знаю, что это слишком поздно, но я пришел сюда с той же проблемой, и вот как я решил ее, используя интерфейсы. Думал, что выложу это для других

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

Аналогично для Entity1.

Теперь в моем классе (мой бизнес-уровень) у меня есть этот метод

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

Из пользовательского интерфейса я бы назвал это так

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

Надеюсь это поможет