【问题标题】:C# Memory allocation/de-allocation question regarding scope关于范围的 C# 内存分配/取消分配问题
【发布时间】:2010-08-10 21:09:00
【问题描述】:

我最近正在阅读一篇垃圾收集文章,并决定继续尝试并尝试获得更深入的了解。我编写了以下代码,玩弄了using 语句,但对结果感到惊讶......我希望在 using 块之外的 e.Parent.Name 会去 ka-blooey。

这里到底发生了什么?

static void Main(string[] args)
        {
            Employee e = new Employee();

            using (Parent p = new Parent())
            {
                p.Name = "Betsy";
                e.Parent = p;
                Console.WriteLine(e.Parent.Name);
            }

            Console.WriteLine(e.Parent.Name);            

            Console.ReadLine();
        }

        public class Employee
        {
            public Parent Parent;
        }

        public class Parent : IDisposable
        {
            public string Name;

            public void Dispose()
            {
                Console.WriteLine("Disposing Parent");
            }
        }

【问题讨论】:

标签: c# memory-management


【解决方案1】:

您的 Dispose 方法实际上并没有对 Parent 的实例做任何事情,因此它仍然是公平的游戏/作为类的可用实例工作。

IDisposable 通常在您的类持有非托管资源(例如数据库连接或文件)时使用,以便在调用 Dispose() 时可以清理它。仅仅调用Dispose 不会对非托管资源做任何事情,方法中必须有一些代码对这些资源做一些事情。虽然 c# 可能使用 using() {} 语法将 IDisposable 对象的实例化和处置包装在 try/catch/finally 中,但这并不意味着它对处置的对象有任何“特殊”作用。

假设,Name 实际上是一个非托管资源,而不仅仅是一个字符串,您的 Dispose() 方法可以这样写:

public void Dispose()
{
    Name = null;
    Console.WriteLine("Disposing Parent");
}

因为您已将p 分配给e.Parent,所以对象本身仍然在“范围内”,因为有对它的引用,因此Console.WriteLine(e.Parent.Name); 仍然可以访问它来生成输出。

The Old New Thing 目前也是“CLR 周”,本周的前 3 篇文章正在讨论垃圾收集器及其工作/行为方式。它们非常值得一读:

【讨论】:

  • 啊,当然。因此,仅仅因为我将其放入 using 块中,就没有发生任何“神奇”的事情。这就像我将那个 Employee 传递给一个方法,在该方法中创建了 Parent 并将其设置为 Employee 一样。在方法之外,Parent 不能被完全收集,因为它仍然被引用。对吗?
【解决方案2】:

IDisposable.Dispose 旨在让您使用它来清理您拥有的非托管资源(如文件句柄等)。它本身不做任何事情。最常见的用法是,如果您的类有自己实现 IDisposable 的成员变量,那么您现在有责任处置它们。这只是一种帮助你的模式,与垃圾收集无关——事实上恰恰相反。

【讨论】:

    【解决方案3】:

    Dispose 方法不会从内存中销毁对象。通常一个 dispose 方法只会释放它创建的资源。

    【讨论】:

      【解决方案4】:

      由于 e 仍然存在于范围内,因此与 e 相关联的任何内容(分配给 e 的父级)仍然存在,直到 e 超出范围。

      【讨论】:

        【解决方案5】:

        IDisposable 不是语言功能,在运行时没有什么特别的作用。它只是一个与其他任何接口/方法一样的接口/方法。它恰好是一个有用的模式,因此他们添加了语法以以特定模式自动调用该方法(using),并且您可以抛出名称中包含“Dispose”的特殊异常(如ObjectDisposedException)。

        使用积木转向:

        using(SomeType t = new SomeType())
        {
          t.Something();
        }
        

        变成这样的:

        {
          SomeType t;
        
          try
          {
            t = new SomeType();
            t.Something();
          }
          finally
          {
            t.Dispose();
          }
        }
        

        绝对没有办法强制 GC 收集任何东西。如果堆栈中某处有对您的对象的引用(忽略不安全和 C++/CLI 代码),或者从堆栈上的某个对象链接到它的引用,那么您的对象将存在。

        如果你想让这段代码爆炸,你可以这样做:

        public class Parent : IDisposable
        {
            public string Name
            {
                get
                {
                    AssertNotDisposed();
                    return name;
                }
                set
                {
                    AssertNotDisposed();
                    name = value;
                }
            }
        
            public void Dispose()
            {
                AssertNotDisposed();
                Console.WriteLine("Disposing Parent");
                isDisposed = true;
            }
        
            private void AssertNotDisposed()
            {
                if(isDisposed)
                    throw new ObjectDisposedException("some message");
            }
        
            private string name;
            private bool isDisposed = false;
        }
        

        【讨论】:

          【解决方案6】:

          如果您正在寻找另一个在尝试对已处置对象执行某些操作时会崩溃的示例。

          static void Main(string[] args)
                  {
                  Employee e = new Employee();
          
                  using (Parent p = new Parent("test.txt"))
                  {
          
                      e.Parent = p;
          
          
          
                    using ( System.IO.StreamWriter fileWriter  = 
                          new System.IO.StreamWriter(e.Parent.File))
                          {
                          fileWriter.WriteLine("Betsy");
                      }
          
                  }
          
          
          
             using (System.IO.StreamWriter fileWriter =
                     new System.IO.StreamWriter(e.Parent.File))
                  {
                      fileWriter.WriteLine("Betsy"); //uh-oh
                  }
          
          
                  Console.ReadLine();
              }
          
          
              public class Employee
              {
                  public Parent Parent;
              }
          
              public class Parent : IDisposable
              {
                  public System.IO.FileStream File;
          
                  public Parent(string fileName)
                  {
                      File = System.IO.File.Open(fileName, System.IO.FileMode.OpenOrCreate);
          
                  }
          
                  public void Dispose()
                  {
                      ((IDisposable)File).Dispose(); //or File.Close();
                  }
              }
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2021-08-27
            • 2012-12-11
            • 2010-12-10
            • 2021-05-04
            • 1970-01-01
            • 1970-01-01
            • 2011-03-24
            相关资源
            最近更新 更多