简体   繁体   中英

How to serialize class as attribute?

I am trying to serialize a class as an attribute. Here's the use case: I need to store a large number of engineering parameters in an xml config file. Scientific notation is involved, and it is required by the customer that the entered values remain in the exact form the user entered them. For example, if someone enters "5.3e-1" then it must remain like that, and cannot be converted into, say, "0.53". To this end I've created a Params class that stores both the entered string and double values (actually storing the double values is only for processing efficiency later). Now here's the trick: I only want the string value to be serialized, and I want that to serialize as an attribute.

For example, if an object contains two parameters, A and B, where the string values are A.stringVal = "1.2e5" and B.stringVal = "2.0" then I would like:

public class MyObject
{
    [XmlAttribute]
    public MyParam A { get; set; }

    [XmlAttribute]
    public MyParam B { get; set; }

    ...more stuff...
}

to serialize to:

<myObject A="1.2e5" B="2.0">
    more stuff...
</myObject>

My question is very similar to one asked here . Unlike him I am fine with implementing IXmlSerializable (and would prefer to), I just can't make it work. When I try to serialize it I get a cryptic exception saying, "There was error reflection type." What am I doing wrong?

  public class MyParam : IXmlSerializable
  {
     string name;
     string stringVal;
     double doubleVal;

     public string Val
     {
        get
        {
           return stringVal;
        }
        set
        {
           stringVal = value;
           doubleVal = double.Parse(value);
        }
     }

     public double DoubleVal
     {
        get
        {
           return doubleVal;
        }
        set
        {
           doubleVal = value;
           stringVal = value.ToString();
        }
     }

     public MyParam(string name)
     {
        this.name = name;
        this.stringVal = string.Empty;
        this.doubleVal = double.NaN;
     }

     public System.Xml.Schema.XmlSchema GetSchema()
     {
        return null;
     }

     public void ReadXml(System.Xml.XmlReader reader)
     {
        throw new NotImplementedException();
     }

     public void WriteXml(System.Xml.XmlWriter writer)
     {
        writer.WriteAttributeString(name, stringVal);
     }
  }

After I've added the parameterless constructor, I got the following exception: The element 'A' type ConsoleApplication1.MyParam can not be serialized. XmlAttribute / XmlText can not be used to encode types that implement IXmlSerializable. The element 'A' type ConsoleApplication1.MyParam can not be serialized. XmlAttribute / XmlText can not be used to encode types that implement IXmlSerializable.

After removing IXmlSerializable I got an exception XmlAttribute/XmlText cannot be used to encode complex types. And found this answer

and this .

So, I think, it is better to find another way to serialize attributes.

To get what you want you need to control the serialization on the container that holds the properties, not the property itself. You can still encapsulate the serialization code in the property however, see below.

public class MyObject : IXmlSerializable
{
    public MyParam A { get; set; }

    public MyParam B { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        throw new NotImplementedException();
    }

    public void WriteXml(XmlWriter writer)
    {
        //Call each properties "WriteAttribute" call.
        A.WriteAttribute(writer);
        B.WriteAttribute(writer);
    }
}

public class MyParam
{
    string name;
    string stringVal;
    double doubleVal;

    public string Val
    {
        get
        {
            return stringVal;
        }
        set
        {
            stringVal = value;
            doubleVal = double.Parse(value);
        }
    }

    public double DoubleVal
    {
        get
        {
            return doubleVal;
        }
        set
        {
            doubleVal = value;
            stringVal = value.ToString();
        }
    }

    public MyParam(string name)
    {
        this.name = name;
        this.stringVal = string.Empty;
        this.doubleVal = double.NaN;
    }

    internal void WriteAttribute(XmlWriter writer)
    {
        writer.WriteAttributeString(name, stringVal);
    }
}

class Program
{
    static void Main(string[] args)
    {
        var test = new MyObject()
        {
            A = new MyParam("A"),
            B = new MyParam("B"),
        };

        test.A.Val = "1.2e5";
        test.B.Val = "2.0";

        var ser = new XmlSerializer(typeof(MyObject));
        var sb = new StringBuilder();
        using (var stream = new StringWriter(sb))
        {
            ser.Serialize(stream, test);
        }

        Console.WriteLine(sb);
        Console.ReadLine();
    }
}

Outputs

<?xml version="1.0" encoding="utf-16"?>
<MyObject A="1.2e5" B="2.0" />

If you don't need the name of the property in the property itself you can modify the code to the following.

public class MyObject : IXmlSerializable
{
    //....

    public void WriteXml(XmlWriter writer)
    {
        //Call each properties "WriteAttribute" call.
        A.WriteAttribute(writer, "A");
        B.WriteAttribute(writer, "B");
    }
}

public class MyParam
{
    //...

    public MyParam()
    {
        this.stringVal = string.Empty;
        this.doubleVal = double.NaN;
    }

    internal void WriteAttribute(XmlWriter writer, string name)
    {
        writer.WriteAttributeString(name, stringVal);
    }
}

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