Well, you don't need the first call to ToCharArray()
to start with - string implements IEnumerable<char>
. However, I agree that a StringBuilder and a loop would probably be more appropriate in this case.
I'm not sure what string.Concat(char[]) does offhand, btw - why aren't you just using the string constructor which takes a char array? In other words, after these modifications:
static string SanitizeXml(string xml)
{
return new string (xml.Where(c => IsLegalXmlChar(c)).ToArray());
}
I still prefer a StringBuilder solution, but that could be improved for the common case (where there are few illegal characters) by giving an appropriate capacity to start with:
string SanitizeXml(string xml)
{
var buffer = new StringBuilder(xml.Length);
foreach(char c in xml)
{
if (IsLegalXmlChar(c))
{
buffer.Append(c);
}
}
return buffer.ToString();
}
One alternative I hadn't thought of before might be an extension method on StringBuilder:
// Can't just call it Append as otherwise StringBuilder.Append(object) would
// be used :(
public static StringBuilder AppendSequence(this StringBuilder builder,
IEnumerable<char> sequence)
{
foreach (char c in sequence)
{
builder.Append(c);
}
return builder;
}
Then you could use it like this:
xml = new StringBuilder(xml.Length)
.AppendSequence(xml.Where(IsLegalXmlChar)
.ToString();
(You could have other overloads for AppendSequence to take IEnumerable etc, if you wanted.)
EDIT: Another alternative might be to avoid calling Append so often, using instead the overload which appends a substring. You could then again build an extension method for StringBuilder, something like (completely untested, I'm afraid - I haven't even tried compiling it):
public static StringBuilder AppendWhere(this StringBuilder builder,
string text,
Func<char, bool> predicate)
{
int start = 0;
bool lastResult = false;
for (int i=0; i < text.Length; i++)
{
if (predicate(text[i]))
{
if (!lastResult)
{
start = i;
lastResult = true;
}
}
else
{
if (lastResult)
{
builder.Append(text, start, i-start);
lastResult = false;
}
}
}
if (lastResult)
{
builder.Append(text, start, text.Length-start);
}
return builder;
}
Usage for the example:
xml = new StringBuilder(xml.Length).AppendWhere(xml, IsLegalXmlChar)
.ToString();
Another alternative would be to change it to be an extension method on String, create the StringBuilder lazily, and if you get to the end with start=0, just return the original string.