-
Notifications
You must be signed in to change notification settings - Fork 4.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Proposal: Guard statement in C# #8181
Comments
@alrz right. While I like contract I think this is quite different from #119: the int fib(int n) {
guard (n > 1) else return 1;
return fib(n - 1) + fib(n - 2);
} The
void Configure(Configuration c) {
guard (URLExists(c.URL) or FileExists(c.File)) else {
Console.WriteLine("Supplied configuration does not contain a valid file or URL.");
return;
}
// ...
} |
And what's wrong with if (!(b > 0)){
throw new InvalidArgumentException(...)
} It handles your case. I don't find that |
@mburbea This is what I explained in the "Problem" section, the main issue with the if (b == null) {
// print a complicated error
// forget to return or throw
}
foo = b.bar(); // <-- control drops here in the b is null case and we get an error So yes, benefits are:
|
I will say that the one reason that I dislike this and #119 is that it doesn't offer any proposal for metadata attached to the method to declare such constraints. Handling illegal arguments is one thing, preventing them from happening is significantly more. |
@HaloFour See my examples in the comment above:
|
@Eyas You're right. I think after the |
I still think that the requirements this proposal seeks to address would be supported by pattern matching -- I think it would even look fairly similar. Consider this pseudocode:
You get all the requested features above:
|
The big problem with guard statements (and pattern matching in general) is that the boolean test isn't able to be abstractable out e.g. Bar Foo(int b) {
guard (b > 0) else {
throw new IllegalArgumentException(nameof(b));
}
body;
} Forcing you to repeat the [Message("Number isn't positive")]
guard Positive<T>(T value) {
return value > 0;
}
Bar Foo(Positive<int> b) {
int c = b;
if (c is Positive<int>) print("asdf");
...
} was syntax sugar for: Bar Foo(int b) {
if (!(b > 0)) throw ArgumentException("Number isn't positive", nameof(b));
int c = b;
if (c is int && ((int)c > 0)) print("asdf");
...
} |
I don't know what that has to do with public static void GuardIsPositive(int param, string name) {
guard (x > 0) else throw new ArgumentException("Number isn't positive.", name);
}
Bar Foo(int b) {
GuardIsPositive(b, nameof(b));
} That seems significantly cleaner than trying to hack something into the type system. As for reusability in pattern matching, that sounds like it would be active patterns. |
There are 2 annoyances with the helper function approach that make it less cleaner than hacking it into the type system for common places in code:
Bar Foo(GuardIsPositive b) {
}
// vs
Bar Foo(int b) {
GuardIsPositive(b, nameof(b));
}
[Message("Number isn't positive.")]
guard positive(int value) {
return value > 0;
}
Bar Foo(positive b) {
object c = b;
if (c is positive) print("yay");
}
// vs
public static void IsPositive(int value) {
return value > 0;
}
public static void GuardIsPositive(int value, string name) {
if (!IsPositive(value)) else new ArgumentException("Number isn't positive.", name);
}
Bar Foo(int b) {
GuardIsPositive(b, nameof(b));
object c = b;
if (c is int and IsPositive((int)c)) print("yay");
} |
@DerpMcDerp It's less code, but that doesn't make it cleaner. It hides much of what it does through syntactic voodoo. It blends the concerns of normal conditions and validation. It hijacks the type system in a completely unprecedented way and changes the public contract of methods using them. As "erased" it can't be reused, requiring all of these common "guards" to be constantly reimplemented over and over again. If we're going to go down the path of declarative validation I'd rather explore AOP options through code generators. That or #119. |
What |
Issue moved to dotnet/csharplang #511 via ZenHub |
For those whose searching lands you here, the canonical issue for this is dotnet/csharplang#138. |
Background
Swift has a guard statement that can be very valuable in writing code that is easy to reason about. See Swift Guard Statement - why you should use it.
Problem
If we have a function
Foo
that takes an integer that must be positive (> 0
), we can write that as:Proposed Solution:
guard
statementsBenefits
The guard statement introduces two benefits over
if
-based guards:Grammar
The text was updated successfully, but these errors were encountered: