代码之家  ›  专栏  ›  技术社区  ›  Casey Chester

如何处置一次性捕获的变量?

  •  1
  • Casey Chester  · 技术社区  · 6 年前

    考虑以下伪代码:

    public class SomeComponent
    {
        private List<DisposableFoo> _foos = new List<DisposableFoo>();
    
        public void Start()
        {
            for (int i = 0; i < 5; i++)
            {
                var foo = new DisposableFoo();
                var bar = new DisposableBar();                 <-- EXPLICIT DISPOSE NEEDED?
                foo.SomeFunc = x => bar.DoSomethingWithX(x);
                _foos.Add(foo);
            }
        }
    
        public void Stop()
        {
            _foos.ForEach(f => f.Dispose());
        }
    }
    

    基础设施是否在拆除过程中处理任何捕获的IDisposable变量?

    澄清: 我不是在问关于管理一次性物品的最佳实践。我的问题更多的是,在这种情况下,基础设施做了什么。我的理解是,为了捕获变量,基础设施在幕后创建了一个类型,其中包含类型为DisposableBar的字段,并接收对“bar”变量中对象的引用。一旦基础设施捕获了该变量,我就觉得这是一个“灰色地带”,在这一点上,谁有责任确定何时不再需要该变量,何时可以处理该变量。

    3 回复  |  直到 6 年前
        1
  •  2
  •   Michael Puckett II    6 年前

    简单的回答是 最肯定的是。您需要对任何一次性对象调用dispose。这用于清理非托管资源和依赖项。

    另请注意: 垃圾收集器不会调用Dispose on或查找IDisposable类型。

    如果在该方法中是一次性的,则最好使用 using 这样的声明。

    public void Start()
    {
        for (int i = 0; i < 5; i++)
        {
            using (var foo = new DisposableFoo())
            using (var bar = new DisposableBar())
            {
                 foo.SomeFunc = x => bar.DoSomethingWithX(x);
                 _foos.Add(foo);
            }
        }
    }
    

    如果变量是类级别的,那么您的类还应该实现IDisposable,并处理它在其中使用的一次性对象。

    Here 是一个很好的链接,我在其中提供了有关处理对象的更多详细信息。

    另一件需要记住的事情是,有时(在像C#这样的语言中)我们可能会有循环依赖关系(这是一种糟糕的做法)然而这经常发生。如果您的对象是垃圾收集的,并且存在循环依赖关系,那么它会一直挂起,直到另一个对象也是垃圾收集的,这会导致进程变得丑陋。这一切都是在幕后进行的,通常对大多数应用程序来说都不是什么大问题,但意识到这一点很重要。虽然您不应该具有循环依赖关系,但可以实现IDisposable来在转到垃圾收集器之前清除依赖关系,从而使此过程更干净。(请记住,从一开始就拥有它们是不好的……也就是说,实体框架是基于循环依赖关系构建的,所以请参考图。)

    另一个注意事项:Dispose方法也被添加到对象的析构函数中,这并不少见;尤其是如果对象是低级、单例或静态的,以确保在垃圾收集期间处理好可处置类型。这看起来像:

    public class SomeClass : IDisposable
    {
         //pretend we implement a singleton pattern here
         //pretend we implement IDisposable here
        ~SomeClass()
        {
            Dispose(); 
        }
    }
    

    更新:

    为了根据您的澄清更新答案,我相信您是在问,在处理一次性对象后,从一次性对象检索的变量会发生什么变化。这是一个棘手的行为,在开发一次性类型时应该好好考虑。下面的一些代码显示了类似情况的结果,可能有助于您理解。 而且在开发类型时,应该确定谁对此负责,但在大多数情况下,您提供给客户的任何信息都应该对客户有利,即使在您处理完之后。换言之,最好不要删除、处置或操纵您在处置时允许您类型的用户检索的任何信息。

    using System;
    using System.Collections.Generic;
    namespace Disposable_Variables_Reference
    {
        class Program
        {
            static void Main(string[] args)
            {
                List<string> DCNames = null;
                string DCName = string.Empty;
                int DCValue;
                using (var disposableClass = new DisposableClass())
                {
                    DCNames = disposableClass.Names;
                    DCName = disposableClass.Name;
                    DCValue = disposableClass.Value;
                    foreach (var name in DCNames) Console.WriteLine(name);
                    Console.WriteLine(DCName);
                    Console.WriteLine(DCValue);
                }
                foreach (var name in DCNames) Console.WriteLine(name);
                Console.WriteLine(DCName);
                Console.WriteLine(DCValue);
                Console.Read();
            }
    
            public class DisposableClass : IDisposable
            {
                public List<string> Names { get; set; } = new List<string>() { "Michael", "Mark", "Luke", "John" };
                public string Name { get; set; } = "Gabriel";
                public int Value { get; set; } = 20;
                public void Dispose()
                {
                    Names.Clear();
                    Name = string.Empty;
                    Value = 0;
                }
            }
        }
    } 
    

    输出:

    Michael
    Mark
    Luke
    John
    Gabriel
    20
    Gabriel
    20
    

    名称是一个列表(引用类型),并且 不是 重新写入输出。

    名称为string(不可变引用类型),并且 重新写入输出。

    值为int(值类型),并且 重新写入输出。

    然而如果您重新分配 Names Dispose() 方法,而不是清除它,然后 也将被重写 . 示例仅包括dispose方法更改。

    public void Dispose()
    {
        Names = null; //notice here we re-assign Names to null.
        Name = string.Empty;
        Value = 0;
    }
    

    新输出:

    Michael
    Mark
    Luke
    John
    Gabriel
    20
    Michael
    Mark
    Luke
    John
    Gabriel
    20
    

    知道这一点是正确的暴露方式 名称 就是把它留在 Dispose() 方法或这样的公开名称;返回新列表,以便在处理时不会删除对它的任何引用。

    private List<string> names = new List<string>() { "Michael", "Mark", "Luke", "John" };
    public List<string> Names
    {
        get { return names.ToList() ; }
        set { names = value; }
    }
    

    当然,整个答案都是为了逻辑和澄清。没有理由在中使用IDisposable DisposableClass 我举了个例子。

        2
  •  0
  •   programtreasures    6 年前

    如果在中使用了非托管代码 DisposableBar 否则垃圾收集器将负责管理资源。

        3
  •  -1
  •   Stilgar    6 年前

    是和否。正确的做法是手动处理。如果此代码与实际应用程序类似,则应将条形图收集到一个列表中,并在处理完foos后进行处理。根据实际代码的结构,您可能需要另一种策略。如果条形图是非托管资源,则应始终对其进行处置。在大多数情况下,非托管资源包装在托管资源中,例如StreamReader包装非托管文件句柄。在这些情况下,如果dispose模式得到正确实现,则当托管对象被垃圾收集时,将释放该对象。问题是GC不是确定性的,当内存紧张时会运行。可能存在这样一种情况:GC不运行,但您的应用程序缺少文件处理程序,但由于GC只关心内存,所以它不会运行,也不会释放非托管资源。