Должен ли я реализовать IDisposable здесь? - PullRequest
7 голосов
/ 19 мая 2010

Мой метод, который вызывает SQL Server, возвращает DataReader, но из-за того, что мне нужно сделать - возвращать DataReader вызывающему методу, который находится в коде страницы - я не могу закрыть соединение в классе метода, который вызывает SQL-сервер. Из-за этого у меня нет окончательно или использующих блоки.

Является ли правильный способ использования ресурсов для реализации класса IDisposable? Или я должен явным образом избавиться от неуправляемого ресурса (поля уровня класса) от вызывающей стороны?

РЕДАКТИРОВАТЬ: я отправляю обратно в хранилище данных, потому что мне нужно привязать определенные данные из хранилища данных в элемент управления listitem, поэтому в классе вызова (страница Codebehind) я делаю:

 new ListItem(datareader["dc"]); (along those lines).

Ответы [ 7 ]

7 голосов
/ 19 мая 2010

Я бы сказал, да, реализовать IDisposable. Насколько я могу сказать, одной из основных причин является то, что вы не можете доверять пользователю объекта настолько, чтобы сделать это самостоятельно. Это, кажется, главный кандидат на это.

Это, как говорится, однако, есть вопрос к вашей архитектуре. Почему вы хотите отправить саму DataReader на страницу, а не вызывать метод, который сделает это за вас (включая соответствующую очистку), возвращая то, что необходимо? Если это необходимо, чтобы дать реальный читатель на страницу, то пусть будет так.

4 голосов
/ 19 мая 2010

Удержание соединения с базой данных в качестве переменной-члена в вашем классе считывателя и обеспечение реализации вашего класса считывателя 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;
        }
    }
}
3 голосов
/ 19 мая 2010

Во-первых, передача 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 голосов
/ 19 мая 2010

Да, вы должны реализовать IDisposable в своем пользовательском классе, если он содержит DataReader, который открывается при возврате на нижний уровень.

Это принятый шаблон при возврате чего-либо, что необходимо очистить.

2 голосов
/ 20 мая 2010

Я бы ничего не вернул. Вместо этого я бы передал делегата.

Например:

void FetchMeSomeReader(Action<IDataReader> useReader)
{
    using(var reader = WhateverYouDoToMakeTheReader())
        useReader(reader);
}

Тогда в классе вашего звонка:

void Whatever()
{
   FetchMeSomeReader(SetFields);
}

void SetFields(IDataReader reader)
{
   MyListItem = new ListItem(datareader["dc"]);
}
2 голосов
/ 19 мая 2010

Ваш класс

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();
   }
}

Все читатели и экземпляр MyClass очищаются при выходе из блока using.

1 голос
/ 19 мая 2010

Общее правило заключается в том, что ваш класс должен реализовывать IDisposable, если он напрямую содержит неуправляемые ресурсы или содержит ссылку на другой объект IDisposable. Если ваш класс создает IDataReader в одном методе, но никогда не содержит эту ссылку, тогда вашему классу не нужно будет реализовывать IDisposable в соответствии с правилом (если только это не случится так, чтобы IDisposable оставалось в стороне от IDataReader, созданного в этом один метод).

Реальный вопрос, который вам нужно задать себе, заключается в том, должен ли ваш класс действительно удерживать этот IDataReader даже после того, как он доставил его вызывающей стороне. Лично я считаю, что это плохой дизайн, потому что он размывает линию собственности. Кому на самом деле принадлежит IDisposable в этом случае? Кто несет ответственность за его жизнь? Взять, к примеру, IDbCommand классы. Они создают IDataReader экземпляры и возвращают их вызывающим, но освобождают себя от владения. Это делает API чистым, и ответственность за управление временем жизни в этом случае однозначна.

Независимо от проблемы собственности, ваша конкретная ситуация требует внедрения IDisposable; не потому, что ваш класс создает и возвращает экземпляр IDataReader, а потому, что он звучит так, как будто содержит объект IDbConnection.

...