3

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

我想我会尝试使用 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

如果只进行 1 次修改,应该会很好用。但是,如果进行了多次修改,或者调用者没有使用“使用”构造创建对象,我会看到出现问题。

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,但我对其他解决方案全心全意。

4

4 回答 4

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 方法中执行该工作意味着您还缩短了稍后在不同上下文中的重用机会。

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

于 2012-04-24T21:58:37.987 回答
1

听起来您想使用该System.Collections.Generic.Stack<T>实现。每个修改都会将一个值压入堆栈,每个“撤消”都会将该值从堆栈中弹出。

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

于 2012-04-24T22:00:07.943 回答
0

实现该模式有两种通用模式:“准备做某事;做它;清理”:

// 模式 #1

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

无效我的代码(无效)
{
  FancyDoSomething(() => {这里要做的事情});
}

// 模式#2:

// 定义一个 ActionWrapper 以便它的构造函数准备做某事
// 及其 Dispose 方法执行所需的清理。然后...

无效我的代码(无效)
{
  使用(var wrap = new ActionWrapper())
  {
    要做的事情在这里
  }
}

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

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

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

于 2012-04-25T15:07:53.997 回答
0

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

不确定使用您试图通过处置来释放的内存要花多少钱,但它应该可以工作。

于 2012-04-24T22:01:44.067 回答