关于C#中的"using"语句,涉及到风格和可读性的问题。

7

我希望了解您对编码风格的看法,我对此还没有定论。我知道可能没有绝对正确的答案,但我想知道是否有一种更偏向于某个方向的强烈偏好。

我正在处理一个解决方案,在很多地方添加using语句。经常会遇到像这样的情况:

{
    log = new log();
    log.SomeProperty = something;  // several of these
    log.Connection = new OracleConnection("...");
    log.InsertData(); // this is where log.Connection will be used
    ... // do other stuff with log, but connection won't be used again
}

其中log.Connection是一个实现了IDisposable接口的OracleConnection对象。

我想要将其更改为:

{
    using (OracleConnection connection = new OracleConnection("..."))
    {
        log = new log();
        log.SomeProperty = something;
        log.Connection = conn;
        log.InsertData();
        ...
    }
}

但是那些喜欢简洁和更快完成工作的人想要做的是:
{
    log = new log();
    log.SomeProperty = something; 
    using (log.Connection = new OracleConnection("..."))
        log.InsertData();
    ...
}

因为某些原因,我觉得做这件事有点不舒服。您认为这是好还是不好?如果您认为这是不好的,为什么?如果这是好的,为什么?

编辑:请注意这只是许多(有点牵强附会)例子中的一个。请不要纠结于它恰好指示了一个界面设计不良的记录器类。这与我的问题无关,而且我也没有权利改进这些类本身。

5个回答

5
他们都很可怕。不要选择任何一个。
你正在创建一个所谓的“高维护类”。高维护类有一个合同,它说:“我需要你提供一堆资源,并且你必须知道我何时使用完它们并适当地清理它们。”这个合同意味着类的用户必须知道类是如何实现的,从而违反了封装和抽象的原则,这些原则是创建类的初衷。
你可以通过你的评论来判断:这就是连接被使用的地方,我知道连接将不再使用。你怎么知道的?只有在这是类的文档合同的情况下,你才会知道。这不是对类的消费者施加的好合同。
改进的一些方法:
1)使记录器可处理。当完成时,清理连接。缺点是记录器将比必要的时间长地保持连接。
请注意保留HTML标签。

2) 将InsertData作为一个参数接收连接。调用者仍然负责清理连接,因为记录器不会保留它。

3) 创建第三个类"Inserter",它是可处理的,并在其构造函数中接收日志和连接。插入程序在被处理时处理连接;调用者随后负责处理插入器。


1
有趣的是,我大部分时间发现的经验法则是:“如果你创建了它,就由你负责处理它。”看到记录器处理可能被其他记录器共享的连接会让我更惊讶。例如,虽然我已经有一段时间没直接使用 SqlCommand 了,但如果我没记错的话,它不会释放你传递给它的连接。 - Josh
@Josh:如果你想分享,请选择选项#2。连接只是不应该是日志的成员。 - Eric Mickelsen
@Josh:将其与流读取器进行比较,后者拥有对它们正在读取的流的所有处理权。重要的是合同不应该是(1)困难或(2)令人惊讶的。如果发现记录器保留了对已释放集合的引用,我会感到惊讶;我怎么知道记录器已经完成了它的操作? - Eric Lippert
1
不幸的是,进行这些改进超出了我当前任务的范围。我很想在这个解决方案中做出许多这样的改变,但目前没有被指派去这样做。然而,这只是众多例子中的一个。其他一些情况已经符合您的第二个建议。考虑到这些事情,我仍然希望听取您对我的原始问题的意见。 - Igby Largeman
2
还不错,但是有 NetworkStream,它在构造函数中提供了一个明确的选项,让你选择是否将套接字的“所有权”交给它。我喜欢你的第二个建议,但是第一个对我来说也同样含糊不清。 - Josh

2

我认为理想情况下,log本身应该实现IDisposable,但假设这不可能,并回答实际提出的问题。

第二种方式更好,因为它用更少的代码实现了相同的功能。在这里引入额外的connection变量没有任何优势。

另请注意,您可以在using块之外执行其他初始化。在这里不重要,但如果您正在"使用"一些非常昂贵的资源,则可能很重要。也就是说:

log = new log();
log.SomeProperty = something; // This can be outside the "using"
using (OracleConnection connection = new OracleConnection("..."))
{
    log.Connection = conn;
    log.InsertData();
    ...
}

1
感谢您解答实际问题。您提到在 using 块之外执行初始化的观点很有趣,因为我将其移动到块内部的唯一原因是感觉将所有相关代码放在一起更整洁。我没有考虑到可能的开销(这里不是问题,但要牢记这个有效观点)。 - Igby Largeman

2
我会倾听你内心的整洁之声,但我个人更喜欢他的方式。

因为第二种方式对我来说看起来非常丑陋,而我非常注重代码的“外观”。 - Josh
好的,诚实加1分。 - Eric Mickelsen
没关系,我发现这个问题没有正确答案,这就是为什么我标记了我的回答为社区维基的原因。 :) - Josh

1

using 的两种用法完全等效。无论哪种方式,最终都会得到一个仍在作用域内但已释放连接的日志。更好的做法是使日志可被处理并让其处理方法处理连接,将日志放在 using 语句中而不是连接本身。


1
同意,这个例子给人的界面很差,但是现在暂且不谈它。我知道这两种方式是等效的,但是你有没有一种更倾向的方式呢? - Igby Largeman
@Charles:我的直觉告诉我你面临这个困境的原因是最初设计选择不佳(将连接作为成员)。如果你真的无法撤销这个选择,我仍然建议不要使用任何一个。我会做类似于#1的事情,但我会将日志对象完全放在using块的范围内,这样你就不能在连接被处理后尝试使用它了。 - Eric Mickelsen
1
是的,作为一名聘请的程序员,你每天都会面临这个困境,而且往往无法纠正潜在的问题。我经常看到似乎经验丰富的开发人员在SO上无法理解这一事实。你提到将所有消耗连接对象的引用移动到using块内,以防止在被处理后使用连接的可能性,这是一个好想法。感谢您的建议。 - Igby Largeman

0

如果 log 实现了 IDisposable 接口,那么采用第二种选择,因为花括号是显式的。在某些情况下,您可以使用多个 using 语句:

using (Graphics g = ...)
using (Pen p = new Pen ...)
using (Font f = new Font ...)
{



}

你可以只使用一组大括号来完成,这样可以避免疯狂的缩进。


日志不实现IDisposable接口。考虑到这一点,您更喜欢“整洁”的第一个选择吗? - Igby Largeman
那就采纳Eric Lippert的建议吧。在这个领域,他几乎是权威人士。 - Charlie Salts
1
如果我是使用连接编写类的人,Eric的建议可能很好,但他没有提出任何解决我的实际问题的建议。 - Igby Largeman
如果 OracleConnection 实现了 IDisposable 接口,那么请采用您最后的选择。 - Charlie Salts

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