index付きforeach
namespace A | |
{ | |
// IReadOnlyList 専用、アロケーション発生しない実装 | |
public static partial class TupleEnumerable | |
{ | |
public struct IndexedListEnumerable<T> : IEnumerable<(T item, int index)> | |
{ | |
private IReadOnlyList<T> _list; | |
public IndexedListEnumerable(IReadOnlyList<T> list) { _list = list; } | |
public IndexedListEnumerator<T> GetEnumerator() => new IndexedListEnumerator<T>(_list); | |
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | |
IEnumerator<(T item, int index)> IEnumerable<(T item, int index)>.GetEnumerator() => GetEnumerator(); | |
} | |
public struct IndexedListEnumerator<T> : IEnumerator<(T item, int index)> | |
{ | |
public (T item, int index) Current => (_list[_i], _i); | |
int _i; | |
IReadOnlyList<T> _list; | |
internal IndexedListEnumerator(IReadOnlyList<T> list) | |
{ | |
_i = -1; | |
_list = list; | |
} | |
public bool MoveNext() | |
{ | |
_i++; | |
return _i < _list.Count; | |
} | |
object IEnumerator.Current => Current; | |
public void Dispose() { } | |
public void Reset() { throw new NotImplementedException(); } | |
} | |
public static IndexedListEnumerable<T> Indexed<T>(this IReadOnlyList<T> source) | |
{ | |
if (source == null) throw new ArgumentNullException(nameof(source)); | |
return new IndexedListEnumerable<T>(source); | |
} | |
} | |
} |
namespace B | |
{ | |
// 汎用実装。GetEnumeratorでbox化避けれない。 | |
public static partial class TupleEnumerable | |
{ | |
public struct IndexedEnumerable<T> : IEnumerable<(T item, int index)> | |
{ | |
private IEnumerable<T> _e; | |
public IndexedEnumerable(IEnumerable<T> e) { _e = e; } | |
public IndexedEnumerator<T> GetEnumerator() => new IndexedEnumerator<T>(_e.GetEnumerator()); | |
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); | |
IEnumerator<(T item, int index)> IEnumerable<(T item, int index)>.GetEnumerator() => GetEnumerator(); | |
} | |
public struct IndexedEnumerator<T> : IEnumerator<(T item, int index)> | |
{ | |
public (T item, int index) Current => (_e.Current, _i); | |
int _i; | |
IEnumerator<T> _e; | |
internal IndexedEnumerator(IEnumerator<T> e) | |
{ | |
_i = -1; | |
_e = e; | |
} | |
public bool MoveNext() | |
{ | |
_i++; | |
return _e.MoveNext(); | |
} | |
object IEnumerator.Current => Current; | |
public void Dispose() { } | |
public void Reset() { throw new NotImplementedException(); } | |
} | |
public static IndexedEnumerable<T> Indexed<T>(this IEnumerable<T> source) | |
{ | |
if (source == null) throw new ArgumentNullException(nameof(source)); | |
return new IndexedEnumerable<T>(source); | |
} | |
} | |
} |
namespace C | |
{ | |
// イテレーターを使った実装。シンプルだけど、どうしてもヒープ使っちゃう。 | |
public static partial class TupleEnumerable | |
{ | |
public static IEnumerable<(T item, int index)> Indexed<T>(this IEnumerable<T> source) | |
{ | |
if (source == null) throw new ArgumentNullException(nameof(source)); | |
IEnumerable<(T item, int index)> impl() | |
{ | |
var i = 0; | |
foreach (var item in source) | |
{ | |
yield return (item, i); | |
++i; | |
} | |
} | |
return impl(); | |
} | |
} | |
} |
// 動作確認用 | |
using System; | |
using System.Collections; | |
using System.Collections.Generic; | |
// ここの using 切り替えで、どの Indexed が呼ばれるかを切り替え | |
// 手元の環境だと以下の通り(GetTotalMemory の前後差分0が理想) | |
// A: 0 | |
// B: 237568 | |
// C: 720896 | |
using A; | |
class Program | |
{ | |
static void Main() | |
{ | |
var items = new List<int> { 2, 3, 5, 7, 11, 13, 17 }; | |
items.Indexed(); | |
var begin = GC.GetTotalMemory(false); | |
for (int i = 0; i < 10000; i++) | |
{ | |
var sum = 0; | |
foreach (var (item, index) in items.Indexed()) | |
{ | |
sum += item * index; | |
} | |
} | |
var end = GC.GetTotalMemory(false); | |
Console.WriteLine(end - begin); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment