Неверное приведение из IEnumerable to List - PullRequest
0 голосов
/ 05 мая 2018

Я работаю над сценарием C # в рамках проекта Unity3D, где я пытаюсь взять список строк и получить 2D-список перестановок. Используя этого ответа GetPermutations() следующим образом:

List<string> ingredientList = new List<string>(new string[] { "ingredient1", "ingredient2", "ingredient3" });

List<List<string>> permutationLists = GetPermutations(ingredientList, ingredientList.Count);

Но выдает неявную ошибку преобразования:

IEnumerable<IEnumerable<string>> to List<List<string>> ... An explicit conversion exists (are you missing a cast)?

Итак, я посмотрел несколько мест, таких как здесь , и предложил следующую модификацию:

List<List<string>> permutationLists = GetPermutations(ingredientList, ingredientList.Count).Cast<List<string>>().ToList();

Но он ломается во время выполнения, обрабатывается внутри и позволяет продолжить работу без указания сбоя - возможно, потому, что он работает в Unity3D. Вот что я вижу в Unity3D после остановки отладки скрипта:

InvalidCastException: Cannot cast from source type to destination type.
System.Linq.Enumerable+<CreateCastIterator>c__Iterator0`1[System.Collections.Generic.List`1[System.String]].MoveNext ()
System.Collections.Generic.List`1[System.Collections.Generic.List`1[System.String]].AddEnumerable (IEnumerable`1 enumerable) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/List.cs:128)
System.Collections.Generic.List`1[System.Collections.Generic.List`1[System.String]]..ctor (IEnumerable`1 collection) (at /Users/builduser/buildslave/mono/build/mcs/class/corlib/System.Collections.Generic/List.cs:65)
System.Linq.Enumerable.ToList[List`1] (IEnumerable`1 source)

Который я интерпретирую как все еще неверное приведение, поэтому я также попробовал следующие подходы и больше, что я не могу вспомнить:

List<List<string>> permutationLists = GetPermutations(ingredientList, ingredientList.Count).Cast<List<List<string>>>();

List<List<string>> permutationLists = GetPermutations(ingredientList.AsEnumerable(), ingredientList.Count);

, а также явное приведение к круглым скобкам перед вызовом метода, как в C или Java, но безрезультатно.


Итак, как я должен приводить результаты функции GetPermutations(), чтобы получить List<List<string>>? Или, в качестве альтернативы, как я могу изменить функцию, чтобы она возвращала только List<List<string>>, поскольку она не нужна для работы с универсальным типом? Я попытался изменить метод сам, чтобы он был следующим:

List<List<string>> GetPermutations(List<string> items, int count)
{
    int i = 0;
    foreach(var item in items)
    {
        if(count == 1)
            yield return new string[] { item };
        else
        {
            foreach(var result in GetPermutations(items.Skip(i + 1), count - 1))
                yield return new string[] { item }.Concat(result);
        }

        ++i;
    }
}

Однако, удалив <T> из имени функции, он ломается, заявляя, что тело не может быть блоком итератора. У меня нет опыта работы с C #, и я не очень разбираюсь в функциях шаблонов в строго типизированных языках, поэтому любые объяснения / помощь приветствуются.

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

Ответы [ 2 ]

0 голосов
/ 05 мая 2018

Ваш вопрос связан с несколькими аспектами системы типов C # и .net. Я постараюсь дать простое объяснение и приведу ссылки в качестве более формальных ответов.

Итак, согласно вашему описанию это выглядит так: GetPermutations(ingredientList, ingredientList.Count); возвращает IEnumerable<IEnumerable<string>>, но вы пытаетесь присвоить этот результат переменной другого типа в псевдокоде:

List<List<string>> = IEnumerable<IEnumerable<string>>;

List<T> реализует IEnumerable<T>, поэтому в общем случае можно выполнить это назначение:

IEnumerable<T> = List<T>;

но проблема в том, что в вашем случае T на левой стороне отличается от T на правой стороне.

  • для IEnumerable<IEnumerable<string>> T равно IEnumerable<string>.
  • для List<List<string>> T - это List<string>

Чтобы исправить вашу проблему, мы должны изменить код, чтобы иметь одинаковые T слева и справа, т. Е. Преобразовать T в List<string> или IEnumerable<string>.

Вы можете преобразовать T в List<string> следующим образом:

IEnumerable<List<string> GetPermutationsList(List<string> items, int count)
{
    return GetPermutations(items, count).Select(x=>x.ToList())
}
IEnumerable<List<string>> permutationLists = GetPermutations(ingredientList.AsEnumerable(), ingredientList.Count);
// or
List<List<string>> permutationLists = GetPermutations(ingredientList.AsEnumerable(), ingredientList.Count).ToList();

но в общем случае не рекомендуется использовать List во всех местах. Используйте списки только там, где это действительно нужно. Важные моменты здесь:

  • IEnumerable<T> обеспечивает минимальную функциональность (только перечисление), которая должна быть достаточной для ваших целей.
  • IList <T> (List реализует его) обеспечивает максимальную функциональность (Add, Remove, «произвольный» доступ по индексу). Вам действительно нужен максимальный функционал?
  • Также использование ToList() может вызвать проблему нехватки памяти для больших данных.
  • ToList() просто вызывает немедленную оценку запроса и возвращает List<T>

Некоторая полезная информация: covariance-contr-variance , Список , Casting

0 голосов
/ 05 мая 2018

Итак, как я должен приводить результаты из функции GetPermutations (), чтобы получить List<List<string>>

Лучшее решение: не надо. Зачем вам нужно сначала превратить последовательность в список? Сохраняйте это как последовательность последовательностей.

Если вы должны, хотя:

GetPermutations(...).Select(s => s.ToList()).ToList()

Если вы хотите изменить оригинальный метод, просто сделайте то же самое:

IEnumerable<List<string>> GetPermutations(List<string> items, int count)
{
    int i = 0;
    foreach(var item in items)
    {
        if(count == 1)
            yield return new List<T>() { item };
        else
        {
            foreach(var result in GetPermutations(items.Skip(i + 1), count - 1))
                yield return (new string[] {item}.Concat(result)).ToList();
        }

        ++i;
    }
}

А затем выполните GetPermutations(whatever).ToList(), и у вас есть список списков. Но опять же, не делайте этого. Сохраняйте все в последовательности, если возможно.

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

Хорошо, тогда сделай это. Давайте перепишем ваш метод как метод расширения Permute(). И давайте сделаем несколько новых однострочных методов:

static public string CommaSeparate(this IEnumerable<string> items) =>
  string.Join(",", items);

static public string WithNewLines(this IEnumerable<string> items) =>
  string.Join("\n", items);

static public IEnumerable<string> StringSort(this IEnumerable<string> items) =>
  items.OrderBy(s => s);

Тогда у нас будет следующее - я буду комментировать типы по ходу:

string result = 
  ingredients                    // List<string>
  .Permute()                     // IEnumerable<IEnumerable<string>>
  .Select(p => p.StringSort())   // IEnumerable<IEnumerable<string>>
  .Select(p => p.CommaSeparate())// IEnumerable<string>
  .WithNewLines();                   // string

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

...