如何使readonly结构XML可序列化?

Şaf*_*Gür 13 .net c# serialization json.net c#-7.2

我有一个只有一个字段的不可变结构:

struct MyStruct
{
    private readonly double number;

    public MyStruct(double number)
        => this.number = number;
}
Run Code Online (Sandbox Code Playgroud)

我希望能够通过以下方式进行序列化/反序列化:

  • 数据合同序列化器
  • 二进制格式化程序
  • XML序列化程序(编辑:遗忘在原始问题中)
  • Json.NET(不添加Json.NET作为依赖项)

所以结构变成了这样:

[Serializable]
struct MyStruct : ISerializable, IXmlSerializable
{
    private readonly double number;

    public MyStruct(double number)
        => this.number = number;

    private MyStruct(SerializationInfo info, StreamingContext context)
        => this.number = info.GetDouble(nameof(this.number));

    void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        => info.AddValue(nameof(this.number), this.number);

    XmlSchema IXmlSerializable.GetSchema() => null;

    void IXmlSerializable.ReadXml(XmlReader reader)
    {
        // Necessary evil
        reader.Read();
        this = new MyStruct(double.Parse(reader.Value, CultureInfo.InvariantCulture));
    }

    void IXmlSerializable.WriteXml(XmlWriter writer)
        => writer.WriteString(this.number.ToString(CultureInfo.InvariantCulture));
}
Run Code Online (Sandbox Code Playgroud)

因为:

  • [Serializable] 是二进制格式化程序所必需的.
  • Json.NET尊重[DataContract]ISerializable.
  • [DataContract]并且ISerializable不能一起使用.
  • 幸运的是,IXmlSerializer数据合同序列化器支持.

C#7.2引入了readonly结构和MyStruct 的修饰符,作为一个不可变的结构似乎是一个理想的候选者.

问题是IXmlSerializable界面需要变异的能力MyStruct.这就是我们前面所做的那样,分配给thisIXmlSerializable.ReadXml执行.

readonly struct MyStruct : IXmlSerializable
{
    // ...
    void IXmlSerializable.ReadXml(XmlReader reader)
    {
        // No longer works since "this" is now readonly.
        reader.Read();
        this = new MyStruct(double.Parse(reader.Value, CultureInfo.InvariantCulture));
    }
    // ...
}
Run Code Online (Sandbox Code Playgroud)

我尝试通过反射作弊但是FieldInfo.SetValue将值包装起来,并且FieldInfo.SetValueDirect需要一个TypedReference我无法获得的,因为__makeref当它this是只读时也是禁止的.

那么有哪些方法可以让MyStruct被XML序列化器序列化?

我还要提一下,我不关心输出XML的样子,我真的不需要IXmlSerializable接口提供的细粒度控件.我只需要使用我列出的序列化器使MyClass始终可序列化.

Evk*_*Evk 8

为了满足您的要求,您只需要:

[Serializable]
[DataContract]
public readonly struct MyStruct {
    [DataMember]
    private readonly double number;

    public MyStruct(double number)
        => this.number = number;
}
Run Code Online (Sandbox Code Playgroud)

测试代码:

var target = new MyStruct(2);
// with Data Contract serializer
using (var ms = new MemoryStream()) {
    var s = new DataContractSerializer(typeof(MyStruct));
    s.WriteObject(ms, target);
    ms.Position = 0;
    var back = (MyStruct) s.ReadObject(ms);
    Debug.Assert(target.Equals(back));
}

// with Json.NET
var json = JsonConvert.SerializeObject(target);
var jsonBack = JsonConvert.DeserializeObject<MyStruct>(json);
Debug.Assert(target.Equals(jsonBack));

// with binary formatter
using (var ms = new MemoryStream()) {
    var formatter = new BinaryFormatter();
    formatter.Serialize(ms, target);
    ms.Position = 0;
    var back = (MyStruct) formatter.Deserialize(ms);
    Debug.Assert(target.Equals(back));
}
Run Code Online (Sandbox Code Playgroud)

更新.由于您还需要支持XmlSerializer,您可以使用一些不安全的代码来满足您的要求:

[Serializable]    
public readonly struct MyStruct : ISerializable, IXmlSerializable
{        
    private readonly double number;
    public MyStruct(double number)
        => this.number = number;

    private MyStruct(SerializationInfo info, StreamingContext context)
        => this.number = info.GetDouble(nameof(this.number));

    XmlSchema IXmlSerializable.GetSchema() {
        return null;
    }

    unsafe void IXmlSerializable.ReadXml(XmlReader reader) {
        if (reader.Read()) {
            var value = double.Parse(reader.Value, CultureInfo.InvariantCulture);
            fixed (MyStruct* t = &this) {
                *t = new MyStruct(value);
            }
        }
    }

    void IXmlSerializable.WriteXml(XmlWriter writer) {
        writer.WriteString(this.number.ToString(CultureInfo.InvariantCulture));
    }

    public void GetObjectData(SerializationInfo info, StreamingContext context) {
        info.AddValue(nameof(number), this.number);
    }
}
Run Code Online (Sandbox Code Playgroud)


VSa*_*dov 8

作为最后的手段,在readonliness可以被"抛弃"通过Unsafe.AsRefhttps://www.nuget.org/packages/System.Runtime.CompilerServices.Unsafe

假设你对使用不安全代码有限,那么抛弃readonliness比fixed使用托管类型更好一些.

"几乎不可变"的结构是一个已知问题.这是一个相对罕见的情况,现在没有好的和安全的解决方案.

添加一个语言功能,允许只选择性地只生成一个结构的一些成员是建议的长期解决方案之一.