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

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

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

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

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

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

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

    我们可以看以下的代码。

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

引用类型定义的结构
    public class RefForStoreValue : ICloneable
    {
        private string m_strName;
        public string Name
        {
            get { return m_strName; }
            set { m_strName = value; }
        }

        private string m_strSex;
        public string Sex
        {
            get { return m_strSex; }
            set { m_strSex = value; }
        }

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

        public override string ToString()
        {
            return string.Format("Name:{0}, Sex:{1}, Address:{2}", this.Name, this.Sex, this.Address);
        }

        public object Clone()
        {
            return this.MemberwiseClone();
        }
    }


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

值类型定义的结构
    public struct ValueForStoreValue
    {
        private string m_strName;
        public string Name
        {
            get { return m_strName; }
            set { m_strName = value; }
        }

        private string m_strSex;
        public string Sex
        {
            get { return m_strSex; }
            set { m_strSex = value; }
        }

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

        public override string ToString()
        {
            return string.Format("Name:{0}, Sex:{1}, Address:{2}", this.Name, this.Sex, this.Address);
        }
    }


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

测试程序
        private static void TestRefType()
        { 
            Console.WriteLine();
            Console.WriteLine("Test Ref Type");

            RefForStoreValue refValue = new RefForStoreValue();
            refValue.Name = "AAA";
            refValue.Sex = "F";
            refValue.Address = "Beijing China";
            Console.WriteLine(refValue.ToString());

            Console.WriteLine();
            
            RefForStoreValue refValue2 = refValue;
            RefForStoreValue refValue3 = refValue.Clone() as RefForStoreValue;
            refValue2.Name = "XXX";
            refValue2.Sex = "NA";
            refValue2.Address = "Moon";
            Console.WriteLine(refValue.ToString());
            Console.WriteLine(refValue2.ToString());
            Console.WriteLine(refValue3.ToString());

        }

        private static void TestValueType()
        {
            Console.WriteLine();
            Console.WriteLine("Test Value Type");

            ValueForStoreValue valueValue = new ValueForStoreValue();
            valueValue.Name = "BBB";
            valueValue.Sex = "M";
            valueValue.Address = "Shanghai China";
            Console.WriteLine(valueValue.ToString());

            Console.WriteLine();

            ValueForStoreValue valueValue2 = valueValue;
            valueValue2.Name = "XXX";
            valueValue2.Sex = "NA";
            valueValue2.Address = "Moon";
            Console.WriteLine(valueValue.ToString());
            Console.WriteLine(valueValue2.ToString());
        }


    最后,执行结果如下所示

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

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

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

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

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值