• Effective C# Item6:明辨值类型和引用类型的使用场合


        可能是受到Java的影响,我在转向C#后,一直没有注意区分过值类型和引用类型,不论是编写新代码还是重构旧代码时,如果发现有一些信息需要独立出来时,一直都是以新建类的方式进行,很少考虑使用结构体(我到目前为止,对结构体的理解还停留在C语言的层次)。

        C#,或者说.NET,是区分值类型和引用类型的,这一点和C++以及Java都有区别。C++中传参都是以“传值”的方式进行,这种方式效率很高,但是会产生“对象切割”的问题,即在如果基类对象的地方,如果传递了一个派生类的实例,那么程序只会截取派生类实例中包含的基类信息,而忽略派生类自己新追加的信息,对于虚方法,也只会调用基类的方法;Java为了解决这个问题,将传参都做成了“按引用”的方式,这样造成了效率比较低。

        我们在编写C#代码的过程中,应该在新建一个类型时,就要明确这个类型应该是值类型,还是引用类型。如果初期考虑不周全,到了后面再进行修改,很可能会造成问题。

        区分值类型和引用类型的一个原则:值类型用于存储数据,而引用类型用于定义行为。

        其实,对于单纯存储数据的结构,是采用值类型,还是存储类型,也可以从类的职责划分角度来看,无论是《敏捷软件开发》还是《设计模式》中,都对类的划分有说明。作为一个类,应该有其自身的职责,这些职责是通过向外提供一组接口的方式来实现的,对于单纯的存储数据的操作,例如ORM框架或者MVC模式中用于保存和DB表映射关系的Model,它本身没有行为,只是一堆数据,这种情况下,使用类来存储是否合适呢?   

        值类型是直接存储在堆栈上,而引用类型是存储在堆中,对于值类型来说,用户拿到的就是值类型本身,而对于引用类型来说,用户拿到的是指向引用类型的一个引用,这里,可以理解为指针。

        我们可以看以下的代码。

        首先,通过类的方式来定义一个结构

    引用类型定义的结构
    1 public class RefForStoreValue : ICloneable
    2 {
    3 private string m_strName;
    4 public string Name
    5 {
    6 get { return m_strName; }
    7 set { m_strName = value; }
    8 }
    9
    10 private string m_strSex;
    11 public string Sex
    12 {
    13 get { return m_strSex; }
    14 set { m_strSex = value; }
    15 }
    16
    17 private string m_strAddress;
    18 public string Address
    19 {
    20 get { return m_strAddress; }
    21 set { m_strAddress = value; }
    22 }
    23
    24 public override string ToString()
    25 {
    26 return string.Format("Name:{0}, Sex:{1}, Address:{2}", this.Name, this.Sex, this.Address);
    27 }
    28
    29 public object Clone()
    30 {
    31 return this.MemberwiseClone();
    32 }
    33 }

        然后,通过结构体的方式,来定义相同的结构

    值类型定义的结构
    1 public struct ValueForStoreValue
    2 {
    3 private string m_strName;
    4 public string Name
    5 {
    6 get { return m_strName; }
    7 set { m_strName = value; }
    8 }
    9
    10 private string m_strSex;
    11 public string Sex
    12 {
    13 get { return m_strSex; }
    14 set { m_strSex = value; }
    15 }
    16
    17 private string m_strAddress;
    18 public string Address
    19 {
    20 get { return m_strAddress; }
    21 set { m_strAddress = value; }
    22 }
    23
    24 public override string ToString()
    25 {
    26 return string.Format("Name:{0}, Sex:{1}, Address:{2}", this.Name, this.Sex, this.Address);
    27 }
    28 }

        接下来是很对上述代码的测试程序

    测试程序
    1 private static void TestRefType()
    2 {
    3 Console.WriteLine();
    4 Console.WriteLine("Test Ref Type");
    5
    6 RefForStoreValue refValue = new RefForStoreValue();
    7 refValue.Name = "AAA";
    8 refValue.Sex = "F";
    9 refValue.Address = "Beijing China";
    10 Console.WriteLine(refValue.ToString());
    11
    12 Console.WriteLine();
    13
    14 RefForStoreValue refValue2 = refValue;
    15 RefForStoreValue refValue3 = refValue.Clone() as RefForStoreValue;
    16 refValue2.Name = "XXX";
    17 refValue2.Sex = "NA";
    18 refValue2.Address = "Moon";
    19 Console.WriteLine(refValue.ToString());
    20 Console.WriteLine(refValue2.ToString());
    21 Console.WriteLine(refValue3.ToString());
    22
    23 }
    24
    25 private static void TestValueType()
    26 {
    27 Console.WriteLine();
    28 Console.WriteLine("Test Value Type");
    29
    30 ValueForStoreValue valueValue = new ValueForStoreValue();
    31 valueValue.Name = "BBB";
    32 valueValue.Sex = "M";
    33 valueValue.Address = "Shanghai China";
    34 Console.WriteLine(valueValue.ToString());
    35
    36 Console.WriteLine();
    37
    38 ValueForStoreValue valueValue2 = valueValue;
    39 valueValue2.Name = "XXX";
    40 valueValue2.Sex = "NA";
    41 valueValue2.Address = "Moon";
    42 Console.WriteLine(valueValue.ToString());
    43 Console.WriteLine(valueValue2.ToString());
    44 }

        最后,执行结果如下所示

        这样,应该可以看出值类型和引用类型的区别了吧。

        另外一点需要说明,在声明某一个类型的数组时,在分配存储空间方面,值类型是一次完成所有数组元素的分配工作,而引用类型,第一次只是分配了指向这个数组的引用,至于数组中的每一个元素,都是null。

        在创建一个类型时,如果以下问题的回答都是“是”,那么我们就可以将其定义为值类型:

    1. 该类型的主要职责是否用于数据存储?
    2. 该类型的公有接口是否完全由一些数据成员存取属性所定义?
    3. 是否确信该类型永远不可能有子类?
    4. 是否确信该类型永远都不可能具有多态行为?

        如果对于上述问题的答案不太确定,那我们还是将其定义为引用类型吧。

  • 相关阅读:
    算法漫游指北(第六篇)双端队列、排序算法分类、排序算法的稳定性、排序算法复杂度
    横冲直撞vue(第七篇):vue生命周期、vue组件
    还能这么玩?用VsCode画类图、流程图、时序图、状态图...不要太爽!
    带你学够浪:Go语言基础系列
    带你学够浪:Go语言基础系列
    带你学够浪:Go语言基础系列
    带你学够浪:Go语言基础系列-环境配置和 Hello world
    带你学够浪:Go语言基础系列
    最香远程开发解决方案!手把手教你配置VS Code远程开发工具,工作效率提升N倍
    面试总结:鹅厂Linux后台开发面试笔试C++知识点参考笔记
  • 原文地址:https://www.cnblogs.com/wing011203/p/1640274.html
Copyright © 2020-2023  润新知