临时更改变量的值

Temporarily change a variable's value

提问人:sbi 提问时间:3/29/2011 最后编辑:Communitysbi 更新时间:3/29/2011 访问量:2826

问:

在我目前正在研究的 API 实现中,需要在执行某些任务之前临时重复更改一些变量,并在任务完成后将它们更改回以前的状态。

当前代码如下所示:

var _oldValue = _variable;
_variable = tempValue;
try
{
  doIt();
}
finally
{
  _variable = oldValue;
}

经常这样做很烦人、丑陋、难以维护,并且将实际算法埋藏在大量混乱之下,这些混乱只是实现工件。

在 C++ 中,我会创建一个类,该类在构造过程中将旧值存储在某个地方,并在其析构函数中恢复它:

{
  temp_value tmp(variable_, temp_val);
  do_it();
}

当尝试在 C# 中做类似的事情时,我失败了,因为显然 C# 无法在类中存储对其他对象的引用

那么,我必须在 C# 中做些什么来消除这种混乱呢?

P.S.:随意添加您认为合适的任何其他标签。我什么也想不出来。

c#

评论

0赞 Sanjeevakumar Hiremath 3/29/2011
编写一个包装静态方法,例如在任何地方调用它,而不是将这种混乱放在一个地方,并确保你注释它,以便代码的未来读者理解其意图和目的。DoItWithRestore()DoIt()
0赞 vhallac 3/29/2011
你有没有机会找到一个替代的 API 来执行同样的任务?这听起来像是一个非常脆弱的。
0赞 Jonas Elfström 3/29/2011
是否无法更改以采用参数?doIt()
1赞 Jamie Treworgy 3/29/2011
你能解释一下背景吗?这些变量是全局变量吗?像“doit”这样的函数是否都封装在某个类中?我很难从大局出发。为什么“doit”需要影响其范围之外的事情,如果你只是打算在事后切换它们?
0赞 sbi 3/29/2011
@Dysaster、@Jonas和@jamietre:这与其说是一个API,不如说是一个框架。你编写代码并加载到其中,然后框架调用你来处理数据 - 一个经典的插件架构。需要这样做的一个例子是调用插件的代码时。插件编写者可能想要执行的某些操作仅在特定上下文中被允许,当调用某些插件函数集时。因此,框架在调用插件之前设置上下文,从插件调用的函数检查该上下文是否设置好。不过,这还有其他应用。doIt()

答:

6赞 Jim Mischel 3/29/2011 #1

为什么不创建一个为你做这件事的方法,然后把一个 Lamda 传递给它呢?

private void SaveGlobalsAndDoSomething(Action doit)
{
    var _oldValue = _variable;
    _variable = tempValue;
    try
    {
        doit();
    }
    finally
    {
        _variable = _oldValue;
    }
}

并使用它:

SaveGlobalsAndDoSomething(() => { DoSomething(); });

编辑以回应评论:

有时返回值不是问题。我们不会传递到该方法。我们正在传递到方法。所以你可以很容易地写出:doitDoSomething{ DoSomething(); }

int returnValue;
SaveGlobalsAndDoSomething(() => { returnValue = DoSomething(); });

评论

0赞 sbi 3/29/2011
呸。它并不完全漂亮,这可能只是因为我期待别的东西。我想这确实会起作用。鉴于我可以将 per 传递给函数,我甚至可以将变量传递给函数以更改为函数,这样我就不必为每个此类变量重复它。ref
0赞 sbi 3/29/2011
缝补。我刚刚发现有案例是返回一些东西。我想我可以使用泛型来指定返回类型?但其他人没有,我有点怀疑我是否可以像在 C++ 中那样返回泛型函数以返回?这意味着至少两个不同的功能......我想我会把它做成通用的,并把它放在实用程序命名空间中。doIt()void
0赞 sbi 3/29/2011
啊,谢谢!虽然它们是我最喜欢的 C# 功能之一,但似乎我还没有掌握 lambda 的全部功能......
5赞 Eric Lippert 3/29/2011 #2

你正在考虑做各种可怕的、可怕的事情来解决这个问题,这一事实表明你一开始就不应该处于这种情况。如果你的代码依赖于突变,然后取消突变的状态,那么你的设计就很糟糕。解决真正的设计问题,而不是试图想出一个聪明的方法来继续使用糟糕的架构。

当我处于这种情况时,我所做的就是克隆我的状态。假设您正在这样做:

class Frobber
{
    State state;
    ...
    void M()
    {
         ...
         try
         {
             oldstate = state;
             state = newstate;
             this.DoIt();
         }
         finally
         {
             state = oldstate;
         }
    }

相反,请这样做:

class Frobber
{
    State state;
    ...
    void M()
    {
         ...
         Frobber newFrobber = new Frobber(newstate);
         newFrobber.DoIt();
         ...

与其改变一个变量并将其改回原来,不如创建一个全新的变量。完成新变量后,将其丢弃。旧变量不需要变回,因为它从未改变过。

评论

0赞 sbi 3/29/2011
埃里克,我学会了重视你的见解,但请阅读我对这个问题的评论,我在其中解释了为什么这是必要的。此外,即使你是对的,并且可以重新设计它以按照你建议的方式进行(我看不到 ATM),这是一个相当大的系统(单元测试需要一个多小时),它已经在多个客户端中使用,并且需要一个平滑的升级路径。我不能随意重新设计,因为这些客户端的代码需要针对此 API 进行编译,并且重构必须以增量方式完成,以免一次中断太多。
7赞 Alexei Levenkov 3/29/2011 #3

虽然我同意埃里克·利珀特(Eric Lippert)关于理想解决方案的观点,但在某些情况下,当被迫改变可变状态并执行一些操作时。也就是说,SharePoint 对象模型中有几个此类要求的示例,因此无法重新设计代码来避免它。

下面是可用于临时 cahnge 值并使用语句重新分配的代码。将 用于此类非发布未管理资源的目的是有争议的,因此请您判断这种方法是否适合您:usingusing

使用示例:

using(TemporaryChange(true, myValue, v => myValue = v))
{
 // code to run while "myValue" is changed to "true"
}

类:

class TemporaryChange<V> : IDisposable
{
    private V original;
    private Action<V> setValue;

    internal TemporaryChange(V value, V currentValue, Action<V> setValue)
    {
        this.setValue = setValue;
        this.original = currentValue;
        this.setValue(value);
    }

    void IDisposable.Dispose()
    {
        this.setValue(this.original);
    }
}

评论

0赞 sbi 3/29/2011
当然,这闻起来很像 C++ 的 RAII,所以它对我有一定的吸引力。 但是,我认为必须拼出像作业这样简单的事情是相当麻烦的。不过,这可能和 C# 一样好。谢谢!:)