同志们,我遇到了以下问题:
我需要实现一个对象存储之类的东西,它会为每个用户提供一个指定对象的实例
我将尝试用伪代码来解释:
// Добавим в хранилище правило создания объекта
storage.Add("tmp", () => new Foo());
{
// В хранилище пока нет сгенерированных объектов
// Так что создаётся новый, сохраняется и возвращается
Foo tmp0 = storage["tmp"];
}
// Здесь ссылка на tmp0 уже недействительна
// Так что единственная ссылка на тот созданный объект лежит внутри хранилища
// Возвращаем тот же объект, что был в tmp0
Foo tmp1 = storage["tmp"];
// Тот объект, что был создан для tmp0, а теперь хранится в tmp1, уже занят
// Так что создаём новый объект, сохраняем его в хранилище и его же и возвращаем
Foo tmp2 = storage["tmp"];
也就是说,当tmp0包含对同一对象的引用的存储范围结束时,它不会将其删除,而是将其保存到下一次对类似对象的请求为止。
所以逻辑是这样的:
- 按键请求对象
- 如果存储中没有已经创建的对象,则根据给定的规则生成,保存然后返回。否则:
- 检查每个创建的对象是否在用户代码中被引用。如果不是(即唯一的链接位于存储库本身) - 返回找到的对象
我需要这种行为的原因如下:创作Foo是一个费力的过程。此外,Foo它不是线程安全的,因此每个用户Foo必须有自己唯一的实例,这些实例可能之前已经创建和使用过。
似乎在任何地方都没有对对象的引用的“计数器”,因为垃圾收集器并不关心对特定对象的引用已经在那里声明了多少次。重要的是是否有这样的链接
所以我什至不知道是否有可能实现这样的东西C#......
升级版:
我无法选择要打勾的答案,因为iluxa1810和默认语言环境的答案对于一般情况更正确。但是,在我非常具体的任务框架内,约翰的回答中描述的方法......
这是一个类似的问题,并以使用的形式提供了一个解决方案
WeakReference- 这是对不会阻止垃圾收集的对象的引用。它有一个属性IsAlive,告诉对象是活着的还是已经被垃圾回收了。我看到你可以在你的任务中使用它。在您的情况下,只要有人从代码中引用它,该对象就会活着。无法检查代码中是否存在对对象的引用。
.NET我们不再计算对对象的引用数,而是转而构建对象可用性图。作为替代方案,这是在您的存储中输入一个计数,强制用户调用 special。方法。但是这里一切都建立在信任之上...例如,他们突然忘记调用某事 => 该对象将为某人注册。
也许网络中有一种机制可以以您编写的形式解决问题。但是,老实说,这个解决方案对我来说似乎并不明显且脆弱:
不明显,因为 一段代码的结尾在阅读时很容易漏掉,因此很少用于关键操作:
作为开发人员,我希望代码块可以自由重构。在这种情况下,有必要仔细监控所有变量的生命周期。
Foo如果对其他类的引用存储在其他类的字段中(其对象本身将存储在集合中并在 lambdas 中捕获),情况将变得更加复杂。脆弱,因为 在这种情况下,逻辑将严重依赖于代码构建器的工作,这很难控制。
替代方案:对象池
为了解决您的问题(希望重用的昂贵对象),“对象池”设计模式是合适的。池广泛用于类似的任务(例如,存储数据库连接)。
在最简单的版本中,您只需要:
Foo接口IDisposable。storage方法调用。DisposeFoostorage对象包装在一个块中usingFoo到单独的接口,该接口可以从storage.Foo让实现仅可用于,storage以便没有人可以覆盖这些方法。该解决方案很无聊,并且涉及更改调用代码。但是在这个版本中,释放对象的过程变得很明显。
好吧,代码看起来像这样:
您可以通过创建一个包装类来解决。
我现在尽量简短,因为来自电话。
底线是我们使用包装终结器来捕获包装的移除事件并让我们的实例重新启动并运行。
让我们创建我们的包装类:
在程序本身中,我们创建了两个字典。一个用于未使用的 foo 实例,另一个用于已使用的实例。
我认为逻辑或多或少是清楚的?
1) 应用户的请求,在字典中搜索 Foo 的一个实例。在这种情况下,按 ID。
2) 如果不是,则创建 Foo 并将其放置在第二个字典中。我们将它传递给 FooWrapper 并确保订阅 Final 事件。生成的 FooWrapper 已经提供给用户。
3) 在用户丢失对 FooWrapper 的所有引用后,finalizer 触发,其中 Final 事件触发,传递我们的 Foo 的 ID。
4)在主程序中,我们得到这个Id并将它从第二个字典转移到第一个。
主要缺点是在这种情况下,我们完全排除了直接与 foo 交互的可能性,只能通过您在 fooWrapper 中编写的方法。