代码之家  ›  专栏  ›  技术社区  ›  Şafak Gür

如何使只读结构XML可序列化?

  •  13
  • Şafak Gür  · 技术社区  · 7 年前

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

    struct MyStruct
    {
        private readonly double number;
    
        public MyStruct(double number)
            => this.number = number;
    }
    

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

    • 二进制格式化程序
    • 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));
    }
    

    因为:

    • [Serializable] 二进制格式化程序需要。
    • Json。NET同时获得两项荣誉 [DataContract] ISerializable .
    • [数据合同] ISerializable可序列化 不能一起使用。
    • 幸运的是 IXmlSerializer 数据协定序列化程序支持。

    readonly 结构和MyStruct的修饰符,作为一个不可变的结构似乎是一个理想的候选者。

    问题是 IXmlSerializable 接口需要变异的能力 MyStruct . 这就是我们上面所做的,分配给 this 在里面 IXmlSerializable.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));
        }
        // ...
    }
    

    我试图通过反思作弊,但 FieldInfo.SetValue 框中的值,以及 FieldInfo.SetValueDirect 需要 TypedReference ,之后我无法获得 __makeref 在以下情况下也被禁止 是只读的。

    那么,有哪些方法可以让MyStruct通过XML序列化程序进行序列化呢?

    我还应该提到,我不在乎输出XML是什么样子,我真的不需要 IXML可序列化 界面我只需要使用我列出的序列化程序使我的类一致可序列化。

    3 回复  |  直到 7 年前
        1
  •  9
  •   Evk    7 年前

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

    [Serializable]
    [DataContract]
    public readonly struct MyStruct {
        [DataMember]
        private readonly double number;
    
        public MyStruct(double number)
            => this.number = number;
    }
    

    测试代码:

    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));
    }
    

    使现代化因为你也需要支持 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);
        }
    }
    
        2
  •  8
  •   VSadov    7 年前

    作为最后手段,可通过以下方式“抛弃”易用性: Unsafe.AsRef 从…起 https://www.nuget.org/packages/System.Runtime.CompilerServices.Unsafe

    假设您可以有限地使用不安全代码,那么抛弃易用性比 fixed 并且可以处理托管类型。

    添加只允许选择性制作的语言功能 一些 结构只读的成员是拟议的长期解决方案之一。

        3
  •  4
  •   Tanner Gooding    4 年前

    而您可以成功使用 unsafe , Unsafe.AsRef FieldInfo.SetValue 为了在某些场景中改变值,这在技术上是无效的代码,可能会导致未定义的行为。

    来自ECMA-335:

    [ 注: 使用 ldflda ldsflda 仅初始化 字段使代码无法验证。在无法验证的代码中,VES不需要检查 仅初始化 字段在构造函数外变异。如果方法更改常量的值,则虚拟环境无需报告任何错误。但是,此类代码无效。 结束注释 ]

    同样,来自官方API文档 FieldInfo。赋值 :

    此方法不能仅用于设置static和init的值( readonly 在C#)字段中可靠。在里面NET Core 3.0及更高版本中,如果尝试在静态、仅限init的字段上设置值,则会引发异常。

    运行时在技术上可以自由进行优化 initonly 字段和当前在某些情况下 static, initonly 领域。

    你可能会对新的 init only setters C#9中的功能( https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#init-only-setters ). 这提供了一种有效的方法,可以将属性设置为属性初始值设定项语法的一部分,并将获得适当的支持/更改,以确保它们成功工作并生成有效代码。

    推荐文章