简体   繁体   中英

Constructor of a XmlSerializer serializing a List<T> throws an InvalidOperationException when used with XmlAttributeOverrides

Summary

When using the XmlSerializer class, serializing a List<T> (where T can be serialized with XmlSerializer without problems) using XmlAttributeOverrides such as this:

using xmls = System.Xml.Serialization;
...
            xmls.XmlAttributeOverrides attributeOverrides = new xmls.XmlAttributeOverrides();
            attributeOverrides.Add(typeof(T), new xmls.XmlAttributes()
            {
                XmlRoot = new xmls.XmlRootAttribute("foo")
            });
            attributeOverrides.Add(typeof(List<T>), new xmls.XmlAttributes()
            {                    
                XmlArray = new xmls.XmlArrayAttribute("foobar"),
                XmlArrayItems = { new xmls.XmlArrayItemAttribute("foo") },                    
            });

will throw the following InvalidOperationExcpetion at the inner-most exception:

System.InvalidOperationException: XmlRoot and XmlType attributes may not be specified for the type System.Collections.Generic.List`1[[T, programname, Version=versionnumber, Culture=neutral, PublicKeyToken=null]].

What I expect from the serializer

<texparams>
    <texparam pname="TextureMinFilter" value="9729"/>
    <texparam pname="TextureMagFilter" value="9729"/>
</texparams>

What I can sucessfully get at the moment

<ArrayOfTextureParameter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <TextureParameter pname="TextureMinFilter" value="9729" />
    <TextureParameter pname="TextureMagFilter" value="9728" />
</ArrayOfTextureParameter>

Background Info

I have been messing around with XML Serialization lately but have come across a problem. I am trying to serialize and deserialize some classes that wrap OpenGL textures.

In one of my classes (which I appropriately called BitmapTexture2d ) I have a Bitmap field, which I wish to store in a Base64 element like so:

<bitmap64>
    *base64goeshere*
</bitmap64>

Since I want to keep my code as neat as possible I decided to use the IXmlSerializable interface instead of creating a property which can convert a string and a Bitmap back and forth.

Later on in the process I decided to use the XmlSerializer class to generate the XML for a single field defined in Texture2d (which BitmapTexture2d is derived from) called Parameters (which is a List<TextureParameter> and TextureParameter is serializable by the XmlSerialization class). However this is how the serializer defaulted to serializing the List<TextureParameter> :

<ArrayOfTextureParameter xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
    <TextureParameter pname="TextureMinFilter" value="9729" />
    <TextureParameter pname="TextureMagFilter" value="9728" />
  </ArrayOfTextureParameter>

After seeing this, I decided to try and change the names of the nodes. After some research (where I landed at stackoverflow multiple times) I discovered the XmlAttributeOverrides class which can be passed to the constructor of XmlSerializer to add/override node names etc.

After writing out my code, the constructor for the sub-serializer started throwing an exception as described above. I have tried using an array which threw the same exception. I later though to serialize each element in the list one by one, but came to the conclusion that it was harder than I thought to achieve it that way. I posted this question somewhere else with no answer. And here I am...

Your problem is that you are trying to use overrides to attach [XmlArray] and [XmlArrayItem] to the type List<T> . However, as shown in the docs , [XmlArray] cannot be used in this manner:

 [AttributeUsageAttribute(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.ReturnValue, AllowMultiple = false)] public class XmlArrayAttribute : Attribute 

Notice there is no AttributeTargets.Class included? That means that [XmlArray] cannot be applied directly to a type and so attempting to do so via XML overrides rightly throws an exception.

But as to why that exception message states,

System.InvalidOperationException: XmlRoot and XmlType attributes may not be specified for the type System.Collections.Generic.List`1...

Er, well, that message is simply wrong. It would appear to be a minor bug in XmlSerializer . You could even report it to Microsoft if you want.

What you need to do instead is:

  • Override the [XmlRoot] attribute of List<T> to specify the desired name, in this case "texparams", AND

  • Override the [XmlType] attribute of T and set XmlTypeAttribute.TypeName to be the desired collection element name. In the absence of an [XmlArrayItem(name)] override this is what controls the element names of collections whose items are of type T .

Thus your code should look like:

static XmlSerializer MakeListSerializer<T>(string rootName, string elementName)
{
    xmls.XmlAttributeOverrides attributeOverrides = new xmls.XmlAttributeOverrides();
    attributeOverrides.Add(typeof(List<T>), new xmls.XmlAttributes()
    {
        XmlRoot = new xmls.XmlRootAttribute(rootName),
    });
    attributeOverrides.Add(typeof(T), new xmls.XmlAttributes()
    {
        XmlType = new xmls.XmlTypeAttribute(elementName),
    });

    return new XmlSerializer(typeof(List<T>), attributeOverrides);
}

Sample fiddle .

Note that when constructing an XmlSerializer using XmlAttributeOverrides you must cache the serializer for later reuse to avoid a severe memory leak, for reasons explained here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM