2

I was looking for, but could not find, an idiomatic example for a generic class implementing IEnumerable, which does this: constructor takes start and interval, and GetEnumerator returns IEnumerator, which starts from starts and goes on forever returning items with the interval.

In other words, something like:

public class Sequence<T> : IEnumerable<T> where T :... {

    public T start { get; private set; }
    public T interval { get; private set; }

    Sequence(T start, T interval) 
    {
        ...
    }

    IEnumerator<T> GetEnumerator() 
    {
        for(T n = start ; ; n += interval) // does not compile
            yield return n; 
    }

    ... what else?
}

There are lots of related questions, and small snippets, bits&pieces, but I have not been able to find a single nice example of a complete class, which does at least something similar enough. This is about how to implement this, so if there is existing class which does just this, it'd be good to know, but not an answer to the question.

So, actual question(s): Which is the most recent C# version that has introduced new features useful for this, and what is the ideal example code to do this with it?

Also if there are any common pitfalls, mistakes which inexperienced C# developers make related to this kind of a class, those would be good to know.


Update: since the exact thing I'm asking for seems impossible, I guess the next best thing is replacing interval with a lambda for getting the next item, or something like that.

hyde
  • 60,639
  • 21
  • 115
  • 176
  • 3
    There is no way to make such a class generic (since the `+` operator does not apply to all `T`s.) You could certainly make a `Sequence` class that worked this way (and it would probably be reasonably useful!) Alternatively, you can make your class require a `Func` on construction, and call that in order to create a new `T` from the current value and the `interval`. – dlev May 13 '13 at 17:24
  • Adding a constructor parameter, or perhaps replacing the `interval` parameter with a lambda altogether, sounds ok. – hyde May 13 '13 at 17:34
  • Hmm, I guess the exact thing in question being impossible explains why I didn't find an example :) – hyde May 13 '13 at 18:15
  • 1
    [This](http://stackoverflow.com/questions/4039694/generic-c-sharp-code-and-the-plus-operator) question is also relevent. And links to [this](http://www.yoda.arachsys.com/csharp/genericoperators.html) page, which shows a different aproach. – Cemafor May 13 '13 at 18:24

3 Answers3

2

Thanks to dlev for suggesting Func, I was originally just using a helper class.

public class Sequence<T> : IEnumerable<T>
{
  public T Start { get; private set; }
  public T Interval { get; private set; }
  private Func<T, T, T> Adder { get; set; }

  public Sequence(T start, T interval, Func<T,T,T> adder)
  {
    Start = start;
    Interval = interval;
    Adder = adder;
  }

  public IEnumerator<T> GetEnumerator()
  {
    for (T n = Start; ; n = Adder.Invoke(n, Interval))
      yield return n;
  }

  IEnumerator IEnumerable.GetEnumerator()
  {
    return this.GetEnumerator();
  }
}

You can then use it like this:

int i = 0;
foreach (int t in new Sequence<int>(3, 4, (int a, int b) => a + b))
{
    if (i == 10)
    {
        break;
    }
    i++;
    Console.WriteLine(t);
}

Alternatively, you could require that T implement some sort of Addable interface and call that Add method, but I think this is cleaner all around.

Cemafor
  • 1,633
  • 12
  • 27
  • 3
    You could also combine `interval` and `adder` into a single `Func` (e.g. `i => i + 4`). I think that would make the code simpler. – svick May 13 '13 at 18:31
  • 1
    Isn't it a bit unusual to have public properties starting with underscore? Shouldn't those in the answer be `Start` and `Interval` instead, if using the usual naming convention? – hyde May 13 '13 at 20:54
2

What you're describing is very similar to the Generate function of MoreLinq.

The implementation is simple enough:

public static IEnumerable<TResult> Generate<TResult>(TResult initial, Func<TResult, TResult> generator)
{
    if (generator == null) throw new ArgumentNullException("generator");
    return GenerateImpl(initial, generator);
}

private static IEnumerable<TResult> GenerateImpl<TResult>(TResult initial, Func<TResult, TResult> generator)
{
    TResult current = initial;
    while (true)
    {
        yield return current;
        current = generator(current);
    }
}
Servy
  • 202,030
  • 26
  • 332
  • 449
1

As an alternative to using delegates, you could use generic operators from MiscUtil. Using that, your code would look like this:

IEnumerator<T> GetEnumerator() 
{
    for(T n = start ; ; n = Operator.Add(n, interval))
        yield return n; 
}
svick
  • 236,525
  • 50
  • 385
  • 514