C#/.Net · 2018年3月19日 0

C#引用类型的“反常情况”

简介

大部分的开发语言都会有引用类型与值类型,基本上所有的开发人员都能理解两者之间的区别,并使用;这里讲述几个例子看一看引用类型有哪些“反常”的情况,借此深入了解一下C#中引用类型。

本文以下使用VS2013、.Net4.5、控制台应用程序。

1. Test Zero

下边这个例子只要不是初学者很容易看出来输出的结果。
(这里使用了Newtonsoft.Json,只是为了让结果直观一些)

static void Main(string[] args)
{
    Dictionary<int, int> dic_1 = null;
    Dictionary<int, int> dic_2 = new Dictionary<int, int>();
    dic_1 = dic_2;
    dic_2.Add(1, 1);
    dic_2.Add(2, 2);
    Console.WriteLine("dic_1: " + Newtonsoft.Json.JsonConvert.SerializeObject(dic_1));
    Console.WriteLine("dic_2: " + Newtonsoft.Json.JsonConvert.SerializeObject(dic_2));
    Console.ReadLine();
}

这就是引用类型最简单的表现——使用同一块内存地址;虽然的是修改dic_2,但是上边dic_1 = dic_2;将它们绑定起来,故而结果是相同的。

但是下边一个例子有那么一点点反常。

2. Test One

static void Main(string[] args)
{
    test_one();
}
static void test_one()
{
    Console.WriteLine("test_one");
    Dictionary<int, int> dic_1 = new Dictionary<int, int>();
    Dictionary<int, int> dic_2 = new Dictionary<int, int>();
    dic_1.Add(1, 1);
    dic_2.Add(1, 1);
    test_one_part(dic_1, dic_2);
    Console.WriteLine("dic_1:" + Newtonsoft.Json.JsonConvert.SerializeObject(dic_1));
    Console.WriteLine("dic_2:" + Newtonsoft.Json.JsonConvert.SerializeObject(dic_2));
    Console.ReadKey();
}
static void test_one_part(Dictionary<int, int> dic1, Dictionary<int, int> dic2)
{
    dic1 = new Dictionary<int, int>();
    dic1.Add(2, 2);
    dic2.Add(2, 2);
}

猜一猜这里输出的结果会是什么?
最容易想到的便是dic1={2:2};dic2={1:1,2:2};

因为test_one_part()将dic1重新new了,并重新加入{2:2},dic2操作很常规。接下来看结果:


然而结果跟我们想要的差的很多,从结果看来dic1好像完全没有受到test_one_part()的任何影响,根据一般对于引用类型的了解,在test_one_part()内部的修改引用类型的传入参数,必然会像dic2一样将结果体现到test_one()中的dic_2。

这明显不符合我们认识的“引用类型”。

结论一:引用类型在其他方法中被重新new以后不会改变原有对象的值。

3. Test Two

再看一个稍微绕一点的:

static void Main(string[] args)
{
    test_two();
}
static void test_two()
{
    Console.WriteLine("test_two");
    Dictionary<int, int> dic_1 = new Dictionary<int, int>();
    Dictionary<int, int> dic_2 = new Dictionary<int, int>();
    dic_1.Add(1, 1);
    dic_2.Add(1, 1);
    test_two_part(dic_1, dic_2);
    Console.WriteLine("dic_1:" + Newtonsoft.Json.JsonConvert.SerializeObject(dic_1));
    Console.WriteLine("dic_2:" + Newtonsoft.Json.JsonConvert.SerializeObject(dic_2));
    Console.ReadKey();
}
static void test_two_part(Dictionary<int, int> dic1, Dictionary<int, int> dic2)
{
    dic1 = dic2;
    dic1.Add(2, 2);
    dic2 = new Dictionary<int, int>();
    dic2.Add(2, 2);
}

test_two()和刚才的test_one()一样,但是test_two_part()有了一些变动。
按照常规思路再次分析一下:

static void test_two_part(Dictionary<int, int> dic1, Dictionary<int, int> dic2)
{
    //一开始dic1与dic2同值不同址,现在这里他们也同址
    dic1 = dic2;
    //追加了一个值,dic2同样的也拥有
    dic1.Add(2, 2);
    //dic1={1:1,2:2};dic2={1:1,2:2}

    //dic2被重新new,由【结论一】可知,下边不会影响到输出,忽略
    dic2 = new Dictionary<int, int>();
    dic2.Add(2, 2);
    //dic1={1:1,2:2};dic2={2:2}
}

代码看完了,初步的分析也有了,那么预测一般情况下的输出结果:
dic1={1:1,2:2};dic2={1:1,2:2};

执行结果如下图:

dic1又没有追加到值{2,2},然而dic2内却包含了{2,2},好吧再没有搞明白内部原理的情况下只能从结果倒推出一些猜测。

结论二:引用类型在其他方法中被重新赋值以后不会改变原有对象的值。

4. Test Three

下边这个例子可以帮我们理解上边的问题:

static void Main(string[] args)
{
    Dictionary<int, int> dic_0 = new Dictionary<int, int>();
    Dictionary<int, int> dic_1 = new Dictionary<int, int>();
    Dictionary<int, int> dic_2 = new Dictionary<int, int>();
    dic_1.Add(1, 1);
    dic_2.Add(2, 2);
    dic_0 = dic_1;
    dic_1 = dic_2;
    Console.WriteLine("dic_0: " + Newtonsoft.Json.JsonConvert.SerializeObject(dic_0));
    Console.WriteLine("dic_1: " + Newtonsoft.Json.JsonConvert.SerializeObject(dic_1));
    Console.WriteLine("dic_2: " + Newtonsoft.Json.JsonConvert.SerializeObject(dic_2));
    Console.ReadLine();
}

结果如下:

虽然有dic_0 = dic_1;dic_1 = dic_2;
但是从结果看出来dic_0 != dic_2;并不是dic_0 = dic_1 = dic_2。

5. 总结

其实结论一与结论二是一样的,都是再dic1直接被赋值发生了改变,将另外的一个内存地址赋给了dic1;根本上只要理解的引用类型赋值的原理,这些就很容易理解;值类型的赋值的修改内存中存储的值,引用类型修改的是指向值的指针(内存地址)。

Tset_One与Test_Two分了两个方法test_two();test_two_part();第一个方法中的变量被传入第二个方法中时,应该理解为前一个变量的copy;test_two_part()中变量一旦重新赋值,内存地址即被修改,原有方法外的变量地址不会受到影响。两层方法有一定的迷惑,Test_Three中就可以明显看的出来端倪。

所以以上例子的原因就很明确了,我们的引用类型还是引用类型。

.
.
.
.
.
.
.
.
【本文章出自NM1024.com,转载请注明作者出处。】






>>转载请注明原文链接地址:C#引用类型的“反常情况”