J'ai une interface qui, entre autres choses, implémente une méthode "public IEnumerator GetEnumerator ()", afin que je puisse utiliser l'interface dans une instruction foreach.
J'implémente cette interface dans plusieurs classes et dans l'une d'elles, je veux renvoyer un IEnumerator vide. En ce moment je le fais de la manière suivante:
public IEnumerator GetEnumerator()
{
ArrayList arr = new ArrayList();
return arr.GetEnumerator();
}
Cependant, je considère cela comme un bidouillage laid, et je ne peux pas m'empêcher de penser qu'il existe un meilleur moyen de renvoyer un IEnumerator vide. Y a-t-il?
C'est simple en C # 2:
public IEnumerator GetEnumerator()
{
yield break;
}
Vous avez besoin de l'instruction yield break
pour forcer le compilateur à la traiter comme un bloc itérateur.
Ce sera moins efficace qu'un itérateur vide "personnalisé", mais c'est du code plus simple ...
Il y a une fonction supplémentaire dans le cadre:
public static class Enumerable
{
public static IEnumerable<TResult> Empty<TResult>();
}
En utilisant ceci, vous pouvez écrire:
var emptyEnumerable = Enumerable.Empty<int>();
var emptyEnumerator = Enumerable.Empty<int>().GetEnumerator();
Vous pouvez implémenter une classe factice implémentant IEnumerator et en renvoyer une instance:
class DummyEnumerator : IEnumerator
{
public object Current
{
get
{
throw new InvalidOperationException();
}
}
public bool MoveNext()
{
return false;
}
public void Reset()
{
}
}
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 ou IEnumerable générique (utilisez un tableau du type approprié)
J'étais curieux et suis allé un peu plus loin. J'ai fait un test qui vérifie l'efficacité des méthodes comparant yield break
, Enumerable.Emtpy
et la classe personnalisée.
Vous pouvez le vérifier sur dotnetfiddle https://dotnetfiddle.net/vTkmcQ ou utiliser le code ci-dessous.
Le résultat d'une des nombreuses exécutions de dotnetfiddle utilisant 190 000 itérations est le suivant:
Rupture de rendement: 00: 00: 00.0210611
Enumerable.Empty (): 00: 00: 00.019192563
Instance 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; } }
}
}
Vous pouvez implémenter l'interface IEnumerator et IEnumerable, et renvoyer false à partir de la fonction MoveNext de l'interface 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 ça:
public IEnumerator<T> GetEnumerator()
{
return this.source?.GetEnumerator() ??
Enumerable.Empty<T>().GetEnumerator();
}
Vous pouvez créer un NullEnumerator qui implémente l'interface IEnumerator. Vous pouvez simplement passer une instance du NullEnumerator.
here est un exemple de EmptyEnumerator
Vous avez trouvé cette question à la recherche du moyen le plus simple d’obtenir un énumérateur vide. Après avoir vu la réponse comparant les performances, j'ai décidé d'utiliser la solution de classe d'énumérateur vide, mais la mienne est plus compacte que les autres exemples. temps, ce qui devrait encore améliorer les performances.
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() { }
}