【问题标题】:Invalid cast from IEnumerable to List从 IEnumerable 到 List 的无效转换
【发布时间】:2018-05-04 21:23:38
【问题描述】:

我正在 Unity3D 项目中处理 C# 脚本,我正在尝试获取字符串列表并获取排列的 2D 列表。以下列方式使用this answer'sGetPermutations()

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

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

但它会引发隐式转换错误:

IEnumerable&lt;IEnumerable&lt;string&gt;&gt; to List&lt;List&lt;string&gt;&gt; ... An explicit conversion exists (are you missing a cast)?

于是看了几个地方,比如here,想出如下修改:

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&lt;List&lt;string&gt;&gt;?或者,我如何修改函数以仅返回 List&lt;List&lt;string&gt;&gt;,因为我不需要它为泛型类型工作?我尝试自己修改方法如下:

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

但是,从函数名中删除 &lt;T&gt; 后,它会中断,说明主体不能是迭代器块。我之前没有使用 C# 的经验,而且我对强类型语言中的模板函数很生疏,因此感谢任何解释/帮助。

我不知道如何查找此问题,所以如果这是重复的,请在此处发布,我会立即删除此帖子。

【问题讨论】:

  • 你需要像GetPermutations(...).Select(c =&gt; c.ToList()).ToList()这样的东西

标签: c# casting type-conversion


【解决方案1】:

那么我应该如何转换 GetPermutations() 函数的结果以获得List&lt;List&lt;string&gt;&gt;

最佳解决方案:不要。为什么首先需要将序列变成列表?将其保留为序列序列。

如果你必须这样做:

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

我们完成了。 看看当你创建的方法只做一件事并且做得很好时,代码是多么清晰和直接。看看当你把所有东西都按顺序排列时是多么容易。

【讨论】:

  • 我想把这个序列变成一个列表,这样我就可以按字母顺序对元素进行排序,并将它们重新连接为一个排序的、单个逗号分隔的字符串。我从一个列表开始,因为我很熟悉它,但我会考虑使用返回值,因为你说可以按原样使用它。
  • @DarrelHolt 您可以像排序列表一样轻松地对序列进行排序,并且可以像加入列表一样轻松地加入序列。
  • @DarrelHolt:Servy 几乎是正确的。在序列中完成工作比在列表中更容易,因为你永远不必担心有人改变了你的序列
  • @Servy 我没有看到 IEnumerable 的内置排序功能,但我看到 List 的内置排序功能,序列的排序方法名称是什么?
  • @DarrelHolt:OrderBy如果您要使用序列进行编程,最好阅读标准序列运算符
【解决方案2】:

您的问题与 C# 和 .net 类型系统的几个方面有关。我将尝试提供简单的解释,并将提供链接作为更正式的答案。

因此,根据您的描述,GetPermutations(ingredientList, ingredientList.Count); 看起来返回 IEnumerable&lt;IEnumerable&lt;string&gt;&gt;,但您正试图将此结果分配给另一种类型的变量,在伪代码中:

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

List&lt;T&gt; 实现了IEnumerable&lt;T&gt;,所以一般可以做这个赋值:

IEnumerable<T> = List<T>;

但问题是,在您的情况下,左侧的 T 与右侧的 T 不同。

  • 对于IEnumerable&lt;IEnumerable&lt;string&gt;&gt; TIEnumerable&lt;string&gt;
  • 对于List&lt;List&lt;string&gt;&gt; TList&lt;string&gt;

要解决您的问题,我们应该将代码更改为在左侧和右侧具有相同的T,即将T 转换为List&lt;string&gt;IEnumerable&lt;string&gt;

您可以通过这种方式将T 转换为List&lt;string&gt;

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&lt;T&gt; 提供的最少功能(仅限枚举)应该足以满足您的目标。
  • IList &lt;T&gt;(List 实现了它)提供了最大的功能(添加、删除、按索引“随机”访问)。您真的需要最大限度的功能吗?
  • 另外使用ToList() 可能会导致大数据的内存不足问题。
  • ToList() 只强制立即查询评估并返回 List&lt;T&gt;

一些有用的信息:covariance-contr-varianceListcasting

【讨论】:

    猜你喜欢
    • 2012-07-27
    • 2013-06-12
    • 2012-02-24
    • 2011-06-09
    • 2011-11-28
    • 2018-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多