I often come across the following pattern.
while(GetValue(i) != null)
{
DoSomethingWith(GetValue(i++));
}
Here GetValue
is executed twice. It would be much nicer to use a pattern where we can evaluate AND store the outcome of GetValue
. In some case (streams) it is impossible to get the value twice (see alternatives below for workarounds). Is there any pattern or loop construct we can use?
Some alternatives that I thought of with their own drawbacks.
Alternative 1
// Variable outside of loop scope, extra if
var value = null;
do
{
value = GetValue(i++);
if(value != null) { DoSomethingWith(value); }
} while(value != null);
Alternative 2
// Two get values, variable outside of loop
var value = GetValue(i);
while(value != null)
{
DoSometingWith(value);
value = GetValue(++i);
}
Alternative 3
// Out only works on reference types, most enumerables do not have
// a TryGet like method so we need to create our own wrapper
Object value;
while(TryGetValue(i++, out value))
{
DoSomethingWith(value);
}
Ideal world scenario (not valid C#)
while((var value = GetValue(i++)) != null)
{
DoSomethingWith(value);
}
7 Answers 7
Near "Ideal World" Solution
The following is valid C#
public static void Main()
{
var i = 0;
string value;
while ((value = GetValue(i++)) != null)
{
DoSomethingWith(value);
}
}
private static string GetValue(int input)
{
if (input > 20)
{
return null;
}
return input.ToString();
}
private static void DoSomethingWith(string value)
{
Console.WriteLine(value);
}
Compare with your "Ideal world scenario":
var i = 0;
while((var value = GetValue(i++)) != null)
{
DoSomethingWith(value);
}
All I had to do was to pull value
outside the loop. You seem willing to do this for all the presented alternatives. Thus, I don't think this is too much of a stretch.
"Infinite" While alternative
I have been playing around with the alternatives... here is another one:
var i = 0;
while (true)
{
var value = GetValue(i++);
if (value == null)
{
break;
}
DoSomethingWith(value);
}
In this case you can declare value
inside the loop (and using var
), you don't need to check for null
twice, you don't need to call GetValue
twice with the same input, and you don't have to create a wrapper with an out
parameter.
Alternative using For
We may try to express the same thing using a for
loop. Although, the naive approach does not work:
// I repeat, this does not work:
var i = 0;
for (string value = null; value != null; value = GetValue(i++))
{
DoSomethingWith(value);
}
The problem with this version is that it starts with value
being null
, which meets the exit criterion, and thus you get no iterations.
As JimmyJames points out you can write it like this:
var i = 0;
for(string value; (value = GetValue(i++)) != null;)
{
DoSomethingWith(value);
}
The only drawback I see is that you need to write the type (you can't use var
).
Addendum: This is another variant suggested by Maliafo:
var i = 0;
for (var value = GetValue(i); value != null; value = GetValue(++i))
{
DoSomethingWith(value);
}
While this version requires to write GetValue
, it doens't call it twice with the same value.
Do you want to declare i
in the scope too? Have a look at this:
for (var i = 0; ; i++)
{
var value = GetValue(i);
if (value == null)
{
break;
}
DoSomethingWith(value);
}
This is the same loop that we see in the "infinite" while solution I posted above. Yet, since I had no condition on the while... why not use change it to a for
to increment i
?
Addendum: Also see NetMage's answer for an interesting use of C# 7.0 features.
-
I experimented a bit with a similar while syntax, but I thought it didn't work. I never thought of moving the variable declaration outside. I guess that's the closest you can come to my ideal world solution. I also never thought of using a for loop like that. Great answer!Roy T.– Roy T.2017年05月31日 14:04:35 +00:00Commented May 31, 2017 at 14:04
-
Coming a bit late on this one, but isn't it possible to use a more idiomatic
for (var value = GetValue(i) ; value != null ; value = GetValue(++i))
in C# ?Maliafo– Maliafo2017年06月09日 22:12:43 +00:00Commented Jun 9, 2017 at 22:12 -
@Maliafo Yes, you are right, that works! I didn't even consider it because I was avoiding writing
GetValue
twice. I completely missed it. Addendum: I'll add that to the answer.Theraot– Theraot2017年06月09日 22:20:03 +00:00Commented Jun 9, 2017 at 22:20
In C# 7.0, we can just (ab)use is:
while (GetValue(i++) is var value && value != null)
DoSomethingWith(value);
With another C# 7.0 feature, we can create an extension method:
public static T As<T>(this T val, out T newvar) => newvar = val;
And at the expense of an out
, create a variable and test the value:
while (GetValue(i++).As(out var value) != null) {
DoSomethingWith(value);
Or use a variation that emulates the ideal world scenario:
public static T Let<T>(out T newvar, T val) => newvar = val;
while (Let(out var value, GetValue(i++)) != null) {
DoSomethingWith(value);
-
1I didn't think of combining C# 7.0 features with Theraot's suggestion. Neat!Roy T.– Roy T.2017年06月09日 07:34:19 +00:00Commented Jun 9, 2017 at 7:34
-
1I am really hoping declaration expressions (plus something like the C comma operator) makes it in a version of C# soon.NetMage– NetMage2017年06月09日 17:52:22 +00:00Commented Jun 9, 2017 at 17:52
Use a higher order function
Take a leaf out of functional programming's play book: if you have a recurring pattern in the structure of your code, make a function that contains the parts that stay the same, and pass the parts that vary in as function parameters (ie delegates, in C#). That way, it doesn't matter how ugly it is, you only have to wrote it once and hide it a way in a library somewhere.
(You'll have to forgive me dropping into Java here; my C# skillz are a bit rusty)
public static <T> void untilNull (Function<Integer,T> supplier, int i, Action<T> action) {
T v;
while ((v = supplier.apply(i++)) != null)
action.perform(v);
}
....
untilNull (this::getValue, 1, this::doSomethingWithValue);
Update - now with C# version:
public static <T> void UntilNull (Func<int,T> supplier, int i, Action<T> action) {
T v;
while ((v = supplier(i++)) != null) action(v);
}
....
UntilNull (this.GetValue, 1, this.DoSomethingWithValue);
-
I think the problem with functional programming patterns in languages like C# is that they are less efficient than the procedural code, sometimes much less efficient, because there is no inline expansion of the method.NetMage– NetMage2018年09月28日 18:32:41 +00:00Commented Sep 28, 2018 at 18:32
The Simple Solution
This solution is most certainly better than your alternatives 1 and 2 if you need minimal code to work out your problem
while(true){
var value = GetValue(i++);
if(value != null) {
DoSomethingWith(value);
}
else{
break;
}
}
DRY Iterator Based Foreach compatible solution
In my opinion, for the best clean general solution, this iterator with function parameter based solution would work the best. No need to class specific wrappers and duplicate code provided by your third option and works for every problem like this (so I think its still better than the shorter inline while loop implementations). And better yet, if you are going for iterators anyway, why not put the nullcheck in the iterator itself? That way you could use this for just about any function that takes a position and can return null.
public static System.Collections.Generic.IEnumerable<T>
FunctionSequence(Func<int,T> f)
{
int i = 0;
while(true){
value = f(i++);
if(value != null) {
yield return value;
}
else{
break;
}
}
}
foreach(GetValueType value in FunctionSequence<GetValueType>(GetValue)){
DoSomethingWith(value);
}
Notice that now you need no boiler plate code for any function just use the function with the FunctionSequence
iterator and you'll iterate to the first null element, you only needed to write it once, this
Further Generalizing the Iterator Based Solution
If you don't want to start at 0, you could always change the function signature to something like:
public static System.Collections.Generic.IEnumerable<T>
FunctionSequence(Func<int,T> f, int start = 0)
{
int i = start;
...
And you could even change what your checking against, whether its null or an specific object returned!
public static System.Collections.Generic.IEnumerable<T>
FunctionSequence(Func<int,T> f, int start = 0, T terminator = null)
{
int i = start;
while(true){
value = f(i++);
if(value != terminator) {
...
This I think is the best solution for your case.
I'd go with alternative 3, for I don't see a reason why out being available for reference types: foremost if you're not using a reference type you don't need to check for nulls; in case that you want some other validation such as "has this enum the proper value?" you just can use that check in the while and it would be executed only when it's fitting (or you could use ref instead of out, even though that means initializing the variable when declaring it)
-
The check on null is just an example here. It could also be something like
> 0
. Another problem without
is that you will have to write a wrapper for most standard enumerables. But I might be able to capture that in an extension method.Roy T.– Roy T.2017年05月31日 12:26:51 +00:00Commented May 31, 2017 at 12:26 -
As I said in my answer the case that null is just an example you could check whatever you need inside TryGetValue. The way I see it this would be more expressive that your proposed solution that I think it would be just syntactic sugar: since it still needs check the return of the function, assign it to the variable and pass it to DoSomethingWith.Zalomon– Zalomon2017年05月31日 12:49:57 +00:00Commented May 31, 2017 at 12:49
-
Doh, I didn't think of TryGetValue like that. And yes it would be syntactic sugar. But it would not lead to a variable declared in a higher scope than needed, and I would not need to repeat myself :).Roy T.– Roy T.2017年05月31日 13:11:06 +00:00Commented May 31, 2017 at 13:11
How about an alternative 4:
Wrapping the GetValue into a IEnumerable/IEnumerator. You'll then be able to use the normal foreach syntax that allows for your ideal scenario. If you have dynamic content you don't need to implement a full blown Collection class, just a few functions around a yield statement will do the trick.
Should even enable you to use some Linq magic tricks with your data too.
I'm not doing C# but mostly Swift. In most cases I would write
for value in <some collection> { ... }
which is syntactic sugar for
while let value = iterator.next() { }
(The let value = expression evaluates expression, checks whether it is nil, if nil it exits, if not nil it stores a non-optional value into value. next () in an iterator returns an optional value; nil if the iterator is finished).
value
outside the loop.for(var value; (value = GetValue(i++)) != null;)