|
Just FYI, .Net core 6 adds this oneliner.
ArgumentNullException.ThrowIfNull(input)
However, I disprefer it over an extension method that I wrote myself that uses generics and allows this syntax:
input.ThrowIfNull<TException>();
Or for a string
input.ThrowIfNullEmptyOrWhitespace<TException>();
I don't have this in a NuGet package yet, but I plan to add it to a Rhyous.Validations nuget package soon. Here is the source.
public static class ParameterValidationExtensions
{
public static void ThrowIfNull<TException>(this object objectToCheck, params object[] exceptionParams)
where TException : Exception
{
if (objectToCheck == null)
Throw<TException>(exceptionParams);
}
public static void ThrowIfNullEmptyOrWhitespace<TException>(this string stringToCheck, params object[] exceptionParams)
where TException : Exception
{
if (string.IsNullOrWhiteSpace(stringToCheck))
Throw<TException>(exceptionParams);
}
public static void ThrowIfNullOrEmpty<T, TException>(this IEnumerable<T> enumerable, params object[] exceptionParams)
where TException : Exception
{
if (enumerable == null || !enumerable.Any())
Throw<TException>(exceptionParams);
}
public static void ThrowIfZeroOrNegative<TException>(this int i, params object[] exceptionParams)
where TException : Exception
{
if (i < 1)
Throw<TException>(exceptionParams);
}
private static void Throw<TException>(object[] exceptionParams)
where TException : Exception
{
if (exceptionParams.Length == 0)
throw Activator.CreateInstance(typeof(TException)) as TException;
throw Activator.CreateInstance(typeof(TException), exceptionParams) as TException;
}
}
|
|
|
|
|
That's the kind of thing Code Analysis should suggest!
Thank you for your answer!
|
|
|
|
|
First, I don't understand why you're mentioning Visual Studio at all. Visual Studio is an IDE, like Rider, VS Code or Eclipse. It supports programming in C, C++, F#, Typescript, Python, as well as C#. So an IDE certainly isn't preventing you from doing anything that's not supported by C#.
Second, ?? is a binary operator, so it returns an expression, and there is a clear distinction between expressions and statements in C#.
It's the same reason why you cannot simply write a() && b(); in C# like in Javascript, or use if (a = 5) like in C, which is a bug in 99% cases.
|
|
|
|
|
When I say Visual Studio, it's because of the Code Analysis that "shows" in Visual Studio, telling to replace if/throw/assignment by that expression.
Then, I am talking about ?? with throw. ?? for other cases is a different situation. With throw, it's a "new thing". But because of the new thing being recommended and creating that situation where if you take out the assignment everything needs to be rewritten (or we need to use a discard) that I consider it a bad feature "at the moment".
|
|
|
|
|
This is a great inspiring article. I am pretty much pleased with your good work. You put really very helpful information. Keep it up once again.
|
|
|
|
|
... I thought I always knew better how things should be.
As I matured I discovered there were frequently many good reasons for why things were done a certain way and so I began to strive to understand those things which, hopefully, has made me a better developer (my day job) and college instructor (my once a week evening job).
I would urge you to strive to better understand before posting something that wastes people's time in reading.
|
|
|
|
|
I see from other of your messages that you think "this is what happens when code becomes open-source".
Maybe not this particular case... but the idea that lots of new things appear because everyone is putting their "preferred" feature.
In my case... I really think I would prefer not to have the operator ?? at all, or never allow it to finish in throw, then to have it as a short-hand for 3 tasks, but no way to use it for 2 of those 3 tasks.
Also, my work for many years was to create new features/frameworks or the like to help other developers, and the big lesson is that features that "work most of the time" are many times "hidden bombs" and it is better to not have the feature at all or to have it working in 100% of the cases that make sense.
If not, we end-up with cryptic code or bugs because people would either use the feature with some hacks (like using a discard variable only to make the code work) or decide to avoid it altogether because of the problems they found in production (I can tell many LINQ methods hit such a situation, especially by newer developers, in different companies where I worked).
So... things have a reason to exist, that doesn't mean they are always perfectly implemented or couldn't be improved.
|
|
|
|
|
Couple of thoughts:
"prefer not to have the operator ??", you already have that option, just don't use it.
One of the points I make to my students is "There is no magic, understand everything you do and facility you use. Of course that doesn't happen overnight and takes diligence and work".
I would urge you to do the same.
Lastly, when something doesn't work as I think it should I now first assume it is MY lack of understanding and proceed to do the research noted in the previous statement.
But I also understand that view point comes only with a certain level of maturity that takes work to achieve and constant self-monitoring to maintain, And honestly, most developers of any age do not have. But I have also found great success in mentoring other developers to achieve that maturity. Of course, I didn't magically come to this all by myself, I was lucky enough to have a patient mentor in my early days for which I am eternally grateful (Allen, if you happen to read this!).
Good luck on your journey!
|
|
|
|
|
"You already have that option, just don't use it".
That works great when we are developing alone. When working in projects with other people, many resources are going to be used, independently if they are good and improving things, or bad and making everything a mess or just hiding bugs.
That's why I said "I would prefer". That acknowledges that it already exists, but I would prefer if it didn't, at least not the way it does right now.
In fact, this is one of the things I am seeing with many languages, and why some developers even prefer to stick to old C... most languages are evolving in a way to put all the "nice features in", instead of evolving to add the features that make sense to the language origin/paradigm/principles while skipping others.
But as I already see there is no chance to convince you, as you keep saying how much maturity you have, I will stop any further discussion now.
|
|
|
|
|
Member 11941131, the author has a perfectly valid point, and your comment displays an unwelcome intemperance.
It may be that there is a valid technical explanation for the current behaviour, it may also be that it is something that the language designers overlooked. If you know the answer to that please tell us all, otherwise try to be nicer.
|
|
|
|
|
as the compiler says : only assignment \call \inc \dec and new expression can be used as statement.
?? is an operator ,but it does not help get a statement from an expression .
whatever , you are giving a good idea .
|
|
|
|
|
OK, so the immediate answer is ?? or the null coalescing operator has two operands. As such, you would have to supply two parameters.
I think, however, in reading the responses your question raises a larger question, or questions, that is/are alluded to, namely:
- What or how should we handle parameter checking before we begin to execute code inside a method i.e. what do best practices dictate?
- Do we need all the baggage when trying to achieve a simple objective?
Here's where lore weighs us down. The notion of throwing exceptions on null or invalid arguments, or indeed, in general is so ingrained in application developers I sometimes wonder if the notion of an exception is lost that it should be, well.... exceptional. Anyone who develops code in the embedded world will immediately pick up on this... choose your RTOS, we pretty much turn off exception handling support in whatever flavor of the language you are using (typically some version of Eclipse C++).
My point here is this... Microsoft, in its zeal to expand the power of the null coalescing operator to allow throwing exceptions as an operand has now propagated catching exceptions in ways that are difficult to trace and debug, and frankly manage. Anyone who has developed in TPL with parallel tasks and aggregate exception handling across synchronization contexts will immediate recognize what I am talking about... its a hot mess.
Best practices dictates writing exception free/lock free code whenever and where ever possible. That means using exceptions in exceptional cases.
Lets go back to your example, the classic parameter check. Context is everything. Lets say its a name in a database or even a primary key. Maybe that should halt current operation, but that's hardly an exception. I would favor this:
if (arg == null)
{
return null;
}
If, however, this is a method that writes, say, a block of bytes to a sector onto non-volatile flash memory or a USB device, and the pointer is null, then in this case, yeah, that's a real exception. Houston, we have a problem. We probably want to bail.
I think you get the idea.
FWIW, I think it's a good question.
|
|
|
|
|
Your analysis of the problem is the kind of thing to write a real article about.
I wrote an article recently about "should I catch this exception?" and, long story short, is that we should not use exceptions as control flow... so we should either not throw (using return values), or if we throw we should just catch it to log or something and rethrow, never to "eat" the exception.
modified 21-Jan-22 0:04am.
|
|
|
|
|
Quote: if we throw we should just catch it to log or something and rethrow, never to "eat" the exception.
I want to touch on this point, because I worry that this leans too heavily on an PC/Linux centric mindset in application development.
As both a PC/Linux developer and a mobile/embedded developer, I've come to appreciate the differences in resource management. The former take for granted both access to services such as logging, and the ability to recover gracefully when encountering an exception. This leads to bad habits when developers from one world try to move to the other.
Example: Logging. In the embedded world, logging means taking up space in NVM (non-volatile RAM) space, usually a very precious resource. Hence, any logging must be done with careful thought and precision as opposed to PC/Linux, where verbosity is king.
Example: Exceptions: In the embedded world, an exception typically means something very very bad has occurred, so there is not always the option to "log and eat" - often it's halt and brick, or at the very least, degrade to something similar to Windows "Safe Mode".
I'm not suggesting that this is not an appropriate path to take on the PC/Linux application side, that's a topic for another post. I am just trying to widen the lens so that everything comes into focus.
At this point the question becomes, how is this relevant to your post? Here's the thing... .NET is no longer limited to the PC/Wintel space. It lives in the embedded world, it lives in the mobile world (Xamarin), it lives on in Mono and in .NET Core. The point is, it's important, now more than ever, to start developing good/better habits in a language and platform that has gone from niche to a major development ecosystem on par with C/C++.
|
|
|
|
|
I find it very handy and it often saves me quite a few keystrokes and, some times, a few lines of code, eg:
if (MyObject?.OneProperty?.YetAnotherProperty?.WantThisBool ?? false) {
DoSomethingPrettyCool();
} else {
ComplainAboutLife();
}
If I want to ensure several properties of one input parameter are not null, then it also helps simplifying things a little bit. Please note the _ = at the beginning of the statement.
_ = MyInput?.SomeOfItsProperties?.YetAnotherInnerProperty ?? throw new ArgumentException("I dislike your data");
|
|
|
|
|
I recently had to delve into the world of PHP when we dug out an ancient system in need of migrating and modernising. That had code which had been written on an earlier version of PHP, where if you accessed an array using an index that didn't exist it wouldn't throw an error; but when upgraded to the latest version that caused the program to crash.
$myItem = $myArray['InvalidIndexOrKey']; # throws key 'InvalidIndexOrKey' does not exist
The solution? Use the null coalescing operator to ensure that if there is no value found, we can substitute in a default value...
$myItem = $myArray['InvalidIndexOrKey'] ?? $myDefaultValue; # works
The oddity... This works too:
$myItem = $myArray['InvalidIndexOrKey'] ?? null; # works
So the null coalescing operator, in PHP, seems to be an exception suppressing operator.
|
|
|
|
|
Exception suppressing seems "really interesting", hahaha!
|
|
|
|
|
The interesting question is whether it's exception suppressing or catching? If the former, that's very useful (it means it's telling the compiler how we want to handle the invalid key error). If catching, it's less interesting as it means that we're still incurring all the costs associated with throwing and catching without the visibility of those costs.
The biggest problem is that it's unclear how to generally code exception suppression. If the dictionary/hash lookup is a base language primitive, then exception suppression is easy. If it's calling library code, the exception throw could be buried deeply (I'm not an expert on PHP, so I'll assume it's similar to C# and this could happen) and exception suppression is nearly impossible to implement (unless there's a convention for passing 'allow null return' as an additional hidden parameter), while exception catching is (unfortunately) trivial.
I say unfortunately because with such a simple model for catching exceptions, people are likely to use it all the time resulting in significantly slower code with no obvious clue about what makes it so slow (the other day I found my code was hanging because of an error condition in a parser that was causing thousands of exceptions to fire and get ignored, I added code to detect and handle the most common cases without the exception and the hang disappeared).
I find the best model is the C# .TryParse or .TryGetValue methods (or simply allowing a null return rather than an exception) because they completely avoid the exception in the first place. Hopefully, that's what's going on behind the scenes here (the compiler calls the equivalent of TryGetValue instead of Value), but it's hard to tell from the code snippet.
|
|
|
|
|
C# has really become a force to be reckoned with, however, I also see some "funny" things in the later versions that makes one say, really? I like your thought train.
|
|
|
|
|
Because "_input ?? throw new ArgumentNullException("input")" is an expression, not a statement. Any other expression would fail to compile just the same. "flag || true;" won't compile either, for example.
BTW, I realize this isn't what your article is about, but they way you'd do this in C# 10 is: "ArgumentNullException.ThrowIfNull(input);".
|
|
|
|
|
Technically you are right, yet a "throw" statement is not the same as a bool return (or any return, to that matter).
Maybe what's missing is the equivalent of ?. for throws.
We can do a?.b?.c() now and avoid ifs and inner-ifs like:
var temp = a;
if (temp != null)
{
var b = temp.b;
if (b != null)
b.c();
}
So, maybe we should have an option like a?.b ? throw new Exception(); ... although I think that just accepting ?? with throw with no value assignment would do a better job.
In fact, I am not really a fan of the ?? operator... what's annoying, in my opinion, is that assignment with a check for null and a throw can be written more condensed than just check for null and a throw, but no assignment.
The fact that visual studio even recommends replacing those three things with a single line using the ?? operator makes odd code shifts if we just want to get rid of the assignment.
Using the discard variable will probably work... but then code is just getting more and more "cryptic" and full of symbols.
About the ThrowIfNull method. I agree it works... it is just not part of the discussion.
|
|
|
|
|
Paulo Zemek wrote: et a "throw" statement is not the same as a bool return
Except that's not a throw statement, it's a throw expression. If they hadn't added throw expressions you wouldn't be able to use throw with ?? or any other operator or as a subexpression, like in a switch expression pattern match (the real reason for the addition). Throw expressions weren't added strictly for use with ??, it comes up in many different scenarios. Developers just latched onto it as a shorthand for null argument checking.
Adding specific syntax to handle conditional throw statements would be possible, but I doubt the language designers would ever do that. It's too narrow and specific with few benefits over library options, like the ThrowIfNull method... which is why that's actually part of the discussion.
Why is because expressions and statements are different. Why not a statement solution is because "guard methods" such as ThrowIfNull are simpler and more widely applicable.
|
|
|
|
|
Sure... I understand why the parser works that way.
I agree that it could be different, and not sure to which direction the language will evolve.
Yet, I come back to the point that it is odd that if we want to just remove the assignment, we need to go back to use an if when the throw is an action by itself... and also that Code Analysis will recommend changing an if with a throw and an assignment into the operator ?? format instead of using a helper method.
|
|
|
|
|
In C# throw is a unique keyword. It can be used either as a statement, or as an expression. When you use the ?? operator you're writing an expression, so with throw on the RHS it's an expression. "Removing the assignment" means you're writing an expression, not a statement. If this would be valid...
public void Foo(string bar)
{
bar ?? throw ArgumentNullException("bar");
}
Then this would need to be valid as well...
public void Foo(string bar)
{
bar ?? "baz";
}
While you can sort of see the purpose of the former, the latter makes no sense.
|
|
|
|
|
The same way "throw" is special now... the ?? operator could just be a "test before going to the right replacement"... which can end at returning a value, throwing or calling a void method.
obj ?? ObjIsNull();
obj ?? throw new Exception();
obj ?? x;
var y = obj ?? x;
Is it worth doing this? Well... I could question if it was worth to create the ?? operator at all.
Just for completeness, I would "translate" this example like:
if (obj == null)
ObjIsNull();
if (obj == null)
throw new Exception();
if (obj == null)
x;
var y = obj;
if (y == null)
y = x;
So, I believe that it is easy to see that ?? could be just synctatic sugar for an if, and then putting the "thing on the right" as the body of the if.
It doesn't matter how it is implemented... it could do that. So, I will stop discussing this now.
|
|
|
|
|