Enumerating text (ranges) vertically and horizontally
I would go with standard IEnumerator and IEnumerable, so it could be consumed as:

    static void Main(string[] args)
    {
        var template = new Template("foo", "bar", "baz", "qux");

        foreach (var row in template)
            Console.WriteLine(row.Text);

        Console.WriteLine();
        foreach (var row in template.Select(1, 3))
            Console.WriteLine(row.Text);

        Console.WriteLine();
        foreach (var row in template.Select(0))
            foreach (var column in row)
                Console.WriteLine(column.Text);

        Console.WriteLine();
        foreach (var row in template.Select(0))
            foreach (var column in row.Range(0,3))
                Console.WriteLine(column.Text);
    }


Where Select allows to subset by indecies provided, while Range is working in terms of start and count. Row and Column objects have Index (current selection) and Indecies (allowed to be selected) properties:

public class Template : IEnumerable&lt;Row&gt;
{
    public Template(params string[] lines)
    {
        Lines = lines;
    }

    public IEnumerable&lt;Row&gt; Range(int start, int count) =&gt; Select(Enumerable.Range(start, count));
    public IEnumerable&lt;Row&gt; Select(params int[] indecies) =&gt; Select(indecies as IEnumerable&lt;int&gt;);
    public IEnumerable&lt;Row&gt; Select(IEnumerable&lt;int&gt; indecies)
    {
        foreach (var index in indecies)
            yield return new Row(Lines.ElementAtOrDefault(index), index, indecies);
    }

    public IEnumerator&lt;Row&gt; GetEnumerator() =&gt; Range(0, Lines.Length).GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() =&gt; GetEnumerator();
    string[] Lines { get; }
}


And:

public struct Row : IEnumerable&lt;Column&gt;
{
    public Row(string text, int index, IEnumerable&lt;int&gt; indecies)
    {
        Text = text;
        Index = index;
        Indecies = indecies;
    }

    public string Text { get; }
    public int Index { get; }
    public IEnumerable&lt;int&gt; Indecies { get; }

    public IEnumerable&lt;Column&gt; Range(int start, int count) =&gt; Select(Enumerable.Range(start, count));
    public IEnumerable&lt;Column&gt; Select(params int[] indecies) =&gt; Select(indecies as IEnumerable&lt;int&gt;);
    public IEnumerable&lt;Column&gt; Select(IEnumerable&lt;int&gt; indecies)
    {
        foreach (var index in indecies)
            yield return new Column(Text.ElementAtOrDefault(index), index, indecies);
    }

    public IEnumerator&lt;Column&gt; GetEnumerator() =&gt; Range(0, Text.Length).GetEnumerator();
    IEnumerator IEnumerable.GetEnumerator() =&gt; GetEnumerator();
}


And:

public struct Column
{
    public Column(char text, int index, IEnumerable&lt;int&gt; indecies)
    {
        Text = text;
        Index = index;
        Indecies = indecies;
    }

    public char Text { get; }
    public int Index { get; }
    public IEnumerable&lt;int&gt; Indecies { get; }
}