我在我编写的代码中面临一个反复出现的问题:修改一些全局值(我将使用注册表值作为示例),然后尝试将修改恢复为原始状态。
我以为我会尝试使用IDisposable来解决这个问题。创建时,该对象将读取注册表值,将其存储在本地,然后进行修改。销毁后,它将恢复设置。它会用到这样的东西:
using(RegistryModification mod = new RegistryModification("HKCR\SomeValue", 42))
{
// reg value now modified to 42, do stuff
} // Object gets disposed, which contains code to revert back the setting
如果只进行了一次修改,那么应该效果很好。但是如果进行了多次修改,或者调用者没有使用'using'结构创建对象,我会发现出现问题。
public void Foo()
{
// assume HKCR\SomeValue starts as '13'
// First object reads HKCR\SomeValue and stores '13', then modifies to 42
RegistryModification mod1 = new RegistryModification("HKCR\SomeValue", 42);
// Next object reads HKCR\SomeValue and stores '42', then modifies to 12
RegistryModification mod2 = new RegistryModification("HKCR\SomeValue", 12);
}
// objects are destroyed. But if mod1 was destroyed first, followed by mod2,
// the reg value is set to 42 and not 13. Bad!!!
如果呼叫者手动处理对象,则问题会恶化。这让我觉得我的方法简直就是有缺陷。
是否有某种可接受的模式来解决这个问题?我在想为类添加静态堆栈可能有所帮助。
以任何方式保证对象被销毁的顺序?我以为我会尝试使用IDisposable,但我会全神贯注于其他解决方案。
答案 0 :(得分:4)
IDisposable不保证以这种方式回滚所描述的是回滚。 Dispose()方法不是为了这个目的,而是负责释放对象所拥有的本机资源(网络连接,文件等)
然而,恢复状态的方法可以像这样做
public void Foo(SomeObjectType theObject)
{
int initialValue = theObject.SomeProperty;
theObject.SomeProperty = 25;
Console.Out.WriteLine("Property is:" + theObject.SomeProperty);
// reset object.
theObject.SomeProperty = initialValue;
Console.Out.WriteLine("Property oringinal value is:" + theObject.SomeProperty);
}
请记住,仅仅因为资源被处置掉它不会反转使用它执行的操作,如果你处理数据库连接它不会撤消用它执行的工作,只会破坏对象。
除非你使用回滚代码覆盖Dispose(),这是对该方法的误用,否则它不会提取你的值,这是你作为程序员的责任。我说它是滥用dispose方法的原因是因为.net文档说明了Dispose()
使用此方法关闭或释放非托管资源,例如文件, 流和由实现的类的实例持有的句柄 这个界面。按照惯例,此方法用于所有任务 与释放对象持有的资源或准备一个对象相关联 对象重用。
这通常意味着例如释放重量级句柄(例如某些GDI资源)。必须释放本机资源或有时将其置于某种状态以避免内存泄漏或访问的不良后果。在这种情况下很容易说,也许在您的Dispose方法中,您应该将注册表设置回以前的状态,但我认为这不是dispose方法的目的。目的是释放资源并将其重新置于可以再次使用的状态。你想要做的是重置一个值(这实际上是另一个设置操作),在自定义dispose方法中完成这项工作意味着你以后也会在不同的上下文中缩短重用机会。
我所说的是,当您完成修改后,您必须编写显式代码以将对象设置回其初始状态。您可以将其存储在数据结构(例如堆栈)中,如果有多个操作,则回读值,或者只使用上述简单方法进行一次操作。
答案 1 :(得分:1)
听起来您想使用System.Collections.Generic.Stack<T>
实施。每次修改都会将值推入堆栈,每次“撤消”都会将值从堆栈中弹出。
答案 2 :(得分:0)
使用静态工厂方法而不是构造函数来获取RegistryModification
对象,并且具有静态操作堆栈(类可以理解但不需要的RegistryModifcation
的一些简化表示在RegistryModification
类中要表示的整个对象,指示其对象是否已被处置。生成新的时,在堆栈上粘贴表示。在处理时,将表示标记为表示已处置的对象,并尝试从上到下反转堆栈中的操作(当您从未处置的对象中找到操作时停止)。
不确定使用你试图通过处置来释放的内存会花多少钱,但它应该有效。
答案 3 :(得分:0)
实施模式有两种一般模式:“准备做某事;做好;清理”:
// Pattern #1 void FancyDoSomething(MethodInvoker ThingToDo) { try { PrepareToDoSomething(); ThingToDo.Invoke(); // The ".Invoke" is optional; the parens aren't. } finally { Cleanup(); } } void myCode(void) { FancyDoSomething( () => {Stuff to do goes here}); } // Pattern #2: // Define an ActionWrapper so its constructor prepares to do something // and its Dispose method does the required cleanup. Then... void myCode(void) { using(var wrap = new ActionWrapper()) { Stuff to do goes here } }
模式#2在某些方面更加通用,因为它允许使用不遵循严格嵌套规则的使用模式。这是IEnumerator<T>
使用该模式而不是简单地使用枚举方法来获取委托并在每个列表项上调用它的部分原因。如果一个人必须遵守与枚举器的嵌套语义,那么尝试做类似于列表合并之类的事情最多也会很尴尬。
另一方面,某些类型的受保护资源只能以严格嵌套的方式有意义地使用。因此,上面给出的第一种方法有时可能更合适,因为它在任何特定线程中严格执行嵌套语义。
如果您不想使用第一种方法,我建议您安排您的对象,以便Disposing
与受保护资源相关联的实例将使其后生成的所有实例无效。为了最大限度地减少“延迟惊讶”因素,您可能希望在创建第一个包装器对象时让资源分配线程关联,并禁止任何其他线程访问,直到所有包装器都为Dispose
d(您可能也允许)如果创建该对象的线程不再存在,则访问该线程,如果该线程在其消失之前对该对象做了任何不好的事情似乎不太可能。)