13

更新: 基本的に全会一致の反対意見で構成されたすべてのコメントに感謝します。提起されたすべての反論は有効でしたが、棺桶の最終的な釘は、最終的には、このアイデアが表面上提供したわずかな利点でさえ、定型コードの排除という事実によって無効にされたというアニの鋭い観察であったと思いますアイデア自体には、独自のボイラープレート コードが必要です。

ええ、私が確信していると考えてください。それは悪い考えです。

そして、私の尊厳をいくらか救うために:私は議論のためにそれを演じたかもしれませんが、そもそもこのアイデアに本当に納得したことはありませんでした. 本音。


この質問をばかげているとして却下する前に、次のことを検討してください。

  1. IEnumerable<T>from* を継承しますIEnumerable。つまり、実装する型はIEnumerable<T>一般に(明示的に)の両方 を実装する必要があります。これは基本的に定型コードに相当します。IEnumerable<T>.GetEnumerator IEnumerable.GetEnumerator
  2. そのメソッドがメソッドとプロパティを持つ何らかの型のオブジェクトを返す限りforeach、メソッドを持つ任意の型を上書きできます。したがって、型がシグネチャを持つ1 つのメソッドを定義している場合、 を使用してそれを列挙することは合法です。GetEnumeratorMoveNextCurrentpublic IEnumerator<T> GetEnumerator()foreach
  3. 明らかに、インターフェイスを必要とする多くのコードがありIEnumerable<T>ます。たとえば、基本的にすべての LINQ 拡張メソッドです。幸いなことに、C# がキーワードを介して提供する自動反復子生成を使用すると、可能な型からforeachに移行するのは簡単です。IEnumerable<T>yield

これをすべてまとめると、次のような独自のインターフェースを定義するとどうなるかというクレイジーなアイデアが浮かびました。

public interface IForEachable<T>
{
    IEnumerator<T> GetEnumerator();
}

次に、列挙可能にしたい型を定義するたびに、 の代わりにこのインターフェイスを実装し、2 つのメソッド (1 つは明示的)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>ように拡張メソッドを定義して、 anyIForEachable<T>から anに移動することができます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ます)。しかし、それは頭に浮かんだだけで、このアプローチがもたらす明らかな問題は見られません。

一般に、少量のコードを何度も書かなければならないという手間を省くために、適度な量のコードを 1 回書くことができれば、それだけの価値があります。

*それは使用する正しい用語ですか? あるインターフェイスが別のインターフェイスを「継承する」と言うのはいつも躊躇します。でも当たってるかも。

4

6 に答える 6

13

あなたは1つの大きなものを見逃しています -

の代わりに独自のインターフェイスを実装するとIEnumerable<T>、クラスは期待されるフレームワーク メソッドで動作しません。IEnumerable<T>主に、LINQ を使用したり、クラスを使用してList<T>やその他の多くの有用な抽象化を構築したりすることはできません。

あなたが言及したように、別の拡張メソッドを介してこれを達成できますが、これにはコストがかかります。拡張メソッドを使用して に変換するとIEnumerable<T>、クラスを使用するために必要な別のレベルの抽象化が追加され (クラスを作成するよりも頻繁に FAR を実行します)、パフォーマンスが低下します (拡張メソッドは、実際には、新しいクラスの実装を内部で生成しますが、これは実際には不要です)。最も重要なことは、あなたのクラスの他のユーザー (または後であなた) が、何も達成しない新しい API を学ばなければならないということです。ユーザーの期待に反するため、標準インターフェイスを使用しないことで、クラスの使用が難しくなります。

于 2010-09-17T18:39:54.803 に答える
9

その通りです。非常に簡単な問題に対する非常に複雑な解決策のようです

また、反復のすべてのステップに追加レベルの間接参照が導入されます。おそらくパフォーマンスの問題ではありませんが、あなたが本当に重要なものを何も得ていないと私が思うときは、それでも多少不必要です。

また、拡張メソッドを使用すると、anyIForEachable<T>をに変換できますがIEnumerable<T>、型自体が次のようなジェネリック制約を満たさないことを意味します。

public void Foo<T>(T collection) where T : IEnumerable

など。基本的に、変換を実行する必要があるため、単一のオブジェクトを実装実際の具象型の両方として扱うことができなくなります。IEnumerable<T>

また、を実装しないことIEnumerableで、コレクション初期化子から自分自身を数えていることになります。(コレクションの初期化をオプトインするためだけIEnumerableに明示的に実装し、から例外をスローすることがあります。)GetEnumerator()

ああ、そしてあなたはまた、すでに知っている膨大な数のC#開発者と比較して、世界中の他の誰にもなじみのない余分なインフラストラクチャを導入しましたIEnumerable<T>

于 2010-09-17T18:40:24.140 に答える
5

私たちのコードベースには、この正確なスニペットのインスタンスがおそらく 100 以上あります。

IEnumerator IEnumerable.GetEnumerator()
{
    return this.GetEnumerator();
}

そして、私はそれで本当に大丈夫です。これまでに作成された .NET コードの他のすべての部分との完全な互換性のために支払うのは、わずかな代償です ;)

提案されたソリューションでは、基本的に、新しいインターフェイスのすべてのコンシューマー/呼び出し元が、それが有用になる前に特別な拡張メソッドを呼び出すことも忘れないようにする必要があります。

于 2010-09-17T18:39:55.673 に答える
5

ボイラープレートを別の場所に移動しているだけではありませんか?各クラスでメソッドを記述することから、期待されるたびIEnumerable.GetEnumeratorに拡張機能を呼び出すことまで? 通常、列挙可能な型は、記述された回数 (正確に 1 回) よりもはるかに多くのクエリに使用されると予想されます。これは、このパターンが平均してより多くのボイラープレートにつながることを意味します。AsEnumerableIEnumerable<T>

于 2010-09-17T18:44:31.330 に答える
1

IEnumerable反射に使用する方がはるかに簡単です。リフレクションを介してジェネリック インターフェイスを呼び出そうとするのは非常に面倒です。ボクシング ビアのペナルティはIEnumerable、リフレクション自体のオーバーヘッドによって失われるのに、わざわざ汎用インターフェイスを使用する必要はありません。例として、シリアル化が思い浮かびます。

于 2010-09-17T18:38:02.407 に答える
0

これを行わない技術的な理由については誰もが既に言及していると思うので、これをミックスに追加します。コレクションで AsEnumerable() を呼び出して、列挙可能な拡張機能を使用できるようにすることをユーザーに要求することは、最小の驚きの原則に違反します。

于 2010-09-17T18:55:33.533 に答える