Return an empty IEnumerator

2019-02-03 21:23发布

问题:

I have an interface that, among other things, implements a "public IEnumerator GetEnumerator()" method, so I can use the interface in a foreach statement.

I implement this interface in several classes and in one of them, I want to return an empty IEnumerator. Right now I do this the following way:

public IEnumerator GetEnumerator()
{
    ArrayList arr = new ArrayList();
    return arr.GetEnumerator();
}

However I consider this an ugly hack, and I can't help but think that there is a better way of returning an empty IEnumerator. Is there?

回答1:

This is simple in C# 2:

public IEnumerator GetEnumerator()
{
    yield break;
}

You need the yield break statement to force the compiler to treat it as an iterator block.

This will be less efficient than a "custom" empty iterator, but it's simpler code...



回答2:

There is an extra function in the framework:

public static class Enumerable
{
    public static IEnumerable<TResult> Empty<TResult>();
}

you can write

var NullEnumerable = Enumerable.Empty<int>();


回答3:

You could implement a dummy class that implements IEnumerator, and return an instance of it:

class DummyEnumerator : IEnumerator
{
    public object Current
    {
        get
        {
            throw new InvalidOperationException();
        }
    }

    public bool MoveNext()
    {
        return false;
    }

    public void Reset()
    {
    }
}


回答4:

The way I use is to use the enumerator of an empty array:

public IEnumerator GetEnumerator() {
    return new object[0].GetEnumerator();
}

It can also be used for generic IEnumerator or IEnumerable (use an array of the appropriate type)



回答5:

I was curious and went a bit further. I made a test that checks how efficient the methods are comparing yield break, Enumerable.Emtpy and custom class.

You can check it out on dotnetfiddle https://dotnetfiddle.net/vTkmcQ or use the code below.

The result of one of the many dotnetfiddle runs using 190 000 iterations was:

Yield break: 00:00:00.0210611

Enumerable.Empty(): 00:00:00.0192563

EmptyEnumerator instance: 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; } }
    }
}


回答6:

You can implement IEnumerator interface and IEnumerable, and return false from MoveNext function of IEnumerable interfase

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();
    }
}


回答7:

You can make a NullEnumerator which implements the IEnumerator interface. You can just pass an instance off the NullEnumerator.

here is an example of an EmptyEnumerator



回答8:

I wrote it like this:

public IEnumerator<T> GetEnumerator()
{
    return this.source?.GetEnumerator() ??
            Enumerable.Empty<T>().GetEnumerator();
}


回答9:

Found this question looking for the simplest way to get an empty enumerator. After seeing the answer comparing performance I decided to use the empty enumerator class solution, but mine is more compact than the other examples, and is a generic type, and also provides a default instance so you don't have to create new instances all the time, which should even further improve 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() { }
}