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:
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:
// 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:
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 (??
):
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:
// 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:
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:
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:
try
{
var email = new EmailMessageBuilder()
.SetFrom("[email protected]")
.SetTo("[email protected]")
.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
-
Keep it Simple: Throw expressions are best for simple validation scenarios. For complex validation logic, consider using dedicated validation methods.
-
Be Consistent: Choose a consistent approach for parameter validation across your codebase.
-
Documentation: Make sure your exceptions are well-documented, especially when using throw expressions in public APIs.
-
Meaningful Exceptions: Always use the most appropriate exception type and provide meaningful error messages.
-
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
- Refactor the following method to use throw expressions:
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();
}
-
Create a
Person
class with properties forName
,Age
, andEmail
. Use throw expressions to validate that:- Name is not null or empty
- Age is between 0 and 120
- Email is not null
-
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 [email protected]. I’d greatly appreciate your feedback! :)