参数 IEnumerable<T> C#

77
为什么不能在params中使用IEnumerable?这将来会被修复吗?我真的希望他们可以重写旧的库以使用泛型...

20
这是一个抱怨还是一个问题? - dtb
11
明显地,这是两者兼备。加一赞为这个问题。希望我还能再加一赞支持那段发泄。 - BrainSlugs83
2
@BrainSlugs83 在编辑之前,这个问题看起来像是在发牢骚,但从问题标题所传达的意思来看,这是一个非常好的问题。 - nawfal
2
回滚到原始问题,主要是因为下面的答案来自当时的 C# 编译器开发者 Eric Lippert,他提到了原始措辞。如果他没有对措辞表示异议,那么我们也可能不需要对此作出更改! - Olly
稍作挑剔,params array 仍然是通用的,将其变为 IEnumerable 不会增加它的通用性。也许你的意思是使其更加 普适 - nawfal
3个回答

117
这个问题预设了一个设计团队必须提供不添加语言功能的原因。这个假设是错误的。实际上,为了让你使用一个功能,需要经过思考、设计、规定、实现、测试、文档编写和发布等多个环节,这些都需要付出巨大的成本。"params enumerable"功能已经被思考和设计过,但从未被规定、实现、测试、文档化或发布。因此,您不能使用此功能。
更新:截至本文撰写时(2015年初),已经对其进行了规定,但在2014年后半段,C# 6.0的实现、测试、文档化和发布工作被取消了。请参见Lucian在此处的公告:http://roslyn.codeplex.com/discussions/568820 。既然它仍然没有被实现、测试、文档化和发布,那么就没有这样的功能。希望这将在C#的一个假设的未来版本中实现。
更新:我应该澄清一下我的意思,“该功能”的含义可能因人而异。我所说的功能是允许您这样说的功能
void Frob(params IEnumerable<int> x)
{
    foreach(int y in x) ...
}

然后调用地点可以是以传递整数序列的“正常形式”或以Frob(10, 20, 30)的“展开形式”。如果以展开形式呈现,编译器会生成类似于Frob(new int[] { 10, 20, 30})的调用,就像对于param数组一样。该特性的重点在于通常情况下方法从不使用数组的随机访问,因此我们可以放宽参数必须是数组的要求,参数可以仅是序列。

你可以通过创建一个重载来实现:

void Frob(params int[] x) { Frob((IEnumerable<int>)x); }

void Frob(IEnumerable<int> x)
{
    foreach(int y in x) ...
}

这有点麻烦。我们本可以让您使用IEnumerable作为参数类型,然后就可以完成了。

这个问题会被解决吗?

我希望如此。这个功能已经在列表中很长时间了。它会使许多函数与LINQ更好地协作。

Frob(from c in customers select c.Age);

无需编写两个不同版本的Frob。

然而,这只是一个“小便利”功能;它实际上并没有为语言增加很多新的功能。这就是为什么它从未高到足以进入“规范编写”阶段的优先级列表。

我真希望他们能重写旧的库来使用泛型。

注意到评论了。


4
实现这个功能基本上意味着隐式调用ToArray,对吗? - Guffa
8
为什么被调用方一定需要那个?我曾经编写的绝大多数需要参数数组的方法都是直接将其放入foreach循环中。 - Eric Lippert
3
我认为这不仅仅是一个方便的功能,因为当涉及到通用类型时,你的解决方法就会失效。很高兴知道它至少在被关注。在我看来,参数特性应该一开始就使用IEnumerable(尽管我知道当时还没有这个选项)。 - Joel Coehoorn
8
这件看似微不足道的事情真是让人头疼。修复它将大大简化API……请为C# 5修复一下,可以吗? - Ed Blackburn
5
今天我又一次碰到了这个愿望!(这以及缺少一个简单/清晰的方法来创建一个单独的枚举项,让C#感觉像是一个“老”语言,已经被LINQ添加上去了..) - Ian Ringrose
显示剩余9条评论

14
啊,我想我现在可能已经理解你的意思了。我认为你想要声明这样一个方法:
```java public void myMethod(int param1, String param2) { // method body here } ```
public void Foo<T>(params IEnumerable<T> items)
{
}

然后可以像这样使用“普通”参数调用它:
IEnumerable<string> existingEnumerable = ...;
Foo(existingEnumerable);

或者像这样使用多个参数:
Foo("first", "second", "third");

这是你想要的吗?(需要注意的是,你希望第一个表单使用T=string,而不是T=IEnumerable<string>,只有一个元素...)

如果是这样,我同意它可能会很有用 - 但很容易实现:

public void Foo<T>(params T[] items)
{
    Foo((IEnumerable<T>) items);
}

public void Foo<T>(IEnumerable<T> items)
{
}

我不认为我经常这样做,以使上述方法成为特别丑陋的解决方案。
请注意,在调用上述代码时,您需要“明确”指定类型参数,以避免编译器优先选择“params”示例。例如:
List<string> x = new List<string>();
Foo<string>(x);

1
你确定你试过这个吗?当我尝试时,它首先匹配参数表单即匹配“public void Foo<IEnumerable<int>>(params IEnumerable<int>[])"。 - bradgonesurfing
2
好的,我再次测试了一下,并找到了你的错误所在。你必须提供确切的类型“IEnumerable<T>”,而不是“List<T>”,否则编译器会更喜欢“params”版本。请参见https://gist.github.com/bradphelan/9fddca0fa5f1c07ba736以获取可执行示例。 - bradgonesurfing
1
@bradgonesurfing:啊,我明白你的意思了。那是因为它是泛型的。如果你显式地调用 Foo<string> 那么它在两种情况下都能工作。我会编辑我的答案以澄清这一点。 - Jon Skeet
5
需要指定泛型类型参数是将params声明为IEnumerable的一个好理由,这会为语言增添更多优势。 - Søren Boisen
1
@SørenBoisen:同意。如果您不指定通用类型参数,编译器会欣然接受它,只有在运行时才会失败(希望是在单元测试中而不是在生产环境中...)。 - sondergard
显示剩余3条评论

5

参数params以数组形式发送,而IEnumerable<T>不提供所需的随机访问以充当数组。

在调用方法时,您必须从IEnumerable创建数组:

TheMethod(theIEnumerable.ToArray());

3
如果方法的编写者想要将参数设置为 IEnumerable<T>,为什么不能这样做? - Ian Ringrose
@Ian Ringrose:一个原因是当语言添加params时,IEnumerable<T>接口并不存在。为什么之后没有被添加可能是因为它不是很有用的功能。您可以添加重载以支持params T[]IEnumerable<T>两者。 - Guffa
为什么要点踩?如果你不解释哪里出错了,答案就无法得到改善。 - Guffa
1
@Guffa 不行,你不能重载它——当你尝试调用它时,编译器会感到困惑,并让你写一堆额外的语法。——我们不妨只是添加 .ToArray()——这很愚蠢。 - BrainSlugs83

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接