Effective C# Item25:尽可能将类型实现为可序列化的类型

    持久化是类型的一个核心特性,有时我们需要通过不同的方式传输和创建同一个对象,例如需要通过网络传输对象,或者需要将对象信息存储到文本文件或者XML文件中,这时,如何能够保持对象的状态,并在将来使用时,可以准确的还原到原来的状态,是非常重要的。

    .NET可以使用序列化的方式来持久化对象,我们在可能的情况下,应该将类型定义为可以序列化的。序列化时,我们可以使用Serializable特性。

    我们来看下面的简单示例,将对象信息存储到XML文件中。

    首先定义两个可以序列化的类型。

    [Serializable]
    public class Employee
    {
        private PersonName m_Name;
        public PersonName Name
        {
            get { return m_Name; }
            set { m_Name = value; }
        }

        private string m_strAddress;
        public string Address
        {
            get { return m_strAddress; }
            set { m_strAddress = value; }
        }

        public Employee()
        {
            m_Name = new PersonName();
            m_strAddress = string.Empty;
        }

        public Employee(PersonName name, string address)
        {
            m_Name = name;
            m_strAddress = address;
        }

        public override string ToString()
        {
            return string.Format("Name : {0} {1}, Address : {2}", m_Name.FirstName, m_Name.LastName, m_strAddress);
        }
    }

    [Serializable]
    public class PersonName
    {
        private string m_strFirstName;
        public string FirstName
        {
            get { return m_strFirstName; }
            set { m_strFirstName = value; }
        }

        private string m_strLastName;
        public string LastName
        {
            get { return m_strLastName; }
            set { m_strLastName = value; }
        }

        public PersonName()
        {
            m_strFirstName = string.Empty;
            m_strLastName = string.Empty;
        }

        public PersonName(string firstName, string lastName)
        {
            m_strFirstName = firstName;
            m_strLastName = lastName;
        }
    }


    下面是测试方法,包含了序列化和反序列化的过程。

    private static void Test()
    {
        Employee emp = new Employee(new PersonName("Wing", "Lee"), "BeiJing");
        Console.WriteLine("Ouput emp info before serialize:");
        Console.WriteLine(emp.ToString());

        XmlSerializer serializer = new XmlSerializer(typeof(Employee));
        StreamWriter writer = new StreamWriter("emp.xml");
        serializer.Serialize(writer, emp);
        writer.Close();
        Console.WriteLine("Serialization Success.");

        StreamReader reader = new StreamReader("emp.xml");
        XmlSerializer desrializer = new XmlSerializer(typeof(Employee));
        object o = desrializer.Deserialize(reader);
        if (o is Employee)
        {
            Console.WriteLine("Ouput emp info after deserialize:");
            Console.WriteLine((o as Employee).ToString());
        }
    }


    上述代码的执行结果如下所示。

    可以看到序列化前和序列化后的对象信息,被完全一致的反应出来,这说明,序列化确实实现了对象的持久化。

    有以下两个问题需要注意:

  1. 对于可以序列化的类型,必须提供没有参数的构造函数,上述代码中,如果Employee类型和PersonName类型没有显示的提供默认构造函数,那么程序在编译时,就会报错,提示在不提供没有参数的构造函数的情况下,无法执行序列化操作。
  2. 出于性能的考虑,我们可以不用将类型全部内容都置为可序列化。

    C#在序列化时,可以采用上面代码中写的XmlSerializer的方式,也可以采用BinaryFormatter的方式,如果采用XmlSerializer的方式,那么NonSerialized特性是不会发挥作用的,同时这种方式允许序列化中的类型包含不能序列化的类型;但是对于BinaryFormatter来说,可以使用NonSerialized特性,同时进行序列化的类型所包含的其他所有类型,都必须是可序列化的,否则就会在序列化的过程中发生异常。

    我们来看以下的代码,使用Formatter的正常方式。

        private static void TestWithFormatter()
        {
            Employee emp = new Employee(new PersonName("Wing", "Lee"), "BeiJing");
            Console.WriteLine("Ouput emp info before serialize:");
            Console.WriteLine(emp.ToString());

            BinaryFormatter serializer = new BinaryFormatter();
            Stream stream = File.Open("emp.txt", FileMode.Create);
            serializer.Serialize(stream, emp, null);
            stream.Close();
            Console.WriteLine("Serialization Success.");

            stream = File.Open("emp.txt", FileMode.Open); 
            BinaryFormatter deserializer = new BinaryFormatter();
            object o = deserializer.Deserialize(stream, null);
            stream.Close();
            if (o is Employee)
            {
                Console.WriteLine("Ouput emp info after deserialize:");
                Console.WriteLine((o as Employee).ToString());
            }
        }


    我们来修改一下Employee类型的代码,将m_strAddress字段用NonSerialized特性进行修饰,然后执行上述Test()方法和TestWithFormatter()方法,其中,Test()方法的执行结果是不会改变的;但是TestWithFormatter()方法的执行结果如下所示。

    我们来修改一下Employee类型的代码,将m_strAddress字段用NonSerialized特性进行修饰,然后执行上述Test()方法和TestWithFormatter()方法,其中,Test()方法的执行结果是不会改变的;但是TestWithFormatter()方法的执行结果如下所示。

    [Serializable]
    public class Employee : IDeserializationCallback
    {        
        private PersonName m_Name;
        public PersonName Name
        {
            get { return m_Name; }
            set { m_Name = value; }
        }

        [NonSerialized]
        private string m_strAddress;
        public string Address
        {
            get { return m_strAddress; }
            set { m_strAddress = value; }
        }

        public Employee()
        {
            m_Name = new PersonName();
            m_strAddress = string.Empty;
        }

        public Employee(PersonName name, string address)
        {
            m_Name = name;
            m_strAddress = address;
        }

        public override string ToString()
        {
            return string.Format("Name : {0} {1}, Address : {2}", m_Name.FirstName, m_Name.LastName, m_strAddress);
        }

        public void OnDeserialization(object sender)
        {
            this.Address = "Vernus";
        }
    }


    上述代码实现了IDeserializationCallBack接口,在接口的OnDeserialization()方法中,我们对Address属性进行重新赋值,在代码修改后,TestWithFormatter()方法的执行结果如下图所示。

    我们在编码的过程中,可能在将对象序列化后,又修改了类型的结构,这时,单纯采用Serialzable特性,是不能对修改进行区分的,甚至可能会引发异常。这时,我们可以实现ISerializable接口,来定制序列化过程。

    我们来看下面的代码。

    [Serializable]
    public class Employee : IDeserializationCallback, ISerializable
    {        
        private PersonName m_Name;
        public PersonName Name
        {
            get { return m_Name; }
            set { m_Name = value; }
        }

        [NonSerialized]
        private string m_strAddress;
        public string Address
        {
            get { return m_strAddress; }
            set { m_strAddress = value; }
        }

        public Employee()
        {
            m_Name = new PersonName();
            m_strAddress = string.Empty;
        }

        public Employee(PersonName name, string address)
        {
            m_Name = name;
            m_strAddress = address;
        }

        private Employee(SerializationInfo info, StreamingContext context)
        {
            Name = info.GetValue("name", typeof(PersonName)) as PersonName;
            Address = info.GetString("address");
        }

        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("name", Name);
            info.AddValue("address", Address);
        }

        public void OnDeserialization(object sender)
        {
            this.Address = "Vernus";
        }

        public override string ToString()
        {
            return string.Format("Name : {0} {1}, Address : {2}", m_Name.FirstName, m_Name.LastName, m_strAddress);
        }
    }


    上述代码在执行TestWithFormatter()方法后的结果没有发生变化。我们在ISerializable接口的GetObjectData()方法中,以键/值对的方式将类型中的数据项进行存储,同时添加了一个构造函数,用于得到反序列化后对象中各数据项的值。

    一般情况下,我们对实现了ISerializable接口的类声明为sealed,这样可以免于被继承,因为继承后的子类,还要针对自身的数据情况,对GetObjectData()方法进行扩充,比较复杂。

    编程时要注意,从序列化流中写入和读取数据的顺序必须一致。

    总结:.NET框架为对象序列化提供了一个简单、标准的算法。如果我们的类型需要持久化,那就应该遵循标准的实现。如果我们不为类型添加序列化支持,那么其他使用我们类型的类也就不能支持序列化。我们所做的工作应该尽可能的使类型的使用者更加方便。如果可以,应该使用默认的方式来支持序列化;如果默认的Serializable特性不能够满足要求,则应该实现ISerializable接口。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ava实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),可运行高分资源 Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现的毕业设计&&课程设计(包含运行文档+数据库+前后端代码),Java实现
C语言是一种广泛使用的编程语言,它具有高效、灵活、可移植性强等特点,被广泛应用于操作系统、嵌入式系统、数据库、编译器等领域的开发。C语言的基本语法包括变量、数据类型、运算符、控制结构(如if语句、循环语句等)、函数、指针等。下面详细介绍C语言的基本概念和语法。 1. 变量和数据类型 在C语言中,变量用于存储数据,数据类型用于定义变量的类型和范围。C语言支持多种数据类型,包括基本数据类型(如int、float、char等)和复合数据类型(如结构体、联合等)。 2. 运算符 C语言中常用的运算符包括算术运算符(如+、、、/等)、关系运算符(如==、!=、、=、<、<=等)、逻辑运算符(如&&、||、!等)。此外,还有位运算符(如&、|、^等)和指针运算符(如、等)。 3. 控制结构 C语言中常用的控制结构包括if语句、循环语句(如for、while等)和switch语句。通过这些控制结构,可以实现程序的分支、循环和多路选择等功能。 4. 函数 函数是C语言中用于封装代码的单元,可以实现代码的复用和模块化。C语言中定义函数使用关键字“void”或返回值类型(如int、float等),并通过“{”和“}”括起来的代码块来实现函数的功能。 5. 指针 指针是C语言中用于存储变量地址的变量。通过指针,可以实现对内存的间接访问和修改。C语言中定义指针使用星号()符号,指向数组、字符串和结构体等数据结构时,还需要注意数组名和字符串常量的特殊性质。 6. 数组和字符串 数组是C语言中用于存储同类型数据的结构,可以通过索引访问和修改数组中的元素。字符串是C语言中用于存储文本数据的特殊类型,通常以字符串常量的形式出现,用双引号("...")括起来,末尾自动添加'\0'字符。 7. 结构体和联合 结构体和联合是C语言中用于存储不同类型数据的复合数据类型。结构体由多个成员组成,每个成员可以是不同的数据类型;联合由多个变量组成,它们共用同一块内存空间。通过结构体和联合,可以实现数据的封装和抽象。 8. 文件操作 C语言中通过文件操作函数(如fopen、fclose、fread、fwrite等)实现对文件的读写操作。文件操作函数通常返回文件指针,用于表示打开的文件。通过文件指针,可以进行文件的定位、读写等操作。 总之,C语言是一种功能强大、灵活高效的编程语言,广泛应用于各种领域。掌握C语言的基本语法和数据结构,可以为编程学习和实践打下坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值