Skip to main content

C# Throw Expressions

Introduction​

In C# 7.0, Microsoft introduced a new feature called "throw expressions" that expanded where you can use the throw statement in your code. Prior to C# 7.0, throw could only be used as a statement on its own line. With throw expressions, you can now use throw in expression contexts like conditional expressions, null-coalescing expressions, and expression-bodied members.

This feature helps make your code more concise while still maintaining proper error handling, which is particularly useful in expression-based code where previously you'd need multiple lines and additional logic.

Understanding Throw Statements vs. Throw Expressions​

Before we dive into throw expressions, let's clarify the difference between traditional throw statements and the newer throw expressions.

Traditional Throw Statement​

A throw statement has always been available in C# and looks like this:

csharp
if (value == null)
{
throw new ArgumentNullException(nameof(value));
}
return value.ToString();

The throw statement can only be used on its own line as a standalone statement.

Throw Expression​

With C# 7.0, you can use throw as part of an expression:

csharp
// Using throw in a conditional (ternary) operator
string result = value ?? throw new ArgumentNullException(nameof(value));

This makes your code more concise while maintaining proper error handling.

Common Use Cases for Throw Expressions​

In Conditional (Ternary) Operators​

One common use case is within a conditional (ternary) operator:

csharp
public void ProcessValue(string input)
{
// Traditional way (pre-C# 7.0)
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}

// Using throw expression (C# 7.0 and later)
var processedValue = input != null
? input.ToUpper()
: throw new ArgumentNullException(nameof(input));

Console.WriteLine($"Processed value: {processedValue}");
}

With the Null-Coalescing Operator​

Throw expressions work elegantly with the null-coalescing operator (??):

csharp
public string FormatName(string name)
{
// If name is null, throw an exception instead of proceeding
return (name ?? throw new ArgumentNullException(nameof(name))).Trim();
}

// Example usage:
try
{
string result = FormatName(null);
}
catch (ArgumentNullException ex)
{
Console.WriteLine($"Error: {ex.Message}");
// Output: Error: Value cannot be null. (Parameter 'name')
}

In Expression-Bodied Members​

You can use throw expressions in expression-bodied methods, properties, and constructors:

csharp
// Expression-bodied property with validation
public string Username
{
get => _username;
set => _username = value ?? throw new ArgumentNullException(nameof(value));
}

// Expression-bodied method with validation
public string GetValueOrDefault(string value) =>
value ?? throw new ArgumentNullException(nameof(value));

In Null-Conditional Expressions​

Combined with null-conditional operators:

csharp
public void ProcessOrder(Order order)
{
// If order is null, throw an exception
// Otherwise, access the CustomerId property
var customerId = order?.CustomerId
?? throw new ArgumentNullException(nameof(order));

// Process the order using the customer ID
Console.WriteLine($"Processing order for customer: {customerId}");
}

Real-World Example: Builder Pattern with Validation​

Here's a real-world example using throw expressions in a builder pattern:

csharp
public class EmailMessageBuilder
{
private string _from;
private string _to;
private string _subject;
private string _body;

public EmailMessageBuilder SetFrom(string email) =>
email == null
? throw new ArgumentNullException(nameof(email))
: this.Apply(b => b._from = email);

public EmailMessageBuilder SetTo(string email) =>
email == null
? throw new ArgumentNullException(nameof(email))
: this.Apply(b => b._to = email);

public EmailMessageBuilder SetSubject(string subject) =>
this.Apply(b => b._subject = subject ?? throw new ArgumentNullException(nameof(subject)));

public EmailMessageBuilder SetBody(string body) =>
this.Apply(b => b._body = body ?? throw new ArgumentNullException(nameof(body)));

public EmailMessage Build()
{
return new EmailMessage(
_from ?? throw new InvalidOperationException("From address is required"),
_to ?? throw new InvalidOperationException("To address is required"),
_subject ?? throw new InvalidOperationException("Subject is required"),
_body ?? throw new InvalidOperationException("Body is required")
);
}

// Helper method to enable fluent interface
private EmailMessageBuilder Apply(Action<EmailMessageBuilder> action)
{
action(this);
return this;
}
}

public class EmailMessage
{
public string From { get; }
public string To { get; }
public string Subject { get; }
public string Body { get; }

public EmailMessage(string from, string to, string subject, string body)
{
From = from;
To = to;
Subject = subject;
Body = body;
}
}

Example usage:

csharp
try
{
var email = new EmailMessageBuilder()
.SetFrom("sender@example.com")
.SetTo("recipient@example.com")
.SetSubject("Hello")
.SetBody(null) // This will throw an ArgumentNullException
.Build();
}
catch (ArgumentNullException ex)
{
Console.WriteLine($"Error building email: {ex.Message}");
// Output: Error building email: Value cannot be null. (Parameter 'body')
}

Best Practices for Throw Expressions​

  1. Keep it Simple: Throw expressions are best for simple validation scenarios. For complex validation logic, consider using dedicated validation methods.

  2. Be Consistent: Choose a consistent approach for parameter validation across your codebase.

  3. Documentation: Make sure your exceptions are well-documented, especially when using throw expressions in public APIs.

  4. Meaningful Exceptions: Always use the most appropriate exception type and provide meaningful error messages.

  5. Don't Overuse: While throw expressions can make code more concise, overusing them might decrease readability for complex conditions.

Limitations of Throw Expressions​

  • Throw expressions cannot be used everywhere. For example, they cannot be used as operands of operators like +, -, *, etc.
  • Complex validation logic might be harder to read when using throw expressions.
  • Debugging can sometimes be more challenging with condensed code.

Summary​

Throw expressions, introduced in C# 7.0, allow you to use the throw statement in expression contexts, making your code more concise and expressive. They work particularly well with:

  • Conditional (ternary) operators
  • Null-coalescing operators
  • Expression-bodied members
  • Fluent interfaces and builder patterns

This feature enhances your ability to write clean and concise code while still maintaining proper validation and error handling. While it's a powerful feature, it's important to use it judiciously and maintain code readability.

Additional Resources​

Exercises​

  1. Refactor the following method to use throw expressions:
csharp
public string ProcessData(string input)
{
if (input == null)
{
throw new ArgumentNullException(nameof(input));
}

if (input.Length == 0)
{
throw new ArgumentException("Input cannot be empty.", nameof(input));
}

return input.ToUpper();
}
  1. Create a Person class with properties for Name, Age, and Email. Use throw expressions to validate that:

    • Name is not null or empty
    • Age is between 0 and 120
    • Email is not null
  2. Implement a calculator class that uses throw expressions to handle division by zero and other invalid operations.



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)