c#: how to hide a field which is used only for XML serialization retrocompatibility?

只愿长相守 提交于 2021-02-05 08:55:07

问题


The field is used only during the serialization / deserialization process but I would like to immediately encapsulate it and hide from the class.

Is it possible?


回答1:


Basically, no.

XmlSerializer only works with public members, so you can't make it internal or private. You can add some attributes to make it less glaring especially in UIs that data-bind:

[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
public int Foo {get; set; }

but that only masks it. You could also look at IXmlSerializable, but that is a horrible API and most implementations of it are simply buggy - I do not recommend implementing this interface.

But: best practice is that whenever serialization requirements conflict with your model's design: create a dedicated DTO model - one that matches perfectly your chosen serialization library and exists purely for that purpose. And then map between the two. Then you don't have to compromise.




回答2:


Its not possible with XML-Serialization in C# , if you want to do like that than you should make use of DataContractSerialization, It allows this kind of functionality i.e. you can serialize private field of you object.

Below is possible with DataContractSerialization, I hope you like to try out

[DataContract]
class Person
{
    [DataMember]
    public string m_name;

    [DataMember]
    private int m_age;
}

This what I tried when I was learning XML to Linq , and this is wired solution but if you want to try , here i created xml string by using xml to linq

here is my article : Object to XML using LINQ or XmlSerializer

Note : here code field of product class is private field but still you can generate xml string

using System.Collections.Generic;
using System.Xml.Linq;
using System.Linq;

class Program
{
     public class Product
        {
            public Product()
            { }

            public Product(string name,int code, List<productType> types)
            {
                this.Name = name;
                this.Code = code;
                this.types = types;
            }

            public string Name { get; set; }
            private int Code { get; set; }
            public List<productType> types { get; set; }

            public string Serialize(List<Product> products)
            {
                XElement productSer = new XElement("Products",
               from c in products
               orderby c.Code
               select new XElement("product",
                   new XElement("Code", c.Code),
                   new XElement("Name", c.Name),
                   new XElement("Types", (from x in c.types
                                          orderby x.type//descending 
                                           select new XElement("Type", x.type))
               ))
          );
                return productSer.ToString();
            }
        }
        public class productType
        {
            public string type { get; set; }
        }

 public static void Main()
    {    
        List<productType> typ = new List<productType>();
        typ.Add((new productType() { type = "Type1" }));
        typ.Add((new productType() { type = "Type2" }));
        typ.Add((new productType() { type = "Type3" }));

        List<Product> products =new List<Product>() { new Product ( "apple", 9,typ) ,
                      new Product ("orange", 4,typ   ),
                      new Product ("apple", 9 ,typ),
                      new Product ("lemon", 9,typ ) };

   Console.WriteLine(new Product().Serialize(products));
        Console.ReadLine();
 }
}



回答3:


Assuming you are using XmlSerializer, then only public fields and properties can be serialized, as explained in Troubleshooting Common Problems with the XmlSerializer:

The serializer examines all public fields and properties of the Type to learn about which types an instance references at runtime. It then proceeds to create C# code for a set of classes to handle serialization and deserialization using the classes in the System.CodeDOM namespace.

So, what are your options? If you are able to construct your XmlSerializer directly, you could make use of the XmlSerializer.UnknownElement event to forward the unknown elements to the object being deserialized for processing.

First, define the following attribute and extension methods:

[System.AttributeUsage(System.AttributeTargets.Method, AllowMultiple = false)]
public class XmlUnknownElementEventHandlerAttribute : System.Attribute
{
}

public static partial class XmlSerializationHelper
{
    public static T LoadFromXml<T>(this string xmlString, XmlSerializer serial = null)
    {
        serial = serial ?? new XmlSerializer(typeof(T));
        serial.UnknownElement += UnknownXmlElementEventHandler;
        using (StringReader reader = new StringReader(xmlString))
        {
            return (T)serial.Deserialize(reader);
        }
    }

    public static void UnknownXmlElementEventHandler(object sender, XmlElementEventArgs e)
    {
        var obj = e.ObjectBeingDeserialized;

        foreach (var method in obj.GetType().BaseTypesAndSelf()
            .SelectMany(t => t.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly))
            .Where(m => Attribute.IsDefined(m, typeof(XmlUnknownElementEventHandlerAttribute))))
        {
            method.Invoke(obj, BindingFlags.Public | BindingFlags.NonPublic, null, new object[] { sender, e }, null);
        }
    }
}

public static class TypeExtensions
{
    public static IEnumerable<Type> BaseTypesAndSelf(this Type type)
    {
        while (type != null)
        {
            yield return type;
            type = type.BaseType;
        }
    }
}

Next, say you have some class like:

public partial class MyClass
{
    public string MyValue { get; set; }
}

And some XML containing an element that needs to be post-processed and converted into the current model, e.g. <OldValue>:

<MyClass><OldValue>Hello</OldValue></MyClass>

Then add a method to MyClass that:

  • Can be private or internal (in full trust) or public;

  • Has the same signature as XmlElementEventHandler;

  • Is marked with your custom attribute [XmlUnknownElementEventHandler];

  • Performs the necessary post-processing on the old element.

And now the unknown element will be forwarded to it when using a serializer constructed by XmlSerializationHelper.LoadFromXml().

E.g., your method might look like:

public partial class MyClass
{
    [XmlUnknownElementEventHandler]
    void HandleOldElement(object sender, XmlElementEventArgs e)
    {
        if (e.Element.Name == "OldValue")
        {
            Debug.WriteLine("{0}: processed property {1} with value {2}", this, e.Element.Name, e.Element.OuterXml);
            MyValue = "Old value was: " + e.Element.InnerText;
        }
    }
}

And you would deserialize as follows:

var model = xmlString.LoadFromXml<MyClass>();

One advantage of this solution is that it doesn't modify the XSD generated for your types in any way.

Sample fiddle. (Note that, because the dotnetfiddle code executes in partial trust, the handlers must be public. That's not necessary in full trust.)



来源:https://stackoverflow.com/questions/47571925/c-how-to-hide-a-field-which-is-used-only-for-xml-serialization-retrocompatibil

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!