Return unknown Общий список <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;

   }
}

В приведенном ниже примере я получаю ошибку, подобную: Can not Convert List<A> to List<T>

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

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

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

Ответ 1

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

Ответ 2

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

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

}

Ответ 3

Вы не можете напрямую вернуть List<T>, как это.

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

Подробнее см. здесь.

Итак, вам нужно вернуть то, из чего происходят оба типа (что у них "есть общее" )

Как Джон Раш говорит, вы можете вернуть IList (обратите внимание на NON generic, так что это просто список object s) или просто вернуть его как object. К сожалению, нет способа сохранить тип списка.

Ответ 4

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

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

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

Ответ 5

РЕДАКТИРОВАТЬ на один Орион ответ ниже, добавил, что 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;
    }

Ответ 6

Мне пришлось решить аналогичную проблему в последнее время, когда ни одно из предлагаемых решений не было удовлетворительным; ограничение типа параметра было нецелесообразным. Вместо этого я позволяю потребителям метода решить, как настроить данные. Например, вы можете написать родовую версию 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);
}

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

Ответ 7

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

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

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

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

Ответ 8

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

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

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

Ответ 9

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

Первый интерфейс, соответствующий шаблону:

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

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