以与创建相反的顺序处理对象?

我在我编写的代码中面临一个反复出现的问题:修改一些全局值(我将使用注册表值作为示例),然后尝试将修改恢复为原始状态。

我以为我会尝试使用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,但我会全神贯注于其他解决方案。

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方法中完成这项工作意味着你以后也会在不同的上下文中缩短重用机会。

我所说的是,当你完成修改后,你必须编写显式代码来将对象设置回初始状态。 您可以将其存储在数据结构(例如堆栈)中,如果有多个操作,则回读值,或者只使用上面的简单方法进行一次操作。

听起来好像你想使用System.Collections.Generic.Stack实现。 每次修改都会将值推送到堆栈上,每次“撤消”都会将值从堆栈中弹出。

http://msdn.microsoft.com/en-us/library/3278tedw.aspx

使用静态工厂方法而不是构造函数来获取RegistryModification对象,并具有静态操作堆栈(该类可以理解的RegistryModifcation一些简化表示,但不需要整个对象来表示,指示其对象是否具有已经处理好)在RegistryModification类中。 生成新的时,在堆栈上粘贴表示。 处置时,将表示标记为表示已处置对象,并尝试从上到下反转堆栈中的操作(当您从未处置的对象中找到操作时停止)。

不知道使用你试图通过处置释放的内存来计算多少费用,但它应该有效。

实现模式有两种一般模式:“准备做某事;做到;清理”:

 //模式#1

 void FancyDoSomething(MethodInvoker ThingToDo)
 {
  尝试
   {
     PrepareToDoSomething();
     ThingToDo.Invoke();  //“。Invoke”是可选的;  parens不是。
   }
  最后
   {
    清理(​​);
   }
 }

 void myCode(void)
 {
   FancyDoSomething(()=> {Stuff to do goes here});
 }

 //模式#2:

 //定义ActionWrapper,使其构造函数准备执行某些操作
 //并且其Dispose方法执行所需的清理。 然后...

 void myCode(void)
 {
  使用(var wrap = new ActionWrapper())
   {
    东西要做到这里
   }
 }

模式#2在某些方面更加通用,因为它允许使用不遵循严格嵌套规则的使用模式。 这是IEnumerator使用该模式而不是简单地使用枚举方法的一部分,该枚举方法接受委托并在每个列表项上调用它。 如果必须遵守与枚举器的嵌套语义,尝试执行列表合并之类的操作最多也会很尴尬。

另一方面,某些类型的受保护资源只能以严格嵌套的方式有意义地使用。 因此,上面给出的第一种方法有时可能更合适,因为它在任何特定线程中严格执行嵌套语义。

如果您不想使用第一种方法,我建议您应该安排您的对象,以便Disposing与受保护资源相关联的实例将使其后生成的所有实例无效。 为了最大限度地减少“延迟惊讶”因素,您可能希望在创建第一个包装器对象时让资源分配线程关联,并禁止任何其他线程访问,直到所有包装器都是Dispose d(如果线程可能也允许访问)创建该对象的对象不再存在,如果线程似乎不太可能在对象消失之前对该对象做了任何不好的事情)。