XElement constructor extremly slow with byte[] array

Recently I’ve tried to build a xml document using Linq to XML classes like XDocument and XElement One element has to contain byte array. I’ve checked XElement(XName name, object content) constructor and found that it works fine for DateTime, string decimal or bool.

Then I’ve wrote something like code below:

var c = 1;
var buf = GetBytes(c);

x = new XElement("testB", buf);

(I will show the GetBytes method later but it returns requested array of bytes)

For small arrays (up to 100KB) code above works fine. However I’ve tried to put 512KB using this approach and program halted.

I’ve made some investigations trying to put byte array to the xml and below are results:

testB in 2592ms (50KB)
testB in 11514ms (100KB)
testB in 29173ms (150KB)
testB in 40936ms (175KB)

it means that to execute XElement constructor with 175KB byte array takes about 40 seconds (!)

I’ve checked XElement source code and found that this behaviour is caused by string concatenation

// System.Xml.Linq.XContainer
internal void AddStringSkipNotify(string s)
{
    this.ValidateString(s);
    if (this.content == null)
    {
        this.content = s;
        return;
    }
    if (s.Length > 0)
    {
        if (this.content is string)
        {
            this.content = (string)this.content + s; // exactly this code executed 512000 times shows why string concatenation should not be made
            return;
        }
...
    }
}

The AddStringSkipNotify method is executed for each element of the byte array (IEnumerable object passed to the XElement content parameter):

// System.Xml.Linq.XContainer source code
internal void AddContentSkipNotify(object content)
{
... // some checks are here for predefined types
IEnumerable enumerable = content as IEnumerable;
    if (enumerable != null)
    {
        foreach (object current in enumerable)
        {
            this.AddContentSkipNotify(current);
        }
        return;
    }
    this.AddStringSkipNotify(XContainer.GetStringValue(content));
}

To resolve this issue I converted byte array to the Base64 string and pass a sting as content parameter value:

var c = 175;
var buf = GetBytes(c);

x = new XElement("testB", Convert.ToBase64String(buf));