A GapHelper static class to create unique (non-existing) size restricted names I am working with a 3th party library which has following interface defined for a processing task:

public interface IProcessor {
    void Execute( FileContent fileContent );
}


Such an IProcessor gives me the possibility to change the FileContent before it gets written to the database. For legacy reasons, and for integration reasons with an unnamed ERP, its unique identifiers are string identifiers which have a limitation in length, and they are padded on the left with padding char '0'.

Names will therefore always have the form of 00100 or 02301 or 10179 for a 5 digit identifier.

Sometimes, we need to add new content inside the fileContent.Components, but we are not allowed to change the existing identifiers. Theoretically any number of items could be added to the components, but for the question at hand, I simplified it to just adding 20 items.

The existing identifiers do not have any order to them, nor can I be sure that the highest number inside the string would be lower than the theoretical maximum number for this identifier.

To handle this addition, I thought I could use a IEnumerator&lt;string&gt; function (not class) as an iterator, that creates the next number starting from the theoretical minimum value to the theoretical maximum value, and throw when it exceeds the maximum value.

public static class GapHelper {
    public static IEnumerator&lt;string&gt; GetNextName( int maxSize, IEnumerable&lt;string&gt; items, Func&lt;int, string&gt; transform ) {
        var maxLength = (long)Math.Pow( maxSize, 10 );
        var hashSet = new HashSet&lt;string&gt;( items );
        for (var i = 0; i &lt; maxLength; i++) {
            var text = transform(i);
            if (hashSet.Contains( text )) {
                continue;
            }
            yield return text;
        }
        throw new ArgumentOutOfRangeException( "Iteration exceeded maximum numbers of items at " + maxSize );
    }

    public static IEnumerator&lt;string&gt; GetNextName( int maxSize, IEnumerable&lt;string&gt; items) {
        return GetNextName( maxSize, items, i =&gt; i.ToString().PadLeft( maxSize, '0' ) );
    }
}


and this would be used inside such an IProcessor in the following way:

public class ProcessingTask : IProcessor {
    public void Execute( FileContent fileContent ) {
        using (var nameGenerator = GapHelper.GetNextName( 5, fileContent.Components.Select( c =&gt; c.Name ) ) ) {
            for (var i = 0; i &lt; 20; i++) {
                nameGenerator.MoveNext();
                fileContent.Components.Add( new Component { 
                    Name = nameGenerator.Current, Content = "Foo"
                } );
            }
        }
    }
}


What I cannot change:


How the processor is called, and with which parameters
How the file is processed afterwards, and might have been processed before
My bracket positioning, we have them at the end of each line
I am aware I didn't add documentation to the methods


A full simplified example can be found in this dotnetfiddle.

I am interested in how this code looks from maintainability perspective, and what other potential options I might have overlooked. I am also curious if having an IEnumerator&lt;string&gt; implemented as a method rather than a class makes a big difference, either for this implementation or from an iterator perspective.
Possible Bug

Since Func&lt;int, string&gt; transform is not guarded against collisions (different int values may yield the same string result), the next line is wrong. It only checks for unique elements in the already available data items, not in the new results fetched from transform.


 if (hashSet.Contains( text )) {



This could be solved by trying to add each element to the set instead:

if (!hashSet.Add( text )) {


LINQ

Furthermore, I would use an IEnumerable to allow for some LINQ chaining.

public static IEnumerable&lt;string&gt; GetNextName(
  int maxSize, IEnumerable&lt;string&gt; items, Func&lt;int, string&gt; transform) {


So we could just call:

public class ProcessingTask : IProcessor {
    public void Execute( FileContent fileContent ) {
        GapHelper.GetNextName(5, fileContent.Components
            .Select(c =&gt; c.Name)).Take(20).Select(x 
                =&gt; new Component { Name = x, Content = "Foo" })
            .ToList().ForEach(fileContent.Components.Add);
    }
}


Which yields better readability. Also a fun fact about disposing an enumerator from the initial code:


 using (var nameGenerator = // ..