Search code examples
c#.netienumerator

.NET creating an IEnumerator on top of "GetEnumerator()" method from another class


I'm trying to create an IEnumerator based on the Directory.EnumerateFiles as you can see below. My enumerator works on top of Directory.EnumerateFiles by using LINQ .Select to convert the FileInfo into a specialized class MyFile.

    struct Enumerator : IEnumerator<MyFile>
    {
        private readonly Lazy<IEnumerator<MyFile>> enumerator;

        public Enumerator(DirectoryInfo directory)
        {
            enumerator = new Lazy<IEnumerator<MyFile>>(() =>
            {
                return directory
                .EnumerateFiles($"*{MyFile.Extension}", SearchOption.TopDirectoryOnly)
                .OrderBy(f => f.Name)
                .Select(MyFile.Read)
                .GetEnumerator();
            });
        }

        WalFile IEnumerator<MyFile>.Current => enumerator.Value.Current;
        object IEnumerator.Current => enumerator.Value.Current;
        void IDisposable.Dispose() => enumerator.Value.Dispose();
        bool IEnumerator.MoveNext() => enumerator.Value.MoveNext();
        void IEnumerator.Reset() => enumerator.Value.Reset();
    }

But it only works when I iterate once. If I iterate the enumerator twice, the 2nd time it's empty. Even removing the Lazy, it doesn't change the result.

This IEnumerator is used in another class such as MyFileCollection : IEnumerable<MyFile>, where I implement IEnumerable with the specialized Enumerator class above.

Any insights to be this right?

UPDATE: Since people started asking why I'm doing this. This question is not about the design. I didn't show every class or the design around it, because I wanted to simplify the question to get an answer of how to use IEnumerator class.

public interface ILocalRepository : IEnumerable<MyFile>
{
    DirectoryInfo Directory { get; }
    MyFile? Get(string name);
    MyFile Create(MyVersion script);
    Task<MyFile> DownloadLatestAsync(IScriptResource resource, string name, CancellationToken cancellation);
    Task<MyFile> DownloadAsync(IScriptResource resource, string name, MyVersion version, CancellationToken cancellation);
    void Delete(string name);
}

class LocalFileRepository : ILocalRepository
{
    protected readonly DirectoryInfo directory;
    private readonly Enumerator enumerator;

    public LocalFileRepository(DirectoryInfo directory)
    {
        this.directory = directory;
        enumerator = new Enumerator(directory);
    }

    DirectoryInfo ILocalRepository.Directory => directory;

    IEnumerator<MyFile> IEnumerable<MyFile>.GetEnumerator() => enumerator;
    IEnumerator IEnumerable.GetEnumerator() => enumerator;
    //implementation hidden...
}

Solution

  • Instead of writing a custom enumerator, you could just use the following method:

    public IEnumerable<MyFile> EnumerateFiles(DirectoryInfo directory)
    {
      return directory
        .EnumerateFiles($"*{MyFile.Extension}", SearchOption.TopDirectoryOnly)
        .OrderBy(f => f.Name)
        .Select(MyFile.Read);
    }
    

    This is much simpler than creating an enumerator from scratch; if you need the code in various places, you can implement it in a helper class that you inject into the classes that rely on it. This is especially a good way if you need to mock the code in unit tests. If this does not matter, you can also make it a static helper method or an extension method to DirectoryInfo.