如何使用BinaryFormatter自定义将空值反序列化为C#Nullable <t>?

时间:2016-04-06 16:03:46

标签: c# .net serialization nullable binaryformatter

我有一个旧的遗留类型,它与C#2.0中引入的C#nullable基本相同。我有旧的持久化数据,包含使用二进制格式化程序序列化的遗留类型。我想完全删除遗留类型(并用C#nullable替换它)并在持久化数据的反序列化中处理所有必需的类型转换。

我几乎设法使用序列化绑定器和代理进行此操作。然而。我无法弄清楚如何将我的遗留空值反序列化为C#nullable类型的正确空值,而不是获取空值我得到底层(非可空)类型的未初始化对象。

以下示例将打印出以下内容,但我想在最后一行使用null而不是0:

Before serialization: 17
Before serialization: <null>
After serialization: 17
After serialization: 0

我想在下面的示例中替换的类型是NullableInt,它充当非可空Int类的包装器(后者用于创建强类型整数,但这对我的问题并不重要)。

using System;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;

namespace ConsoleApplication7 {
    /// <summary>
    /// Non-nullable typed integer. We use these for creating strongly typed
    /// integers to make it harder to mix up integers having different
    /// purpose (like mixing up Int<PatientId> with Int<ExaminationId>).
    /// </summary>
    [Serializable]
    internal struct Int<T> {

        public int Inner;

        public Int(int inner) {
            this.Inner = inner;
        }
    }

    /// <summary>
    /// Used as generic type argument to the typed integer
    /// </summary>
    internal struct PatientId {
    }

    /// <summary>
    /// Nullable typed integer. This is the legacy type I want to 
    /// replace with the C# nullable, i.e. Int<T>?
    /// </summary>
    [Serializable]
    internal struct NullableInt<T> {

        private object inner;
        public static NullableInt<T> Null = new NullableInt<T>();

        public Int<T> Value { get { return new Int<T>((int)inner); } }
        public bool IsNull { get { return (inner == null); } }

        public NullableInt(Int<T> value) {
            this.inner = value.Inner;
        }
    }

    /// <summary>
    /// SerializationBinder used during deserialization. It will map the legacy
    /// type to the C# nullable type.
    /// </summary>
    internal class Binder : SerializationBinder {
        public override Type BindToType(string assemblyName, string typeName) {
            if (typeName == typeof(NullableInt<PatientId>).FullName) {
                return typeof(Int<PatientId>?);
            } else {
                throw new NotSupportedException();
            }
        }
    }

    /// <summary>
    /// SerializationSurrogate converting the legacy binary format
    /// of NullableInt<T> to C# nullable
    /// </summary>
    internal class Surrogate<T> : ISerializationSurrogate {
        public object SetObjectData(object obj, SerializationInfo info, StreamingContext context, ISurrogateSelector selector) {
            // Get the object serialized by NullableInt<T>
            object inner = info.GetValue("inner", typeof(object));

            // Always return Int<T>?
            Int<T>? nullable = (inner == null) ? (Int<T>?)null : new Int<T>((int)inner);
            return nullable;
        }
        public void GetObjectData(object obj, SerializationInfo info, StreamingContext context) {
            throw new NotImplementedException();
        }
    }

    /// <summary>
    /// Main program testing serialization and deserialization
    /// </summary>
    class Program {
        static void Main(string[] args) {
            // Create data to serialize
            var data = new NullableInt<PatientId>[2] {                
                new NullableInt<PatientId>(new Int<PatientId>(17)),
                NullableInt<PatientId>.Null
            };
            data.ToList().ForEach(x => Console.WriteLine(
                "Before serialization: " + (x.IsNull ? "<null>" : x.Value.Inner.ToString())));

            // Serialize into memory stream
            var formatter = new BinaryFormatter();
            var ms = new MemoryStream();
            formatter.Serialize(ms, data);

            // Set up binder for deserialization
            formatter = new BinaryFormatter();
            formatter.Binder = new Binder();

            // Setup surrogate for deserialization
            var surrogateSelector = new SurrogateSelector();
            surrogateSelector.AddSurrogate(
                typeof(Int<PatientId>), 
                new StreamingContext(StreamingContextStates.All), 
                new Surrogate<PatientId>());
            surrogateSelector.AddSurrogate(
                typeof(Int<PatientId>?), 
                new StreamingContext(StreamingContextStates.All), 
                new Surrogate<PatientId>());
            formatter.SurrogateSelector = surrogateSelector;

            // Deserialize
            ms.Seek(0, SeekOrigin.Begin);            
            var deserialized = (Int<PatientId>?[])formatter.Deserialize(ms);
            deserialized.ToList().ForEach(x => Console.WriteLine(
                "After serialization: " + (x.HasValue ? x.Value.Inner.ToString() : "<null>")));
        }
    }
}

最后请注意,我确实得到了一个nullables数组,最后一个不是null(而是一个内部值为0的未初始化的Int)。

另外两个观察结果:

  1. 如果我删除第一个代理,即第一次调用AddSurrogate,则ISerializationSurrogate根本不会被调用
  2. 如果删除第二个代理,ISerializationSurrogate会被调用,但SerializationInfo参数始终设置为null。
  3. 有没有人知道从序列化代理返回什么来使二进制格式化器返回空值而不是未初始化的值?或者还有另一种方法可以做我想做的事情吗?我也玩过实现IObjectReference接口,但没有任何运气。

0 个答案:

没有答案