【问题标题】:Generic Method Argument Type Not Recognized?通用方法参数类型无法识别?
【发布时间】:2015-06-27 04:01:49
【问题描述】:

这可能是由于我对泛型方法的理解很糟糕,所以我提前道歉。

我正在尝试编写一个继承自 StreamWriter 的 csvWriter 类。我希望它拥有的一种方法是获取像数组或列表这样的集合并使用 String.Join 将其更改为逗号分隔的字符串,然后像往常一样编写它。我认为这会很简单,如果我使用方法重载它会很简单,但是我习惯了 python 并且泛型的想法对我来说感觉更好,所以我试了一下。

但是,我的尝试没有奏效。该方法似乎不知道传递给它的是什么类型,还是什么?

类:

public class csvWriter : System.IO.StreamWriter
    {
        public csvWriter(string filename) : base(filename)
        {
        }

        public void WriteLineFromCollection<T>(T line)
        {
            string newLine = String.Join(",", line);
            base.WriteLine(newLine);
        }
    }

调用它的行:

using (var reader = new csvReader(filename_in))
using (var writer = new csvWriter(filename_out))
            {
                while (!reader.EndOfStream)
                {
                    string[] line = reader.ReadLine();
                    writer.WriteLineFromCollection<string[]>(line);
                }
                Console.Out.Write("PAUSE");
            }

该文件以“System.String[]”结尾,而不是包含逗号分隔值的字符串。我认为我实际上是在给它参数的类型,而不是参数本身,但不知道为什么会这样。 WriteLineFromCollection 内部的调试器给了我感觉像是相互冲突的信息: 它说 line 是 string[] 类型,但是当我尝试 line[0] 时,它说“无法将 [] 索引应用于 T 类型的表达式。”

【问题讨论】:

  • 如果传给它的不是集合怎么办?
  • 需要将line的类型改为IEnumerable&lt;T&gt;
  • 您是否尝试过将 T 更改为 IList
  • @Lee IEnumberable 也不支持索引。
  • @Mike_G:他不需要索引。所以IEnumerable&lt;T&gt; 在这里完全合适(甚至更可取)。

标签: c# generics


【解决方案1】:

在你的函数中,你使用string[] 作为你的泛型类型参数,但是你的函数除了它是一个对象之外对你传入的内容一无所知(你没有类型限制)。所以你的代码必须能够处理你可能通过的任何东西。现在你是,大概希望它会使用this重载string.Join

public static string Join(
    string separator,
    IEnumerable<string> values
)

因为string[] 实现了IEnumerable&lt;string&gt;,但是编译器不能这样做。它根本不知道T 是否有任何信息。它只知道它是一个对象。所以它唯一可以使用的重载是this one

public static string Join(
    string separator,
    params Object[] values
)

现在由于这里的params 关键字,您不必显式传递Object[],您只需传递一堆参数,它就会将其视为对象数组。例如,您可以这样做(并不是说这是一个好主意,因为它可能不是):

string.Join(",","somestring",1,true,new object());

所以当你传入 string[] 时会发生什么基本上相当于你这样做了:

string.Join("," new object[] { line });

现在重载的作用是遍历对象数组中的每一项,并在其上调用ToString,然后将结果连接在一起。如您所见,在string[] 上调用ToString 将返回System.String[]。这是object.ToString() 的基本实现,它只返回类型名称。

显然,这不是您想要的。这样的事情应该可以工作:

public void WriteLineFromCollection<T>(IEnumerable<T> line)
{
    string newLine = String.Join(",", line);
    base.WriteLine(newLine);
}

您正在使用Jointhis 重载,它采用IEnumerable&lt;T&gt; 并将在您集合中的每个项目上调用ToString()

public static string Join<T>(
    string separator,
    IEnumerable<T> values
)

所以现在,当您传入T 的集合(实现IEnumerable&lt;T&gt;string[] 执行此操作)时,它将被视为一个集合,而不是像以前那样隐式包装在对象数组中。现在它遍历您的T 集合并调用ToString,在实际string 的情况下,它只返回string 本身。请注意,如果您将 string 以外的其他内容作为类型参数传递,那么您将得到他们的 ToString 所做的任何事情,这可能只是类型名称。因此,如果您打算在此处使用 ToString,您可能希望在自己的类中覆盖它来做一些明智的事情。

当然,与仅内联对 String.Join 的调用相比,您实际上并没有从中获得太多收益,但它可能更具可读性,并且至少可以在有人决定时为您提供一个必须更改的地方您应该使用不同的分隔符。

注意,如果您不需要索引到您的集合,则在此处使用IEnumerable 比使用IList 更可取。例如,这样你就可以从 linq 传递一些东西,而不必先 ToList 它。

你可以简单地调用它:

writer.WriteLineFromCollection(line);

如果编译器可以识别,则无需显式指定T

【讨论】:

  • 请注意,他的调用者现在看起来像这样:writer.WriteLineFromCollection&lt;string&gt;(line) 而不是 writer.WriteLineFromCollection&lt;string[]&gt;(line) 编辑: 我的例子倒过来了
  • @Tom:在大多数情况下,您甚至不需要显式指定泛型类型参数。
  • 哇,这行得通,非常感谢。这里发生了一些深刻的魔力,如果您能解释以下几点,我将非常感激:1) 通常,“T”将是我传入的任何类型的通用占位符,对吗?这里的论点是否基本上只是说“从传入的任何类型中生成一个可枚举”? 2) 为什么WriteLineFromCollection&lt;string&gt; 有效?我以为我传递的参数是一个字符串[]? 3) 什么情况下编译器无法弄清楚?我是否没有传递某种类型的数据,然后它可以弄清楚?谢谢!
  • @Msg:我厌倦了为你扩展我的答案。 1) 是的。 2) 你传递了一个IEnumerable&lt;T&gt;,这里的类型参数是你的集合in的东西的类型。 3)想不出头顶的例子,但是编译器如果不能推断出类型参数会告诉你的。
【解决方案2】:

发生这种情况是因为编译器选择了带有第二个参数params object[] value 的String.Join 重载,因此您将字符串数组作为一个参数传递。要解决此问题,您可以向 WriteLineFromCollection 方法添加约束:

public void WriteLineFromCollection<T>(T line) where T : IEnumerable<string>
{
    string newLine = String.Join(",", line);
    base.WriteLine(newLine);
}

这将强制编译器使用IEnumerable&lt;string&gt; 类型的第二个参数选择重载

【讨论】:

  • 在我看来,他甚至不需要泛型;这就是像IEnumerable 这样的接口存在的原因——所以你可以用任何实现该接口的东西来调用它。我的版本:public void WriteLineFromCollection(IEnumerable&lt;string&gt; line)
  • string 是他想要的通用名称,这将他与 string 联系在一起——我相信他想要传递任何东西,比如整数列表、字符串、字符或其他任何东西
  • 如果你打算使用where T : IEnumerable&lt;string&gt;,你不妨直接使用WriteLineFromCollection(IEnumerable&lt;string&gt; line)。在这种情况下,您对泛型一无所获。
【解决方案3】:

我认为你应该尝试从 public void WriteLineFromCollection&lt;T&gt;(T line) 更改为 public void WriteLineFromCollection&lt;IList&lt;T&gt;&gt;(IList&lt;T&gt; line)

如果你想支持索引,或者像 Sergey 提到的那样,添加一个约束。

【讨论】:

    【解决方案4】:

    您的代码的方法几乎没有问题,因为将传递的唯一类型是 String 使得这成为滥用 Genrics... Prehapes 你想为字符串类型编写一个扩展方法,输出将是 CSV 格式的字符串?

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-16
      • 2016-09-06
      相关资源
      最近更新 更多