3

As you can see in the C# documentation, we can write null coalescing operators combined with a throw expression. Something like below:

public string Name
{
    get => name;
    set => name = value ?? 
        throw new ArgumentNullException(paramName: nameof(value), message: "Name cannot be null");
}   

But in many cases, I need a return statement if the left value is null, for example something like below:

public double CalculateSomthing(ClassType someInstance)
{
    var someValue = someInstance?.Value ?? return double.NaN;

    // Call some method
    AnyMethod(someValue);

    // Some Computation
    return someValue + 17;
}

But this does not work. Why can I write a throw expression with a null coalescing operator, but can't use a return statement with the coalescing operator? Is there any intrinsic difference between the two?

I'm wondering if some version of C# allows me to write something like the above code, or if I'll have to write some boilerplate code, such as:

public double CalculateSomthing(ClassType someInstance)
{

    var someValue = someInstance?.Value;
    if (someValue == null) return double.NaN;  // Boiler plate

    // Call some method
    AnyMethod(someValue);

    // Some Computation
    return someValue + 17;
}
13
  • You can write such code since C# 6. The throw expression was added in C# 7. Commented Sep 16, 2019 at 16:14
  • 2
    @LGSon OP is asking for a return expression instead of a return statement. Just like the throw statement has been changed from C#6 to C#7 so that it can be used in places where the compiler normally expects an expression. They don't want to continue the method in case the first value is null, but instead of throwing only want to return. This is technically possible, but obviously nobody asked hard enough for it and no one wanted to implement, test, document and ship it. Commented Sep 16, 2019 at 16:18
  • someValue??double.NaN is valid. What did you try, what failed? Are you sure the problem isn't the invalid return statement after ?? ? Commented Sep 16, 2019 at 16:18
  • 1
    You can post a proposal on the C# Language GitHub page. If it makes to the Champions, maybe you'll see this feature in an upcoming language version. Commented Sep 16, 2019 at 16:20
  • 1
    Sometimes there is no substitute for an if statement and I would hardly call it boilerplate, any more than ?? return ... would be. Commented Sep 16, 2019 at 22:12

2 Answers 2

6

Return/break/continue expressions have been discussed but are hard to implement correctly as they interfere with other constructs. At best, it's a convenience feature. As the design meeting notes say:

This is a convenience. However, it risks a syntactic conflict with other potential futures, especially "non-local returns" (allowing a lambda to return from its enclosing method) and "block expressions" (allowing statements inside expressions). While we can imagine syntaxes for those that do not conflict, we don't want to limit the design space for them at this point, at least not for a feature that is merely "nice to have".

Also, while we've been talking about this in analogy with throw expressions, that isn't quite right. throw is a dynamic effect, whereas return, break and continue are statically bound control transfers with a specific target.

As the discussion shows, it's hard to come up with a compelling example while there are many alternatives already.

In any case return expressions aren't similar to throw expressions. An exception is neither a control flow nor a return mechanism. It's a blown fuse that needs handling, otherwise the application can't continue.

Throw expressions aren't just convenience, they allow throwing exceptions in places where only an expression is valid. That's why they are used in functional languages like F#'s raise and failwith functions. Without them pattern matching constructs like C#'s switch expressions or F#'s match expressions would be impossible to write and analyze at compile time.

Without throw expressions this:

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        _              => throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand)),
    };

Would have to be rewritten to include a dummy return statement , making analysis of the code during compilation needlessly hard. The compiler would have to recognize this patter, ignore the return statement and use the throw statement to verify whether the code is valid or not :

public static RGBColor FromRainbow(Rainbow colorBand) =>
    colorBand switch
    {
        Rainbow.Red    => new RGBColor(0xFF, 0x00, 0x00),
        Rainbow.Orange => new RGBColor(0xFF, 0x7F, 0x00),
        _              => { 
                              throw new ArgumentException(message: "invalid enum value", paramName: nameof(colorBand));
                              return default;
                          }
    };

That return default would also play havoc with C# 8's nullable reference types and nullability analysis.

C# 8 took advantage of C# 7's throw expressions to offer switch expressions. C# 9 will take advantage of both to offer discriminated unions and (hopefully) exhaustive pattern matching.

Sign up to request clarification or add additional context in comments.

2 Comments

We're now at C#... 12, I think? Is there anything in this answer that could be updated to reflect any new additions (e.g., the last paragraph of the answer)?
@TylerH if you mean DUs, nothing. They keep getting pushed to the next version every year. The GH discussion is going on for 7 years. I see there was another meeting this July and now people are talking about type unions but everything is still at the proposal level. Which means, probably not on .NET 10 either
-2

A few years later...

I literally tried { continue; } but I think I understand what others are saying about undesirable side effects.

What the null-coalescing operator will not do is infer the return type of a lambda, so you have to declare it somewhere else.

Option A is to coalesce to an inline delegate:

double Trivial(string a, string b)
{
  a == b ? 1 : string.IsNullOrEmpty(a) ? 0 : null;
}

double MyCalc(string a, string b)
{
  double calc(string a, string b)
  {
    // non-trivial calculation
  }

  return Trivial(a, b) ?? calc(a, b);
}

Option B is verbose null-coalescing:

var trivial = Trivial(a, b);

if (trivial.HasValue)
{
    return trivial.Value;
}

// non-trivial calculation

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.