C#中的值类型和引用类型

栏目: ASP.NET · 发布时间: 6年前

内容简介:值类型和引用类型,是c#比较基础,但是也必须掌握的知识点,但是也不是那么轻易就能掌握,今天让我们一起来看看。首先我们看看这两种不同的类型有哪些比较典型的代表。int, long, float, double等原始类型中表示数字的类型都是值类型,表示时间的datatime也是值类型,除此之外我们还可以通过关键字struct自定义值类型。

前言

值类型和引用类型,是c#比较基础,但是也必须掌握的知识点,但是也不是那么轻易就能掌握,今天让我们一起来看看。

典型类型

首先我们看看这两种不同的类型有哪些比较典型的代表。

典型值类型

int, long, float, double等原始类型中表示数字的类型都是值类型,表示时间的datatime也是值类型,除此之外我们还可以通过关键字struct自定义值类型。

典型引用类型

原始类型中,array, list, dictionary, queue, stack和string都是引用类型,除此之外我们通过关键字class自定义引用类型。

基类

c#中所有的类型都最终继承自Object,这是没有疑问的,但是这其中还有些微区别。

值类型基类

对于值类型来说,除了最终继承自Object,还继承自ValueType,继承链如下

C#中的值类型和引用类型

但是请不要误解,这里仅仅指的是值类型天然是ValueType,但是不代表值类型能够这么声明

struct Struct1 : ValueType
{

}

这样是会引起编译错误的,值类型不能继承任何其他类型,值类型只能实现接口,不能继承自其它类型。只有引用类型既可以实现接口也能继承自其它类型。

引用类型基类

对于引用类型就没有那么麻烦,引用类型不会继承自ValueType。引用类型可以继承其他类型。

在内存中的表现

我们都知道,C#将内存分为了两部分,一个是Stack,另外一个是Managed Heap。一般来说,用于函数调用进栈,函数返回出栈,用的是Stack,而当创造一个新的实例时,会根据创建的实例属于值类型还是引用类型决定使用Stack还是Managed Heap。

值类型在内存中

当创建一个值类型对象时,c#会在Stack上面创建一块空间,这块空间就存放这个值类型对象。

int是一个典型的值类型,如下语句

int age = 10;

会存在于内存中的Stack上面。

C#中的值类型和引用类型

如果把值类型的实例赋值给另外一个值类型,那么效果就是复制一个新的值类型实例。

int myAge = age;

C#中的值类型和引用类型

引用类型在内存中

与值类型在内存中的表现不一样,创建一个引用类型的实例,不但会在Stack上面新建一个引用,还会在Heap上面划分出内存以容纳该引用类型实例。用户在使用的时候通过Stack上面的变量间接引用该实例。

class Author
{
    public string Name{get;set;}
    public int Age{get;set;}
}

Author author = new Author(){Name="deatharthas", Age= 32};

C#中的值类型和引用类型

注意看和值类型在内存中的区别,引用类型通过Stack上的变量访问位于Heap上面的实例。

在赋值的时候,拷贝的仅仅是Stack上面的变量,新拷贝出来的对象和旧的对象指向的是同一块内存。

Author myAuthor = author;

C#中的值类型和引用类型

这个时候,author和myAuthor指向同一块内存,称为同一性,通过调用

object.ReferenceEquals(myAuthor, author);

可以得到验证。

但可能有细心的朋友会有疑问了,不是说int是值类型,值类型是存在于Stack上面的吗?为什么在author类里面,它会在Heap里面呢?赞一个细心!值类型一般存在于Stack上面,但如果某个值类型包含于引用类型,那么它也会随着那个引用类型存放在Heap上面。

当参数时的行为区别

c#中的参数传递默认都是传值(by value),但是根据所传递对象是值类型还是引用类型,它们的行为还是有所区别,现在我们来看看。

值类型当参数

值类型当参数的时候,传递到函数内部的是一份值类型的拷贝,所以在函数内部修改这个拷贝不会影响原对象。

引用类型当参数

如果参数是引用类型,传递到函数内部的依然是一份拷贝,但是这个拷贝是其在Stack上面的变量的拷贝,就像上面的赋值那个例子。所以这个时候这份拷贝其实和原对象指向同一块内存(指向同一性),修改这个对象可以反映到原对象上面。

谨慎返回引用类型

编程是一项需要谨慎的工作,有时候我们经常会犯一些错误,而这些错误又是那么的不明显以至于不摔坑几次,我们根本察觉不了,考虑下面一个例子。

class People
    {
        public string Name { get; set; }
        public int Age { get; set; }
        private People _Father = null;
        public People Father { get { return _Father; } }
        public People(People father)
        {
            _Father = father;
        }
        public void ShowFather()
        {
            Console.WriteLine("father's name is " + Father.Name + " and his age is " + Father.Age);
        }
    }

    class Program
    {        
        static void Main(string[] args)
        {
            People father = new People(null) { Name = "father", Age = 60 };
            People son = new People(father);
            son.ShowFather();
            Console.ReadLine();
        }
    }

C#中的值类型和引用类型

看起来没什么问题,对吧?Father没有提供setter,似乎是安全的。但是我们试试下面的代码。

static void Main(string[] args)
        {
            People father = new People(null) { Name = "father", Age = 60 };
            People son = new People(father);
            var f = son.Father;
            f.Name="Changed";
            son.ShowFather();
            Console.ReadLine();
        }

C#中的值类型和引用类型

看,发现了什么,外部改变了本来应该被封装所保护的Father属性,封装被破坏了!

稍微一想我们应该能明白这个道理,Father属性返回的拷贝的变量和原Father变量指向同一块实例。要想解决这个问题,我们要么返回一个值类型,要么返回一个全新的对象。修改Father属性如下:

public People Father { get { return new People(_Father._Father) { Name = _Father.Name, Age = _Father.Age }; } }

再次测试,

C#中的值类型和引用类型

这次封装就没问题了。

总结

我们大概知道了值类型和引用类型的区别,包括它们的行为,在内存的居住方式,以及使用引用类型时可能会遇到的暗坑,希望大家通过阅读这篇文章,能够加深一些对它们的了解,少走一些弯路。

今天也简单的提到了比较时的同一性,和预防封装被破坏所采用的返回一个新的实例拷贝的策略(这个时候适合使用DeepCopy),我们之后会再详细聊它们。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Sprint

Sprint

Jake Knapp、John Zeratsky、Braden Kowitz / Simon & Schuster / 2016-3-8 / GBP 14.60

媒体推荐 “Every business leader I know worries about the same thing: Are we moving fast enough? The genius of Jake Knapp’s Sprint is its step-by-step breakdown of what it takes to solve big problems an......一起来看看 《Sprint》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

MD5 加密
MD5 加密

MD5 加密工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具