Поиск…


Вступление

Обеспечивает удобный синтаксис, который обеспечивает правильное использование объектов IDisposable .

Синтаксис

  • используя (одноразовое) {}
  • используя (IDisposable disposable = new MyDisposable ()) {}

замечания

Объект в инструкции using должен реализовывать интерфейс IDisposable .

using(var obj = new MyObject())
{
}

class MyObject : IDisposable
{
    public void Dispose()
    {
        // Cleanup
    }
}

Более полные примеры реализации IDisposable можно найти в документах MSDN .

Использование основ ведения

using - это синтаксический сахар, который позволяет гарантировать очистку ресурса без необходимости использования явного блока try-finally . Это означает, что ваш код будет намного чище, и вы не будете пропускать неконтролируемые ресурсы.

Стандартный Dispose шаблон очистки, для объектов, реализующих интерфейс IDisposable (который имеет базовый класс FileStream Stream в .NET):

int Foo()
{
    var fileName = "file.txt";

    {
        FileStream disposable = null;

        try
        {
            disposable = File.Open(fileName, FileMode.Open);

            return disposable.ReadByte();
        }
        finally
        {
            // finally blocks are always run
            if (disposable != null) disposable.Dispose();
        }
    }
}

using упрощает ваш синтаксис, скрывая явное try-finally :

int Foo()
{
    var fileName = "file.txt";

    using (var disposable = File.Open(fileName, FileMode.Open))
    {
        return disposable.ReadByte();
    }
    // disposable.Dispose is called even if we return earlier
}

Подобно тому, как блоки finally всегда выполняются независимо от ошибок или возвратов, using всегда вызывает Dispose() даже в случае ошибки:

int Foo()
{
    var fileName = "file.txt";

    using (var disposable = File.Open(fileName, FileMode.Open))
    {
        throw new InvalidOperationException();
    }
    // disposable.Dispose is called even if we throw an exception earlier
}

Примечание. Так как Dispose гарантированно вызывается независимо от потока кода, рекомендуется убедиться, что Dispose никогда не генерирует исключение при реализации IDisposable . В противном случае фактическое исключение будет переопределено новым исключением, результатом чего станет кошмар отладки.

Возврат из блока

using ( var disposable = new DisposableItem() )
{
    return disposable.SomeProperty;
}

Из-за семантики try..finally к которой выполняется блок using , оператор return работает как ожидалось - возвращаемое значение вычисляется до того, как будет выполнен finally блок, и значение будет выбрано. Порядок оценки выглядит следующим образом:

  1. Оцените тело try
  2. Вычислить и кешировать возвращаемое значение
  3. Выполнить окончательный блок
  4. Возвращает значение кэшированного возврата

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

Несколько операторов с одним блоком

Можно использовать несколько вложенных операторов using без добавления нескольких уровней вложенных фигурных скобок. Например:

using (var input = File.OpenRead("input.txt"))
{
    using (var output = File.OpenWrite("output.txt"))
    {
        input.CopyTo(output);
    } // output is disposed here
} // input is disposed here

Альтернативой является запись:

using (var input = File.OpenRead("input.txt"))
using (var output = File.OpenWrite("output.txt"))
{
    input.CopyTo(output);
} // output and then input are disposed here

Это в точности эквивалентно первому примеру.

Примечание: Вложенный с using заявления может вызвать Microsoft Code Analysis правило CS2002 (см этот ответ для осветления) и генерирует предупреждение. Как поясняется в связанном ответе, обычно безопасно вставлять using операторов.

Когда типы внутри оператора using имеют один и тот же тип, вы можете их разграничить и указать тип только один раз (хотя это редкость):

using (FileStream file = File.Open("MyFile.txt"), file2 = File.Open("MyFile2.txt"))
{
}

Это также можно использовать, когда типы имеют общую иерархию:

using (Stream file = File.Open("MyFile.txt"), data = new MemoryStream())
{
}

Ключевое слово var не может использоваться в приведенном выше примере. Возникла ошибка компиляции. Даже декларация, разделенная запятыми, не будет работать, если объявленные переменные имеют типы из разных иерархий.

Gotcha: возврат ресурса, который вы утилизируете

Ниже приведена плохая идея, потому что она должна была бы выставить переменную db перед ее возвратом.

public IDBContext GetDBContext()
{
    using (var db = new DBContext())
    {
        return db;
    }
}

Это также может создавать более тонкие ошибки:

public IEnumerable<Person> GetPeople(int age)
{
    using (var db = new DBContext())
    {
        return db.Persons.Where(p => p.Age == age);
    }
}

Это выглядит нормально, но catch заключается в том, что оценка выражения LINQ является ленивой и, возможно, будет выполнена только позже, когда базовый DBContext уже был удален.

Короче говоря, выражение не оценивается перед тем, как покинуть using . Одно из возможных решений этой проблемы, которое все еще использует using , заключается в том, чтобы заставить выражение немедленно оценить, вызвав метод, который будет перечислять результат. Например, ToList() , ToArray() и т. Д. Если вы используете новейшую версию Entity Framework, вы можете использовать async аналоги, такие как ToListAsync() или ToArrayAsync() .

Ниже вы найдете пример в действии:

public IEnumerable<Person> GetPeople(int age)
{
    using (var db = new DBContext())
    {
        return db.Persons.Where(p => p.Age == age).ToList();
    }
}

Важно отметить, однако, что, вызывая ToList() или ToArray() , выражение будет с нетерпением оценено, что означает, что все лица с указанным возрастом будут загружены в память, даже если вы не будете их перебирать.

Использование операторов является нулевым

Вам не нужно проверять объект IDisposable на null . using не будет вызывать исключение, а Dispose() не будет вызываться:

DisposableObject TryOpenFile()
{
    return null;
}

// disposable is null here, but this does not throw an exception 
using (var disposable = TryOpenFile())
{
    // this will throw a NullReferenceException because disposable is null
    disposable.DoSomething(); 

    if(disposable != null)
    {
        // here we are safe because disposable has been checked for null
        disposable.DoSomething();
    }
}

Gotcha: исключение в методе Dispose маскирование других ошибок в использовании блоков

Рассмотрим следующий блок кода.

try
{
    using (var disposable = new MyDisposable())
    {
        throw new Exception("Couldn't perform operation.");
    }
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}

class MyDisposable : IDisposable
{
    public void Dispose()
    {
        throw new Exception("Couldn't dispose successfully.");
    }
}

Вы можете ожидать увидеть «Не удалось выполнить операцию», напечатанную на консоли, но на самом деле вы увидите «Не удалось успешно утилизировать». поскольку метод Dispose все еще вызывается даже после того, как будет выбрано первое исключение.

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

Использование утверждений и подключений к базам данных

Ключевое слово using гарантирует, что ресурс, определенный внутри оператора, существует только в пределах самой инструкции. Любые ресурсы, определенные в инструкции, должны реализовывать интерфейс IDisposable .

Они невероятно важны при работе с любыми соединениями, реализующими интерфейс IDisposable поскольку он может гарантировать, что соединения не только закрыты должным образом, но и освобождаются из-за того, что их ресурсы освобождены после того, как оператор using выходит за рамки.

Общие классы данных IDisposable

Многие из следующих относятся к классам, связанным с данными, которые реализуют интерфейс IDisposable и являются идеальными кандидатами для using оператора:

  • SqlConnection , SqlCommand , SqlDataReader и т. Д.
  • OleDbConnection , OleDbCommand , OleDbDataReader и т. Д.
  • MySqlConnection , MySqlCommand , MySqlDbDataReader и т. Д.
  • DbContext

Все они обычно используются для доступа к данным через C # и обычно встречаются во всех приложениях, ориентированных на данные. Многие другие классы, которые не упомянуты, которые реализуют те же FooConnection , FooCommand , FooDataReader можно ожидать, что они будут вести себя одинаково.

Общий шаблон доступа для подключений ADO.NET

Общая схема, которая может использоваться при доступе к вашим данным через соединение ADO.NET, может выглядеть следующим образом:

// This scopes the connection (your specific class may vary)
using(var connection = new SqlConnection("{your-connection-string}")
{
    // Build your query
    var query = "SELECT * FROM YourTable WHERE Property = @property");
    // Scope your command to execute
    using(var command = new SqlCommand(query, connection))
    {
         // Open your connection
         connection.Open();

         // Add your parameters here if necessary

         // Execute your query as a reader (again scoped with a using statement)
         using(var reader = command.ExecuteReader())
         {
               // Iterate through your results here
         }
    }
}

Или, если вы просто выполняете простое обновление и не нуждаетесь в читателе, будет применяться одна и та же базовая концепция:

using(var connection = new SqlConnection("{your-connection-string}"))
{
     var query = "UPDATE YourTable SET Property = Value WHERE Foo = @foo";
     using(var command = new SqlCommand(query,connection))
     {
          connection.Open();
          
          // Add parameters here
          
          // Perform your update
          command.ExecuteNonQuery();
     }
}

Использование выражений с DataContexts

Многие ORM, такие как Entity Framework, выставляют классы абстракции, которые используются для взаимодействия с базовыми базами данных в виде классов, таких как DbContext . Эти контексты, как правило, также реализуют интерфейс IDisposable и должны воспользоваться этим, using возможные возможности:

using(var context = new YourDbContext())
{
      // Access your context and perform your query
      var data = context.Widgets.ToList();
}

Использование Dispose Syntax для определения настраиваемой области

Для некоторых случаев использования вы можете использовать синтаксис using чтобы определить пользовательскую область. Например, вы можете определить следующий класс для выполнения кода в определенной культуре.

public class CultureContext : IDisposable
{
    private readonly CultureInfo originalCulture;

    public CultureContext(string culture)
    {
        originalCulture = CultureInfo.CurrentCulture;
        Thread.CurrentThread.CurrentCulture = new CultureInfo(culture);
    }

    public void Dispose()
    {
        Thread.CurrentThread.CurrentCulture = originalCulture;
    }
}

Затем вы можете использовать этот класс для определения блоков кода, которые выполняются в определенной культуре.

Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");

using (new CultureContext("nl-NL"))
{
    // Code in this block uses the "nl-NL" culture
    Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 25-12-2016 00:00:00
}

using (new CultureContext("es-ES"))
{        
    // Code in this block uses the "es-ES" culture
    Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 25/12/2016 0:00:00
}

// Reverted back to the original culture
Console.WriteLine(new DateTime(2016, 12, 25)); // Output: 12/25/2016 12:00:00 AM

Примечание: поскольку мы не используем экземпляр CultureContext мы создаем, мы не назначаем ему переменную.

Этот метод используется помощником BeginForm в ASP.NET MVC.

Выполнение кода в контексте ограничений

Если у вас есть код ( подпрограмма ), который вы хотите выполнить в определенном (ограничение) контексте, вы можете использовать инъекцию зависимостей.

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

public static class SSLContext
{
    // define the delegate to inject
    public delegate void TunnelRoutine(BinaryReader sslReader, BinaryWriter sslWriter);

    // this allows the routine to be executed under SSL
    public static void ClientTunnel(TcpClient tcpClient, TunnelRoutine routine)
    {
        using (SslStream sslStream = new SslStream(tcpClient.GetStream(), true, _validate))
        {
            sslStream.AuthenticateAsClient(HOSTNAME, null, SslProtocols.Tls, false);

            if (!sslStream.IsAuthenticated)
            {
                throw new SecurityException("SSL tunnel not authenticated");
            }

            if (!sslStream.IsEncrypted)
            {
                throw new SecurityException("SSL tunnel not encrypted");
            }

            using (BinaryReader sslReader = new BinaryReader(sslStream))
            using (BinaryWriter sslWriter = new BinaryWriter(sslStream))
            {
                routine(sslReader, sslWriter);
            }
        }
    }
}

Теперь клиентский код, который хочет что-то сделать под SSL, но не хочет обрабатывать все данные SSL. Теперь вы можете делать все, что хотите, в туннеле SSL, например, обмен симметричным ключом:

public void ExchangeSymmetricKey(BinaryReader sslReader, BinaryWriter sslWriter)
{
    byte[] bytes = new byte[8];
    (new RNGCryptoServiceProvider()).GetNonZeroBytes(bytes);
    sslWriter.Write(BitConverter.ToUInt64(bytes, 0));
}

Вы выполняете эту процедуру следующим образом:

SSLContext.ClientTunnel(tcpClient, this.ExchangeSymmetricKey);

Для этого вам понадобится предложение using() , потому что это единственный способ (кроме блока try..finally ) вы можете гарантировать, что клиентский код ( ExchangeSymmetricKey ) никогда не выходит из строя, не располагая надлежащим образом располагаемых ресурсов. Без using() вы никогда не узнаете, может ли подпрограмма нарушить ограничение контекста, чтобы избавиться от этих ресурсов.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow