Question
J'ai une interface qui, entre autres, met en œuvre une méthode « IEnumerator publique GetEnumerator () », donc je peux utiliser l'interface dans une instruction foreach.
J'implémentent cette interface dans plusieurs classes et dans l'un d'eux, je veux retourner un IEnumerator vide. En ce moment je fais de la manière suivante:
public IEnumerator GetEnumerator()
{
ArrayList arr = new ArrayList();
return arr.GetEnumerator();
}
Cependant, je trouve ce bidouille horrible, et je ne peux pas empêcher de penser qu'il ya une meilleure façon de retourner un IEnumerator vide. Est-il?
La solution
est simple en C # 2:
public IEnumerator GetEnumerator()
{
yield break;
}
Vous avez besoin de la déclaration de yield break
pour forcer le compilateur à le traiter comme un bloc itérateur.
Ce sera moins efficace qu'une « mesure » iterator vide, mais il est plus simple ... le code
Autres conseils
Il y a une fonction supplémentaire dans le cadre:
public static class Enumerable
{
public static IEnumerable<TResult> Empty<TResult>();
}
En utilisant cela, vous pouvez écrire:
var emptyEnumerable = Enumerable.Empty<int>();
var emptyEnumerator = Enumerable.Empty<int>().GetEnumerator();
Vous pouvez implémenter une classe fictive qui implémente IEnumerator et retourner une instance de celui-ci:
class DummyEnumerator : IEnumerator
{
public object Current
{
get
{
throw new InvalidOperationException();
}
}
public bool MoveNext()
{
return false;
}
public void Reset()
{
}
}
J'étais curieux et je suis allé un peu plus loin. J'ai fait un test qui vérifie la façon efficace les méthodes comparent yield break
, Enumerable.Emtpy
et classe personnalisée.
Vous pouvez le vérifier sur https://dotnetfiddle.net/vTkmcQ ou utiliser le code ci-dessous.
Le résultat de l'un des nombreux dotnetfiddle essais utilisant 190 000 itérations est:
Pause Rendement: 00: 00: 00,0210611
Enumerable.Empty (): 00: 00: 00,0192563
EmptyEnumerator par exemple: 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; } }
}
}
La façon dont j'utilise est d'utiliser l'énumérateur d'un tableau vide:
public IEnumerator GetEnumerator() {
return new object[0].GetEnumerator();
}
Il peut également être utilisé pour IEnumerator générique ou IEnumerable (utiliser un tableau de type approprié)
Vous pouvez implémenter l'interface IEnumerator et IEnumerable et return false de la fonction MoveNext de 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();
}
}
Je l'ai écrit comme ceci:
public IEnumerator<T> GetEnumerator()
{
return this.source?.GetEnumerator() ??
Enumerable.Empty<T>().GetEnumerator();
}
trouvé cette question à la recherche de la façon la plus simple d'obtenir un recenseur vide. Après avoir vu la réponse comparer les performances que j'ai décidé d'utiliser la solution de classe recenseur vide, mais le mien est plus compact que les autres exemples, et est un type générique, et fournit également une instance par défaut de sorte que vous ne devez pas créer de nouvelles instances tous les temps, ce qui devrait améliorer encore la performance.
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() { }
}