Возвращает пустой IEnumerator
-
19-09-2019 - |
Вопрос
У меня есть интерфейс, который, помимо прочего, реализует метод "public IEnumerator GetEnumerator()", поэтому я могу использовать интерфейс в операторе foreach .
Я реализую этот интерфейс в нескольких классах, и в одном из них я хочу вернуть пустой IEnumerator .Прямо сейчас я делаю это следующим образом:
public IEnumerator GetEnumerator()
{
ArrayList arr = new ArrayList();
return arr.GetEnumerator();
}
Однако я считаю это уродливым взломом, и я не могу не думать, что есть лучший способ вернуть пустой IEnumerator .Есть ли?
Решение
В C # 2 это просто:
public IEnumerator GetEnumerator()
{
yield break;
}
Вам нужен yield break
оператор, заставляющий компилятор обрабатывать его как блок итератора.
Это будет менее эффективно, чем "пользовательский" пустой итератор, но это более простой код...
Другие советы
В фреймворке есть дополнительная функция:
public static class Enumerable
{
public static IEnumerable<TResult> Empty<TResult>();
}
Используя это, вы можете написать:
var emptyEnumerable = Enumerable.Empty<int>();
var emptyEnumerator = Enumerable.Empty<int>().GetEnumerator();
Вы могли бы реализовать фиктивный класс, который реализует IEnumerator, и возвращать его экземпляр:
class DummyEnumerator : IEnumerator
{
public object Current
{
get
{
throw new InvalidOperationException();
}
}
public bool MoveNext()
{
return false;
}
public void Reset()
{
}
}
Мне стало любопытно, и я пошел немного дальше.Я провел тест, который проверяет, насколько эффективны сравниваемые методы yield break
, Enumerable.Emtpy
и пользовательский класс.
Вы можете проверить это на dotnetfiddle https://dotnetfiddle.net/vTkmcQ или используйте приведенный ниже код.
Результатом одного из многих запусков dotnetfiddle с использованием 190 000 итераций было:
Разрыв в доходности:00:00:00.0210611
Перечислимый.Пустой():00:00:00.0192563
Экземпляр EmptyEnumerator:00:00:00.0012966
using System;
using System.Diagnostics;
using System.Collections;
using System.Linq;
public class Program
{
private const int Iterations = 190000;
public static void Main()
{
var sw = new Stopwatch();
sw.Start();
for (int i = 0; i < Iterations; i++)
{
IEnumerator enumerator = YieldBreak();
while(enumerator.MoveNext())
{
throw new InvalidOperationException("Should not occur");
}
}
sw.Stop();
Console.WriteLine("Yield break: {0}", sw.Elapsed);
GC.Collect();
sw.Restart();
for (int i = 0; i < Iterations; i++)
{
IEnumerator enumerator = Enumerable.Empty<object>().GetEnumerator();
while(enumerator.MoveNext())
{
throw new InvalidOperationException("Should not occur");
}
}
sw.Stop();
Console.WriteLine("Enumerable.Empty<T>(): {0}", sw.Elapsed);
GC.Collect();
sw.Restart();
var instance = new EmptyEnumerator();
for (int i = 0; i < Iterations; i++)
{
while(instance.MoveNext())
{
throw new InvalidOperationException("Should not occur");
}
}
sw.Stop();
Console.WriteLine("EmptyEnumerator instance: {0}", sw.Elapsed);
}
public static IEnumerator YieldBreak()
{
yield break;
}
private class EmptyEnumerator : IEnumerator
{
//public static readonly EmptyEnumerator Instance = new EmptyEnumerator();
public bool MoveNext()
{
return false;
}
public void Reset()
{
}
public object Current { get { return null; } }
}
}
Способ, который я использую, заключается в использовании перечислителя пустого массива:
public IEnumerator GetEnumerator() {
return new object[0].GetEnumerator();
}
Он также может быть использован для универсального IEnumerator или IEnumerable (используйте массив соответствующего типа).
Вы можете реализовать интерфейс IEnumerator и IEnumerable и возвращать false из функции MoveNext interfase IEnumerable
private class EmptyEnumerator : IEnumerator
{
public EmptyEnumerator()
{
}
#region IEnumerator Members
public void Reset() { }
public object Current
{
get
{
throw new InvalidOperationException();
}
}
public bool MoveNext()
{ return false; }
}
public class EmptyEnumerable : IEnumerable
{
public IEnumerator GetEnumerator()
{
return new EmptyEnumerator();
}
}
Я написал это вот так:
public IEnumerator<T> GetEnumerator()
{
return this.source?.GetEnumerator() ??
Enumerable.Empty<T>().GetEnumerator();
}
Вы можете создать NullEnumerator, который реализует интерфейс IEnumerator.Вы можете просто передать экземпляр из NullEnumerator .
здесь является примером EmptyEnumerator
Нашел этот вопрос в поисках самого простого способа получить пустой перечислитель.Увидев ответ, сравнивающий производительность, я решил использовать решение класса empty enumerator , но мое решение более компактно, чем в других примерах, и является универсальным типом, а также предоставляет экземпляр по умолчанию, поэтому вам не нужно постоянно создавать новые экземпляры, что должно еще больше повысить производительность.
class EmptyEnumerator<T> : IEnumerator<T>
{
public readonly static EmptyEnumerator<T> value = new EmptyEnumerator<T>();
public T Current => throw new InvalidOperationException();
object IEnumerator.Current => throw new InvalidOperationException();
public void Dispose() { }
public bool MoveNext() => false;
public void Reset() { }
}