Try catch c что это
Перейти к содержимому

Try catch c что это

  • автор:

Обработка исключений

Иногда при выполнении программы возникают ошибки, которые трудно предусмотреть или предвидеть, а иногда и вовсе невозможно. Например, при передачи файла по сети может неожиданно оборваться сетевое подключение. такие ситуации называются исключениями . Язык C# предоставляет разработчикам возможности для обработки таких ситуаций. Для этого в C# предназначена конструкция try. catch. finally .

При использовании блока try. catch..finally вначале выполняются все инструкции в блоке try . Если в этом блоке не возникло исключений, то после его выполнения начинает выполняться блок finally . И затем конструкция try..catch..finally завершает свою работу.

Если же в блоке try вдруг возникает исключение, то обычный порядок выполнения останавливается, и среда CLR начинает искать блок catch , который может обработать данное исключение. Если нужный блок catch найден, то он выполняется, и после его завершения выполняется блок finally.

Если нужный блок catch не найден, то при возникновении исключения программа аварийно завершает свое выполнение.

Рассмотрим следующий пример:

В данном случае происходит деление числа на 0, что приведет к генерации исключения. И при запуске приложения в режиме отладки мы увидим в Visual Studio окошко, которое информирует об исключении:

Исключения в C#

В этом окошке мы видим, что возникло исключение, которое представляет тип System.DivideByZeroException , то есть попытка деления на ноль. С помощью пункта View Details можно посмотреть более детальную информацию об исключении.

И в этом случае единственное, что нам остается, это завершить выполнение программы.

Чтобы избежать подобного аварийного завершения программы, следует использовать для обработки исключений конструкцию try. catch. finally . Так, перепишем пример следующим образом:

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

выполнение программы остановится. CLR найдет блок catch и передаст управление этому блоку.

После блока catch будет выполняться блок finally.

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

Следует отметить, что в этой конструкции обязателен блок try . При наличии блока catch мы можем опустить блок finally:

И, наоборот, при наличии блока finally мы можем опустить блок catch и не обрабатывать исключение:

Однако, хотя с точки зрения синтаксиса C# такая конструкция вполне корректна, тем не менее, поскольку CLR не сможет найти нужный блок catch, то исключение не будет обработано, и программа аварийно завершится.

Обработка исключений и условные конструкции

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

Если пользователь передаст в метод не число, а строку, которая содежит нецифровые символы, то программа выпадет в ошибку. С одной стороны, здесь как раз та ситуация, когда можно применить блок try..catch , чтобы обработать возможную ошибку. Однако гораздо оптимальнее было бы проверить допустимость преобразования:

Метод int.TryParse() возвращает true , если преобразование можно осуществить, и false — если нельзя. При допустимости преобразования переменная x будет содержать введенное число. Так, не используя try. catch можно обработать возможную исключительную ситуацию.

С точки зрения производительности использование блоков try..catch более накладно, чем применение условных конструкций. Поэтому по возможности вместо try..catch лучше использовать условные конструкции на проверку исключительных ситуаций.

Try catch c что это

One of the advantages of C++ over C is Exception Handling. Exceptions are runtime anomalies or abnormal conditions that a program encounters during its execution. There are two types of exceptions: a)Synchronous, b)Asynchronous (i.e., exceptions which are beyond the program’s control, such as disc failure, keyboard interrupts etc.). C++ provides the following specialized keywords for this purpose:
try: Represents a block of code that can throw an exception.
catch: Represents a block of code that is executed when a particular exception is thrown.
throw: Used to throw an exception. Also used to list the exceptions that a function throws but doesn’t handle itself.

Why Exception Handling?
The following are the main advantages of exception handling over traditional error handling:

1) Separation of Error Handling code from Normal Code: In traditional error handling codes, there are always if-else conditions to handle errors. These conditions and the code to handle errors get mixed up with the normal flow. This makes the code less readable and maintainable. With try/catch blocks, the code for error handling becomes separate from the normal flow.

2) Functions/Methods can handle only the exceptions they choose: A function can throw many exceptions, but may choose to handle some of them. The other exceptions, which are thrown but not caught, can be handled by the caller. If the caller chooses not to catch them, then the exceptions are handled by the caller of the caller.
In C++, a function can specify the exceptions that it throws using the throw keyword. The caller of this function must handle the exception in some way (either by specifying it again or catching it).

3) Grouping of Error Types: In C++, both basic types and objects can be thrown as exceptions. We can create a hierarchy of exception objects, group exceptions in namespaces or classes and categorize them according to their types.

C++ Exceptions:

When executing C++ code, different errors can occur: coding errors made by the programmer, errors due to wrong input, or other unforeseeable things.

When an error occurs, C++ will normally stop and generate an error message. The technical term for this is: C++ will throw an exception (error).

C++ try and catch:

Exception handling in C++ consists of three keywords: try, throw and catch:

The try statement allows you to define a block of code to be tested for errors while it is being executed.

The throw keyword throws an exception when a problem is detected, which lets us create a custom error.

The catch statement allows you to define a block of code to be executed if an error occurs in the try block.

The try and catch keywords come in pairs:

We use the try block to test some code: If the value of a variable “age” is less than 18, we will throw an exception, and handle it in our catch block.

In the catch block, we catch the error if it occurs and do something about it. The catch statement takes a single parameter. So, if the value of age is 15 and that’s why we are throwing an exception of type int in the try block (age), we can pass “int myNum” as the parameter to the catch statement, where the variable “myNum” is used to output the value of age.

If no error occurs (e.g. if age is 20 instead of 15, meaning it will be greater than 18), the catch block is skipped.

Exception Handling in C++

1) The following is a simple example to show exception handling in C++. The output of the program explains the flow of execution of try/catch blocks.

Name already in use

docs / docs / csharp / language-reference / keywords / try-catch.md

  • Go to file T
  • Go to line L
  • Copy path
  • Copy permalink
  • Open with Desktop
  • View raw
  • Copy raw contents Copy raw contents

Copy raw contents

Copy raw contents

try-catch (C# Reference)

The try-catch statement consists of a try block followed by one or more catch clauses, which specify handlers for different exceptions.

When an exception is thrown, the common language runtime (CLR) looks for the catch statement that handles this exception. If the currently executing method does not contain such a catch block, the CLR looks at the method that called the current method, and so on up the call stack. If no catch block is found, then the CLR displays an unhandled exception message to the user and stops execution of the program.

The try block contains the guarded code that may cause the exception. The block is executed until an exception is thrown or it is completed successfully. For example, the following attempt to cast a null object raises the xref:System.NullReferenceException exception:

Although the catch clause can be used without arguments to catch any type of exception, this usage is not recommended. In general, you should only catch those exceptions that you know how to recover from. Therefore, you should always specify an object argument derived from xref:System.Exception?displayProperty=nameWithType. The exception type should be as specific as possible in order to avoid incorrectly accepting exceptions that your exception handler is actually not able to resolve. As such, prefer concrete exceptions over the base Exception type. For example:

It is possible to use more than one specific catch clause in the same try-catch statement. In this case, the order of the catch clauses is important because the catch clauses are examined in order. Catch the more specific exceptions before the less specific ones. The compiler produces an error if you order your catch blocks so that a later block can never be reached.

Using catch arguments is one way to filter for the exceptions you want to handle. You can also use an exception filter that further examines the exception to decide whether to handle it. If the exception filter returns false, then the search for a handler continues.

Exception filters are preferable to catching and rethrowing (explained below) because filters leave the stack unharmed. If a later handler dumps the stack, you can see where the exception originally came from, rather than just the last place it was rethrown. A common use of exception filter expressions is logging. You can create a filter that always returns false that also outputs to a log, you can log exceptions as they go by without having to handle them and rethrow.

A throw statement can be used in a catch block to re-throw the exception that is caught by the catch statement. The following example extracts source information from an xref:System.IO.IOException exception, and then throws the exception to the parent method.

You can catch one exception and throw a different exception. When you do this, specify the exception that you caught as the inner exception, as shown in the following example.

You can also re-throw an exception when a specified condition is true, as shown in the following example.

[!NOTE] It is also possible to use an exception filter to get a similar result in an often cleaner fashion (as well as not modifying the stack, as explained earlier in this document). The following example has a similar behavior for callers as the previous example. The function throws the InvalidCastException back to the caller when e.Data is null .

From inside a try block, initialize only variables that are declared therein. Otherwise, an exception can occur before the execution of the block is completed. For example, in the following code example, the variable n is initialized inside the try block. An attempt to use this variable outside the try block in the Write(n) statement will generate a compiler error.

For more information about catch, see try-catch-finally.

Exceptions in async methods

An async method is marked by an async modifier and usually contains one or more await expressions or statements. An await expression applies the await operator to a xref:System.Threading.Tasks.Task or xref:System.Threading.Tasks.Task%601.

When control reaches an await in the async method, progress in the method is suspended until the awaited task completes. When the task is complete, execution can resume in the method. For more information, see Asynchronous programming with async and await.

The completed task to which await is applied might be in a faulted state because of an unhandled exception in the method that returns the task. Awaiting the task throws an exception. A task can also end up in a canceled state if the asynchronous process that returns it is canceled. Awaiting a canceled task throws an OperationCanceledException .

To catch the exception, await the task in a try block, and catch the exception in the associated catch block. For an example, see the Async method example section.

A task can be in a faulted state because multiple exceptions occurred in the awaited async method. For example, the task might be the result of a call to xref:System.Threading.Tasks.Task.WhenAll%2A?displayProperty=nameWithType. When you await such a task, only one of the exceptions is caught, and you can’t predict which exception will be caught. For an example, see the Task.WhenAll example section.

In the following example, the try block contains a call to the ProcessString method that may cause an exception. The catch clause contains the exception handler that just displays a message on the screen. When the throw statement is called from inside ProcessString , the system looks for the catch statement and displays the message Exception caught .

. code language=»csharp» source=»./snippets/RefKeywordsExceptions.cs» .

Two catch blocks example

In the following example, two catch blocks are used, and the most specific exception, which comes first, is caught.

To catch the least specific exception, you can replace the throw statement in ProcessString with the following statement: throw new Exception() .

If you place the least-specific catch block first in the example, the following error message appears: A previous catch clause already catches all exceptions of this or a super type (‘System.Exception’) .

. code language=»csharp» source=»./snippets/RefKeywordsExceptions.cs» .

Async method example

The following example illustrates exception handling for async methods. To catch an exception that an async task throws, place the await expression in a try block, and catch the exception in a catch block.

Uncomment the throw new Exception line in the example to demonstrate exception handling. The task’s IsFaulted property is set to True , the task’s Exception.InnerException property is set to the exception, and the exception is caught in the catch block.

Uncomment the throw new OperationCanceledException line to demonstrate what happens when you cancel an asynchronous process. The task’s IsCanceled property is set to true , and the exception is caught in the catch block. Under some conditions that don’t apply to this example, the task’s IsFaulted property is set to true and IsCanceled is set to false .

. code language=»csharp» source=»./snippets/AsyncExceptionExamples.cs» .

The following example illustrates exception handling where multiple tasks can result in multiple exceptions. The try block awaits the task that’s returned by a call to xref:System.Threading.Tasks.Task.WhenAll%2A?displayProperty=nameWithType. The task is complete when the three tasks to which WhenAll is applied are complete.

Each of the three tasks causes an exception. The catch block iterates through the exceptions, which are found in the Exception.InnerExceptions property of the task that was returned by xref:System.Threading.Tasks.Task.WhenAll%2A?displayProperty=nameWithType.

. code language=»csharp» source=»./snippets/AsyncExceptionExamples.cs» .

C# language specification

For more information, see The try statement section of the C# language specification.

Try catch, finally throw — or Exception Handling 101 for .NET

This post is written as a follow-up on one of my Facebook conversations. Exception handling is one of topics almost everyone understands differently. But any code we write is somehow relying on exception handling, so understanding all the details of this process is important — that’s why I finally made myself to write it 🙂

The post is mostly focused on .NET exception handling, though the rules are almost the same in nearly any other language supporting exceptions; some of C++, Python, and JavaScript specific rules are mentioned explicitly here. In short, if you use some other language and don’t know much about exception handling, reading this post definitely won’t be a waste of your time.

The most common questions on exception handling:

  • Which exceptions a particular piece of code (or method call) throws?
  • Which of these must be explicitly handled?
  • Are there some exceptions that might be thrown on almost any call?
  • When should I use “try-catch-rethrow”, and when — “try-finally”?
  • Which language keywords somehow use exception handling?
  • To throw or not to throw?
  • When a custom exception type is needed?
  • And finally, what’s the reasoning behind all these rules?

I’ll try to answer all these questions, so let’s start.

What exceptions a particular method call may throw?

Short answer is: it’s better for you to assume it can throw anything — no matter what you call. What can be thrown is not important — you should care only about what you can handle.

  • Even if you know a precise list of exceptions it throws now, in future its own implementation, or an implementation of one of its dependencies, may change.
  • If you call a virtual method (i.e. interface method as well), you don’t know which of its implementations is going to be invoked.
  • Almost any piece of code can throw OutOfMemoryException, StackOverflowException, ThreadAbordException, etc.

Of course, if you call a very simple method, such as int.Parse(…), you can more or less safely assume that it throws only X + a set of exceptions that could be thrown by any code. But you shouldn’t make any assumptions for even moderately complex methods you call.

Also note that interface / virtual method calls are very frequent in apps relying on Inversion of Control / Dependency Injection (in other words, in any apps built by experienced engineers): since the actual implementations of dependencies are injected into the constructor / properties, almost any class you write uses interfaces of these dependencies — so you never know the exact implementation.

All of this sounds crazy, right? I mean, how am I even supposed to write a code if any piece of code I write can throw anything?

The good side is: there are other rules that actually make all of this easy and logical. Let’s look at one of them:

Which exceptions must be explicitly handled?

First, let’s define what “to handle an exception” means. Actually, there are just 3 possible cases you should keep in mind:

  1. You want to catch and suppress an exception
  2. You want a part of your code to perform a set of actions independently on whether an exception was thrown or not
  3. You want to either mask an exception, i.e catch it and throw another one, or catch and rethrow it.

If your case doesn’t fall into one of these 3 categories, most likely you’re doing something wrong: other possible scenarios (e.g. when you need to take an action only for a certain kind of exception but still rethrow it) are necessary in maybe just 0.1% of all the cases.

Now let’s look closer on these 3 patterns.

1. Catch-and-suppress blocks

All these cases are similar to this one:

As you can see, we look for specific errors here (FormatException, OverflowException).

How do we know which errors we should look for?

  • Either from the documentation — in this case, from https://msdn.microsoft.com/en-us/library/b3h1hf19(v=vs.110).aspx
  • Or from the source code. If you use Visual Studio with ReSharper or Project Rider, it’s a single Ctrl-Click, even if you don’t have the source. Otherwise you can use dotPeek (free decompiler for .NET)— it’s a bit slower there, but still quite fast (Go To Anywhere and type the name).

Note that there is a 3rd kind of exception that can be thrown by int.Parse:

  • ArgumentNullException: we don’t handle it, since if it’s the case, most likely there is a mistake in caller’s code (it passes us null instead of a valid string), and thus he deserves to get the ground truth.

So when do you need a “catch” block? The key question you should ask is: can you do anything meaningful to gracefully handle some of possible errors in this specific call?

  • If you can, you should check out what kinds of exceptions can be thrown there
  • If no, you shouldn’t do anything.

Also note that the example I provided here is actually an example of wrong use of int.Parse: this method has a sibling allowing to us write the same code as:

TryParse returns true when parse succeeds, and false otherwise. So in fact, MyIntParse is redundant.

And here we come to another interesting question: why there are two “flavors”of int.Parse? Or in other words, if I have to write a method that throws some exceptions — when do I need to provide a similar overload that doesn’t throw an exception?

This naturally leads us to…

The cost of throwing an exception

You should know two points here:

  • Throwing an exception is extremely expensive operation in most of statically typed languages.
  • On contrary, try-catch-finally itself is extremely cheap — e.g. in C# its cost is close to zero.

You can try it online here. The output of this code:

As you see, try-catch version is

150 … 300x slower when there is an error, otherwise it’s similarly fast. In other words, you can throw and catch only about 90K exceptions per second in a single thread on CoreCLR, and only about 50K / second — on “vanilla” .NET.

Presence of try-catch doesn’t add anything to the overall time. That’s expected: try-catch-finally only leads to a bit different layout of a code generated by JIT compliler (there are more jmp instructions), but it’s still the same code. The addresses of “catch” and “finally” blocks to process are identified only when an exception is thrown — when a stack walk happens (note that it is anyway necessary to capture the stack trace). A return address on each stack frame is enough to identify the address of corresponding exception handling code, and .NET runtime actually has this mapping.

As a result, the version with exception handling is the fastest one — mainly, because it doesn’t allocate as much on call stack as others do, though in this case the difference is tiny: TryParse accepts a single extra pointer. But as you see, this 1ms (

2%) difference is still measurable.

And that’s a nice example of why exception handling is good, and how it’s supposed to be used: all “try” blocks add zero overhead in case of normal execution flow. So if you throw exceptions only in truly exceptional cases, your performance won’t suffer at all. Based on above measurements, if you throw

5000 exceptions per second per thread, you should expect

1% performance degrade. In reality, you should minimize the frequency of throws to a bare minimum, and later we’ll discuss what are more precise criteria of when an exception has to be thrown.

The cost of throwing an exception in dynamically typed languages

Interestingly that in these languages it is typically very different:

    Throwing an exception is nearly as cheap as returning from the function. That’s because under the hood exceptions are implemented as a special result type. Stack walk there is still required, but since stack in many of such languages is stored in heap (each stack frame is

All that I wrote here is mainly based on my past experience with CPython. One of pretty unexpected consequences of such a behavior there is that all generators (

enumerators in C#) have a single next() method expected to return the next item from the sequence, or throw StopIteration exception, if there are no more items. And that’s a huge language design issue:

  • Exception is misused here: it signals about the end of sequence, which is absolutely normal case — and moreover, a very frequent one.
  • If you use an IDE with a debugger, this single thing makes “break on any exception” feature useless — unless you always tell to explicitly ignore this exception.
  • Probably the worst consequence of this is that this single feature will slow down any JIT-based Python interpreter (PyPy, IronPython, etc.) — there interpreters have to either rely on native exceptions and pay a huge cost per every generator, or implement non-native exception handling closer to the way CPython does this, but in this case they have to pay an extra cost per every method call.

This is a remarkable example showing that even Guido van Rossum probably didn’t know how to deal with exceptions at the moment he’ve made this decision. As a result of this single mistake, his creation (Python) will never be as fast as e.g. JavaScript — no matter how sophisticated the Python interpreter or JIT compiler is. Honestly, there are many other design issues making Python one of the worst choices, if you care about speed, but it’s still a very good choice for any research / data science / ML projects, where speed is either not a concern, or it’s “covered” by non-Python code.

Anyway, this issue seems to be a very strong “showcase” explaining why it makes sense to know the fundamentals of exception handling for every developer.

2. Try-finally blocks

We covered one out of three cases I listed earlier:

  1. You want to catch and suppress an exception — we just discussed this
  2. You want a part of your code to perform a set of actions independently on whether an exception was thrown or not — we’re here now

So the question for this section is: when do I need a “finally” block?

The answer here is actually the simplest one: you need “finally” only in case you need to take certain actions immediately after the completion of “try” block, and independently on whether there was an error or not.

This sounds like a definition of what “finally” does, so let me clarify this further:

  • The only action you should worry about is resource disposal.
  • If it’s about language without garbage collection (e.g. C++), deallocation is probably #1 problem you should worry about here. If there is no “finally” and you don’t rely on auto pointers, you’re inevitably getting a memory leak on any exception.
  • Languages with GC are very different though: GC will anyway destroy any unreferenced object, so it sounds like you should’t do anything here. But note that GC normally doesn’t provide any guarantee on how quickly the object is going to be destroyed after it becomes unreachable for the code — and that’s mostly why finally blocks are useful there.

Resource disposal in .NET

There are two main features in .NET runtime that are dedicated to resource disposal:

  • Finalizers — these methods are similar to destructors in C++, the only difference is that they are invoked automatically at some point between the moment the object becomes unreachable (i.e. should be collected by GC) and the moment it’s actually collected. The underlying implementation of this feature is actually more complex than it seems, and fairly expensive — later I’ll show how much expensive it is. But for now it’s enough to know that finalizer is called very reliably, i.e. if you really need to do some cleanup no matter what, likely, you need a finalizer.
  • IDisposable — that’s an interface with a single Dispose() method. This method is supposed to be invoked right at the point when you know for certain you won’t need the object that implements IDisposable further. The method itself is supposed to instantly dispose any resources held by the object.
  • Both features are closely related: if you implement finalizer, you are always expected to implement IDisposable as well.

A few other rules related to IDisposable and finalizers are less well-known:

  • Since finalizer should always go with IDisposable implementation, the only thing finalizer should do is to call Dispose. If you care about speed and inheritance, it’s better to implement even more complex pattern, but for now it’s enough to remember this.
  • Ideally, Dispose() method must render the object completely unusable. I.e. it’s a good idea to nullify all its fields inside Dispose(), or do something else to make sure that almost any method call on this object will lead to an exception. Microsoft recommends to throw ObjectDisposedException in all of such cases, but on practice this requirement is not quite useful: it requires a boilerplate check for disposal in every method and property, which will impacts performance a bit, and moreover, no one really catches ObjectDisposedException, so debugging of too early disposal is the only thing you’ll simplify by making sure this exception is always thrown. This is why most of developers prefer to simply render the object unusable.
  • Dispose() should never throw exceptions when called. It doesn’t mean you have to put “try-catch-everything” there — just note that normally all this method does is calling other Dispose methods on fields, and they also aren’t supposed to throw anything. But it’s a very very bad idea to explicitly throw an exception from Dispose — in fact, the only case you may want to do this is when you know for sure that the state you have now corrupted, and it’s important to signal about this right now rather than waiting for some (possibly hidden) problems in future. Also note that Dispose is almost always called from “finally” blocks, so throwing an exception from Dispose means you’re going to mask the original exception (which in turn supposed to mean that the new one you’re throwing is much more important).
  • Dispose() should dispose all “owned” objects. Normally these objects are referenced by some fields, so if any object there supports IDisposable, you should dispose it from your own Dispose().
  • Dispose() must support multiple invocations — i.e. not only the first call to Dispose should complete without an error, but also the second, third, etc. — for the same instance. This is necessary because the order of invocation of finalizers isn’t defined, and finalizers usually call Dispose. As I wrote above, Dispose implementation is supposed to call Dispose on all “owned” objects, thus some of such owned objects may get two Dispose call — one from the owner (when owner’s finalizer is invoked), and another one from its own finalizer.
  • When to implement a finalizer? The only case you need it is when your object “owns” some resource that needs to be reliably disposed no matter what (i.e. even if developer will forget to do that explicitly), and moreover, this resource itself doesn’t have its own finalizer. Note that an “official” rule on of when you need a finalizer is a bit different: it’s necessary when you have some unmanaged resources, and as a result, even smart developers misunderstand that. Let me give you an example of when you need a finalizer, though you don’t have an unmanaged resource: you design a type requiring a temporary file to work, and when the instance of this type is created, it also creates that file. But when the object is going to be disposed, this file also needs to be removed. The class you’ll use to access the file will almost certainly have a finalizer, but this finalizer will only ensure that the underlying OS file handle is closed on disposal, i.e. it won’t delete the file itself. Consequently, you need a finalizer here, and the only reason for this is: if developer using your class somehow forgets to dispose it, the file won’t be removed. But your finalizer will ensure that file will be eventually removed — e.g. a few minutes later, but still, it will happen.
  • When to implement IDisposable? If you have a finalizer, or “own” other disposables (i.e. there are fields castable to IDisposable), or you need to do some extra cleanup neither of your “owned” disposables does (e.g. a file removal in previous example).

Let’s see how disposables and finalizers are supposed to be implemented:

Now, let’s get back to “finally” — how all of this is related to finally blocks?

  • If you allocate an IDisposable, and its lifetime ends inside the same method, you need try-finally; “finally” there should call Dispose for any of such disposables.

Note that IDisposable itself implements the same pattern, but for “owned” disposables referenced from object’s fields.

Now, are there any language features that simplify writing these try-finally blocks? Yes:

C# keywords relying on try-finally

There are 3 other C# keywords that use try-finally under the hood:

  • “lock” is a shortcut for critical section; it needs “finally”, since otherwise the lock may not be released, and you end up with a deadlock. Note that “lock” is the only of these 3 keywords that doesn’t rely on IDisposable — the reasoning here is that you don’t want to do an extra allocation per every enter-exit, though the same is possible with “using” + IDisposable, if IDisposable is implemented by a value type (struct). So in reality the reasons for this are only historical — .NET “borrowed” lock syntax from Java, and as a result, you can use Monitor.Enter/Exit with any reference type there. Interestingly that no one needs this in practice— locks are typically held on private dedicated fields of Object type, and “lock (this)” syntax is explicitly recognized as an anti-pattern. So if you’ll be designing your own language some day, please borrow the patterns from other languages carefully 🙂
  • “using” is a shortcut for “get disposable, use it, and dispose it”. This is an absolutely nice and quite useful feature missing in many other languages. Interesting that e.g. Python has context managers + “with” syntax, but “using” is actually much nicer: it solves a very common problem in absolutely minimalistic way (the only extra method you need to implement is IDisposable.Dispose). On contrary, in Python you have to implement both __enter__ and __exit__, you are allowed to return another object from __enter__, must consume an exception and lots of other info in __exit__ (which in turn means that “with” requires try-catch under the hood rather than try-finally, + locals for exception, etc.) — in short, this feature was never designed with performance in mind in Python. And on contrary, “using” in .NET is — it adds literally zero overhead, and as I mentioned above, it doesn’t box value types passed as IDisposables, so if you want to use it for something like locking or “scopes”, you may rely on structs implementing IDisposable to have zero heap allocations as well.
  • “foreach” is probably something you didn’t expect to see here, but it relies on IEnumerable<T>\IEnumerator<T> API, and IEnumerator<T> is inherited from IDisposable — this is why it also requires “using”. But why all IEnumerator<T> instances are supposed to implement IDisposable? It’s clear that some complex enumerators may need this to dispose the resources right when you exit the loop (e.g. with “break”), but what about simple enumerators, e.g. for .Where or other LINQ-to-enumerable methods? In reality, it’s very logical for them as well — just imagine a case when a simple enumerator is relying on complex one under the hood, i.e. you write something like someQueryable.AsEnumerable().Where(…). Since most of enumerators own other enumerators (i.e. they own disposables), it’s totally logical to make all of them to implement IDisposable.

Note on finalization cost

I promised to show there is an extra cost associated with having a finalizer. A part of this cost is paid when such an instance is created:

The output (this time it’s from my Core-i7 laptop):

In plain English:

    You can allocate

30% slower on regular allocations, but

Summary on try-finally

If there would be no “using” keyword, your “finally” section would be responsible mainly for resource disposal. But since there is “using”, normally you won’t need even this — typically, you’ll have this code instead:

So normally you’ll use “finally” for other purposes — e.g. to implement lock-unlock pattern or something similar. In reality even this is rarely necessary in .NET — mainly, because most of types requiring this kind of behavior also provide IDisposable implementation to support dispose pattern — e.g. that’s how it works with AsyncLock from AsyncEx.

This is why if your “finally” looks complex (or even exists) in .NET, there is a good chance you’re doing something wrong. Probably the most frequent case is when your code needs it because your own types require some finalization, although they don’t implement IDisposable — i.e. basically, you have to rely on “finally” instead of “using” only because you don’t know .NET well enough.

3. Try-catch-(re)throw and exception masking

To recap, that’s where we are now:

  1. You want to catch and suppress an exception — we discussed this
  2. You want a part of your code to perform a set of actions independently on whether and exception was or wasn’t thrown — we discussed this
  3. You want to either mask an exception, i.e catch it and throw another one, or catch and re-throw it — this is where we are now.

Catch-and-rethrow case is fairly simple: it’s mostly used for logging, and for passing the information about exception to the “finally” block. Consider this example:

The last case to cover is catch-and-mask, i.e. throw some other exception instead. In general you should avoid this by all means, though there are a few rare cases where it makes sense:

  • Cases similar to AggregateException — i.e. when you run a set of independent jobs, and some of them may fail. Rethrowing the error that occurred first is, of course, one of options in such cases, but in this case you won’t know anything about other exceptions. So if the second is definitely more important, you should aggregate all the errors and throw the exception that allows to enumerate all of them.
  • Cases similar to TargetInvocationException — i.e. when it’s more important to indicate that an error has occurred on remote side (this isn’t confusing) rather than try to deserialize and throw it on your side like if it was local (this is definitely confusing). Note that TargetInvocationException.InnerException typically references the original error here, so in fact, you don’t lose anything — moreover, if you want, you can even re-throw the inner exception manually.

I can’t remember any other well-known examples of this, which means that you should think about doing something similar in a very very rare cases.

Are there some exceptions that might be thrown on almost any call?

Let’s list all of them:

    StackOverflowException — obviously. It’s interesting that with async-await it’s probably more likely to get it, because continuations there can be started right after a completion of a task, i.e. each continuation in chain of continuations recursively awaiting each other can extend the depth of call stack by

So there 4 of such exceptions, but I suspect there might be something else 🙂

Should you do anything special about these exceptions? Nope. To deal with them, you should just properly place your try-finally / using / lock blocks.

To throw or not to throw?

Obviously, any throw can be replaced with a special type of result. So it’s good to know what’s preferable in a particular case — a special type of result, or an exception?

I don’t remember if I ever saw a very precise description of how to approach this — probably, because there is no “perfect” answer. The most important criteria are:

Throw an exception, if, when thrown:

  1. It normally can’t be meaningfully handled with try-catch — i.e. it indicates there is a mistake in caller’s logic.NullReferenceException, ArgumentException, ArgumentNullException, OverflowException, IndexOutOfRangeException, and KeyNotFoundException are examples of such errors: they almost always mean there is some mistake in caller’s logic. In fact, such exceptions are thrown by assertions that can be done only in runtime, though if it would be possible, you’d prefer them to trigger even at compile time.
  2. It indicates a relatively rare condition of uncontrollable nature, that makes it impossible to perform the requested operation.IsolatedStorageException, HttpRequestException and SmtpException are examples of such errors. Note that such errors may indicate there is a mistake in caller’s logic as well, though they can be thrown even if there is no error. Exceptions of this kind normally require try-catch blocks, and there is a high chance that such blocks will exist only in indirect callers — so if that’s the case, quite likely you should also think about custom exception type (I’ll write more on this further).

Return a special type of result, when:

  1. You can easily imagine a scenario when this type of result is going to be very frequent.int.Parse is a good example: imagine you’re a developer writing this method, and now you think how it’s going to be used. Counting all the numbers in a random text file is clearly one of imaginable scenarios, right? So if you want your method to support this scenario, you should always opt out for special type of result: frequently thrown exceptions make debugging way more complex, and moreover, they impact on performance. When you throw, it really should be an exception, but not an ordinary result. You may think that something similar is possible with e.g. HttpRequestException — e.g. when your internet is down. But actually that’s a very different case: if your internet (or some servers) is down, you can simply reduce the rate of requests / attempts to reduce the error rate without any negative consequences. But you can’t achieve the same with int.Parse assuming it’s used for counting numbers in a random text file without sacrificing the performance.

And finally, when in doubt, think of implementing both. In .NET such methods are normally implemented with TryXxx pattern:

  • TResult Xxx(args…) — the one that throws on any errors
  • bool TryXxx(args, out TResult result) — the one that throws only on “type 1” errors (i.e. when it’s clear there is a mistake in caller’s logic); otherwise it either returns false + default(TResult) (the operation can’t be performed), or true + actual result.

Why both methods can be useful? Let’s think about int.Parse again:

  • If you’re parsing an integer from configuration file and there is no good way to handle non-integer value there, you should prefer int.Parse — in this case it will automatically cause the code reading configuration to fail, which, in turn, simply won’t let your app to start normally. IMO that’s way better than e.g. to implicitly substitute that integer with zero and get a completely unexpected behavior at some later point.
  • If you’re counting all the integers in text file, you should prefer int.TryParse.

Note that C# 7 also supports tuples, and I suspect returning a tuple of (bool, TResult) is a bit faster alternative to bool + out TResult. But since no one uses this alternative just yet, I recommend to stick to the old one for now.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *