C#----值类型与引用类型

     要了解一门编程语言,首先就要了解它的类型。我们知道,C#一共分为两大类型:值类型和引用类型,但值类型并不单纯是我们java中的基本数据类型那么简单,有关于是否使用值类型还是个值得讨论的问题:因为装箱机制。C#的值类型还能够自定义方法,甚至能够实现引用类型的接口类型!这已经超出了我的想象范围了!

    先来点基础的东西:

基本内容.

     文档是我们学习的好帮手,在C#的文档中,我们必须注意,凡是引用类型的,名字都是"xx类",凡是值类型的,就叫"xx结构"或"xx枚举"。

     很多时候,我们的初始化操作的右值是表达式。如果左值是值类型,那么它的值就是表达式的值,但如果是引用类型,则是一个引用,并不是该引用指向的对象(学过java,所以对这现象非常熟悉),所以,在C#中,String.Empty的值不是一个空字符串,而是对空字符串的引用。

     声明一个变量,除了它的类型信息外,最重要的就是值的存储地点。变量的值一般是在它声明时的位置存储的,而实例变量的值存储在实例本身存储的地方,引用类型和静态变量则存储在堆中。

     也许我们会以为,值类型一定在栈(stack,有些地方叫线程栈)上,引用类型一定在堆(heap,有些地方叫托管堆)上,其实这是错误的,值类型也可以在堆上,像是前面讲过的,变量的值存储在它声明的地方,如果我的值类型是在一个引用类型中声明的,那么该值类型就是在堆上。方法参数一定是在栈(方法是用栈帧存储它的信息)上,但局部变量不一定,它也可以是在堆上(让我们想想匿名方法,如果它捕获了一个外部变量,该变量就会作为隐藏委托类型而保存在堆上)。好了,一个潜在的想法出现了:如果一个引用类型保存在值类型中,像是结构中,会怎么样呢?引用类型的数据依然保存在堆中,但它的引用保存在栈上,因为值类型也只拥有它的引用而已。

     值类型不能派生出其他类型,因为它是隐式密封(sealed)的,所以它并没有引用类型实例对象开头的额外信息(用于标识对象的实际类型和其他信息),即类型对象指针和同步块索引。这些额外信息并不能修改,所以我们永远也不能修改对象的类型,但我们可以转换为其他类型。执行强制类型转换时,我们会获取一个引用,该引用会检查它引用的对象本身是否是该目标类型的有效对象,若是,返回该引用并赋给目标类型,否则抛出异常。引用并不清楚它实际引用的对象的类型,所以我们的引用可以指向其他类型。

    基础的东西讲完后,就应该讲讲一些不一样的东西(虽然大部分很多人都已经非常熟悉了)。细节不讨论,只是单单从几个话题出发并做一下延伸。

话题1:结构是轻量级的类

      结构虽然是值类型,但它可以定义属性和方法,类的行为它都具备,加上它是值类型,比起引用类型来说,对内存更加友好。所以,就有一种说法:结构是轻量级的类。这种说法见仁见智,值类型的确是"轻":不需要垃圾回收(不在堆上分配),不会因类型标识而产生开销(没有类型对象指针和同步块索引),而且不需要取值这一步操作(引用类型的字段一般都是私有的,我们只能通过访问器getter取得其值)。但引用类型也有它的好处:在执行传参,赋值,返回值等类似操作时,只需要赋值4或8字节(具体得看CLR),因为我们传递的只是一个引用(我想说指针,但又觉得不合适,从来就没有任何一种说法认为引用就等同于指针,虽然CLR的引用的确是一个地址,但CLR强调,它们是引用),而不是复制所有数据。这点在容器那里非常好用,像是List,如果容量很大,那这个传参动作就太可怕了。

    值类型也并不总是对内存友好,因为隐式的装箱机制,会使某些情况像是循环等,使用值类型会造成可怕的负担。C#中的值类型使用起来并不像java的基本数据类型那么简单(java并没有隐式的装箱机制,基本数据类型不可能直接转换为类),除非是以下几种情况,我们才能放心使用值类型:

    1.值类型是不可变(immutable)的,即该类型没有提供任何方法来修改它的字段。要做到这点,我们必须将值类型的所有字段都设置为readonly(只读)。这主要针对结构这种封装其他值类型的值类型。

   2.类型的实例较小(小于16字节),因为按值传递需要复制字段,但类型实例较大以致不可能作为实参传递,也不可能作为返回值的话,也可以考虑使用值类型。

   就算满足上面的条件,我们也必须考虑到值类型的缺点(装箱机制不在列表中,因为这个话题前面已多次提醒了):

   1.值类型继承自System.ValueType,该类除了提供与System.Object一样的方法外,还做了一个动作:覆写了Equals()方法和GetHashCode()方法(值类型的比较需要考虑到它的字段,但默认的比较是引用),而默认的实现存在性能问题。我们不能苛求设计者能够考虑到所有情况,所以,大部分情况都要我们自己覆写这两个方法(这个问题不知道是不是从java而来,java也存在这样的问题)。

   2.值类型可以有自己的方法,但它不能派生也不能继承(虽然能实现接口),因此它不能含有虚方法,所有方法也是不可覆写的。

   3.引用类型的默认值是null,但我们引用一个null的引用类型时会抛出异常:NullReferenceException,但值类型默认值是0,并不会抛出异常。CLR为了弥补这点,提供了可空性(nullability)标识---可空类型(nullable)。

   4.值类型之间的相互赋值,会导致字段的复制,但引用类型只是复制引用。

  5.值类型并不在堆上分配,所以当它被销毁时,不会通过Finalize方法接到一个通知(这点在有些地方很重要,这时就需要装箱)。

  看了以上的讨论,相信对使用值类型是有点怕怕的:自己是否用错了呢?程序员是不需要顾虑那么多的,写代码最主要是能够表达清楚自己的意图,至于性能这方面,是可以在后期进行重构和优化的。

话题2:对象在C#中默认是通过引用传递

     引用传递(pass by reference)的定义非常复杂,百度百科的解释是这样的:可以将一个变量通过引用传递给函数,这样该函数就可以修改其参数的值,而引用的解释就是某一变量的一个别名,对引用的操作与对变量直接操作完全一样(很多人都说java是按引用传递,其实这种说法是不严谨的,严格意义上是传递引用对象地址值的按值传递)。如果我们以按引用传递的方式传递变量,那么调用的方法可以通过更改其参数值,来改变调用者的变量值。但C#中引用类型变量的值虽然叫引用,但不是对象本身,它更接近于指针。

      就算是传参,有些情况也不能修改它的值:

class Program
    {
        public static void Main(String[] args)
        {
            String builder = "hello";
            Show(builder);
            Console.WriteLine(builder);
        }

        static void Show(String str)
        {
            str = "word";
            Console.WriteLine(str);
        }
    }

       在Show()方法里,修改的只是builder的一个副本。当然,String虽然是引用类型,但它是不可变的。我们来传一个真正的引用类型:

 class Program
    {
        public static void Main(String[] args)
        {
            People people = new People();
            Show(people);
            Console.WriteLine(people.name);
        }

        static void Show(People people)
        {
            people.name = "男人";
        }
    }

    public class People
    {
        public String name = "";
    }

        这里我们看到,字段name改变了。

       很困惑吧?为什么还说C#是按值传递呢?C#中,引用类型作为方法参数确实是按"值"传递,因为引用类型的值是引用,而该引用是一个地址值,相当于指针(只是相当于,并不等于)。真正的引用传递的就是对象本身,因为引用本身就是对象的别名,但C#是不会传递对象本身的。

      这个问题非常让人纠结,尤其是CLR采取了引用这个说法,使得我们更加困扰了。

       C#的值类型非常奇怪,我们甚至可以用new来声明:

int number = new int();
number = 5;

      这并没有错,但刚从java中跳出来的我非常惊讶!

     C#编译器是很聪明的,它知道number是一个值类型,因为它并没有类型对象指针,于是在栈上为它分配内存,然后确保所有字段都初始化为0。这样的动作就算不用new也行:

int number;

    但是用new,编译器就认为该实例已经初始化了,而上面的情况如果我们为它赋值就会发生错误。所以,声明一个值类型最好就是为它进行初始化,哪怕只是默认值。

    关于这方面的讨论,很多时候我都有心无力,毕竟自己这个初学者要想啃下CLR,难度很大,有什么不对的地方还请见谅。

    

   

 

 

 

 

 

 

   

posted @ 2013-03-14 10:48  文酱  阅读(1892)  评论(6编辑  收藏  举报