In this article we will look at helper extension methods of StringBuilder first to better support chaining StringBuilder.
We will work on the same StringBuilder instance and add support for appending lines or character to the StringBuilder given a
condition. Also example showing how to aggregate lines from a sequence is shown and appending formatted lines. Since C# interpolation
has become more easy to use, I would suggest you keep using AppendLine instead.
Here is the helper methods in the extension class :
public static class StringBuilderExtensions {
public static StringBuilder AppendSequence<T>(this StringBuilder @this, IEnumerable<T> sequence, Func<StringBuilder, T, StringBuilder> fn)
{
var sb = sequence.Aggregate(@this, fn);
return sb;
}
public static StringBuilder AppendWhen(this StringBuilder @this, Func<bool> condition, Func<StringBuilder, StringBuilder> fn) =>
condition() ? fn(@this) : @this;
public static StringBuilder AppendFormattedLine(
this StringBuilder @this,
string format,
params object[] args) =>
@this.AppendFormat(format, args).AppendLine();
}
Now consider this example usage:
void Main()
{
var countries = new Dictionary<int, string>{
{ 1, "Norway" },
{ 2, "France" },
{ 3, "Austria" },
{ 4, "Sweden" },
{ 5, "Finland" },
{ 6, "Netherlands" }
};
string options = BuildSelectBox(countries, "countriesSelect", true);
options.Dump("Countries"); //dump is a method available in Linqpad to output objects
}
private static string BuildSelectBox(IDictionary<int, string> options, string id, bool includeUnknown) =>
new StringBuilder()
.AppendFormattedLine($"<select id=\"{id}\" name=\"{id}\">")
.AppendWhen(() => includeUnknown, sb => sb.AppendLine("\t<option value=\"0\">Unknown</option>"))
.AppendSequence(options, (sb, item) => sb.AppendFormattedLine("\t<option value=\"{0}\">{1}</option>", item.Key, item.Value))
.AppendLine($"</select>").ToString();
What if we wanted to inspect the state of the stringbuilder in the middle of these chained expression. Is it possible to output state in such lengthy chained functional expressions?
Yes, that is called the
Tee method inside functional programming patterns. Other might call it for
Tap such as used in Rx languages.
The Tee method looks like this:
public static class FunctionalExtensions {
public static T Tee<T>(this T @this, Action<T> act) {
act(@this);
return @this;
}
}
We can now inspect state in the middle of chained expressions in functional expressions.
private static string BuildSelectBox(IDictionary<int, string> options, string id, bool includeUnknown) =>
new StringBuilder()
.AppendFormattedLine($"<select id=\"{id}\" name=\"{id}\">")
.AppendWhen(() => includeUnknown, sb => sb.AppendLine("\t<option value=\"0\">Unknown</option>"))
.Tee(Console.WriteLine)
.AppendSequence(options, (sb, item) => sb.AppendFormattedLine("\t<option value=\"{0}\">{1}</option>", item.Key, item.Value))
.AppendLine($"</select>").ToString();
The picture below shows the output:
So there you have it, if you have lengthy chained functional expressions, make such a
Tee helper method to peek into the state this far. The name
Tee stems from the Unix Command by the same name. It copies contents from STDIN to STDOUT.
More about
Tee Unix command here:
https://shapeshed.com/unix-tee/