Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
CSharpNotesForProfessionals.pdf
Скачиваний:
65
Добавлен:
20.05.2023
Размер:
6.12 Mб
Скачать

Chapter 60: Using Statement

Provides a convenient syntax that ensures the correct use of IDisposable objects.

Section 60.1: Using Statement Basics

using is syntactic sugar that allows you to guarantee that a resource is cleaned up without needing an explicit try- finally block. This means your code will be much cleaner, and you won't leak non-managed resources.

Standard Dispose cleanup pattern, for objects that implement the IDisposable interface (which the FileStream's

base class Stream does in .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 simplifies your syntax by hiding the explicit 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

}

Just like finally blocks always execute regardless of errors or returns, using always calls Dispose(), even in the event of an error:

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

GoalKicker.com – C# Notes for Professionals

319

}

Note: Since Dispose is guaranteed to be called irrespective of the code flow, it's a good idea to make sure that Dispose never throws an exception when you implement IDisposable. Otherwise an actual exception would get overridden by the new exception resulting in a debugging nightmare.

Returning from using block

using ( var disposable = new DisposableItem() )

{

return disposable.SomeProperty;

}

Because of the semantics of try..finally to which the using block translates, the return statement works as expected - the return value is evaluated before finally block is executed and the value disposed. The order of evaluation is as follows:

1.Evaluate the try body

2.Evaluate and cache the returned value

3.Execute finally block

4.Return the cached return value

However, you may not return the variable disposable itself, as it would contain invalid, disposed reference - see related example.

Section 60.2: Gotcha: returning the resource which you are disposing

The following is a bad idea because it would dispose the db variable before returning it.

public IDBContext GetDBContext()

{

using (var db = new DBContext())

{

return db;

}

}

This can also create more subtle mistakes:

public IEnumerable<Person> GetPeople(int age)

{

using (var db = new DBContext())

{

return db.Persons.Where(p => p.Age == age);

}

}

This looks ok, but the catch is that the LINQ expression evaluation is lazy, and will possibly only be executed later when the underlying DBContext has already been disposed.

So in short the expression isn't evaluated before leaving the using. One possible solution to this problem, which still makes use of using, is to cause the expression to evaluate immediately by calling a method that will enumerate the result. For example ToList(), ToArray(), etc. If you are using the newest version of Entity Framework you could use the async counterparts like ToListAsync() or ToArrayAsync().

GoalKicker.com – C# Notes for Professionals

320

Below you find the example in action:

public IEnumerable<Person> GetPeople(int age)

{

using (var db = new DBContext())

{

return db.Persons.Where(p => p.Age == age).ToList();

}

}

It is important to note, though, that by calling ToList() or ToArray(), the expression will be eagerly evaluated, meaning that all the persons with the specified age will be loaded to memory even if you do not iterate on them.

Section 60.3: Multiple using statements with one block

It is possible to use multiple nested using statements without added multiple levels of nested braces. For example:

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

An alternative is to write:

using (var input = File.OpenRead("input.txt")) using (var output = File.OpenWrite("output.txt"))

{

input.CopyTo(output);

} // output and then input are disposed here

Which is exactly equivalent to the first example.

Note: Nested using statements might trigger Microsoft Code Analysis rule CS2002 (see this answer for clarification) and generate a warning. As explained in the linked answer, it is generally safe to nest using statements.

When the types within the using statement are of the same type you can comma-delimit them and specify the type only once (though this is uncommon):

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

{

}

This can also be used when the types have a shared hierarchy:

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

{

}

The var keyword cannot be used in the above example. A compilation error would occur. Even the comma separated declaration won't work when the declared variables have types from di erent hierarchies.

GoalKicker.com – C# Notes for Professionals

321

Section 60.4: Gotcha: Exception in Dispose method masking other errors in Using blocks

Consider the following block of code.

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.");

}

}

You may expect to see "Couldn't perform operation" printed to the Console but you would actually see "Couldn't dispose successfully." as the Dispose method is still called even after the first exception is thrown.

It is worth being aware of this subtlety as it may be masking the real error that prevented the object from being disposed and make it harder to debug.

Section 60.5: Using statements are null-safe

You don't have to check the IDisposable object for null. using will not throw an exception and Dispose() will not be called:

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

}

}

Section 60.6: Using Dispose Syntax to define custom scope

For some use cases, you can use the using syntax to help define a custom scope. For example, you can define the following class to execute code in a specific culture.

GoalKicker.com – C# Notes for Professionals

322