1
votes

As you can see in C# documentation, we can write null coalescing operator combine with 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 return statement if 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 I know I could not write code something like above, my question is why? why C# language designer permit to write throw expression with null coalescing operator but don't permit use return statement with coalescing operator?

Is any intrinsic difference between them?

Is there any near solution like above example in exist version of C#? or I have to write some boiler plate code, something like below

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;
}
1
You can write such code since C# 6. The throw expression was added in C# 7. - Panagiotis Kanavos
@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. - René Vogt
someValue??double.NaN is valid. What did you try, what failed? Are you sure the problem isn't the invalid return statement after ?? ? - Panagiotis Kanavos
@PanagiotisKanavos The invalid statement is what OP is asking for: a return expression (instead of a statement) just like there is a throw expression. They want to return the right side value in case the left side value is null. - René Vogt
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. - dymanoid

1 Answers

3
votes

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.