Perhaps the absence of such means I'm doing it wrong?
From my viewpoint, yes. In the general case, what value there is in encapsulating a string into an HtmlString before converting it back to string for concatenation purpose? This just complicates the code and creates additional pressure on the garbage collector.
The code should avoid unneeded allocations and so, it should first assemble the complete string to be encapsulated, all using string (or preferably StringBuilder), and only then encapsulate it as an HtmlString. (Instead of building bits of HtmlString, concatenating them which implies converting them back to string, then re-encapsulating them into an HtmlString.)
If the application happens to need to concatenate data already encapsulated as HtmlString by other parts of it, well, were the HtmlString typing of those other parts actually sound? (It might very well indicate some premature conversion to HTML.) Anyway, when having string manipulations to do, better do them on strings by converting your HtmlString back to string first.
That would avoid this suboptimal code from a performance viewpoint taken from your answer:
return Concat(first, new HtmlString(plainString));
Here it creates an HtmlString just to give a bit more work for the StringBuilder, which will convert it back to string.
So, if you still need a Concat utility on HtmlString, it could be better to adapt your answer code like this:
public static class HtmlStringExtensions
{
public static HtmlString Concat(this HtmlString first, params HtmlString[] values)
{
return Concat(first, values.Select(v => v.ToString()).ToArray());
}
public static HtmlString Concat(this HtmlString first, params string[] values)
{
var sb = new StringBuilder();
sb.Append(first);
foreach (var val in values)
{
sb.Append(val);
}
return new HtmlString(sb.ToString());
}
}
But if actually most of your code uses the params HtmlString[] overload, then it causes an additional array instantiation compared to your answer. You might as well relax the typing then, which by the way would allow to concatenate in one call a mix of strings and HtmlString:
public static class HtmlStringExtensions
{
public static HtmlString Concat(this HtmlString first, params object[] values)
{
var sb = new StringBuilder();
sb.Append(first);
foreach (var val in values)
{
sb.Append(val);
}
return new HtmlString(sb.ToString());
}
}