ほぼ4年間の経験の後、yieldキーワードが使用されているコードを見たことはありません。誰かがこのキーワードの(説明に沿って)実用的な使用法を私に示すことができますか?
yield
、一般的に何に使用できるのかを尋ねるので、Stack Overflowには適していません。
ほぼ4年間の経験の後、yieldキーワードが使用されているコードを見たことはありません。誰かがこのキーワードの(説明に沿って)実用的な使用法を私に示すことができますか?
yield
、一般的に何に使用できるのかを尋ねるので、Stack Overflowには適していません。
回答:
このyield
キーワードは、コレクションアイテムの遅延列挙を効率的に作成し、はるかに効率的にすることができます。たとえば、foreach
ループが100万アイテムの最初の5アイテムのみを反復する場合、それはすべてyield
戻り、最初に内部で100万アイテムのコレクションを構築しませんでした。同様に、独自のプログラミングシナリオyield
でIEnumerable<T>
戻り値を使用して、同じ効率を達成することができます。
特定のシナリオで得られた効率の例
イテレータメソッドではなく、大きなコレクションを非効率的に使用する可能性があります
(中間コレクションは多くのアイテムを使用して構築されます)
// Method returns all million items before anything can loop over them.
List<object> GetAllItems() {
List<object> millionCustomers;
database.LoadMillionCustomerRecords(millionCustomers);
return millionCustomers;
}
// MAIN example ---------------------
// Caller code sample:
int num = 0;
foreach(var itm in GetAllItems()) {
num++;
if (num == 5)
break;
}
// Note: One million items returned, but only 5 used.
イテレータバージョン、効率的
(中間コレクションは作成されません)
// Yields items one at a time as the caller's foreach loop requests them
IEnumerable<object> IterateOverItems() {
for (int i; i < database.Customers.Count(); ++i)
yield return database.Customers[i];
}
// MAIN example ---------------------
// Caller code sample:
int num = 0;
foreach(var itm in IterateOverItems()) {
num++;
if (num == 5)
break;
}
// Note: Only 5 items were yielded and used out of the million.
別のケースでは、yield
アイテムを中間コレクションに並べ替えてスワップするのではなく、目的の順序に戻すだけなので、リストの並べ替えとマージを簡単に行うことができます。このようなシナリオは数多くあります。
1つの例は、2つのリストのマージです。
IEnumerable<object> EfficientMerge(List<object> list1, List<object> list2) {
foreach(var o in list1)
yield return o;
foreach(var o in list2)
yield return o;
}
この方法では、アイテムの1つの連続したリストが返され、中間コレクションを必要とせずに効果的にマージされます。
yield
キーワードのみ(の戻り型を有するイテレータ方法の文脈で使用することができIEnumerable
、IEnumerator
、IEnumerable<T>
、またはIEnumerator<T>
。)で特別な関係がありますforeach
。イテレータは特別なメソッドです。MSDN降伏文書とイテレータドキュメントは興味深い情報や概念の説明の多くが含まれています。それを関連付けるようにしてくださいキーワードのイテレータのご理解を補うために、あまりにもそれについて読んで。foreach
反復子がどのように効率を達成するかを知るために、秘密はC#コンパイラーによって生成されたILコードにあります。イテレータメソッド用に生成されるILは、通常の(非イテレータ)メソッド用に生成されるILとは大幅に異なります。この記事(Yieldキーワードが実際に生成するもの)は、そのような洞察を提供します。
database.Customers.Count()
顧客の列挙全体を列挙しないため、すべてのアイテムを通過するためにより効率的なコードが必要ですか?
少し前に私は実用的な例を持っていました、あなたがこのような状況を持っていると仮定しましょう:
List<Button> buttons = new List<Button>();
void AddButtons()
{
for ( int i = 0; i <= 10; i++ ) {
var button = new Button();
buttons.Add(button);
button.Click += (sender, e) =>
MessageBox.Show(String.Format("You clicked button number {0}", ???));
}
}
ボタンオブジェクトは、コレクション内での自分の位置を知りません。同じ制限がDictionary<T>
他のコレクションタイプにも適用されます。
yield
キーワードを使用した私のソリューションは次のとおりです。
interface IHasId { int Id { get; set; } }
class IndexerList<T>: List<T>, IEnumerable<T> where T: IHasId
{
List<T> elements = new List<T>();
new public void Clear() { elements.Clear(); }
new public void Add(T element) { elements.Add(element); }
new public int Count { get { return elements.Count; } }
new public IEnumerator<T> GetEnumerator()
{
foreach ( T c in elements )
yield return c;
}
new public T this[int index]
{
get
{
foreach ( T c in elements ) {
if ( (int)c.Id == index )
return c;
}
return default(T);
}
}
}
そして、それが私がそれを使用する方法です:
class ButtonWithId: Button, IHasId
{
public int Id { get; private set; }
public ButtonWithId(int id) { this.Id = id; }
}
IndexerList<ButtonWithId> buttons = new IndexerList<ButtonWithId>();
void AddButtons()
{
for ( int i = 10; i <= 20; i++ ) {
var button = new ButtonWithId(i);
buttons.Add(button);
button.Click += (sender, e) =>
MessageBox.Show(String.Format("You clicked button number {0}", ( (ButtonWithId)sender ).Id));
}
}
for
インデックスを見つけるために、コレクションをループする必要はありません。私のボタンはIDを持っており、これはまたのインデックスとして使用されているIndexerList<T>
ので、あなたは、IDのか、インデックス任意の冗長を避ける -それは私が好きなものです!インデックス/ IDは任意の番号にすることができます。
実際の例はここにあります:
http://www.ytechie.com/2009/02/using-c-yield-for-readability-and-performance.html
標準コードよりもyieldを使用することには多くの利点があります。
ただし、Jan_Vが言ったように(数秒で私をbeatりました:-)、内部でコンパイラは両方のケースでほぼ同一のコードを生成するため、それなしで生きることができます。
以下に例を示します。
https://bitbucket.org/ant512/workingweek/src/a745d02ba16f/source/WorkingWeek/Week.cs#cl-158
このクラスは、稼働週に基づいて日付計算を実行します。私はクラスのインスタンスに、ボブが平日の毎日9:30から17:30に働いており、昼休みは12:30に働いていることを伝えることができます。この知識により、AscendingShifts()関数は、指定された日付間で作業シフトオブジェクトを生成します。今年の1月1日から2月1日までのボブの勤務シフトをすべてリストするには、次のように使用します。
foreach (var shift in week.AscendingShifts(new DateTime(2011, 1, 1), new DateTime(2011, 2, 1)) {
Console.WriteLine(shift);
}
このクラスは実際にはコレクションを反復処理しません。ただし、2つの日付間のシフトはコレクションと考えることができます。yield
オペレータは、コレクション自体を作成せずに、この想像コレクションを反復処理することが可能となります。
command
SQLコマンドテキスト、コマンドタイプを設定し、「コマンドパラメーター」のIEnumerableを返すクラスを持つ小さなdbデータレイヤーがあります。
基本的には、SqlCommand
常にプロパティとパラメーターを手動で入力するのではなく、CLRコマンドを入力するという考え方です。
そのため、次のような関数があります。
IEnumerable<DbParameter> GetParameters()
{
// here i do something like
yield return new DbParameter { name = "@Age", value = this.Age };
yield return new DbParameter { name = "@Name", value = this.Name };
}
このクラスを継承するcommand
クラスには、プロパティAge
とがありName
ます。
次にcommand
、プロパティを埋めたオブジェクトdb
を更新し、実際にコマンド呼び出しを行うインターフェイスに渡すことができます。
全体として、SQLコマンドの操作と入力の維持が非常に簡単になります。
マージのケースはすでに受け入れられた回答でカバーされていますが、yield-merge params extension method™を示します。
public static IEnumerable<T> AppendParams<T>(this IEnumerable<T> a, params T[] b)
{
foreach (var el in a) yield return el;
foreach (var el in b) yield return el;
}
これを使用して、ネットワークプロトコルのパケットを作成します。
static byte[] MakeCommandPacket(string cmd)
{
return
header
.AppendParams<byte>(0, 0, 1, 0, 0, 1, 0x92, 0, 0, 0, 0)
.AppendAscii(cmd)
.MarkLength()
.MarkChecksum()
.ToArray();
}
MarkChecksum
この方法は、例えば、次のようになります。そしてyield
、もあります:
public static IEnumerable<byte> MarkChecksum(this IEnumerable<byte> data, int pos = 6)
{
foreach (byte b in data)
{
yield return pos-- == 0 ? (byte)data.Sum(z => z) : b;
}
}
ただし、列挙メソッドでSum()などの集計メソッドを使用する場合は、個別の列挙プロセスをトリガーするため、注意が必要です。
Elastic Search .NETのサンプルリポジトリにはyield return
、指定されたサイズのコレクションを複数のコレクションに分割するための優れた使用例があります。
public static IEnumerable<IEnumerable<T>> Partition<T>(this IEnumerable<T> source, int size)
{
T[] array = null;
int count = 0;
foreach (T item in source)
{
if (array == null)
{
array = new T[size];
}
array[count] = item;
count++;
if (count == size)
{
yield return new ReadOnlyCollection<T>(array);
array = null;
count = 0;
}
}
if (array != null)
{
Array.Resize(ref array, count);
yield return new ReadOnlyCollection<T>(array);
}
}