【问题标题】:Bad practice? Non-canon usage of c#'s using statement不好的做法? c# using 语句的非规范用法
【发布时间】:2010-11-08 21:19:02
【问题描述】:

C# 有 using 语句,专门用于 IDisposable 对象。据推测,using 语句中指定的任何对象都将持有某种应该被确定性释放的资源。

然而,在我看来,编程中有许多设计都有一个单一的、明确的开始和结束,但缺乏内在的语言支持。 using 构造提供了使用代码编辑器的内置功能的机会,至少可以清晰自然地突出此类设计或操作的范围。

我想到的是那种经常以 BeginXXX()EndXXX() 方法开始的操作,尽管有很多不同的风格,例如涉及“开始”和“加入”。

以这个幼稚的例子为例。

webDataOperation.Start();
GetContentFromHardDrive();
webDataOperation.Join();
// Perform operation that requires data from both sources

如果 Start 方法返回一个对象,该对象的 IDisposable.Dispose 方法执行连接操作。

using(webDataOperation.Start()) {
    GetContentFromHardDrive();
}
// Perform operation that requires data from both sources

或者,更好的是,我特别想到的是:我有一个执行高度专业化的图形位图传输的对象,并有一个 Begin()End() 方法(DirectX 和 XNA 中也存在这种设计)。而是……

using(blitter.BlitOperation()) {
    // Do work
}
// Use result

它似乎更自然、更易读,但它是否不可取,因为它使用IDisposable 接口和using 语句用于非预期目的?换句话说,这是否等同于以非直观方式加载运算符?

【问题讨论】:

  • 为什么不直接用 { 开头, } 结尾?这提供了范围而不会滥用 using。

标签: c# using-statement


【解决方案1】:

我建议不要这样做;我的信念是代码是为了与代码的维护者而不是编译器进行有效的沟通,并且应该在编写时考虑到维护者的理解。我尝试仅使用“使用”来处理资源,通常是非托管资源。

我是少数。大多数人似乎使用“使用”作为通用目的“即使抛出异常,我也希望运行一些清理代码”机制。

我不喜欢这个,因为(1)我们已经有一个机制,称为“try-finally”,(2)它使用一个特性来达到它不打算用于的目的,以及(3)如果调用清理代码很重要,那为什么在调用它的时候看不到呢?如果它很重要,那么我希望能够看到它。

【讨论】:

  • 在我看来,这种对using 的看法是非常严格的。出于好奇,C# 中是否还有其他语言功能您会建议不要使用它——即使它功能完美而清晰——因为所说的用途不是它的设计目的?
  • @KirkWoll:是的。我建议不要将 所有 语言特性用于其他用途,而不是用于它们的设计用途。
  • @EricLippert - 当然,语言功能的设计目的并不总是很清楚。事实上,即使在using 的具体情况下,我认为有一些迹象表明“使用”这个名称是专门选择的,因为预计该功能不会仅用于处理非托管资源。
  • 我个人无法接受这种信念,否则将违背您对use Java on anything other than embedded systems的信念。
【解决方案2】:

仅仅因为你可以(或者因为 Phil Haack 说没关系),并不意味着你应该这样做。

基本经验法则:如果我可以阅读您的代码并理解它在做什么以及您的意图是什么,那么它是可以接受的。另一方面,如果你需要解释你做了什么,或者你为什么这样做,这可能会让维护代码的初级开发人员绊倒。

还有许多其他模式可以通过更好的封装来实现这一点。

底线:这种“技术”不会给你带来任何好处,只会让其他开发者感到困惑。

【讨论】:

  • +1 表示对理解的赞扬。我完全同意——尽管总的来说,我认为将 IDisposable 用于分解类型提供了一种非常干净、易于理解的方法来处理它们在许多情况下......但这是正确编写代码的问题,并且非常清楚什么你在做。即:BCL 中的 TransactionScope 对我来说很自然-这里另一个答案中的缩进代码不是。像许多事情一样,我认为这很有帮助,但很容易被滥用。
  • 同意。什么有效,有效。很容易被有趣的概念冲昏头脑,但在现实世界的项目中不应该使用只会混淆的东西。
  • 如果这让我感到困惑,我就不会问了。忽略潜在的混乱,您是否有更整洁或更简单的替代方案?我喜欢“使用”是如何内联、缩进和支持语言的,并且明确、具体和简单地封装了范围。
  • @Snarfblam:在某些情况下,使用接受委托的方法(某种形式的 Action/Func)并在您的启动/拆卸代码之间调用该方法效果很好。但是,这种方法无法处理某些情况。有关详细信息,请参阅 Pavel Minaev 的答案。
【解决方案3】:

这是一种常见的模式,但就个人而言,我相信没有任何借口可以滥用 IDisposable,因为您可以使用匿名委托和/或 lambda 以更明显的方式实现相同的效果;即:

blitter.BlitOperation(delegate
{
   // your code
});

【讨论】:

  • 而且,如果你想,比如说,总是在另一个线程中完成工作,你不需要拆开你的代码(即,这种设计增加了价值)
  • 虽然我也很喜欢这种模式,但这增加了它自身的复杂性——在某些情况下,它可能比通过 using 语句处理变得不那么明显。许多(尤其是入门级)开发人员在理解 lambdas 时遇到问题。
  • 这在某些情况下也不允许等效的安全性。例如,您不能在带有 yield 语句的方法内部使用它并获得保证清理...
  • 里德,我不确定你的意思。如果 BlitOperation 的主体在调用委托后进行清理,那么调用者是否是迭代器方法又有什么关系?
【解决方案4】:

我会说这是可以接受的——事实上,我已经在一些项目中使用过它,我希望在特定代码块的末尾触发一个动作。

Wes Deyer 在他的 LINQ to ASCII Art 程序中使用了它,他称之为一次性操作(Wes 在 C# 编译器团队工作 - 我相信他的判断 :D):

http://blogs.msdn.com/wesdyer/archive/2007/02/23/linq-to-ascii-art.aspx

class ActionDisposable: IDisposable
{
    Action action;

    public ActionDisposable(Action action)
    {
        this.action = action;
    }

    #region IDisposable Members

    public void Dispose()
    {
        this.action();
    }

    #endregion
}

现在您可以从函数中返回它,然后执行以下操作:

using(ExtendedConsoleWriter.Indent())
{
     ExtendedConsoleWriter.Write("This is more indented");
}

ExtendedConsoleWriter.Write("This is less indented");

【讨论】:

  • 哇,这是对 using 语句的公然滥用。如果不打开 ExtendedConsoleWriter 的代码,我将不知道该代码试图做什么。如果在代码周围调用了IncreaseIndent 和DecreaseIndent,那就没有问题了。
  • 我不会为此使用“using”,但是,假设有类似的关键字和相关的接口,例如“with”: with(writer.Indent()){ stuff; },这将是一个方便的构造。
  • 哎呀,瑞恩。这是一个快速编写的示例。我同意,我将其称为“IncreaseIndent”——在这种情况下,我不会将其称为“对 using 语句的公然滥用”。这种概念可以在整个 .NET 范围内的事务中看到 - 看起来你在细节上有点严厉。
【解决方案5】:

这是一种完全可以接受的做法。这些被称为因子类型,Framework Design Guidelines 建议这样做。

基本上,如果该类型包装了一个具有特定生命周期的操作,那么使用 IDisposable 和 using 语句就成为一个合适的考虑因素。

我实际上也写过关于 this specific topic here 的博客。

【讨论】:

  • 很好的发现,但你的论点有什么理由吗?文章推荐它,但没有讨论原因。
  • 这是因为它保证 EndOperation() 总是被调用(通过 Dispose())
  • @Snarfblam:基本上,操作本身就是需要清理的资源。阅读我的博客文章了解更多详情 - 这是第二个链接。我在其中提出的理由比在这里要多得多。
  • 另请注意,“使用”不是“专门针对 IDisposable 对象”,请参阅我关于别名泛型的文章ebersys.blogspot.com/2006/08/hiding-generics-complexity.html
  • 这里讨论的是 using 语句,而不是 using 指令(即导入命名空间和定义别名与主题无关)。
【解决方案6】:

我认为您应该将 IDisposable 用于它的用途,而不是其他。也就是说,如果可维护性对您很重要。

【讨论】:

  • John:请参阅有关因子类型的框架设计指南:blogs.msdn.com/brada/archive/2009/02/23/…——现在这是处理它们的推荐方法。
  • 这对可维护性有何影响?这是您建议严格遵守规范的唯一原因吗?
  • 它会影响其他开发人员了解正在发生的事情的能力。我推荐的原因包括,让人们出于正常原因使用 IDisposable 已经足够困难了——我们不需要额外的原因。
  • 我同意 - 我不(也不建议)在任何地方使用它。话虽如此,我认为它有很好的用例,尤其是当您必须调用结束论点或有副作用时。我相信这就是为什么它现在在设计指南中明确提到,并以这种方式在 BCL 中频繁使用。 IMO,一旦它在 BCL 中的多个位置使用并包含在官方设计指南中,它就会成为使用它的“正常理由”。
猜你喜欢
  • 2013-05-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多