A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

© 周琪 中级黑马   /  2013-5-15 17:48  /  1473 人查看  /  5 人回复  /   0 人收藏 转载请遵从CC协议 禁止商业使用本文

看下这段代码,为什么垃圾回收了还能输出Name呢?我用的vs2010
class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person() { Name = "qq" };
            WeakReference wr = new WeakReference(p);
            p = null;

            GC.Collect();            //垃圾回收

            object obj = wr.Target;
            if (obj != null)
            {
                Console.WriteLine(((Person)obj).Name);

            }
            else
            {
                Console.WriteLine("被回收了、、、、、、、");
            }
            Console.ReadKey();
        }
    }
    class Person
    {
        public string Name { set; get; }
        public int Age { set; get; }


    }

评分

参与人数 1技术分 +1 收起 理由
苏波 + 1

查看全部评分

5 个回复

倒序浏览
呵呵!!new Person() { Name = "qq" }这是匿名内部类的特性。这句话是创建了一个Person的子类。
他不是垃圾回收不了。

这是我的理解,如果有不正确的地方,希望指定一下。这能对我有很大的帮助。
回复 使用道具 举报
看了看代码,感谢应该被回收了,于是我写了一下自己的代码:
            Father f1 = new Father();
            f1.Name = "John";
            WeakReference wrt = new WeakReference(f1);
            f1 = null;
            GC.Collect();
            object ob = wrt.Target;
            if (ob!=null)
            {
                Console.WriteLine(((Father)ob).Name);
            }
            else
            {
                Console.WriteLine("回收啦...");
            }

测试了一下,果然已经回收啦~
但为什么LZ的代码确没有回收,对比后发现,红色的部分不同,但究竟什么原因,实在没有搞明白,留待过几天研究。
附测试图:


回复 使用道具 举报
为了做一下实验,我修改了一下源码:
  1.         class Person
  2.         {
  3.             public string Name { set; get; }
  4.         }
  5.         class PersonTwo
  6.         {
  7.             public string Name { set; get; }
  8.         }
  9.         static void Main(string[] args)
  10.         {
  11.             PersonTwo f1 = new PersonTwo();
  12.             f1.Name = "John";
  13.             WeakReference wrt = new WeakReference(f1);
  14.             f1 = null;
  15.             GC.Collect();

  16.             object ob = wrt.Target;
  17.             if (ob != null)
  18.             {
  19.                 Console.WriteLine(((Father)ob).Name);
  20.             }
  21.             else
  22.             {
  23.                 Console.WriteLine("回收啦...");
  24.             }
  25.             //------------------------------------------------
  26.             Person p = new Person() { Name = "qq" };
  27.             WeakReference wr = new WeakReference(p);
  28.             p = null;
  29.             GC.Collect();            //垃圾回收

  30.             object obj = wr.Target;
  31.             if (obj != null)
  32.             {
  33.                 Console.WriteLine(((Person)obj).Name);
  34.             }
  35.             else
  36.             {
  37.                 Console.WriteLine("被回收了、、、、、、、");
  38.             }
  39.         }
复制代码
而后,反汇编了一下(C#反汇编真心轻松哇~),看到了其中的玄机~~


如图,
  1.             PersonTwo f1 = new PersonTwo();
  2.             f1.Name = "John";
复制代码
这两行代码,反汇编后生成的IL,是一模一样的。创建一个对象,并将引用保存在f1中。
  1.             Person p = new Person() { Name = "qq" };
复制代码
而这一行代码,生成的IL代码,确做了两件事。1.创建一个Person对象保存在一个中间变量中 2.将中间变量指向的地址赋值给p。


这样,p=null后,调用GC回收时,p指向的内存实际上还在被使用中,当然就不会被回收啦。





评分

参与人数 1技术分 +1 收起 理由
苏波 + 1

查看全部评分

回复 使用道具 举报
崔宏奎 发表于 2013-5-15 19:09
为了做一下实验,我修改了一下源码:而后,反汇编了一下(C#反汇编真心轻松哇~),看到了其中的玄机~~

就是<>g_initLocal0那儿,这是啥意思。还有你说的中间变量是指?。。。

告诉我下谢谢。。。
回复 使用道具 举报
周琪 发表于 2013-5-18 21:22
就是g_initLocal0那儿,这是啥意思。还有你说的中间变量是指?。。。

告诉我下谢谢。。。 ...

先看我写的那段C#代码,除了初始化Person对象的代码不同以外, 其他的完全一样。
但输出的结果却是(参照上一个帖子):上半部分代码创建的弱引用,被回收。下半部分代码创建的弱引用没有被回收。
既然只有两句代码不同,那问题的关键肯定就是这不同的代码啦:
  1. <span style="background-color: rgb(255, 255, 255); ">Person</span><span style="background-color: rgb(255, 255, 255); ">  </span>f1 = new <span style="background-color: rgb(255, 255, 255); "> Person  </span>();
  2. f1.Name = "John";
复制代码
  1. Person p = new Person() { Name = "qq" };
复制代码
上面两段代码分别定义了两个对象,f1和p。(为了更好理解,这里我修改了:Person f1, Person p)
f1是用无参的构造函数创建的对象,然后为对象的Name属性赋值。
p是用初始化器来创建的对象,并设置Name属性。

为了确认为什么第二段代码中弱引用没有被回收,我使用 Reflector7.3 进行了反汇编:

【1】部分:
1.先看这行代码 PersonTwo f1 = new PersonTwo();
对照C#源码不难看出,程序首先创建一个 PersonTwo  f1变量,然后使用new PersonTwo 申请了一片内存,并创建了一个PersonTwo实例,最后通过等号运算符 [f1 = 地址] ,将堆内存中创建的PersonTwo实例地址保存在f1变量中。
2.代码  Name = "John";
为堆内存中实例的Name属性赋值"John"。

【2】:
1.代码 Person <>g__initLocal0 = new Person();
同样,先创建了一个 Person 类型的变量 <>g__initLocal0(这是变量的名字,虽然它不符合命令规则),而后在堆内存中创建一个 Person类的实例,并将指向这个实例的地址赋值给 <>g__initLocal0
2.代码 Name = "qq";
为堆内存中实例的Name属性赋值"John"。
3.Person p =<>g__initLocal0;
将变量 <>g__initLocal0 中保存的地址赋值给 p。此时,p与<>g__initLocal0指向了同一个内存位置了。。。

通过对照【1】、【2】,再结合我们写的C#源码,我们发现了问题的所在:
由于.Net程序是通过IL代码来执行的,那么反汇编后得到的"C#代码"(上图)就是程序执行时的情况,【1】中的代码和我们写的程序源码完全相同;而【2】中的代码在执行时,程序自己创建了一个中间变量<>g__initLocal0 ,由于这个变量同样的指向我们申请的那片内存,所以之后调用GC.Collect()当然就不会被回收啦。。。
{:soso_e113:}

----------------------------------------------------分割线----------------------------------------------------

下面我还想知道编译器为什么要给我加上这么一个中间代码,百度了一下后才发现,这只是C#中为了使程序员少打一行代码而自动处理的(Person p = new Person() { Name = "qq" };原来是不是需要两行代码?),另一方法也是为了更好得支持匿名类型。

参考资料:
不能不说的C#特性-对象集合初始化器
http://kb.cnblogs.com/page/42575/2/

回复 使用道具 举报
您需要登录后才可以回帖 登录 | 加入黑马