c#中值类型和引用类型的基础教程

前言

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

典型类型

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

典型值类型

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

典型引用类型

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

基类

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

值类型基类

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

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

struct Struct1 : ValueType
{

}

这样是会引起编译错误的,值类型不能继承任何其他类型,值类型只能实现接口,不能继承自其它类型。只有引用类型既可以实现接口也能继承自其它类型。顺便说一下,还有一点比较重要的是,ValueType重写了Object基类的Equals方法和GetHashCode方法,所以当使用Equals比较两个值类型的时候,系统会比较两个值类型的各个属性是否相等,再返回结果,这就是所谓的相等性。与此相对,引用类型在使用Equals的时候,会在后台调用object.ReferenceEquals,换言之,引用类型在比较相等性的时候会考虑同一性。

引用类型基类

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

在内存中的表现

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

值类型在内存中

当创建一个值类型对象时,c#会在Stack上面创建一块空间,这块空间就存放这个值类型对象。
int是一个典型的值类型,如下语句

int age = 10;

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

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

int myAge = age;

引用类型在内存中

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

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

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

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

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

Author myAuthor = author;

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

object.ReferenceEquals(myAuthor, author);

可以得到验证。

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

当参数时的行为区别

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

值类型当参数

值类型当参数的时候,传递到函数内部的是一份值类型的拷贝,所以在函数内部修改这个拷贝不会影响原对象。除非我们在传递参数的时候使用了ref或者out。

引用类型当参数

如果参数是引用类型,传递到函数内部的依然是一份拷贝,但是这个拷贝是其在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();
  }
 }

看起来没什么问题,对吧?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();
  }

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

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

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

再次测试,

这次封装就没问题了。

总结

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

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

到此这篇关于c#中值类型和引用类型的文章就介绍到这了,更多相关c#值类型和引用类型内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 浅谈C#中的值类型和引用类型

    一.基本概念 C#只有两种数据类型:值类型和引用类型 值类型在线程栈分配空间,引用类型在托管堆分配空间 值类型转为引用类型称成为装箱,引用类型转为值类型称为拆箱 以下是值类型和引用类型对照表 从上图可以简单看出:string,Object,数组,class是引用类型,简单类型,枚举,结构是值类型. 二.代码展示 定义一个类和结构调用赋值 内存分配情况如下图: 从这张图可以看出,class实例化出来的对象,指向了内存堆中分配的空间:truct实例化出来的对象,是在内存栈中分配. 修改代码如下: 内

  • c#字符串值类型与引用类型比较示例

    复制代码 代码如下: classProgram{    staticvoid Main() {        int a = 9;    //给变量a赋值为9        int b = a;   //将a的副本给变量b        b = 10;        Console.WriteLine(string.Format("a={0},b={1}", a, b));        Person ZS = newPerson();       //张三        ZS.Age

  • c# 引用类型和值类型

    CLR支持两种类型:引用类型和值类型. 引用类型总是从托管堆上分配的. c#中的New操作符返回对象的内存地址. 引用对象的注意点: 1.内存从托管堆中分配 2.堆上分配对象,有一些额外的操作,影响一些性能的 3.从托管堆中分配一个对象时,可能强制执行一次垃圾回收. CLR中的值类型是轻量级的.不需要提领一个指针,不需要垃圾回收,可以减少垃圾回收的次数. 在CLR中一般称为"类"的都是引用类型,所有的值类型都称为结构或者枚举. 所有的结构都是抽象类ValueType的直接派生类.Val

  • C#中的DateTime是值类型还是引用类型

    近期遇到了DateTime到底是值类型还是引用类型的疑惑,顺势较深入地了解一下DateTime相关的内容 结论:DateTime是值类型,因为DateTime是结构体,而结构体继承自System.ValueType,属于值类型 一.DateTime是值类型还是引用类型的探索 二.了解DateTime结构体 三.DateTime.Now和DateTime.UtcNow是怎么计算出来的 一.DateTime是值类型还是引用类型的探索 1. 先编写测试代码 将dateTime1赋值给dateTime2

  • c# 引用类型与值类型的区别详解

    解析:CLR支持两种类型:值类型和引用类型.用Jeffrey Richter(<CLR via C#>作者)的话来说,"不理解引用类型和值类型区别的程序员将会把代码引入诡异的陷阱和诸多性能问题".这就要求我们正确理解和使用值类型和引用类型.值类型包括C#的基本类型(用关键字int.char.float等来声明),结构(用struct关键字声明的类型),枚举(用enum关键字声明的类型):而引用类型包括类(用class关键字声明的类型)和委托(用delegate关键字声明的特

  • C#中值类型和引用类型的区别深度分析

    本文通俗易懂的分析了C#中值类型和引用类型的区别.分享给大家供大家参考.具体分析如下: 似乎"值类型和引用类型的区别"是今年面试的流行趋势,我已然是连续三次(目前总共也就三次)面试第一个问题就遇到这个了,这是多大的概率啊,100%,哈哈,我该买彩票去! 言归正传,咱还是先来探讨探讨这二者之间有什么区别吧.记得有一次电话面试中,我直接跟面试官说:"值类型是现金,引用类型是存折",后来想想当时说这话虽是有点儿冲动地脱口而出,但也没什么不妥.我这人不善于背理论的教条,喜欢

  • C#值类型和引用类型的深入理解

    从概念上看,值类型直接存储其值,而引用类型存储对其值的引用.这两种类型存储在内存的不同地方.在C#中,我们必须在设计类型的时候就决定类型实例的行为.这种决定非常重要,用<CLR via C#>作者Jeffrey Richter的话来 说,"不理解引用类型和值类型区别的程序员将会给代码引入诡异的bug和性能问题(I believe that a developer who misunderstands the difference between reference types and

  • C#值类型、引用类型中的Equals和==的区别浅析

    引言 最近一个朋友正在找工作,他说在笔试题中遇到Equals和==有什么区别的题,当时跟他说如果是值类型的,它们没有区别,如果是引用类型的有区别,但string类型除外.为了证实自己的说法,也研究了一下,以免误导别人,这里将研究结果总结一下,如果我有什么地方说的不对的地方,望指出. 相等性 在定义类或结构时,您将决定为类型创建值相等性(或等效性)的自定义定义是否有意义. 通常,当类型的对象预期要添加到某类集合时,或者当这些对象主要用于存储一组字段或属性时,您将实现值相等性. 您可以基于类型中所有

  • 一看就懂:图解C#中的值类型、引用类型、栈、堆、ref、out

    C# 的类型系统可分为两种类型,一是值类型,一是引用类型,这个每个C#程序员都了解.还有托管堆,栈,ref,out等等概念也是每个C#程序员都会接触到的概念,也是C#程序员面试经常考到的知识,随便搜搜也有无数的文章讲解相关的概念,貌似没写一篇值类型,引用类型相关博客的不是好的C#程序员.我也凑个热闹,试图彻底讲明白相关的概念. 程序执行的原理 要彻底搞明白那一堆概念及其它们之间的关系似乎并不是一件容易的事,这是因为大部分C#程序员并不了解托管堆(简称"堆")和线程栈(简称"栈

  • c#基础系列之值类型和引用类型的深入理解

    前言 不知不觉已经踏入坑已10余年之多,对于c#多多少少有一点自己的认识,写出来渴求同类抨击,对自己也算是个十年之痒的一个总结. C#把数据类型分为值类型和引用类型 1.1:从概念上来看,其区别是值类型直接存储值,而引用类型存储对值的引用. 1.2:这两种类型在内存的不同地方,值类型存储在堆栈中,而引用类型存储在托管对上.存储位置的不同会有不同的影响. 下面话不多说了,来一起看看详细的介绍吧 基本概念 CLR支持两种类型:值类型和引用类型. 面试过很多5年左右的同学,有很多连值类型和引用类型的基

随机推荐