これはC#にあります。他のDLLから使用しているクラスがあります。IEnumerable を実装していませんが、IEnumerator を返す 2 つのメソッドがあります。これらに対して foreach ループを使用する方法はありますか。私が使用しているクラスは封印されています。
11 に答える
foreach
一般に信じられていることに反して、は必要ありません。IEnumerable
必要なのは、適切なシグネチャを持つメソッドと get-propertyをGetEnumerator
持つオブジェクトを返すメソッドだけです。MoveNext
Current
/EDIT:しかし、あなたの場合、あなたは運が悪い. ただし、オブジェクトを簡単にラップして、列挙可能にすることができます。
class EnumerableWrapper {
private readonly TheObjectType obj;
public EnumerableWrapper(TheObjectType obj) {
this.obj = obj;
}
public IEnumerator<YourType> GetEnumerator() {
return obj.TheMethodReturningTheIEnumerator();
}
}
// Called like this:
foreach (var xyz in new EnumerableWrapper(yourObj))
…;
/編集: 複数の人によって提案された次のメソッドは、メソッドが を返す場合は機能しませんIEnumerator
。
foreach (var yz in yourObj.MethodA())
…;
Re: foreach が明示的なインターフェイス コントラクトを必要としない場合、リフレクションを使用して GetEnumerator を見つけますか?
(評判が良くないのでコメントできません。)
実行時のリフレクションを暗示している場合は、いいえ。それはすべてコンパイル時に行われますが、あまり知られていないもう 1 つの事実は、IEnumerator を実装する可能性のある返されたオブジェクトが破棄可能かどうかもチェックすることです。
これを実際に確認するには、この (実行可能な) スニペットを検討してください。
using System;
using System.Collections.Generic;
using System.Text;
namespace ConsoleApplication3
{
class FakeIterator
{
int _count;
public FakeIterator(int count)
{
_count = count;
}
public string Current { get { return "Hello World!"; } }
public bool MoveNext()
{
if(_count-- > 0)
return true;
return false;
}
}
class FakeCollection
{
public FakeIterator GetEnumerator() { return new FakeIterator(3); }
}
class Program
{
static void Main(string[] args)
{
foreach (string value in new FakeCollection())
Console.WriteLine(value);
}
}
}
MSDNによると:
foreach (type identifier in expression) statement
式は次のとおりです。
オブジェクト コレクションまたは配列式。コレクション要素の型は、識別子の型に変換できる必要があります。null に評価される式は使用しないでください。IEnumerable を実装する型、または GetEnumerator メソッドを宣言する型に評価されます。後者の場合、GetEnumerator は、IEnumerator を実装する型を返すか、IEnumerator で定義されたすべてのメソッドを宣言する必要があります。
簡潔な答え:
既に持っている IEnumerator を返すGetEnumeratorという名前のメソッドを持つクラスが必要です。シンプルなラッパーでこれを実現します。
class ForeachWrapper
{
private IEnumerator _enumerator;
public ForeachWrapper(Func<IEnumerator> enumerator)
{
_enumerator = enumerator;
}
public IEnumerator GetEnumerator()
{
return _enumerator();
}
}
使用法:
foreach (var element in new ForeachWrapper(x => myClass.MyEnumerator()))
{
...
}
C# 言語仕様から:
foreach ステートメントのコンパイル時の処理では、最初に式のコレクションの型、列挙子の型、および要素の型が決定されます。この決定は次のように行われます。
式の型 X が配列型の場合、X から System.Collections.IEnumerable インターフェイスへの暗黙的な参照変換が行われます (System.Array がこのインターフェイスを実装しているため)。コレクション型は System.Collections.IEnumerable インターフェイス、列挙子型は System.Collections.IEnumerator インターフェイス、要素型は配列型 X の要素型です。
それ以外の場合は、型 X に適切な GetEnumerator メソッドがあるかどうかを判断します。
識別子 GetEnumerator を使用し、型引数を指定せずに、型 X に対してメンバー ルックアップを実行します。メンバー ルックアップで一致が生成されない場合、あいまいさが生成される場合、またはメソッド グループではない一致が生成される場合は、以下で説明する列挙可能なインターフェイスを確認します。メンバ ルックアップがメソッド グループ以外のものを生成する場合、または一致しない場合は、警告を発行することをお勧めします。
結果のメソッド グループと空の引数リストを使用して、オーバーロードの解決を実行します。オーバーロードの解決によって適用可能なメソッドがない場合、あいまいさが生じる場合、または 1 つの最適なメソッドが生成されるが、そのメソッドが静的であるか公開されていない場合は、以下で説明するように列挙可能なインターフェイスを確認します。オーバーロードの解決によって明確なパブリック インスタンス メソッド以外のものが生成されるか、適用可能なメソッドがない場合は、警告を発行することをお勧めします。
GetEnumerator メソッドの戻り値の型 E がクラス、構造体、またはインターフェイス型ではない場合、エラーが発生し、それ以上の手順は実行されません。
メンバ ルックアップは、識別子 Current を使用し、型引数を指定せずに E に対して実行されます。メンバー ルックアップが一致しない場合、結果がエラーである場合、または結果が読み取りを許可するパブリック インスタンス プロパティ以外の場合、エラーが生成され、それ以上の手順は実行されません。
メンバ ルックアップは、識別子 MoveNext を使用し、型引数なしで E に対して実行されます。メンバー ルックアップで一致しない場合、結果がエラーである場合、または結果がメソッド グループ以外の場合、エラーが発生し、それ以上の手順は実行されません。
オーバーロードの解決は、引数リストが空のメソッド グループに対して実行されます。オーバーロード解決の結果、適用可能なメソッドがない場合、あいまいな結果になる場合、または単一の最適なメソッドが返されるが、そのメソッドが static または public でない場合、またはその戻り値の型が bool でない場合、エラーが発生し、それ以上の手順は実行されません。
コレクションの型は X、列挙子の型は E、要素の型は Current プロパティの型です。
それ以外の場合は、列挙可能なインターフェイスを確認します。
X からインターフェイス System.Collections.Generic.IEnumerable<T> への暗黙的な変換が行われる型 T が 1 つだけ存在する場合、コレクションの型はこのインターフェイスであり、列挙子の型はインターフェイス System.Collections.Generic です。 IEnumerator<T> であり、要素の型は T です。
それ以外の場合、そのような型 T が複数ある場合は、エラーが発生し、それ以上の手順は実行されません。
それ以外の場合、X から System.Collections.IEnumerable インターフェイスへの暗黙的な変換がある場合、コレクションの型はこのインターフェイスであり、列挙子の型はインターフェイス System.Collections.IEnumerator であり、要素の型は object です。
そうしないと、エラーが発生し、それ以上の手順は実行されません。
厳密ではありません。クラスに必要な GetEnumerator、MoveNext、Reset、および Current メンバーがある限り、foreach で機能します。
いいえ、必要ありませんし、GetEnumerator メソッドも必要ありません。たとえば、次のようになります。
class Counter
{
public IEnumerable<int> Count(int max)
{
int i = 0;
while (i <= max)
{
yield return i;
i++;
}
yield break;
}
}
これは次のように呼ばれます。
Counter cnt = new Counter();
foreach (var i in cnt.Count(6))
{
Console.WriteLine(i);
}
いつでもラップできます。「foreachable」であるための余談として、適切なシグネチャを持つ「GetEnumerator」というメソッドが必要です。
class EnumerableAdapter
{
ExternalSillyClass _target;
public EnumerableAdapter(ExternalSillyClass target)
{
_target = target;
}
public IEnumerable GetEnumerator(){ return _target.SomeMethodThatGivesAnEnumerator(); }
}
@Brian:メソッド呼び出しまたはクラス自体から返された値をループしようとしているかどうかわかりません。クラスが必要な場合は、foreachで使用できる配列にします。
両方とも IEnumerable を返すメソッド A と B を持つクラス X を考えると、次のようにクラスで foreach を使用できます。
foreach (object y in X.A())
{
//...
}
// or
foreach (object y in X.B())
{
//...
}
おそらく、A と B によって返される列挙型の意味は明確に定義されています。
クラスを foeach で使用できるようにするために必要なのは、返される public メソッドと、GetEnumerator() という名前の IEnumerator を用意することだけです。
次のクラスを取ります。IEnumerable または IEnumerator を実装していません。
public class Foo
{
private int[] _someInts = { 1, 2, 3, 4, 5, 6 };
public IEnumerator GetEnumerator()
{
foreach (var item in _someInts)
{
yield return item;
}
}
}
別の方法として、GetEnumerator() メソッドを次のように書くこともできます。
public IEnumerator GetEnumerator()
{
return _someInts.GetEnumerator();
}
foreach で使用する場合 (ラッパーは使用されず、クラス インスタンスのみが使用されることに注意してください):
foreach (int item in new Foo())
{
Console.Write("{0,2}",item);
}
プリント:
1 2 3 4 5 6
型には、パブリックメソッドとパブリックプロパティGetEnumerator
を持つものを返す必要があるという名前の public/non-static/non-generic/parameterless メソッドのみが必要です。Eric Lippert 氏をどこかで思い出すように、これは、値型の場合の型安全性とボクシング関連のパフォーマンスの問題の両方について、ジェネリック以前の時代に対応するように設計されました。MoveNext
Current
たとえば、これは機能します:
class Test
{
public SomethingEnumerator GetEnumerator()
{
}
}
class SomethingEnumerator
{
public Something Current //could return anything
{
get { }
}
public bool MoveNext()
{
}
}
//now you can call
foreach (Something thing in new Test()) //type safe
{
}
これは、コンパイラによって次のように変換されます。
var enumerator = new Test().GetEnumerator();
try {
Something element; //pre C# 5
while (enumerator.MoveNext()) {
Something element; //post C# 5
element = (Something)enumerator.Current; //the cast!
statement;
}
}
finally {
IDisposable disposable = enumerator as System.IDisposable;
if (disposable != null) disposable.Dispose();
}
注目に値するのは、関与する列挙子の優先順位です。メソッドがある場合、それを実装している人に関係なくpublic GetEnumerator
、それがデフォルトの選択です。foreach
例えば:
class Test : IEnumerable<int>
{
public SomethingEnumerator GetEnumerator()
{
//this one is called
}
IEnumerator<int> IEnumerable<int>.GetEnumerator()
{
}
}
(公開された実装がない場合 (つまり、明示的な実装のみ)、優先順位はIEnumerator<T>
>のようになりますIEnumerator
。 )