1つのgetEnumeratorを定義できる場合、なぜienumerable(t)を実装するのですか?
-
03-10-2019 - |
質問
アップデート: :本質的に全会一致の反対で構成されているすべてのコメントに感謝しています。提起されたすべての異議は有効でしたが、ffinの究極の釘はあったと感じています アニの鋭い観察 それは、最終的には、 1 ごくわずか このアイデアが表面上提供した利益 - ボイラープレートコードの排除 - は、アイデア自体がその要求を必要とするという事実によって否定されました 自分の ボイラープレートコード。
ええ、私が確信していると考えてください:それは悪い考えでしょう。
そして、ちょうど私の尊厳をある程度救うために:私は議論のためにそれを再生したかもしれませんが、私は最初にこのアイデアで本当に売られたことはありませんでした - 他の人がそれについて何を言わなければならないかを聞きたいだけです。本音。
この質問を不条理として却下する前に、私はあなたに次のことを検討するようにお願いします:
IEnumerable<T>
*から継承するIEnumerable
, 、つまり、実装する任意のタイプを意味しますIEnumerable<T>
通常、実装する必要があります 両方ともIEnumerable<T>.GetEnumerator
と (明示的に)IEnumerable.GetEnumerator
. 。これは基本的にボイラープレートコードに相当します。- あなたはできる
foreach
aを持っている任意のタイプにGetEnumerator
メソッド、そのメソッドが何らかのタイプのオブジェクトをで返す限りMoveNext
方法とaCurrent
財産。したがって、タイプが定義されている場合 1 署名を備えた方法public IEnumerator<T> GetEnumerator()
, 、それを使用して列挙することは合法ですforeach
. - 明らかに、必要なコードがたくさんあります
IEnumerable<T>
インターフェイス - たとえば、基本的にすべてのLINQ拡張法。幸いなことに、あなたができるタイプから行くためにforeach
にIEnumerable<T>
C#が介して提供する自動イテレーター生成を使用して些細なことですyield
キーワード。
だから、これをすべてまとめると、私はこのクレイジーなアイデアを持っていました:もし私がこのように見える私自身のインターフェイスを定義するだけならどうでしょう:
public interface IForEachable<T>
{
IEnumerator<T> GetEnumerator();
}
それで 列挙したいタイプを定義するたびに、実装します これ の代わりにインターフェイス IEnumerable<T>
, 、2つを実装する必要性を排除します GetEnumerator
方法(1つの明示的)。例えば:
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>
期待されています?通常、列挙可能なタイプが、書かれているよりもはるかに多くの時間を照会するために使用されると予想されます(これは正確に1回です)。これは、このパターンがつながることを意味します もっと 平均してボイラープレート。
他のヒント
あなたは1つの大きなことを見逃しています -
ではなく独自のインターフェイスを実装する場合 IEnumerable<T>
, 、あなたのクラスは、期待するフレームワークの方法で動作しません IEnumerable<T>
- 主に、LINQを完全に使用することも、クラスを使用して構築することもできません List<T>
, 、または他の多くの有用な抽象化。
あなたが言及しているように、別の拡張法を介してこれを達成することができます - ただし、これにはコストがかかります。拡張法を使用してanに変換します IEnumerable<T>
, 、クラスを使用するために必要な別のレベルの抽象化を追加します(クラスを作成するよりもはるかに頻繁に行います)。 、これは本当に不要です)。最も重要なことは、クラスの他のユーザー(または後で)は、何も達成しない新しい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()を呼び出すように要求すると、最も驚きの原則に違反します。