在类级变量上使用using语句是不好的做法吗?

问题描述:

我有类似于以下代码。在类级变量上使用using语句是不好的做法吗?

class MyController 
{ 
    [ThreadStatic] private DbInterface db; 

    public void ImportAllData() 
    { 
     using (db = new DbInterface()) 
     { 
      var records = PullData(); 
      PushData(records); 
     } 
    } 

    private DbRecord[] PullData() 
    { 
     return db.GetFromTableA(); 
    } 

    private void PushData(DbRecord[] records) 
    { 
     db.InsertIntoTableB(records); 
    } 
} 

另一种方法是维护起来很麻烦。

class MyController 
{ 
    public void ImportAllData() 
    { 
     using (var db = new DbInterface()) 
     { 
      var records = PullData(db); 
      PushData(records, db); 
     } 
    } 

    private DbRecord[] PullData(DbInterface db) 
    { 
     return db.GetFromTableA(); 
    } 

    private void PushData(DbRecord[] records, DbInterface db) 
    { 
     db.InsertIntoTableB(records); 
    } 
} 

据我所看到的,我的第一个执行:

  • 是线程安全的(假设DbInterface是线程安全的),
  • 防止任何其它进程从触摸db变量,
  • 确保db将始终处置,即使在例外情况下也是如此。

对类范围的变量使用using语句是不好的做法吗?我错过了什么吗?

+0

考虑使用扩展?或者你打算做很多事情。 – 2013-02-12 00:51:54

+3

有趣的是,如果你曾经做过Win Forms绘画代码,那么OnPaint和Paint事件就非常像这样,至少在将参考传递给使用块之外。您传递的是分配给您的图形上下文,其管理/处置由调用者处理,因此您不会将其自行处理为实际的绘制方法。 – tcarvin 2013-02-12 00:57:19

+0

@DanSaltmer,我打算在其他控制器中使用'DbInterface'类。我希望控制器可以访问所有'DbInterface'方法,但我不想让控制器知道其他私有方法。据我的理解,扩展方法将适用于所有'DbInterface'实例。或者我误解了你? – 2013-02-12 01:03:12

个人而言,我更喜欢你的第二个选项。

第一个设计的问题是,您有效地为设计添加了不必要的耦合。您的PullDataPushData方法不能单独使用 - 他们要求呼叫ImportAllData或其他一些方法,将设置和正确清理变量db首先被调用。

第二个选项,虽然略微更多的代码(虽然不是很多),使每个方法的意图非常清楚。每种方法都知道它需要在传入其中的外部实例上工作。未来很少或根本没有机会被滥用。

您的第一个变体在using块管理的范围之外暴露了db。这打开了意想不到的副作用的可能性。例如,另一种方法可能会使用甚至处理db。如果您或稍后的维护人员忘记了db的隐式合同,或甚至通过代码中的拼写错误,则可能会发生这种情况。

我不会使用第一个变体。

下面是一个替代:

sealed class MyImporter 
{ 
    private readonly DbInterface db; 

    public MyImporter(DbInterface db) 
    { 
     this.db = db; 
    } 

    public void ImportAllData() 
    { 
     var records = PullData(); 
     PushData(records); 
    } 

    private DbRecord[] PullData() 
    { 
     return db.GetFromTableA(); 
    } 

    private void PushData(DbRecord[] records) 
    { 
     db.InsertIntoTableB(records); 
    } 
} 

在这种情况下,保持在参考是类责任的清晰部分。它现在也将处置责任推给用户。这个更加明确的构造减少了向'Controller'添加附加功能的诱惑,这是您的第一种方法在长期内可能变坏的原因。实质上,我们将Import函数重构为一个单独的类,以便共享字段访问不再成为问题。

+1

+1:我也将依赖注入添加到构造函数中:public MyImporter(DbInterface _db) { db = _db;} – horgh 2013-02-12 01:07:10

+0

@KonstantinVasilcov,同意,这实际上更清洁;它实际上消除了在课堂上进行处置的需要,以及 – 2013-02-12 01:09:07

+0

这将处理db的责任移交给调用者。可能是好事还是坏事,但是这是对班级责任的改变。如果调用者不知道如何创建'db'的实例,这种模式将无法工作。 – 2013-02-12 01:23:53

这就是结构在那里。由于约定规定属性访问是轻量级的,并且当用户认为他们只是访问变量时,并不一定要触发create-dispose循环,所以我会注意将使用置入属性。

但是下面有关代码结构的注释很重要 - 如果您想要导入一次,它将成为用户需要了解的设置步骤。如果您可以设想不同的访问模式,则依赖注入设置允许用户控制连接的创建和处置。