编写高质量代码改善C#程序的157个建议——建议56:使用继承ISerializable接口更灵活地控制序列化过程

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编写高质量代码改善C#程序的157个建议——建议56:使用继承ISerializable接口更灵活地控制序列化过程相关的知识,希望对你有一定的参考价值。

 

建议56:使用继承ISerializable接口更灵活地控制序列化过程

接口ISerializable的意义在于,如果特性Serializable,以及与其像配套的OnDeserializedAttribute、OnDeserializingAttribute、OnSerializedAttribute、OnSerializingAttribute、NoSerializable等特性不能完全满足自定义序列化的要求,那就需要继承ISerializable了。

 

以下是格式化器的工作流程:如果格式化器在序列化一个对象的时候,发现对象继承了ISerializable接口,那它就会忽略掉类型所有的序列化特性,转而调用类型的GetObjectData方法来构造一个SerializationInfo对象,方法内部负责向这个对象添加所有需要序列化的字段(“添加”这个词可能不太恰当,因为我们在添加前可以随意处置这个字段)。以建议55中的例子为例,如果要为ChineseName构造对应的值,在类继承ISerializable接口的情况下,应该这样去实现:

    class Program
    {
        static void Main()
        {
            Person liming = new Person() { FirstName = "Ming", LastName = "Li" };
            BinarySerializer.SerializeToFile(liming, @"c:\", "person.txt");
            Person p = BinarySerializer.DeserializeFromFile<Person>(@"c:\person.txt");
            Console.WriteLine(p.FirstName);
            Console.WriteLine(p.LastName);
            Console.WriteLine(p.ChineseName);           
        }
    }

    [Serializable]
    public class Person : ISerializable
    {
        public string FirstName;
        public string LastName;
        public string ChineseName;

        public Person()
        {
        }

        protected Person(SerializationInfo info, StreamingContext context)
        {
            FirstName = info.GetString("FirstName");
            LastName = info.GetString("LastName");
            ChineseName = string.Format("{0} {1}", LastName, FirstName);
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("FirstName", FirstName);
            info.AddValue("LastName", LastName);
        }
    }

序列化工具类:

技术分享图片
    public class BinarySerializer
    {
        //将类型序列化为字符串
        public static string Serialize<T>(T t)
        {
            using (MemoryStream stream = new MemoryStream())
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, t);
                return System.Text.Encoding.UTF8.GetString(stream.ToArray());
            }
        }

        //将类型序列化为文件
        public static void SerializeToFile<T>(T t, string path, string fullName)
        {
            if (!Directory.Exists(path))
            {
                Directory.CreateDirectory(path);
            }
            string fullPath = Path.Combine(path, fullName);
            using (FileStream stream = new FileStream(fullPath, FileMode.OpenOrCreate))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                formatter.Serialize(stream, t);
                stream.Flush();
            }
        }

        //将字符串反序列化为类型
        public static TResult Deserialize<TResult>(string s) where TResult : class
        {
            byte[] bs = System.Text.Encoding.UTF8.GetBytes(s);
            using (MemoryStream stream = new MemoryStream(bs))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                return formatter.Deserialize(stream) as TResult;
            }
        }

        //将文件反序列化为类型
        public static TResult DeserializeFromFile<TResult>(string path) where TResult : class
        {
            using (FileStream stream = new FileStream(path, FileMode.Open))
            {
                BinaryFormatter formatter = new BinaryFormatter();
                return formatter.Deserialize(stream) as TResult;
            }
        }
    }
View Code

我们在方法GetObjectData中处理序列化,然后在一个带参数的构造方法中处理反序列化。虽然在接口中没有地方指出需要这样一个构造器,但这确实是需要的,除非我们序列化后不再打算把它反序列化回来。

这个例子不能表现出ISerializable接口比Serializable特性的优势,见下面的例子:

将Person序列化,然后在反序列化中将其变为另一个对象:PersonAnother类型对象。要实现这个功能,需要Person和PersonAnother都实现ISerializable接口,原来其实很简单,就是在Person类的GetObjectData方法中处理序列化,在PersonAnother的受保护构造方法中反序列化。

    class Program
    {
        static void Main()
        {
            Person liming = new Person() { FirstName = "Ming", LastName = "Li" };
            BinarySerializer.SerializeToFile(liming, @"c:\", "person.txt");
            PersonAnother p = BinarySerializer.DeserializeFromFile<PersonAnother>(@"c:\person.txt");
            Console.WriteLine(p.Name);
        }
    }

    [Serializable]
    class PersonAnother : ISerializable
    {
        public string Name { get; set; }

        protected PersonAnother(SerializationInfo info, StreamingContext context)
        {
            Name = info.GetString("Name");
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
        }
    }

    [Serializable]
    public class Person : ISerializable
    {
        public string FirstName;
        public string LastName;
        public string ChineseName;

        public Person()
        {
        }

        protected Person(SerializationInfo info, StreamingContext context)
        {
        }

        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.SetType(typeof(PersonAnother));
            info.AddValue("Name", string.Format("{0} {1}", LastName, FirstName));
        }
    }

在Person类型的GetObjectData方法中,有句代码非常重要:

info.SetType(typeof(PersonAnother));

它负责告诉序列化器:我要被反序列化为PersonAnother。而类型PersonAnother则很简单,它甚至都不需要知道谁会被反序列化成它,它不需要做任何特殊处理。

ISerializable接口这个特性很重要,如果运用得当,在版本升级中,它能处理类型因为字段变化而带来的问题。

 

 

转自:《编写高质量代码改善C#程序的157个建议》陆敏技

以上是关于编写高质量代码改善C#程序的157个建议——建议56:使用继承ISerializable接口更灵活地控制序列化过程的主要内容,如果未能解决你的问题,请参考以下文章

编写高质量代码改善C#程序的157个建议——建议157:从写第一个界面开始,就进行自动化测试

编写高质量代码改善C#程序的157个建议——建议19:使用更有效的对象和集合初始化

编写高质量代码改善C#程序的157个建议——建议141:不知道该不该用大括号时,就用

编写高质量代码改善C#程序的157个建议——建议52:及时释放资源

编写高质量代码改善C#程序的157个建议——建议41:实现标准的事件模型

编写高质量代码改善C#程序的157个建议——建议28:理解延迟求值和主动求值之间的区别