“If you look long enough into the void the void begins to look back through you." – Nietzsche
Returning types rather than void on methods that do not necessarily require a return value, as occurs in the implementation of the Append methods on the StringBuilder, allows us to do some interesting method chaining in our code.
Bill Wagner suggests doing something similar with collections in More Effective C# . My examples are a bit more contrived, however.
Suppose – for the sake of the premise if for nothing else – that we have a business requirement to take a decimal collection of prices and then add 6 percent to each collection member. We could write the following custom collection to do this:
public class Prices: IEnumerable<decimal>
{
private List<decimal> _mySequence;
public Prices()
{
_mySequence = new List<decimal>();
}
public Prices Add(decimal d)
{
_mySequence.Add(d);
return this;
}
public IEnumerator<decimal> GetEnumerator()
{
foreach (decimal d in _mySequence)
yield return d;
}
IEnumerator IEnumerable.GetEnumerator()
{
foreach (decimal d in _mySequence)
yield return d;
}
public Prices AddSixPercentAndWrite()
{
foreach(decimal d in _mySequence)
Console.WriteLine(string.Format("{0:C}",d * 1.06m));
return this;
}
}
Which we would then call thus:
var prices = new Prices();
for(decimal d = 0; d< 20m;d++)
{
prices.Add(d);
}
prices.AddSixPercentAndWrite();
The business is initially happy with this, but then comes back a week later to say that the six percent tax is nine percent in a different store. Additionally, while sometimes the business wants to see the final price, they also sometimes want to see the original price alongside of it.
Besides our original output, we now also need to be able to push a list that looks something like this:
$0.00 $0.00
$1.00 $1.06
$2.00 $2.12
$3.00 $3.18
$4.00 $4.24
$5.00 $5.30
$6.00 $6.36
$7.00 $7.42
$8.00 $8.48
$9.00 $9.54
$10.00 $10.60
$11.00 $11.66
$12.00 $12.72
. . .
Seeing where this is headed, we realize that to handle all foreseen and unforeseen scenarios, we should come up with a way to chain up our methods in order to make the code more flexible.
We could restructure the AddSixPercent method to take a parameter; we could then break out the Write method so we can call it multiple times if needed. We could also change the signatures on our methods to return our Prices collection instead of void, allowing us to chain the methods together.
There’s a hitch, however. While returning Prices would allow us to do some chaining, we still wouldn’t get the right results since each method would iterate through the whole collection before returning. Consequently we would end up printing out all the items in the first column before getting around to printing the items in the second column (the calculated cost with tax), which isn’t what we want at all.
. . .
$12.00
$13.00
$14.00
$15.00
$16.00
$17.00
$18.00
$19.00
$0.00
$1.06
$2.12
$3.18
$4.24
$5.30
$6.36
$7.42
$8.48
$9.54
$10.60
$11.66
$12.72
. . .
By using iterators, on the other hand, every method in our chain would act on a particular iteration rather than on the whole sequence. Besides getting the output that we want, this would also happily give us much more efficient code since we only have to iterate through our collection once rather than once per chained method call.
If we are to use iterators, however, we can’t return the Prices type. Instead we have to return an IEnumerable type. Leaving the rest of our Prices custom collection class alone, here’s what that new code would look like:
public static IEnumerable<decimal> AddXPercent(IEnumerable<decimal> sequence
, short percent)
{
foreach (decimal d in sequence)
yield return d + (d * percent * .01m);
}
public static IEnumerable<decimal> WriteToConsole(IEnumerable<decimal> sequence)
{
foreach (decimal d in sequence)
{
Console.Write(string.Format("{0:C} ", d));
yield return d;
}
}
Our calling code, in turn, would look like this:
foreach (decimal d in Prices.WriteToConsole(
Prices.AddXPercent(
Prices.WriteToConsole(
prices), 6)
)
)
Console.WriteLine();
We’ve managed to achieve chaining, but not so elegantly, all things considered. First, we have to include the name of our Type in order to chain our methods together. Second, the chaining is done with nested methods instead of the clean With-like syntax I was extolling in the previous post. Finally, the sequence of activity isn’t particularly clear. Because we are chaining our methods by using nesting, the innermost nested method occurs first, while the first shall be processed last.
Fortunately extension methods allow us to alter the way our methods are called. In order to get the sort of syntax we want, we just need to pull our iterator methods into an extension methods class like so:
public static class PricesExtension
{
public static IEnumerable<decimal> AddXPercent(
this IEnumerable<decimal> sequence
, short percent
)
{
foreach (decimal d in sequence)
yield return d + (d * percent * .01m);
}
public static IEnumerable<decimal> WriteToConsole(
this IEnumerable<decimal> sequence
)
{
foreach (decimal d in sequence)
{
Console.Write(string.Format("{0:C} ", d));
yield return d;
}
}
}
Giving us the results we were looking for:
$0.00 $0.00
$1.00 $1.06
$2.00 $2.12
$3.00 $3.18
$4.00 $4.24
$5.00 $5.30
$6.00 $6.36
$7.00 $7.42
$8.00 $8.48
$9.00 $9.54
$10.00 $10.60
$11.00 $11.66
$12.00 $12.72
. . .
We can now call our code like this:
foreach(var d in prices
.WriteToConsole()
.AddXPercent(6)
.WriteToConsole()
)
Console.WriteLine();
This alternative to returning void involves three tricks:
1) Returning an IEnumerable type instead of void.
2) Taking an IEnumerable type as a parameter.
3) Using these signatures in extension methods rather than the custom collection class itself.
If the syntax looks familiar, this is because this is also how LINQ to Objects is implemented. For that very reason, we could have accomplished similar results by using LINQ:
foreach(var d in prices
.Select(d => { Console.Write(string.Format("{0:C} ", d)); return d; })
.Select(d => d + (d * 6 * .01m))
.Select(d => { Console.Write(string.Format("{0:C} ", d)); return d; })
)
{
Console.WriteLine();
}
It’s funky code, no doubt, but I also find it highly compelling. I know that as I loop through my prices, I am writing out the original price, I add 6 percent to it in the following line, then I write out that result in the final line. Inside the foreach block, I then perform any follow-up operations: in this case, writing a new line.
1st, on the button posting in Alternatives to returning Void: Continued, just drifting through several web sites, appears to be a very capable platform you are employing. Keep the articles arriving. As Arnie stated I will be back.