Fork me on Gitee

【C# .Net】List循环add,出现数据相同现象? 引发对引用类型和值类型的底层逻辑的思考。

赶项目时发现了一个问题,定义一个引用对象,如果在循环外定义对象,在循环内list.add(object)。最后的结果却是所有的对象值都是一样的,即每add一次,都会把之前的数据覆盖。

解决方法:把对象在循环里new就行了,这样并不会造成很大的内存消耗,因为循环结束new的对象很快都被GC了。

虽然解决了此问题,但想感觉里面的逻辑有点意思,想深入了解里面包含了一些.net底层存储的知识,引用类型和值类型的区别,还有String这个特殊的引用类型的探究。

 

首先说一下结论:对于List<T>来说,如果T是引用类型,那保存的是引用,如果是值类型,保存的是值本身。

下面是demo,分别是 object,int ,string 。

         

结果分别是:

              

如果不在循环内部创建对象, 一般情况下,引用类型(除了string)会被覆盖,而值类型不会被覆盖,这是什么原因呢?

分析:

请观察下图,new User1对象,然后用User2 = User1 给User2 赋值。

 

值类型(int,stuct,bool,enum,float,decimal),声明后,无论是否有值,编译器先分配其内存(分配在栈)。

引用类型(object,interface,delegate,array)引用类型当声明一个类时,只在栈中分配内存用于容纳地址,而此时并没有为其分配堆上的内存空间。当new 一个实例时,分配至堆上,并把堆的地址保存到栈上。

回到上面的例子,对于引用类型,在循环外new了 user 对象后,这个对象的引用地址就确定了。到第二次ladd时,list[0]中保存的User对象和list[1]对象是同一个对象,使用的是同一个地址,也就是说在添加list[1]是,list[0]也被修改了,因为它俩指针指向同一个地址,结果就是list都是最后的list[i]的数据。 

 

其他:String是一种特殊的引用类型

String类型直接继承自Object,这使得它成为一个引用类型,也就是说栈上不会有任何字符串。但是与其他引用类型不同是,string具有不变性。

String的不变性:

String的值改变时,会检查内存,如果与原来的值不同,则会重新分配内存空间,分配地址到栈上,数据到堆上,而不会影响到原有的值。

这个原因也是为什么字符串大范围修改要用StringBuilder,而不是string,每次改变string时,会消耗内存,频繁的处理string对象,会消耗大量的内存。

 

发散一下思考:

出现以下情况是因为 == 如果比较的是引用类型,那么比较的是引用地址指向的数据是否是同一个,而不是底层对象的实际值。

public class A
{
  public string Name;
  public A(string n) { Name = n; }
}

A a1 = new A("sima");
A a2 = new A("sima");
Console.WriteLine(a1 == a2);     // False
A a3 = a1;
Console.WriteLine(a1 == a3);     // true

 

第二种问题,无法交换两个string。

出现以下原因是因为,参数传递是默认是值传递,Swap方法中的a,b是新在栈中开辟的内存数据,并非参数本身。

 解决方法也很简单:使用ref关键字传参改为引用传递。

P.S string是特殊的引用类型,值存储在栈上。

       

 

 

 

 再进一步思考,比如 List 这种东西就有一个奇怪的事情。如果在传参的时候直接把List变为空,竟然无法修改值,引用类型为什么无法修改原有的值

 但是如果我对List进行Add 或者Remove 或者赋值的时候原来的值还是会改变???

            

 

 

 思考了许久,看了下上文描述引用类型标红语句 引用类型当声明一个类时,只在栈中分配内存用于容纳地址,而此时并没有为其分配堆上的内存空间

再参考了栈堆分配的图,明白了为什么会这样。

因为 List (a1) 在主方法是  值传递过去 创建副本List(a2) 这个栈中属于不同的两个内存,但是他们储存的引用地址是一样的,指向的是堆中同一个内存。

所以副本 List(a2) =null 不影响 List(a1) ,但是对引用到的堆中的数据的修改,会使得指向同一个堆的两个不同 List(a1) List(a2) 结果相同。 

 

 

posted @ 2021-02-03 17:56  Roushan_IT  阅读(1856)  评论(0编辑  收藏  举报