C# Language
Использование заявления
Поиск…
Вступление
Обеспечивает удобный синтаксис, который обеспечивает правильное использование объектов 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
блок, и значение будет выбрано. Порядок оценки выглядит следующим образом:
- Оцените тело
try
- Вычислить и кешировать возвращаемое значение
- Выполнить окончательный блок
- Возвращает значение кэшированного возврата
Тем не менее, вы не можете возвращать переменную 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()
вы никогда не узнаете, может ли подпрограмма нарушить ограничение контекста, чтобы избавиться от этих ресурсов.