Throw vs. Throw ex - Back to the basics
This is another article in the “Back to the basics” series. The first part covered Boxing and UnBoxing, today we’ll look at the difference between throw
and throw ex
.
💁 If you know the difference between
throw
andthrow ex
you don’t need to read any further, you won’t learn anything new.
Throw
and throw ex
are both used to raise/forward the exception above. However, their behavior is different. The difference is whether the stack trace is preserved or not.
try
{
var dataProvider = new DataProvider();
var responseBody = await dataProvider.GetData();
Console.WriteLine(responseBody);
}
catch (Exception ex)
{
// 👇 throw ex - create and throw new exception based on ex
throw ex;
}
public class DataProvider
{
public async Task<string> GetData()
{
using var client = new HttpClient();
var response = await client.GetAsync("https://www.nonexistingdomain.com");
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsStringAsync();
}
}
In the example above, if an exception occurs when GetData
is called, the exception is caught in the catch
block and forwarded upstream using throw ex
. However, the original stack trace will be lost and the exception will only contain information from where it was raised:
Unhandled exception. System.Net.Http.HttpRequestException: The requested name is valid, but no data of the requested type was found. (www.nonexistingdomain.com:443)
---> System.Net.Sockets.SocketException (11004): The requested name is valid, but no data of the requested type was found.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at Program.<Main>$(String[] args) in D:\Zmaz\throwVsThrowEx\Program.cs:line 12
We lost information about where the exception went and therefore the ability to identify the problem faster.
Conversely, if we use throw
without ex
:
try
{
var responseBody = await GetData();
Console.WriteLine(responseBody);
}
catch (Exception ex)
{
// 👇 throw - rethrow the original exception
throw;
}
The exception will also contain the original stack trace:
Unhandled exception. System.Net.Http.HttpRequestException: The requested name is valid, but no data of the requested type was found. (www.nonexistingdomain.com:443)
---> System.Net.Sockets.SocketException (11004): The requested name is valid, but no data of the requested type was found.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
// 👇👇👇 an important part of the stack trace that was missing in the previous example
at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem)
at System.Threading.Tasks.TaskCompletionSourceWithCancellation`1.WaitWithCancellationAsync(CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.<SendAsync>g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken)
at DataProvider.GetData() in D:\Zmaz\throwVsThrowEx\Program.cs:line 20
at Program.<Main>$(String[] args) in D:\Zmaz\throwVsThrowEx\Program.cs:line 7
In this example, we can see that there are parts of HttpConnectionPool
, HttpClient
and our DataProvider
class that were missing in the previous example and can help us identify the problem faster.
So, if we want to keep the original stack trace, let’s use throw
without ex
. If we want to discard the stack trace, let’s use throw ex
.