问题描述
更新:我感谢所有评论,这些评论基本上包括一致反对.虽然提出的每一个反对意见都是有效的,但我觉得棺材里的最终钉子是 Ani 的敏锐观察 最终,即使是这个想法表面上提供的one 微不足道好处——消除样板code -- 被这个想法本身需要它自己的样板代码这一事实否定了.
Update: I appreciate all of the comments, which have essentially comprised unanimous opposition. While every objection raised was valid, I feel that the ultimate nail in the coffin was Ani's astute observation that, ultimately, even the one miniscule benefit that this idea ostensibly offered -- the elimination of boilerplate code -- was negated by the fact that the idea itself would require its own boilerplate code.
所以,是的,相信我:这将是一个坏主意.
So yeah, consider me convinced: it would be a bad idea.
而且只是在某种程度上挽救了我的尊严:我可能会为了争论而夸大其词,但我从来没有真正接受过这个想法——只是好奇地想听听其他人对它的看法.诚实.
And just to sort of salvage my dignity somewhat: I might have played it up for argument's sake, but I was never really sold on this idea to begin with -- merely curious to hear what others had to say about it. Honest.
在你认为这个问题很荒谬之前,我请你考虑以下几点:
Before you dismiss this question as absurd, I ask you to consider the following:
IEnumerable
继承自*IEnumerable
,也就是说任何实现了IEnumerable
的类型一般都必须实现IEnumerable.GetEnumerator
和(显式)IEnumerable.GetEnumerator
.这基本上相当于样板代码.- 您可以在任何具有
GetEnumerator
方法的类型上foreach
,只要该方法返回带有MoveNext
的某种类型的对象方法和Current
属性.因此,如果您的类型定义了 one 方法和签名public IEnumerator;GetEnumerator()
,使用foreach
枚举它是合法的. - 显然,有很多代码需要
IEnumerable
接口——例如,基本上所有的 LINQ 扩展方法.幸运的是,使用 C# 通过yieldforeach
到IEnumerable
的类型是微不足道的代码>关键字.
IEnumerable<T>
inherits from*IEnumerable
, which means that any type that implementsIEnumerable<T>
generally must implement bothIEnumerable<T>.GetEnumerator
and (explicitly)IEnumerable.GetEnumerator
. This basically amounts to boilerplate code.- You can
foreach
over any type that has aGetEnumerator
method, as long as that method returns an object of some type with aMoveNext
method and aCurrent
property. So if your type defines one method with the signaturepublic IEnumerator<T> GetEnumerator()
, it's legal to enumerate over it usingforeach
. - Clearly, there is a lot of code out there that requires the
IEnumerable<T>
interface -- for instance, basically all of the LINQ extension methods. Luckily, to go from a type that you canforeach
on to anIEnumerable<T>
is trivial using the automatic iterator generation that C# supplies via theyield
keyword.
所以,把这一切放在一起,我有一个疯狂的想法:如果我只是定义自己的界面,看起来像这样:
So, putting this all together, I had this crazy idea: what if I just define my own interface that looks like this:
public interface IForEachable<T>
{
IEnumerator<T> GetEnumerator();
}
然后每当我定义一个我想要可枚举的类型时,我都会实现this接口而不是IEnumerable
,从而消除需要实现两个 GetEnumerator
方法(一个显式).例如:
Then whenever I define a type that I want to be enumerable, I implement this interface instead of IEnumerable<T>
, eliminating the need to implement two GetEnumerator
methods (one explicit). For example:
class NaturalNumbers : IForEachable<int>
{
public IEnumerator<int> GetEnumerator()
{
int i = 1;
while (i < int.MaxValue)
{
yield return (i++);
}
}
// Notice how I don't have to define a method like
// IEnumerator IEnumerable.GetEnumerator().
}
最后,为了使这种类型与确实期望IEnumerable
接口的代码兼容,我可以定义一个从任何 IForEachable
到 IEnumerable
的扩展方法,如下所示:
Finally, in order to make this type compatible with code that does expect the IEnumerable<T>
interface, I can just define an extension method to go from any IForEachable<T>
to an IEnumerable<T>
like so:
public static class ForEachableExtensions
{
public static IEnumerable<T> AsEnumerable<T>(this IForEachable<T> source)
{
foreach (T item in source)
{
yield return item;
}
}
}
在我看来,这样做使我能够设计在各种方面都可用的类型作为 IEnumerable
的实现,但没有那种烦人的显式 IEnumerable.GetEnumerator
在每一项中实施.
It seems to me that doing this enables me to design types that are usable in every way as implementations of IEnumerable<T>
, but without that pesky explicit IEnumerable.GetEnumerator
implementation in each one.
例如:
var numbers = new NaturalNumbers();
// I can foreach myself...
foreach (int x in numbers)
{
if (x > 100)
break;
if (x % 2 != 0)
continue;
Console.WriteLine(x);
}
// Or I can treat this object as an IEnumerable<T> implementation
// if I want to...
var evenNumbers = from x in numbers.AsEnumerable()
where x % 2 == 0
select x;
foreach (int x in evenNumbers.TakeWhile(i => i <= 100))
{
Console.WriteLine(x);
}
你们觉得这个主意怎么样?我是否错过了为什么这会是一个错误的原因?
What do you guys think of this idea? Am I missing some reason why this would be a mistake?
我意识到对于开始时没什么大不了的事情来说,这似乎是一个过于复杂的解决方案(我怀疑有人真的非常关心必须明确定义IEnumerable
接口);但它只是突然出现在我的脑海中,我没有看到这种方法会带来任何明显的问题.
I realize it probably seems like an overly complex solution to what isn't that big of a deal to start with (I doubt anybody really cares that much about having to explicitly define the IEnumerable
interface); but it just popped into my head and I'm not seeing any obvious problems that this approach would pose.
一般来说,如果我能一次编写适量的代码,以免自己不得不多次编写少量代码,对我来说,值得.
In general, if I can write a moderate amount of code once to save myself the trouble of having to write a small amount of code lots of times, to me, it's worth it.
推荐答案
您是不是只是将样板文件移到其他地方 - 从在每个类上编写 IEnumerable.GetEnumerator
方法到调用您的 AsEnumerable
扩展每次需要 IEnumerable
时?通常,我希望可枚举类型用于查询的次数远多于写入的次数(恰好是一次).这意味着平均而言,这种模式将导致更多样板文件.
Aren't you just moving the boilerplate somewhere else - from writing the IEnumerable.GetEnumerator
method on each class to calling your AsEnumerable
extension every time an IEnumerable<T>
is expected? Typically, I would expect an enumerable type to be used for querying far more times than it is written (which is exactly once). This would mean that this pattern will lead to more boilerplate, on average.
这篇关于如果我只能定义一个 GetEnumerator,为什么要实现 IEnumerable(T)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!