C#.NET阻止对象处理它不应该的东西

时间:2014-11-27 15:32:59

标签: c# .net memory-leaks garbage-collection dispose

我在一个大项目上工作并且发生了一个问题:假设我有一个数据库加载到内存,它存储了广泛使用的数据。但是如果数据没有加载到内存中我必须管理,所以我必须下载它,然后在我完成后处理它。

但我可以很容易地犯错:我可以手动处理数据库。 即使我在数据库上调用Dispose()方法,我也想阻止自己处理数据库。

我想出了跟踪谁可以处理数据库的想法。当然,唯一允许这样做的是创建数据库实例的人。

问题示例,记录在案:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DisposePrevention
{
    /// <summary>
    /// A bottle containing alcoholics
    /// </summary>
    class Bottle:IDisposable
    {
        private static int Id = 0;

        private int localId;

        public Bottle()
        {
            this.localId = Bottle.Id++;
        }

        public void Dispose()
        {
            //do the trick.
        }

        public override string ToString()
        {
            return "Bottle - " + this.localId.ToString();
        }
    }

    /// <summary>
    /// A shelf storing bottles
    /// </summary>
    class Shelf : IDisposable
    {
        public List<Bottle> Content;

        public void Fill()
        {
            if (this.Content == null)
            {
                this.Content = new List<Bottle>();
            }

            for (int i = 0; i < 5; i++)
            {
                this.Content.Add(new Bottle());
            }
        }

        public void Dispose()
        {
            if (this.Content == null)
            {
                return;
            }

            foreach (Bottle b in this.Content)
            {
                b.Dispose();
            }
        }
    }

    /// <summary>
    /// A bartender serving drinks
    /// </summary>
    class Bartender : IDisposable // very simplified.
    {
        public List<Shelf> Shelves;

        public Bartender()
        {
            this.Shelves = new List<Shelf>();

            for (int i = 0; i < 3; i++)
            {
                Shelf s = new Shelf();
                s.Fill();
                this.Shelves.Add(s);
            }
        }

        public void Dispose()
        {
            if (this.Shelves != null)
            {
                foreach (Shelf actualShelf in this.Shelves)
                {
                    if ((actualShelf == null) || actualShelf.Content == null)
                    {
                        continue;
                    }

                    foreach (Bottle bottleItem in actualShelf.Content)
                    {
                        bottleItem.Dispose(); // We can call this, because Content is public, but we shouldn't.
                    }

                    actualShelf.Dispose();
                }

                this.Shelves.Clear();
            }
        }

        /// <summary>
        /// What can we drink, Sir?
        /// </summary>
        public void Print()
        {
            Console.WriteLine("------------------");
            if (this.Shelves != null)
            {
                foreach (Shelf actualShelf in this.Shelves)
                {
                    if ((actualShelf == null) || actualShelf.Content == null)
                    {
                        continue;
                    }

                    foreach (Bottle bottleItem in actualShelf.Content)
                    {
                        Console.WriteLine(bottleItem.ToString());
                    }
                }
            }
            Console.WriteLine("------------------");
        }

        /// <summary>
        /// Two bartenders can use the same source of drinks.
        /// </summary>
        /// <param name="list"></param>
        internal void AttachShelves(List<Shelf> list)
        {
            this.Shelves = list;
        }

        /// <summary>
        /// The boss can fire him, so he no longer gets access to the drinks.
        /// </summary>
        internal void DetachShelves()
        {
            this.Shelves = null;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Bartender john = new Bartender();
            Bartender steven = new Bartender();

            steven.AttachShelves(john.Shelves);

            Console.WriteLine("John:");
            john.Print();
            Console.WriteLine("Steven");
            steven.Print();

            Console.WriteLine("");
            Console.WriteLine("Calling Dispose.");
            Console.WriteLine("");

            john.Dispose(); // we kick John. But at this point, we should've called "john.DetachShelves();"
            Console.WriteLine("John");
            john.Print();

            Console.WriteLine("Steven");
            steven.Print(); // Steven is sad. We should not allow John to dispose the alcoholics.

            Console.ReadLine();
        }
    }
}

结果:

John:
------------------
Bottle - 0
Bottle - 1
Bottle - 2
Bottle - 3
Bottle - 4
Bottle - 5
Bottle - 6
Bottle - 7
Bottle - 8
Bottle - 9
Bottle - 10
Bottle - 11
Bottle - 12
Bottle - 13
Bottle - 14
------------------
Steven
------------------
Bottle - 0
Bottle - 1
Bottle - 2
Bottle - 3
Bottle - 4
Bottle - 5
Bottle - 6
Bottle - 7
Bottle - 8
Bottle - 9
Bottle - 10
Bottle - 11
Bottle - 12
Bottle - 13
Bottle - 14
------------------

Calling Dispose.

John
------------------
------------------
Steven
------------------
------------------
  • 我不能使用固定的GCHandle - s来防止泄漏(引用保留在阻止GC收集的对象上)
  • 一般来说,我不能指望垃圾收集器。我必须处理我创建的所有内容,GC只会很少收集。
  • 修改最少的解决方案是最好的。
  • 我无法使用unsafe代码...(这是一个WPF和Silverlight项目)

想法:我可能会写一个包装器,但仍然会出现引用问题。

问题:

我想阻止John在货架上调用Dispose()。这样做有“最佳实践”吗?

提前致谢!

编辑:包装器

    /// <summary>
    /// A shelf storing bottles
    /// </summary>
    class ShelfWrapped : IDisposable
    {
        public List<Bottle> Content;

        public void Fill()
        {
            if (this.Content == null)
            {
                this.Content = new List<Bottle>();
            }

            for (int i = 0; i < 5; i++)
            {
                this.Content.Add(new Bottle());
            }
        }

        public void Dispose()
        {
            if (this.Content == null)
            {
                return;
            }

            foreach (Bottle b in this.Content)
            {
                b.Dispose();
            }
        }
    }

    /// <summary>
    /// Wrapper for a shelf storing bottles
    /// </summary>
    class Shelf:IDisposable
    {
        private ShelfWrapped InnerShelf = null;

        public Shelf()
        {
            this.InnerShelf = new ShelfWrapped();
        }

        public void Fill()
        {
            if (this.InnerShelf == null)
            {
                this.InnerShelf = new ShelfWrapped();
            }

            this.InnerShelf.Fill();
        }

        public ShelfWrapped GetShelf()
        {
            return this.InnerShelf;
        }

        private List<Bartender> AllowedToDispose = new List<Bartender>();

        public void Dispose(object disposer)
        {
            if (this.AllowedToDispose.Contains(disposer))
            {
                if (InnerShelf != null)
                {
                    this.InnerShelf.Dispose();
                }
            }
        }

        public void Dispose()
        {
            // And again, John can dispose the shelf...
        }
    }

2 个答案:

答案 0 :(得分:1)

一般来说,一次性物品应该有明确的所有者,物品应该只处理他们拥有的东西。有时可能需要一个由某种类型的实例拥有而不是其他实例拥有的字段;在这些情况下,应该将字段与另一个字段组合在一起,以指示它是否拥有相关实例。例如,如果某个类FunStream继承自Stream并包装Stream,那么实例应该在它被处置时调用Dispose基础流 >创建它的代码不再使用底层流,但如果创建它的代码在Stream被释放后希望继续使用FunStream,则不应将其丢弃。由于创建FunStream的代码将知道它所期望的模式,因此FunStream构造函数或工厂方法应提供一个参数来指示FunStream是否应该承担流的所有权。

当一个对象实际上是不可变的但仍然封装了资源时,会出现造成重大困难的唯一情况。不可变对象通常是可自由共享的,因此通常是共享的。虽然资源应该在没有人需要时释放,但预测哪个不可变对象将是最后一个使用资源的对象通常很困难。处理这种情况的最佳方法可能是使用LifetimeManager类,它将使用一些ConditionalWeakTable对象将每个类与仍在使用它的对象的弱引用列表相关联,并提供哪些对象一个DisposeIfNotNeeded(IDisposable resource, Object owner);这将从仍需要资源的对象列表中删除owner,并在不再拥有所有者时处置该资源。但是,我不知道任何现有的实现,并且这样的设计可能有点棘手。尽管如此,使用这样的类可能是确保适当时间处理封装资源的共享对象的最简洁方法。

答案 1 :(得分:0)

我认为GC应该由框架来处理。我曾经尝试在.NET出现之前尝试对内存进行微观管理,但它只会造成奇怪的错误。问题。我的建议是不要打扰GC甚至.dispose。只有在项目中实现COM(非托管对象)时才需要使用Dispose。有关详情,请参阅此处 - http://msdn.microsoft.com/en-CA/library/fs2xkftw%28v=vs.110%29.aspx