如果我只能定义一个getEnumerator,为什么要实现iEnumerable(t)?
-
03-10-2019 - |
题
更新: :我感谢所有的评论,这些评论本质上包括一致反对。虽然提出的每一个异议都是有效的,但我觉得棺材中的终极钉子是 ANI的精明观察 最终,甚至 一 微小 表面上提出的这个想法的好处 - 消除了样板代码 - 该想法本身需要它的事实否定了 自己的 样板代码。
是的,考虑到我说服了:这将是一个坏主意。
只是为了挽救我的尊严:我可能是为了争论而演奏的,但是我从来没有真正出售过这个想法 - 只是很好奇听到别人对此有何评论。诚实的。
在您将这个问题视为荒谬之前,我要求您考虑以下内容:
IEnumerable<T>
从*继承IEnumerable
, ,这意味着任何实施的类型IEnumerable<T>
通常必须实施 两个都IEnumerable<T>.GetEnumerator
和 (明确)IEnumerable.GetEnumerator
. 。这基本上等于样板代码。- 你可以
foreach
在任何具有GetEnumerator
方法,只要该方法返回某种类型的对象,MoveNext
方法和Current
财产。因此,如果您的类型定义 一 签名的方法public IEnumerator<T> GetEnumerator()
, ,使用foreach
. - 显然,那里有很多代码需要
IEnumerable<T>
接口 - 例如,基本上所有的LINQ扩展方法。幸运的是,从您可以的类型中走出来foreach
在一个IEnumerable<T>
使用自动迭代器生成C#通过yield
关键词。
因此,将所有这些放在一起,我有一个疯狂的主意:如果我只是定义了自己的界面,该怎么办:
public interface IForEachable<T>
{
IEnumerator<T> GetEnumerator();
}
然后 每当我定义我想枚举的类型时,我都会实现 这个 接口而不是 IEnumerable<T>
, ,消除实施两个的需求 GetEnumerator
方法(一个明确)。例如:
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<T>
界面,我只能定义一个扩展方法以从任何 IForEachable<T>
到一个 IEnumerable<T>
像这样:
public static class ForEachableExtensions
{
public static IEnumerable<T> AsEnumerable<T>(this IForEachable<T> source)
{
foreach (T item in source)
{
yield return item;
}
}
}
在我看来,这样做使我能够设计以各种方式可用的类型 IEnumerable<T>
, ,但没有那个讨厌的明确 IEnumerable.GetEnumerator
每个人的实现。
例如:
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);
}
你们如何看待这个想法?我是否错过了为什么这是一个错误的原因?
我意识到,这似乎是一个过于复杂的解决方案,因为什么不是那么大的问题(我怀疑有人 真的 很关心必须明确定义 IEnumerable
界面);但是它只是突然出现在我的脑海中,我看不到这种方法会带来的明显问题。
通常,如果我可以编写适量的代码 一次 为了节省自己必须编写少量代码的麻烦 很多次, ,对我来说,这是值得的。
*这是正确使用的术语吗?我总是不愿说一个界面“继承”另一个界面,因为这似乎无法正确捕获它们之间的关系。但是也许是正确的。
解决方案
您不是只是将样板移动到其他地方吗? IEnumerable.GetEnumerator
每个班级的方法来调用您的 AsEnumerable
每次扩展 IEnumerable<T>
是期待?通常,我希望将一种枚举类型用于查询的次数远远超过写的次数(完全是一次)。这意味着这种模式将导致 更多的 样板平均。
其他提示
你错过了一件大事 -
如果您实现自己的界面而不是 IEnumerable<T>
, ,您的班级将不适用于期望的框架方法 IEnumerable<T>
- 主要是,您将完全无法使用LINQ或使用类构建 List<T>
, ,或许多其他有用的抽象。
正如您提到的那样,您可以通过单独的扩展方法来完成此操作 - 但是,这是有代价的。通过使用扩展方法转换为 IEnumerable<T>
, ,您正在添加另一种抽象级别的抽象来使用课程(您要做的频率要比创作课程更频繁),并且您降低了性能(实际上,您的扩展方法将在内部生成新的类实现,这确实是不必要的)。最重要的是,您类的任何其他用户(或稍后您)都必须学习一项新的API,这些API无需完成 - 您的班级通过不使用标准接口而难以使用,因为它违反了用户的期望。
你是对的:它 做 似乎是一个非常简单的问题的过于复杂的解决方案。
它还为迭代的每个步骤引入了额外的间接水平。 大概 这不是一个性能问题,但是当我认为您真的获得非常重要的事情时,仍然有些不必要。
另外,尽管您的扩展方法使您可以转换任何 IForEachable<T>
进入 IEnumerable<T>
, ,这意味着您的类型本身 惯于 满足这样的通用约束:
public void Foo<T>(T collection) where T : IEnumerable
之类的。基本上,通过必须执行转换,您将失去将单个对象视为的能力 两个都 实施 IEnumerable<T>
和 真正的混凝土类型。
另外,不实施 IEnumerable
, ,您要算出收集初始化器。 (我有时会实施 IEnumerable
明确 只是 要选择收集初始化,请从 GetEnumerator()
.)
哦,与已经了解的C#开发人员相比 IEnumerable<T>
.
在我们的代码库中,此确切片段可能有100多个实例:
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
我真的很好。与其他所有的.NET代码完全兼容付费是一个很小的价格;)
您提出的解决方案本质上要求您的新界面的每个消费者/呼叫者都记得在它之前调用特殊的扩展方法 有用.
它更容易使用 IEnumerable
进行反思。试图通过反射调用通用界面是如此痛苦。拳击的罚款 IEnumerable
反射本身的开销丢失了,那么为什么要使用通用接口呢?至于一个例子,想到了序列化。
我认为每个人都已经提到了不执行此操作的技术原因,因此我将其添加到混音中:要求您的用户在收藏中调用Asenumerable()能够使用枚举的扩展名,这违反了最少惊喜的原则。