-2

A for loop like

for (T i = a; i < b; i++)
{
    // do something
}

looks quite normal.

On a second glance, you'll notice T instead of a primitive datatype - a generic type. Still shouldn't be a problem, you will think. T could be anything,   not limited to   a number - a string, even a complex object, as long as it is comparable and enumerable (i.e. you need to apply the constraint where T: IEnumerable, IComparable to it).


NOTE:

I am not interested in restricting a generic for loop to numbers only, it should be applicable to all kind of data types.

Because of the fact that it is not limited to numbers, and since Eric gave an answer which is applicable to any type of object this question is NOT a duplicate of the question "Is there a constraint that restricts my generic method to numeric types?"

It seems to be not so easy to remove a mistakenly applied duplicate flag in Stackoverflow, i.e. despite of my explanations the duplicate box is still there. The review queues seem to be not effective enough ...


But back to this question: I thought the same way, then I started playing around a bit with that and tried to write something like

IEnumerable<T> MyFor<T>(T a, T b)
where T: IEnumerable, IComparable
{
    for (T i = a; i < b; i++)
    {
        yield return i;
    }
}

to implement a generator which can be used for double, int, strings ... which would be a generic for-loop. The constraints IEnumerable and IComparable are just there to tell the compiler that elements of this type can be enumerated and compared (because of the i < b expression in the for loop and the increment), and if C# would have that, INumeric would be a quite useful constraint, too (which would then implicitly be comparable and enumerable of course).

To my surprise, this example doesn't compile, it generates the following errors:

CS0019: Operator '<' cannot be applied to operands of type 'T' and 'T'
CS0023: Operator '++' cannot be applied to operand of type 'T'

The reason seems to be that there is no constraint for numerics, but there doesn't seem to be any practical solution available, as some of the answers here are explaining.

Note: A similar (non-generic) version of this does compile:

IEnumerable<double> MyFor(double a, double b)
{
    for (var i = a; i < b; i++)
    {
        yield return i;
    }
}

IEnumerable<int> MyFor(int a, int b)
{
    for (var i = a; i < b; i++)
    {
        yield return i;
    }
}

IEnumerable<string> MyFor(string a, string b)
{
    for (string i = a; i.Length < b.Length; i = i + a)
    {
        yield return i;
    }
}

If you have it overloaded like shown above, you can invoke it like

var intNumbers = MyFor((int)1, 10);
var doubleNumbers = MyFor((double)1, 10);
var stringFor = MyFor("*", "abcde");

and due to the signature of the parameters the correct version is chosen. But of course, this is not elegant because you're duplicating code just for the sake of different data types used.


In essence, in order to make the code more elegant and less redundant, the questions are:

1. Is it possible to write a generic function as the one which can be invoked like

var intNumbers = MyFor<int>(1, 10);
var doubleNumbers = MyFor<double>(1, 10);
var stringFor = MyFor<string>("*", "abcde");

as shown in my first example?

(I am not sure about the correct constraint, I thought that where T: IEnumerable, IComparable would do because you need to compare i < b and you need to iterate to the next bigger item).

2. a) How can I write a generic constraint that will allow me to increment and compare variables of type T?

2. b) If there's no such constraint, is there a way to simulate a for-loop with generic arguments?

3. How can a for-loop be made generic?

Matt
  • 25,467
  • 18
  • 120
  • 187
  • Note that Eric's answer is even applicable to string types, e.g. `MyFor("*", x => x.Length<10, x => x + "*", x => x).Dump();` – Matt Jun 06 '23 at 11:36
  • Note that this is now a generic way to achieve this via https://learn.microsoft.com/en-us/dotnet/standard/generics/math – Jeremy Lakeman Jun 07 '23 at 06:44
  • @JeremyLakeman - Thank you for the link. Can you give an example how it applies to this question? – Matt Jun 07 '23 at 06:52
  • probably just `where T : IIncrementOperators, IComparisonOperators` to implement the `for()` loop from the question. – Jeremy Lakeman Jun 07 '23 at 07:13
  • @JeremyLakeman: Yes, those 2 interfaces from `System.Numerics` would solve the issue for numeric types, but it won't work for strings and complex (object) types. – Matt Jun 07 '23 at 08:48
  • Changed question to make more clear it is about generic data type and not about constraint restriction. Hence, it is no duplicate - wrongly flagged as duplicate. – Matt Jul 19 '23 at 11:41

1 Answers1

4

A for loop has four parts:

  • Initialize the current state
  • Test the current state and stop if the test fails
  • Execute an action
  • Create a new current state

We'll leave break and continue out of it, since they complicate things considerably.

You wish to restrict the action to producing a value. Fine. What we want then is a new version of Aggregate that yields a value:

public static IEnumerable<R> MyFor<S, R>(
  S initial, 
  Func<S, bool> test, 
  Func<S, S> increment, 
  Func<S, R> select) 
{

    for (S current = initial; test(current); current = increment(current))
        yield return select(current);

}

And we're done. You can now make any for loop you like by simply supplying the necessary lambdas:

static IEnumerable<double> MakeDoubles() => 
  MyFor(0.0, x => x <= 10.0, x => x + 1.0, x => x);
Matt
  • 25,467
  • 18
  • 120
  • 187
Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • Great, answer Eric. Thank you! :-) – Matt Jun 28 '18 at 15:33
  • @Matt: You're welcome! I note that there are already LINQ versions of many of the operations that we'd normally think of as loops, but of course not every possible combination has been created. It seems like we ought to be able to build this thing out of `Select` and `TakeWhile`, but it's easier and more efficient to simply write your own wrapper like this. – Eric Lippert Jun 28 '18 at 17:18
  • Exactly! This is why I asked the question, I felt that some nice wrapper was missing. Now with the help of your code it is easy to write similar useful things. You might have noticed there is also a NuGet package "MoreLINQ" out there with a similar idea behind. – Matt Jun 28 '18 at 18:01
  • One way to simplify this is to take the select out of it: `public static IEnumerable MyFor( S initial, Func test, Func increment) { for (S current = initial; test(current); current = increment(current)) yield return current; }` You can always plunk a `Select` after this new `MyFor`. – Eric Lippert Jun 28 '18 at 19:12
  • That's right, I think that is even better, and it doesn't limit flexibility. In most cases, if you use `MyFor` as a number generator, you would pass `x => x` anyway - and if not, as you said, just append a `.Select(...)`. – Matt Jun 29 '18 at 11:00