Об использовании «using» и «finally» для очистки ресурсов - PullRequest
5 голосов
/ 31 марта 2011

В каких случаях требуется следующая структура?

using (Something something = new Something())
{
    try
    {
    }
    finally
    {
        something.SomeCleanup();
    }
}

Или, если все задачи очистки должны выполняться при неявном вызове something.Dispose()?


Вот код нарушения:

public static DataTable GetDataTable(string cmdText, IEnumerable<Parameter> parameters)
{
    // Create an empty memory table.
    DataTable dataTable = new DataTable();

    // Open a connection to the database.
    using (SqlConnection connection = new SqlConnection(ConfigurationTool.ConnectionString))
    {
        connection.Open();

        // Specify the stored procedure call and its parameters.
        using (SqlCommand command = new SqlCommand(cmdText, connection))
        {
            command.CommandType = CommandType.StoredProcedure;

            SqlParameterCollection parameterCollection = command.Parameters;
            foreach (Parameter parameter in parameters)
                parameterCollection.Add(parameter.SqlParameter);

            try
            {
                // Execute the stored procedure and retrieve the results in the table.
                using (SqlDataAdapter dataAdapter = new SqlDataAdapter(command))
                    try
                    {
                        dataAdapter.Fill(dataTable);
                    }
                    catch
                    {
                        dataTable.Dispose();
                        dataTable = null;
                    }
            }
            finally
            {
                //parameterCollection.Clear();
            }
        }
    }

    return dataTable;
}

ПРИМЕЧАНИЕ: Я определил класс Parameter, поэтому пользователям этой функции не приходится иметь дело с созданиемSqlParameter с напрямую.Свойство SqlParameter класса Parameter можно использовать для получения SqlParameter.

. В какой-то момент моя программа выполняет следующее (не может опубликовать код, потому что в нем много классов;в основном у меня есть мини-фреймворк, создающий множество объектов):

  1. Создать массив из Parameter с.
  2. GetDataTable('sp_one', parameters).
  3. GetDataTable('sp_two', parameters).

Ответы [ 3 ]

5 голосов
/ 31 марта 2011

Ключевое слово using только вызывает метод .Dispose(). Если у вас есть необходимый очистка, которая происходит вне метода dispose для объекта IDisposable, то вам нужно будет сделать это в своем собственном блоке finally. Это поднимает два пункта:

  1. В этот момент вы можете утверждать, что вы можете также пропустить блок using и просто вызвать Dispose () внутри блока finally. Лично я бы все-таки пошел с блоком using. Это просто хорошая привычка быть в всегда , чтобы иметь его для ваших IDisposable экземпляров.
  2. Я смиренно предлагаю, чтобы, если вы соблюдаете вышеизложенные условия, вам необходимо изменить дизайн своего класса, чтобы воспользоваться преимуществом шаблона IDisposable.

Судя по опубликованному вами коду, проблема в том, что ваши параметры все еще где-то укоренены (возможно, вы их повторно используете?). Поскольку параметры все еще укоренены, они не могут быть собраны. Они также содержат ссылку на команду, к которой они присоединены, поэтому ваш объект SqlCommand также не может быть собран сразу, потому что теперь он все еще укоренен.

Ключевым моментом является то, что .Net Framework резервирует шаблон Dispose () для неуправляемых ресурсов. Поскольку SqlParameters и SqlParameterCollection являются управляемыми типами, они не затрагиваются до тех пор, пока не будут собраны, что происходит совершенно отдельно от утилизации. Когда ваша SqlCommand будет наконец собрана, о ее SqlParameter также будет позаботиться. Только не путайте сбор, утилизацию и их цели.

Что вы хотите сделать, это сделать копию каждого параметра при добавлении, а не добавлять существующий параметр в коллекцию.

public static DataTable GetDataTable(string cmdText, IEnumerable<Parameter> parameters)
{
    // Create an empty memory table.
    DataTable dataTable = new DataTable();

    // Prepare a connection to the database and command to execute.
    using (SqlConnection connection = new SqlConnection(ConfigurationTool.ConnectionString))
    using (SqlCommand command = new SqlCommand(cmdText, connection))
    {
        command.CommandType = CommandType.StoredProcedure;

        SqlParameterCollection parameterCollection = command.Parameters;
        foreach (Parameter parameter in parameters)
            parameterCollection.Add(CloneParameter(parameter.SqlParameter));

        // Execute the stored procedure and retrieve the results in the table.
        using (SqlDataAdapter dataAdapter = new SqlDataAdapter(command))
        {
             dataAdapter.Fill(dataTable);
        }
    }

    return dataTable;
}

Некоторые вещи, на которые следует обратить внимание: я смог избавиться от всех ваших пробных блоков. Ни один из них не был нужен. Кроме того, метод SqlDataAdapter.Fill () откроет и закроет соединение для вас, поэтому вам не нужна эта часть.

Теперь поговорим об этой функции CloneParameter (). У меня сложилось впечатление, что вы чувствуете, что он побеждает цель вашего кода, а именно попытаться повторно использовать параметры. Я обещаю вам, что повторное использование параметра здесь - плохая идея. Потеря производительности незначительна до точки несуществования, особенно по сравнению с выполнением хранимой процедуры. Я оставил вам реализацию CloneParameter () по двум причинам: во-первых, это тривиально, а во-вторых, мы уже за пределами моей обычной схемы доступа к данным. Обычно я добавляю параметры, принимая делегат Action , а не перечисляемый параметр. Функция объявлена ​​так:

public IEnumerable<IDataRecord>GetData(string cmdText, Action<SqlParameterCollection> addParameters)

и называется так:

foreach(var record in GetData("myprocedurename", p => 
  {
      p.Add( /*new parameter here*/ );
      p.Add( /*new parameter here*/ );
    //...
  })
 .Select( /*Returning a IEnumerable rather than a datatable allows me to use it with linq to objects.*/
          /* For example, you could use this spot to convert from DataRecords returned by ADO.Net to business objects */ 
        ))
{
   // use the results here...
}

Поскольку вы заполняете две таблицы подряд, похоже, у вас есть какая-то работа по выполнению клиентской части, которая может оправдать это по сравнению с подходом DataReader / IEnumerable, но я хочу упомянуть об этом, поскольку в большинстве случаев Ваш код на DataReader является лучшим вариантом.

То, что я бы сделал в вашем случае с моим существующим шаблоном на основе делегатов Action и желанием максимально использовать повторяющийся набор параметров, - это настоящий именованный метод, который знает, как добавлять параметры и совпадения делегат моего действия. Тогда я мог бы просто передать этот метод и получить нужный параметр для повторного использования.

4 голосов
/ 31 марта 2011

Интересный вопрос!

Все зависит от вашего Something класса.Если он был плохо спроектирован и требует многоэтапной очистки, он навязывает свои особенности своим клиентам.

Вы не должны проектировать классы, чтобы быть такими.Если у вас есть временные очистки, инкапсулируйте их в свои собственные классы и используйте такой код:

using (Something something = new Something()) {
  // ...
  using (SomethingElse somethingElse = something.GiveMeSomethingElse()) {
  }
  // ...
} 

ОБНОВЛЕНИЕ:

Для вашего примера это может выглядетькак это:

using (SqlConnection connection = new SqlConnection(connectionString)) {
  connection.Open();

  using (SqlCommand command = new SqlCommand("select * from MyTable where id = @id", connection)) {

    // to "reuse" the parameters collection population, just extract this to a separate method      
    command.Parameters.Add(new SqlParameter("id", id));

    // ... execute the command

  }

}

ОБНОВЛЕНИЕ 2:

Просто сделайте это:

GetDataTable('sp_one', CreateParameters());
GetDataTable('sp_two', CreateParameters());
1 голос
/ 31 марта 2011

Утилизация должна очистить все ваши неуправляемые ресурсы. Использование другого метода очистки, например, для выполнения некоторых функциональных действий или действий с базой данных, вполне возможно.

...