我是否应该在这里实现IDisposable?

7
我的方法调用SQL Server返回一个DataReader,但由于我需要做的事情是将DataReader返回给位于页面代码后台的调用方法,所以我无法在调用SQL Server的方法的类中关闭连接。因此,我没有finally或using块。
正确释放资源的方法是让该类实现IDisposable吗?或者我应该从调用者显式地处理非托管资源(类级字段)?
编辑:我发送数据读取器,因为我需要将数据读取器中的特定数据绑定到列表项控件,因此在调用类(Codebehind页面)中,我执行以下操作:
 new ListItem(datareader["dc"]); (along those lines).

9
为什么您想将数据读取器发送到页面? - Perpetualcoder
直接返回DataReader可能是不好的实践,但在某些情况下它可能是有用的。 - Venemo
@Venemo - 我觉得https://dev59.com/4U3Sa4cB1Zd3GeqPy-TA#2869503可能对他更有帮助。 - dss539
@dss539 - 也许是,但他的问题并不是关于什么可能对他更有帮助。 (个人认为,使用ORM比使用DataReaders会更好为他提供服务。) - Venemo
7个回答

7
我认为可以实现IDisposable接口。我所知道的主要原因之一是当您不能完全信任对象的用户能够正确地使用它时。这似乎是一个非常适合使用IDisposable的场景。
然而,这也引出了一个关于架构的问题。为什么你想将DataReader本身发送到页面而不是调用一个方法来代替你完成所有必要的清理工作并返回必要的内容呢?如果必须将实际的DataReader传递给页面,那就这样做吧。

4

在您的读取器类中将数据库连接保持为成员变量,并使您的读取器类实现IDisposable,这对我来说很好。

但是,您可能考虑使您的方法返回IEnumerable并使用yield return语句遍历数据读取器。这样,您可以返回结果并仍然从您的方法内部清理。

以下是我所指的大致草图:

public IEnumerable<Person> ReadPeople(string name)
{
    using (var reader = OpenReader(...))
    {
        // loop through the reader and create Person objects
        for ...
        {
            var person = new Person();
            ...
            yield return person;
        }
    }
}

或者,使用“public IEnumerable<IDataRecord>”和“yield return reader;”,并让调用者决定如何处理数据。 - Joe
我以前做过非常类似的事情。我将其通用化为一个仅封装连接和读取器的类 - 但您可以级联它们(因此仍然可以拥有多个阅读器)。 运行得非常好。 - philsquared

3

首先,直接传递 DataReader 可能并不是你想要做的事情,但我会假设它是。

正确的处理方式应该是返回一个组合类型,该类型封装或公开 DataReader 并保持连接,然后在该类型上实现 IDisposable。当处理该类型时,同时处理读取器和连接。

public class YourClass : IDisposable
{
    private IDbConnection connection;
    private IDataReader reader;

    public IDataReader Reader { get { return reader; } }

    public YourClass(IDbConnection connection, IDataReader reader)
    {
        this.connection = connection;
        this.reader = reader;
    }

    public void Dispose()
    {
        reader.Dispose();
        connection.Dispose();
    }
}

3

如果你的自定义类在返回给下层时包含一个打开的DataReader,那么你应该在该类上实现IDisposable接口。

当返回需要清理的内容时,这是被接受的模式。


2
你的班级
class MyClass : IDisposable
{
  protected List<DataReader> _readers = new List<DataReader>();
  public DataReader MyFunc()
  {
      ///... code to do stuff

      _readers.Add(myReader);
      return myReader;
  }
  private void Dispose()
  {
      for (int i = _readers.Count - 1; i >= 0; i--)
      {
          DataReader dr = _reader.Remove(i);
          dr.Dispose();
      }
      _readers = null;

      // Dispose / Close Connection
  }
}

然后在你的类外面

public void FunctionThatUsesMyClass()
{
   using(MyClass c = new MyClass())
   {
       DataReader dr = c.MyFunc();
   }
}

using块退出时,所有读取器和MyClass实例都会被清除。

为什么要从“_readers”变量中删除它们呢?而将其设置为“= null”实际上不会做任何事情。 - Adam Robinson
将它们从_readers 中移除是一种很好的去引用的方法,基本上告诉 GC 它可以清理它们。 - Venemo
"_readers = null" 只是出于习惯。因为当 "MyClass" 对象被 GC 回收时,空 List 也会被回收。从 _readers 对象中移除它们可以减少引用计数,并有助于垃圾回收,最终 GC 将意识到它们唯一的引用来自待回收的对象,但这并不会有害。 - Aren
@Venemo:除非消费者在调用Dispose后继续持有MyClass,否则这样做没有任何效果。即使在这种情况下,GC实际上在对象超出范围之前收集它们的可能性也极小。这是不必要的工作。 - Adam Robinson
@Aren:垃圾回收不使用引用计数。你说得对,它不会有什么伤害(尽管当前实现如果Dispose被调用超过一次就会抛出异常),但我的观点是,这是额外的代码(因此需要额外查看和维护),实际上它并没有给你带来任何东西。 - Adam Robinson
@Adam:我同意,在这种特定情况下是正确的,但在更高级的场景中,取消引用未使用的实例是一种非常好的做法。 - Venemo

2
我不会返回任何东西,而是会传递一个委托。
例如:
void FetchMeSomeReader(Action<IDataReader> useReader)
{
    using(var reader = WhateverYouDoToMakeTheReader())
        useReader(reader);
}

然后在你的调用类中:

void Whatever()
{
   FetchMeSomeReader(SetFields);
}

void SetFields(IDataReader reader)
{
   MyListItem = new ListItem(datareader["dc"]);
}

1

一般规则是如果你的类直接持有非托管资源或在其内部引用另一个 IDisposable 对象,那么你的类应该实现 IDisposable 接口。如果你的类在某个方法中创建了 IDataReader 但从未持有该引用,则根据该规则,你的类不需要实现 IDisposable 接口(除非它仅恰好持有与在该方法中创建的 IDataReader 以外的 IDisposable 对象)。

你真正需要问自己的问题是,即使将 IDataReader 返回给调用方后,你的类是否真的需要一直持有它。就我个人而言,我认为这是一种糟糕的设计,因为它模糊了所有权的界限。在这种情况下,谁真正拥有 IDisposable 呢?谁负责管理其生命周期?以 IDbCommand 类为例,它们创建 IDataReader 实例并将其返回给调用方,但却免除了所有权。这使得 API 清晰,并且在此情况下,生命周期管理的责任是明确的。

无论所有权问题如何,您的特定情况都需要实现IDisposable;不是因为您的类恰好创建并返回一个IDataReader实例,而是因为它似乎持有一个IDbConnection对象。

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