使用“Using”来处理资源以外的事情

8
我们都知道,using语句对于需要及时清理的资源非常好,比如打开的文件或数据库连接。
我想知道,在不是为了清理资源而是为了回到先前状态的情况下,使用这个语句是否被认为是一件好事。
例如,一个允许使用using语句来包装需要花费大量时间并将光标更改为等待状态的过程的类。
class CursorHelper : IDisposable
{
   readonly Cursor _previousState;
   public CursorHelper(Cursor newState)
   {
      _previousState = Cursor.Current;
      Cursor.Current = newState;
   }

   public void Dispose()
   {
      Cursor.Current = _previousState;
   }
}

然后,类可以像这样使用,无需担心在完成后还原游标。
public void TimeIntensiveMethod()
{
   using (CursorHelper ch = new CursorHelper(Cursors.WaitCursor))
   {
      // something that takes a long time to complete
   }
}

这是否是using语句的适当使用?


1
我不认为你发布的内容有任何问题,但是我认为为像你的例子这样简单的东西实现一个接口是过度设计和误导性的。 - JonH
1
我的理解是,using 在其作用域结束时调用 IDisposable.Dispose()。我认为这种使用 using 没有问题,但它可能是一种“不好”的 IDisposable 使用方式吗? - Mathieu Guindon
3
http://bytes.com/topic/net/answers/568992-idisposable-beyond-memory-management - I4V
@I4V 很棒的阅读,谢谢分享! - Mathieu Guindon
5个回答

5
实际上,using只是try/finally的语法糖。因此,为什么不像下面这样使用普通的try/finally呢...
try
{
    // do some work
}
finally
{
    // reset to some previous state
}

在我看来,实现Dispose方法以重置到某个状态将会非常具有误导性,特别是如果您的代码有消费者。

不确定我是否同意。using语句可以删除一些没有描述任何有用信息的代码,而且你可以堆叠它们。它们编译成比try/finally更多一点的东西,但本质上就是这样。我用它们来执行需要后续操作的操作。Rx在消息推送中做到了这一点,而且效果很好。归根结底,这种事情完全是主观的,但如果以一种或另一种方式完成,否则不会造成任何伤害。 - Adam Houldsworth
@AdamHouldsworth 我还在学习中,很想知道根据MSFT的说法,“using”实际上被编译器翻译成了“try/finally” :)。http://msdn.microsoft.com/en-us/library/yh598w02.aspx - Jason
@Jason 这将被翻译成嵌套的 try-finally 并直接使用 IDisposable 接口。它的语义效果是 try-finally,但其实现更加微妙。请参见此问题和我的答案:https://dev59.com/MWgt5IYBdhLWcg3w2A77#11778831 - Adam Houldsworth
@Joe 我并不否认这归结于个人偏好的事实,我绝不是一个纯粹主义者,但我确信如果你给我一个名为 Dispose 的方法,它应该做一些清理工作,而不是重置到特定状态。我坚持这个说法,除了几行“更漂亮”的代码外,我还没有看到它提供了什么其他东西,记住,美在观者眼中 ;) - Jason
@Jason,我能理解“滥用”标签的原因,但我会羞愧地承认,在我们的代码中,我已经多次使用了这个确切的用例 >.< 随着年龄的增长,我对代码风格和选择变得更加务实。我不是一个纯粹主义者,但我做出风格选择的最大两个原因是1)表达意图和2)可读性。如果我再次看到这段代码,我会考虑重新编写它。 - Adam Houldsworth
显示剩余5条评论

5
在IT技术中,滥用using语句的先例是很常见的,例如ASP.NET MVC框架中的FormExtensions.BeginForm。当被处理时,它会渲染一个<form>结束标签,它的主要目的是在MVC视图中启用更简洁的语法。即使抛出异常,Dispose方法也会尝试渲染关闭标记,这有点奇怪:如果在渲染表单时抛出异常,您可能不希望尝试渲染结束标记。

另一个例子是log4net框架中现已弃用的NDC.Push方法,它返回一个IDisposable,其目的是弹出上下文。

一些纯粹主义者会说这是一种滥用,我建议您根据具体情况自行判断。就个人而言,我认为您的示例渲染沙漏光标没有任何问题。

评论中@I4V链接的讨论有一些有趣的观点,其中包括来自无处不在的Jon Skeet反对这种类型的“滥用”的论点。


1
我同意Jon的观点,避免在公开展示的项目中使用它,而在内部使用它,这往往是我们的平衡点。 - Adam Houldsworth
@AdamHouldsworth,我也同意Jon Skeet在链接讨论中的一些评论。但是现在这样的滥用已经出现在诸如ASP.NET MVC之类的主流库中,我不禁感到灾难已经不可避免了。 - Joe
确实如此,但是在.NET、LINQ和扩展方法中,追求漂亮的代码与功能上略显丑陋但却可行的代码之间的冲动随着时间的推移而变得更加严重,这对我来说是一种巨大的挑战。有时候,不按照这种方式编码需要极大的意志力。谁知道,这可能是开发人员在创建.NET库之前使用的语言的社会指标。我的个人背景非常有限,因此C#是我主要的编程语言。语言经验影响编码风格,同行的影响也是如此,我认为这是一个风格问题。 - Adam Houldsworth

4

我反对这个想法,认为这是一种滥用。我也认为C++中的RAII是一个可怕的想法。我知道在这两个立场上我处于少数派。

这个问题是一个重复的问题。关于我为什么认为这是一个不必要的using语句的滥用的详细原因,请参见:https://dev59.com/R3I95IYBdhLWcg3w5iU4#2103158


2
不,使用using和/或Dispose是不合适的。这种模式有一个非常明确的用途("定义释放已分配资源的方法"),而这不是它的用途。任何未来使用此代码的开发人员都会对其表示轻蔑,因为这样做是邪恶的。
如果您需要这种行为,则实现事件并公开它们,调用代码可以订阅它们并管理光标(如果需要),否则光标应该可由通用参数和可能使用BeginEnd方法来管理(尽管这种命名约定通常保留给方法的异步实现,但您可以理解)——这种方式的黑客实际上并没有给你带来任何好处。

-1
在我看来,使用可处理的东西除了处理对象之外还有其他用途是有意义的。当然这取决于上下文和用法。如果它能导致更易读的代码,那就没问题。
我曾经在工作单元和存储库模式实现中使用过它,例如:
public class UnitOfWork: IDisposable  {
    // this is thread-safe in actual implementation
    private static Stack<UnitOfWork> _uowStack = new Stack<UnitOfWork>();
    public static UnitOfWork Current {get { return _uowStack.Peek(); }} 

    public UnitOfWork() {
        _uowStack.Push(this);
    }

    public void Dispose() {
        _ouwStack.Pop();
    }

    public void SaveChanges() {
        // do some db operations
    }
}

public class Repository {
    public void DoSomething(Entity entity) {
        // Do Some db operations         

        UnitOfWork.Current.SaveChanges();
    }
}

通过这种实现,保证嵌套操作将使用其相应的UnitOfWork而无需传递参数。使用方法如下。

using (new UnitOfWork()) 
{
    var repo1 = new UserRepository();
    // Do some user operation

    using (new UnitOfWork())  
    {
         var repo2 = new AccountingRepository();
         // do some accounting
    }

    var repo3 = new CrmRepository();
    // do some crm operations
}

在这个示例中,repo1和repo3使用相同的UnitOfWork,而repo2使用不同的存储库。读者所看到的是“使用新的工作单元”,这非常有意义。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接