Today we released a prototype of a C# feature called “nullable reference types“, which is intended to help you find and fix most of your null-related bugs before they blow up at runtime.
We would love for you to install the prototype and try it out on your code! (Or maybe a copy of it! 😄) Your feedback is going to help us get the feature exactly right before we officially release it.
Read on for an in-depth discussion of the design and rationale, and scroll to the end for instructions on how to get started!
The billion-dollar mistake
Tony Hoare, one of the absolute giants of computer science and recipient of the Turing Award, invented the null reference! It’s crazy these days to think that something as foundational and ubiquitous was invented, but there it is. Many years later in a talk, Sir Tony actually apologized, calling it his “billion-dollar mistake”:
I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn’t resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.
There’s general agreement that Tony is actually low-balling the cost here. How many null reference exceptions have you gotten over the years? How many of them were in production code that was already tested and shipped? And how much extra effort did it take to verify your code and chase down potential problems to avoid even more of them?
The problem is that null references are so useful. In C#, they are the default value of every reference type. What else would the default value be? What other value would a variable have, until you can decide what else to assign to it? What other value could we pave a freshly allocated array of references over with, until you get around to filling it in?
Also, sometimes null is a sensible value in and of itself. Sometimes you want to represent the fact that, say, a field doesn’t have a value. That it’s ok to pass “nothing” for a parameter. The emphasis is on sometimes, though. And herein lies another part of the problem: Languages like C# don’t let you express whether a null right here is a good idea or not.
Yet!
What can be done?
There are some programming languages, such as F#, that don’t have null references or at least push them to the periphery of the programming experience. One popular approach instead uses option types to express that a value is either None or Some(T) for a given reference type T. Any access to the T value itself is then protected behind a pattern matching operation to see if it is there: The developer is forced, in essence, to “do a null check” before they can get at the value and start dereferencing it.
But that’s not how it works in C#. And here’s the problem: We’re not going to add another kind of nulls to C#. And we’re not going to add another way of checking for those nulls before you access a value. Imagine what a dog’s breakfast that would be! If we are to do something about the problem in C#, it has to be in the context of existing nulls and existing null checks. It has to be in a way that can help you find bugs in existing code without forcing you to rewrite everything.
Step one: expressing intent
The first major problem is that C# does not let you express your intent: is this variable, parameter, field, property, result etc. supposed to be null or not? In other words, is null part of the domain, or is it to be avoided.
We want to add such expressiveness. Either:
- A reference is not supposed to be null. In that case it is alright to dereference it, but you should not assign null to it.
- A reference is welcome to be null. In that case it is alright to assign null to it, but you should not dereference it without first checking that it isn’t currently null.
Reference types today occupy an unfortunate middle ground where both null assignment and unchecked dereferencing are encouraged.
Naively, this suggests that we add two new kinds of reference types: “safely nonnullable” reference types (maybe written string!) and “safely nullable” reference types (maybe written string?) in addition to the current, unhappy reference types.
We’re not going to do that. If that’s how we went about it, you’d only get safe nullable behavior going forward, as you start adding these annotations. Any existing code would benefit not at all. I guess you could push your source code into the future by adding a Roslyn analyzer that would complain at you for every “legacy” reference type string in your code that you haven’t yet added ? or ! to. But that would lead to a sea of warnings until you’re done. And once you are, your code would look like it’s swearing at you, with punctuation? on! every? declaration!
In a certain weird way we want something that’s more intrusive in the beginning (complains about current code) and less intrusive in the long run (requires fewer changes to existing code).
This can be achieved if instead we add only one new “safe” kind of reference type, and then reinterpret existing reference types as being the other “safe” kind. More specifically, we think that the default meaning of unannotated reference types such as string should be non-nullable reference types, for a couple of reasons:
- We believe that it is more common to want a reference not to be null. Nullable reference types would be the rarer kind (though we don’t have good data to tell us by how much), so they are the ones that should require a new annotation.
- The language already has a notion of – and a syntax for – nullable value types. The analogy between the two would make the language addition conceptually easier, and linguistically simpler.
- It seems right that you shouldn’t burden yourself or your consumer with cumbersome null values unless you’ve actively decided that you want them. Nulls, not the absence of them, should be the thing that you explicitly have to opt in to.
Here’s what it looks like:
class Person
{
public string FirstName; // Not null
public string? MiddleName; // May be null
public string LastName; // Not null
}
This class is now able to express the intent that everyone has a first and a last name, but only some people have a middle name.
Thus we get to the reason we call this language feature “nullable reference types”: Those are the ones that get added to the language. The nonnullable ones are already there, at least syntactically.
Step two: enforcing behavior
A consequence of this design choice is that any enforcement will add new warnings or errors to existing code!
That seems like a breaking change, and a really bad idea, until you realize that part of the purpose of this feature is to find bugs in existing code. If it can’t find new problems with old code, then it isn’t worth its salt!
So we want it to complain about your existing code. But not obnoxiously. Here’s how we are going to try to strike that balance:
- All enforcement of null behavior will be in the form of warnings, not errors. As always, you can choose to run with warnings as errors, but that is up to you.
- There’s a compiler switch to turn these new warnings on or off. You’ll only get them when you turn it on, so you can still compile your old code with no change.
- The warnings will recognize existing ways of checking for null, and not force you to change your code where you are already diligently doing so.
- There is no semantic impact of the nullability annotations, other than the warnings. They don’t affect overload resolution or runtime behavior, and generate the same IL output code. They only affect type inference insofar as it passes them through and keeps track of them in order for the right warnings to occur on the other end.
- There is no guaranteed null safety, even if you react to and eliminate all the warnings. There are many holes in the analysis by necessity, and also some by choice.
To that last point: Sometimes a warning is the “correct” thing to do, but would fire all the time on existing code, even when it is actually written in a null safe way. In such cases we will err on the side of convenience, not correctness. We cannot be yielding a “sea of warnings” on existing code: too many people would just turn the warnings back off and never benefit from it.
Once the annotations are in the language, it is possible that folks who want more safety and less convenience can add their own analyzers to juice up the aggressiveness of the warnings. Or maybe we add an “Extreme” mode to the compiler itself for the hardliners.
In light of these design tenets, let’s look at the specific places we will start to yield warnings when the feature is turned on.
Avoiding dereferencing of nulls
First let’s look at how we would deal with the use of the new nullable reference types.
The design goal here is that if you mark some reference types as nullable, but you are already doing a good job of checking them for null before dereferencing, then you shouldn’t get any warnings. This means that the compiler needs to recognize you doing a good job. The way it can do that is through a flow analysis of the consuming code, similar to what it currently does for definite assignment.
More specifically, for certain “tracked variables” it will keep an eye on their “null state” throughout the source code (either “not null” or “may be null“). If an assignment happens, or if a check is made, that can affect the null state in subsequent code. If the variable is dereferenced at a place in the source code where its null state is “may be null“, then a warning is given.
void M(string? ns) // ns is nullable
{
WriteLine(ns.Length); // WARNING: may be null
if (ns != null)
{
WriteLine(ns.Length); // ok, not null here
}
if (ns == null)
{
return; // not null after this
}
WriteLine(ns.Length); // ok, not null here
ns = null; // null again!
WriteLine(ns.Length); // WARNING: may be null
}
In the example you can see how the null state of ns is affected by checks, assignments and control flow.
Which variables should be tracked? Parameters and locals for sure. There can be more of a discussion around fields and properties in “dotted chains” like x.y.z or this.x, or even a field x where the this. is implicit. We think such fields and properties should also be tracked, so that they can be “absolved” when they have been checked for null:
void M(Person p)
{
if (p.MiddleName != null)
{
WriteLine(p.MiddleName.Length); // ok
}
}
This is one of those places where we choose convenience over correctness: there are many ways that p.MiddleName could become null between the check and the dereference. We would be able to track only the most blatant ones:
void M(Person p)
{
if (p.MiddleName != null)
{
p.ResetAllFields(); // can't detect change
WriteLine(p.MiddleName.Length); // ok
p = GetAnotherPerson(); // that's too obvious
WriteLine(p.MiddleName.Length); // WARNING: saw that!
}
}
Those are examples of false negatives: we just don’t realize you are doing something dangerous, changing the state that we are reasoning about.
Despite our best efforts, there will also be false positives: Situations where you know that something is not null, but the compiler cannot figure it out. You get an undeserved warning, and you just want to shut it up.
We’re thinking of adding an operator for that, to say that you know better:
void M(Person p)
{
WriteLine(p.MiddleName.Length); // WARNING: may be null
WriteLine(p.MiddleName!.Length); // ok, you know best!
}
The trailing ! on an expression tells the compiler that, despite what it thinks, it shouldn’t worry about that expression being null.
Avoiding nulls
So far, the warnings were about protecting nulls in nullable references from being dereferenced. The other side of the coin is to avoid having nulls at all in the nonnullable references.
There are a couple of ways null values can come into existence, and most of them are worth warning about, whereas a couple of them would cause another “sea of warnings” that is better to avoid:
- Assigning or passing null to a non-nullable reference type. That is pretty egregious, right? As a general rule we should warn on that (though there are surprising counterarguments to some cases, still under debate).
- Assigning or passing a nullable reference type to a nonnullable one. That’s almost the same as 1, except you don’t know that the value is null – you only suspect it. But that’s good enough for a warning.
- A
defaultexpression of a nonnullable reference type. again, that is similar to 1, and should yield a warning. - Creating an array with a nonnullable element type, as in
new string[10]. Clearly there are nulls being made here – lots of them! But a warning here would be very harsh. Lots of existing code would need to be changed – a large percentage of the worlds existing array creations! Also, there isn’t a really good work around. This seems like one we should just let go. - Using the default constructor of a struct that has a field of nonnullable reference type. This one is sneaky, since the default constructor (which zeroes out the struct) can even be implicitly used in many places. Probably better not to warn, or else many existing struct types would be rendered useless.
- Leaving a nonnullable field of a newly constructed object null after construction. This we can do something about! Let’s check to see that every constructor assigns to every field whose type is nonnullable, or else yield a warning.
Here are examples of all of the above:
void M(Person p)
{
p.FirstName = null; // 1 WARNING: it's null
p.LastName = p.MiddleName; // 2 WARNING: may be null
string s = default(string); // 3 WARNING: it's null
string[] a = new string[10]; // 4 ok: too common
}
struct PersonHandle
{
public Person person; // 5 ok: too common
}
class Person
{
public string FirstName; // 6 WARNING: uninitialized
public string? MiddleName;
public string LastName; // 6 WARNING: uninitialized
}
Once again, there will be cases where you know better than the compiler that either a) that thing being assigned isn’t actually null, or b) it is null but it doesn’t actually matter right here. And again you can use the ! operator to tell the compiler who’s boss:
void M(Person p)
{
p.FirstName = null!; // ok, you asked for it!
p.LastName = p.MiddleName!; // ok, you handle it!
}
A day in the life of a null hunter
When you turn the feature on for existing code, everything will be nonnullable by default. That’s probably not a bad default, as we’ve mentioned, but there will likely be places where you should add some ?s.
Luckily, the warnings are going to help you find those places. In the beginning, almost every warning is going to be of the “avoid nulls” kind. All these warnings represent a place where either:
- you are putting a null where it doesn’t belong, and you should fix it – you just found a bug! – or
- the nonnullable variable involved should actually be changed to be nullable, and you should fix that.
Of course as you start adding ? to declarations that should be allowed to be null, you will start seeing a different kind of warnings, where other parts of your existing code are not written to respect that nullable intent, and do not properly check for nulls before dereferencing. That nullable intent was probably always there but was inexpressible in the code before.
So this is a pretty nice story, as long as you are just working with your own source code. The warnings drive quality and confidence through your source base, and when you’re done, your code is in a much better state.
But of course you’ll be depending on libraries. Those libraries are unlikely to add nullable annotations at exactly the same time as you. If they do so before you turn the feature on, then great: once you turn it on you will start getting useful warnings from their annotations as well as from your own.
If they add anotations after you, however, then the situation is more annoying. Before they do, you will “wrongly” interpret some of their inputs and outputs as non-null. You’ll get warnings you didn’t “deserve”, and miss warnings you should have had. You may have to use ! in a few places, because you really do know better.
After the library owners get around to adding ?s to their signatures, updating to their new version may “break” you in the sense that you now get new and different warnings from before – though at least they’ll be the right warnings this time. It’ll be worth fixing them, and you may also remove some of those !s you temporarily added before.
We spent a large amount of time thinking about mechanisms that could lessen the “blow” of this situation. But at the end of the day we think it’s probably not worth it. We base this in part on the experience from TypeScript, which added a similar feature recently. It shows that in practice, those inconveniences are quite manageable, and in no way inhibitive to adoption. They are certainly not worth the weight of a lot of extra “mechanism” to bridge you over in the interim. The right thing to do if an API you use has not added ?s in the right places is to push its owners to get it done, or even contribute the ?s yourself.
Become a null hunter today!
Please install the prototype and try it out in VS!
Go to github.com/dotnet/csharplang/wiki/Nullable-Reference-Types-Preview for instructions on how to install and give feedback, as well as a list of known issues and frequently asked questions.
Like all other C# language features, nullable reference types are being designed in the open here: github.com/dotnet/csharplang.
We look forward to walking the last nullable mile with you, and getting to a well-tuned, gentle and useful null-chasing feature with your help!
Thank you, and happy hunting!
Mads Torgersen, Lead Designer of C#

Does this apply also to properties?
So how do we initialize non-nullable reference properties with { get; set; } ?
The initialization requirement applies to fields, including the hidden underlying fields of auto-properties. You can initialize an auto-property in a constructor or with an initializer (string Name { get; set; } = “JohnDoe”; ).
Will creating a constructor that assigns to non nullable fields / props remove the warning of creating a prop without a default value. I’m talking about example six above: You want to ensure that First and Last Name are always set, but don’t want to set arbitrary defaults.
I’d assume that if you have such a constructor you won’t get warnings. However, if you had a second constructor that didn’t assign values to all non-nullable properties, you’d get a warning _in that constructor_.
In general I’d assume the warnings are shown in each affected constructor or, if you don’t have one, on the class declaration itself.
Hmmm. The warning should be on the declaration itself I think i.e. this value hasn’t been proven to always be assigned.
This is where records are a much better fit than classes – single ctor defined for all fields, always.
This is correct.
Warnings? Ok, hobby projects it is. The enterprise/finance teams I have seen are like cattle – they only understand the sharp unavoidable pain of compilation errors not mild friendly warnings. Enumerated same lazy sequence ten times? It’s a warning – doesn’t matter!
We can drag you to the river, but we cannot force you to drink. 😉
Seriously, we just have to be gentle here, and let people and teams adopt this in their own time.
You cannot please everyone! Retrofitting null checks into a mainstream language is an unprecedented challenge. Maintaining popularity of C# is more important than perfection I guess. Just please make an awesome easy to use webassembly toolchain next!
Might want a more fine grained Warnings As Errors; i.e so this can be toggled on as an error, without switching everything on as an error
That’d little bit of as slippery slope though – you end up with a middle ground of some warnings that are more than other warnings but less than errors. Worse still this could change from project to project (and even dev environment) so you end up with different “versions” of C# across a team.
Yes please!
Yes please.
Before optional arguments were available in C#, passing null to a reference type was the only way. You may have run some stats on github projects but I’d expect this to generate tons of warnings. It’s basically a change of conventions, almost a breaking change. Not a bad change but a big deal in my humble opinion.
This should have something akin to “Option Strict” where you can set it to generate errors instead of warnings.
In that environment, it’s easy to add the “warnings as errors” flag to the compiler on the build server. 🙂
I think this is a great example of a solution in search of a problem. I know that’s a bit harsh, and yes nullable problems still happen. They always will happen because we can’t always align and enforce the existence of data with the operations defined in code. Even if you eliminated all nulls, the default or un-set values would be the problem source instead of nulls. It’s just transferring the stated “billion dollar” problem.
But we already have glorious constructs such as the null coalesce (??) and safe navigation operator (?.). Complexity grows incrementally, and thus C# is approaching an overly complex syntax with all these new techniques.
This one, instead of being a concrete implementation of something, is a syntactical expression of intent. What’s so offputting to me in reading this is that the value of default(string) will not change. Why wouldn’t it? I mean I understand from the compiler side that underneath it’s the same type. But shouldn’t the default value now be string.Empty?
This makes it harder for us to teach the language because in the cases of primitives adding a ? changes the underlying type, while in the case of strings and classes it’s merely an annotation for the compiler. Harder to teach means harder to learn, and I don’t the return is there on the investment.
A very well worded response and I agree with all of it
I agree with you. This seems very convoluted. Null coalesce (??) and safe navigation operator (?) were great additions. I’m not convinced that we need anything else.
We know there is a problem, because this feature, in one shape or another, is very highly requested, and has been for many years. We have struggled to find the right approach to addressing it, and after several failed attempts over the years, this is the first time we feel we’ve found it.
Just because we have a problem and a solution doesn’t necessarily mean that the solution is worth it, of course. Part of building and sharing an early prototype is to understand the tradeoffs, shortcomings and obstacles before committing to whether and what we ship.
WRT default(string): we absolutely cannot change the meaning of that. While the warnings we add are a “breaking change” in the mild sense that they can occur on code that used to compile cleanly, changing the runtime semantics of an existing construct would be a much worse break: existing code starts behaving differently! That can be extremely insidious, and we try very hard to avoid ever doing that! Better for you to get a warning on your “default(string)” and decide for yourself to replace it with “string.Empty”.
Agreed that the semantic differences between “int?” and “string?” are unfortunate. We’ve decided to live with it, as the lesser of evils. We do discuss adding the flow analysis to nullable VALUE types as well, and letting you dereference those when known not to be null. This and other similar things would help narrow the gap.
The proper response is to hard fork C#.
We should get a clean C# that uses whatever nice things we want to borrow from TypeScript and F#. Non-nullability from the ground up would be one basic feature.
Call the new language something brilliant like C#++.
The nice thing is that if you guys don’t go too crazy with the new language, you can keep it in sync with new features added to C#.
Moreover, since they are both .Net languages, they would be interoperable.
Lastly, it would be easier to do this and port existing C# to C#++ than to deal with all of these contrivances…
We really do need a way of starting fresh with an awesomer C# without breaking old code…
Another great example would be to have C#++ use F# style async/await implementation since improvements to the async/await concept were thought of after C# already shipped it’s async/await (e.g. it is now known how to implement async/await without allocation of Task classes).
Regards,
We already have a better c#. It’s called f# 😉
Disagree on that, F# and C# are 2 totally different things.
So why don’t you and Mads Torgersen and others who are not mentally able to do a null checks go with F# and leave C# the fuck alone?
I was always thinking of doing something like that, but never got the time to, so sad 🙁
C#++?? I’m sorry, no. That’s just too odd.
It should be C##.
If default(string) was string.Empty we would indeed be in a worse place, as you even brought up at the beginning of your post- we’d have default values instead of nulls and _two_ kinds of mistakenly uninitialized data to bite us in two different kinds of ways. Also, it doesn’t generalize to all reference types and that’s a non-starter.
It’s not simply moving the billion dollar problem around if you address the warnings _properly_. That would only happen if you opt to coalesce all nulls to default values without doing sufficient investigation of the resulting semantic change you’ve made to each occurrence.
It’s no different from that angle than if you’d been passing all your parameters around as `object` all this time and began to tighten up your static type safety. Of course you now have to address the possibilities that you’ve been passing mismatching types without knowing it. Do you address that by using safe casts with a fallback to a default instance of the correct type? Of course not. You fix that by doing the hard thing and requiring the callers to only pass instances of the correct type, which possibly means fixing the callers of the callers, etc.
Except if you have nullable reference types enabled, you end up in the situation where String s = default(String); causes a warning, and could be an error in the future. I don’t think it was ever intended that default return a value that can’t be held by the type in question.
Looking at my code base today I can point to dozens of places where I’ve fixed null reference exceptions. So no, I don’t have to look far to find the problem this is supposed to solve.
Yeah we had a null reference exception stop our entire business like what two weeks ago. QC missed it a warning would not have. This is probably the most helpful change I’ve ever seen.
Also, Scott Hunter ran into an NRE while presenting live at Connect today. I thought of the people in this thread. :’D
This actually simplifies the language. It makes value types and reference types behave the same way, and gives the ? suffix the same meaning in both: it adds null to the set of allowed values.
The ?? and ?. become easier to learn and use because it (for the first time) becomes possible for the IDE to tell the user when they need to be used or not.
Also even though the value of default(string) has not changed, its type has: it’s now string?. The typical use for default is in generics so default(T) is of type T?, e.g. specifying the default value of a parameter and (for the first time) being absolutely clear that null is an allowed value and must be handled by the implementer.
I don’t think it should ever be the case that the result of default(aType) isn’t something that is aType, which makes not changing default(string) problematic. At least in the experimental Roslyn pieces, default(string) and default(string?) have the same System.String type and typeof(string) == typeof(string?).
This seems all right and good, and thoughtfully, well, thought through. The only thing that strikes me is how do you work with tooling such as PostSharp? I have a weaver/aspect on all my projects that do null-checking for me and throw errors accordingly. The code isn’t there, of course, as it is weaved in at compiled time. How will this new day and era work with my projects and setup?
From an IL point of view (the output of compilation), the only change that a tool like PostSharp will see is that some signatures will have attributes on them signifying nullability. They can then choose whether it’s important for them to take them into account or not. Yes, like pretty much any other language feature, downstream tools have to deal with it.
As for the weaving in of null-checking operations, that’s probably still useful. If I offer an API with nonnullable parameters, it can still be called with null by someone who decides not to follow the rules (or just has an older compiler). So on public boundaries, you likely still need to defend yourself.
Ah thank you for taking the time to reply and for all your great replies (as usual!) in this thread. In hindsight I now see I should have been a little more specific in my worry. My primary concern is about tooling in the sense of warnings. If I already have a tool (PostSharp) that handles this problem domain for me, am I still going to be bombarded with warnings in the IDE about these issues? Obviously, it would be ideal for the tooling to be able to interact with one another to provide a synchronous/harmonious experience.
Specific worries are good! 🙂
I see what you mean now. This is a bit of a general problem with code rewriting: What if the rewrite solves a problem in the original code? Can we make the warnings/errors go away? This feature is probably no better or worse than others in that context. That would be a nice problem to try to solve orthogonally sometime!
OK cool. I am assuming that at worst case scenario, we can mute these warnings like we can with general analysis/warnings today. If not, that would be nice to have. 😉
The default will be non-null!? That seems totally bonkers to me. This seems like something you’d want to opt in to, not out of. Well, I’ll try it on a few projects and see what happens, I guess.
The consequence of that would be that everything unannotated would be assumed to maybe be null, and you’d get warnings on all unprotected dereferencing. It seems to me that this would be MORE intrusive.
But I absolutely think both models can work. The arguments for what we chose are in the post.
I must agree with the OP here. If this feature had been worked on for C# 1.0, then non-null as the default would have made sense. It is far too late in the game to make non-null the default now. Additionally, to address “the too many warnings on unannotated types” point, I don’t think we should have those warnings implemented to begin with. We don’t have them for nullable types right now (int?, double?, etc) and those work great without them. Why have them now for unannotated types? They wouldn’t even detect problems in half of the cases as mentioned in this post anyway.
If it’s a problem for you then you can turn off the warnings. This will be a huge help, and a removal of a big stumbling block in the language.
On nullable value types today, we don’t have *warnings* for dereferencing, we have *errors*.
The team has definitely made the right choices in the balance of crossing the language into the non-nullable realm which it needs to in order to survive against Swift and Kotlin, who have been designed around the new paradigm.
We’ve had non-null preconditions in our C# codebases for the past 8 years – cannot wait for this to finally be done at the language level and for the .NET ecosystem to begin making the large leap ahead.
Screetching to what is old and resisting change always leads to failure. I was at RIM/Blackberry – I’ve seen it first-hand.
.NET badly needs to cross this chasm and from having deep experience with both Kotlin and Swift I can tell you their choices are spot on.
I agree. C# badly needs the concepts from Kotlin and Swift becoming first class citizens in the language.
I’m not concerned with old C# libraries that are already compiled that don’t deal with this, (ironically VBnet doesnt deal with it so you still have the same problem right?)
What I am concerned with, is compiling my current libraries and future libraries to be a lot more NRE robust.
We need a way to represent the fact that theres no value, without using Null. Perhaps deprecate the “null” keyword in favor of nil? with wrapped null_safe blocks?
Best addition to C# in the last 10 years. Thanks! (Disclaimer: I work for Microsoft. Still personal opinion)
Nah, lambdas are.
(Disclaimer: I work for Microsoft, too)
Nah, expression-bodied members.
(Disclaimed: I work on the C# language design team 😉 )
Almost ten years (wow!) since we got them
Yes I thought it was odd too when C# started in the days after Databases.
They had a DBNull construct being //something// that represents no value instead of null which is an empty pointer that points nowhere.
The C# language should’ve had null and nil and reserve null keyword within pointer access type of code. Possibly even only within unsafe blocks?
nil then becomes a keyword that indicates something that indicates no value is present, which is totally different.
Perhaps C# should look closely into Swift and Kotlin as somebody else pointed out.
For one, I completely agree with you!! Though now I may have to get rid of my geeky t-shirt with the null reference error message on it…
Thanks Mads and team for the hard work, keep it up!
A few questions, if you don’t mind:
– In the “extreme” scenario, would you be able to get warnings even for arrays or collections somehow? I guess string?[] is obvious, but could List vs List be working as it could be imagined or is that considered like a dependent library as you mentioned?
– How does multithreading fit into all of this? A value might change between checking and deferencing a value, does this complicate things at all (obviously with Typescript there were no such issues)?
– Also for having neatly organized code, you might have to duplicate it sometimes, which I’m not a fan of (I still prefer DRY code), could this have any relation to the previously discussed topic of primary constructors (relating to point 6 in your post for assigning all relevant fields in the constructors)?
Thanks!
Sorry, some content got stripped: In my first question I mean a list of nullable and nonnullable strings.
I didn’t say much about generics. The current plan is to allow unconstrained generics to be constructed with nullable type arguments (and to add a few extra constraints such as ‘object’ and ‘class?’). Assigning between “List-of-string” and “List-of-string?” will yield a warning both ways, because both can lead to exposure to null dereference.
Creating a List-of-string does not have the same problem as creating a string[], because the list will have 0 elements, whereas the array will have a number of null elements in it. The IMPLEMENTATION of the List-of-T generic type may have an array inside, which will start out with nulls inside. It may need to use “!” inside to convince the compiler that this is alright. But it protects the user from ever accessing null elements of the array, because (in a List-of-string) null will never be assigned to elements of the array that represent current elements of the list.
There’s a List-of-T contructor that creates a list of N elements: https://msdn.microsoft.com/en-us/library/dw8e0z9z(v=vs.110).aspx, so it is also not safe from uninitialized elements.
That just sets the initial capacity. The length is still zero until you add elements.
That constructor just reserves the internal memory for the case when you know how many elements you’ll need. It doesn’t make them publicly visible.
This throws, for example:
var list = new List(10);
var first = list[0]; // out of bounds exception
You’ve answered me already on Twitter but for posterity might be useful to answer here as well 🙂
1. How will this work in terms of the relationship between IL and the compiler. Will nullability be represented at runtime somehow in IL, or will this be a compile-time “erased” thing?
2. Will this feature be back-ported to the types and methods that are exposed in the BCL?
Cheers
For posterity, then, here goes!
1. The compiler adds attributes to member signatures to represent where things are nullable. That’s the only representation in the compiled output.
2. One way or another, yes, that’s the plan. It may be that we hack the reference assemblies rather than modify the source code itself, or some other trick we haven’t thought of yet.
Is that even possible without breaking everyone? Like if you change the signature of an interface, this will result in errors, not warnings for whoever implements it. Or if you change the signature of a commonly inherited class like Stream, that’s going to break any override in the child classes. Or will nullable and non nullable types be treated as having compatible signatures?
My (limited) understanding is that this is simply going to be attribute metadata which the C# compiler will understand and treat differently. It won’t be encoded into the type system directly, and therefore won’t need a complete rewrite of the BCL. I do wonder though how one could safely know which return values from BCL are nullable and which aren’t, though.
But that means that a pre-nullable references, already compiled assembly will not break. But what about your current project, where you are implementing an interface that returns a nullable reference but your implementation doesn’t return a nullable reference. Will Visual Studio not treat that mismatch in signature as an error? Or will it be simply a warning too?
To be honest, I find this a bad design choice. Language should not care about perceived errors users may have.
But even then this way have a few problems:
1. There are much more cases when you expect nulls than when you don’t. Why not mark those and left default case unchanged? I’m not looking forward to changing a bunch of completely sound code “for safety” or messing with warnings because someone on the design team made this choice and is pushing his agenda.
2. Marking non-nullable or nullable intent should be semantic, not syntactic. Syntax has to do with inner structure of language, and here it’s not changed. Moreover, it’s confusing with Nullable for value types. Adding NotNull or Null attributes would be much better (and readable) choice. There are even issues on GitHub asking for exactly that.
3. Subjectively, question marks everywhere look horrendous.
All in all compiler should help me write good code, not force me to adhere to unnecessary (and not even guaranteed!) safety rules and/or someone’s ideas of how type system should work. This would have been an ok choice if it was done when the language was created, but not at this point.
“Language should not care about perceived errors users may have.”: We always had warnings to help people with probable bugs, on top of errors when things really don’t make sense.
1. Do you think there are more places that are intended to be null than not? Our impression is the opposite. It’s hard to tell on existing code bases precisely BECAUSE people don’t have a way of expressing that intent. But it probably varies from code base to code base. If you have overwhelmingly have nullable intent (and want to use this feature, which, remember, is entirely opt-in!), then yes, you have some initial annotation work to do. But once you do, it will help you find all those place where you forget to check for null.
2. I’m sorry, I don’t understand the syntactic/semantic distinction here: do you mean it should not have it’s OWN syntax? Agreed about the differences with nullable value types. On the one hand it’s nice that you can reuse some of the intuition and syntax, but on the other it may lead to the wrong expectations where the two features differ. We are aware of this, but currently think of it as the lesser evil.
3. Yeah, that is subjective. Sorry you don’t like it. Hopefully it’s not EVERYwhere!
Just my two cents here coming from the perspective of using F# on a daily basis, but having worked for years beforehand primarily with C#. Although the approach to nullability is different in F# to what’s proposed here, I can say that on a standard LOB codebase, we find that the vast majority of the time having non-nullable as the default and having to opt in to nullability is the most common scenario. In fact I only realised this after working in F# for a reasonable amount of time- it’s simply nothing that I considered when working in C#, since as Mads points out, there’s no real way of specifying it.
Non-nullable by default is the way to go.
I agree, non-nullable should be the default behavior. Why? Because of applications work that way: fvalidation of input (where nullables might be mixed with non-nullable) first, then safe haven of verified references. Not other way around.
Cannot agree more. Reference Type can and needs to be able to pointing to nothing. It’s the programmer’s responsibility to check whether it pointing to nothing or not. Compiler has no idea what the programmer intend to do at all. Also, simply generate a warning cannot prevent someone to be stupid. I’ve seen so many people, including those with a SENIOR title, get used to ignore warnings. This change mean nothing to them. Also, this change mean nothing to the GOOD ones, as they always know what they are doing and the implications. In other words, it’s a catch 22, nothing more.
Right now all our reference typing can give us is `string | null` and `ICanPerformX | null`. There is no purity, no way to express that I need an `ICanPerformX` and nothing else, not a `null`, not a `ICanPerformY`.
Conflating `ICanPerformX` with `null` is every bit as useless as conflating `ICanPerformX` with `ICanPerformY`. If you need to add on the possibility of receiving a `null`, which is inherently a special case not conforming to the contract of `ICanPerformX`, that should be opt-in. You’re effectively asking for one of two incompatible contracts (`ICanPerformX` and `null`) rather than one which brings with it the need to switch behavior depending on the contract. It should be opt-in because you’re inherently raising the complexity.
I have to say, I run into null as more of a special case and I would dread having to opt out of nullability. For the code I’ve seen, opt-out not opt-in would result in punctuation absolutely everywhere. It also fits the mental model we already use with nullable value types, so there’s that.
I do not agree with the idea that null is suspicious. Null is no different than an integer defaulting to 0. This is just the “language of computer science.” Yes, trying to access methods/properties/etc of a null object will cause an exception. But that’s where training and education are important. I have met a lot of developers whose only qualification to do their job is “they like computers.” A professional developer does not have trouble with null.
Where null is different from an integer defaulting to 0 is that it breaks the contract of the type and 0 does not.
When we ask for a parameter of type
ICanPerformXin today’s C#, we’re only able to ask for effectivelyICanPerformX | null. Thinking thatnullfulfills the contractICanPerformXis just as mistaken as thinking thatICanPerformYfulfills the contractICanPerformX. In comparison, 0 fulfills the contract `int` in every way.I’m super happy that we’re getting tools that allow us to finally express, effectively,
ICanPerformXrather thanICanPerformX | null. And I’m just as happy that the tools warn when you’re lying by passing a potentialnullin a place that requires anICanPerformXcontract. Anecdotal statistics and statistics I’m taking on authority both disagree with your assertion that professional developers do not have trouble with inadvertently treatingnullas though it fulfills a contract.Finally,
The progression of software language features over the years is all about making things easier and taking mental load off of the developer. An good example is using a regular for loop instead of foreach. It isn’t that much harder to use the regular for loop, so why bother? Well you might have an off by one error, you might have nested loops and use the wrong increment variable. Sure senior developers will make those mistakes less often than junior ones but it still happens, and can take time to track down. Why should I need to bother with initializing, incrementing, and comparing an index when all I need to do is work with each value in a list? It is noise to the problem I’m solving and an opportunity for mistakes that just doesn’t need to be there.
This is no different. Sure a good developer knows an reference can be null and that they should check in appropriate spots. But here’s a question. Do you have a specific null check in every single function in your codebase that takes in reference parameters? Or do you often have cases where the caller is your own code and you know the value was checked for null already? In those cases, you don’t need a specific null check in the called function. If you add one anyway, it is adding clutter to the code. But not adding it means there is more mental load. What if you’re modifying a function someone else wrote? Should they have added a null check? Now you need to go up through the callers and make sure one won’t be passed in. Or maybe you need to call an existing function in your own code or even a library. Do you know if it can handle null? Maybe you’re lucky and it has documentation saying it can throw a null arg exception. But most won’t want to go that far for regular functions that aren’t being called by the outside word. Have you ever navigated to the definition of a function you’re about to call to see if it handles null?
All of that intent is helped by having something like this in place. By marking the parameter as non-nullable, you know right away that it isn’t doing its own check and expects that to have been done by the caller. If that isn’t done, there is a warning. Now you can easily do a null check at the outer boundaries of your code and have everything inside not have to worry about it. If your particular function is called from a new area where there might be a null value, you get a warning. You either need to modify the caller to check for null or change the signature of the function and put a check there. Otherwise you might assume the function checked for null and have a bug or else you have to spend time looking at the function (and perhaps more time if it send that argument down into yet another function). Or in Redux you construct actions that are dispatched on later. Instead of having an action take a bunch of nullable properties or an “any” property just called value, you can specify different types. If you got an AddFoo action, you know the foo property on it isn’t null. There would have been a warning while constructing it.
TypeScript works this way and it is quite helpful. In fact its type system allows other handy things like specifying a type can be Foo[] | LOADING | NOT_AUTHORIZED. The UI can treat the different states in different ways easily. Instead of trying something like null means unauthorized and empty array means still loading. But then oops an empty array might be valid and we want to display a message for that. Anything that makes intentions explicit is helpful IMO.
I’d argue that integer shouldn’t default to 0 and should require an assignment. It’d be too hard to change right now in C# but it does make some sense. The only reason int defaults to 0 and string doesn’t default to empty string is an implementation detail. Strings can be large and we don’t want to have to make a copy in memory every time we assign it to another variable or pass it to a function. So it is treated as a reference type in C# but other languages do it differently.
Awesome addition! I’ve been pussing for it for a long time (https://roslyn.codeplex.com/discussions/541334) but now we have a brave and smart solution. I think C# 8 is going to be memorable like C# 3 was with LINQ!
Unfortunately, once you start decorating your entity properties with ?, null-propagation becomes mandatory… but doesn’t work on Database Queries!! Example:
public class Person
{
public string? Name;
}
class Program
{
static IQueryable Persons { get; set; }
static void Main(string[] args)
{
//An expression tree lambda may not contain a null propagating operator.
var a = Persons.Select(b => b.Name?.Length);
}
}
Pleeease fix this. Even if generating the same expression than `Persons.Select(b => (int?)b.Name.Length);` to avoid braking LINQ providers.
this problem deserves more attention. the lack of linq support for ?? and ?. is going to be a big deal with nullable emergence types around.
*reference (ty autocorrect)
I think ?? works in expression trees, because it’s from C# 2.0
The problem is with all the new stuff after c# 3.0, like dynamic, async await, or patter maching
Admittedly, I don’t see to much use for async await or even patern maching when translating to sql. Dynamic could be quite useful, but ?. Now with non nullable reference types is going to be a must!
If not… how are we supposed to work with Entity Framework?
The implementation problem is that adding new expression types will break queries that use them till the LINQ provider gets support.
This point is well taken. Thanks!
Yes great point!
A very brave step towards the correct direction. Kudos, this is gonna make other languages jealous.
A more powerful pattern matching would be the next if I am not mistaken.
Oh and speaking of pattern matching how does these nullable types stack up with existing pattern matching syntax?
They’ll work well together. Patterns are one of many ways to learn whether a variable is null or not. The prototype doesn’t fully implement this behavior.
Also, in C# 8.0 we plan to have recursive patterns. All recursive patterns do a null check. One simple form will do nothing but: “expr is {} x”. This is a degenerate form of the property pattern, where we check no properties (hence the empty “{}”). We do a null check, however, and if not null, put the value of the expr into x and return true.
What other languages, pray tell? Most languages have opted out of the null problem a long time ago.
Most languages *perhaps*, but not the languages of most *users*. 😉
Long awaited! Wonderful work!
Will `public string MyString {get; set;}` emit a null parameter check for the setter?
No. We do not change the code we emit. But the person trying to set a null into MyString will get a warning.
It’d be great if there was a straight forward to convert warnings to build failure for specific cases, like this one.
All warnings as failures is egregiously annoying.
Maybe some kind of #pragma statement that says this warning is a failure, or converse to the ability mark a warning as ignored to mark it as failure (this might be simpler at the assembly level instead of grammar level)
You can list the specific warnings you want to be failures in C#. Or you can list the specific warnings you don’t want to be failures using the WarningsNotAsErrors tag. https://stackoverflow.com/questions/267168/treat-all-warnings-as-errors-except-in-visual-studio
https://msdn.microsoft.com/en-us/library/bb629394.aspx
`CSwhatever`
or the /warnaserrors 1594 or whatever command line
https://msdn.microsoft.com/en-us/library/bb629394.aspx
<WarnAsErrors>CSwhatever</WarnAsErrors>
or the /warnaserrors 1594 or whatever command line
What is difference between that approach and Resharper’s CanBeNullAttribute ?
It works for people who don’t have Resharper?
1. The syntax is more concise
2. It’s checked by the compiler itself
3. It works in constructed types, such as string?[] and List<string?>
4. It is tracked by type inference
5. It is shown in hovertips and IntelliSense
6. It is standardized for everyone
7. Warnings can be silenced by “!”
Etc…
Thanks !
IMO 4th point is most significant. Point 3 can be achieved with ItemCanBeNull attribute.
There is no ItemItemCanBeNull though for something like Task
Task<string[]>
Is your intention to revisit all .net framework standard libraries and to switch reference types to nullable reference types when they may return null?
See my question above and Mads answer.
Mads, thank you for the comprehensive post. I’ve been following the basics of this on Github for the past…well…I guess since it’s been first discussed.
Two questions, though.
1) ReSharper from JetBrains provides a set of annoation attributes amongst them NotNullAttribute and CanBeNullAttribute which ReSharper uses to provide a meaningful null-analysis. Are you in contact with JetBrains to make sure that the compiler and ReSharper aren’t going to start fighting over the user’s attention to detail?
2) Custom tools…I’ve got about 6000 argument-not-null-checks embedded in my code via utility methods (was surprised there’s not more of them but apparently my API surface is tighter than I had thought). There’s long since been a discussion about using post-compile steps to inject the argument checks and the NotNull ReSharper Annotations by default / unless the developer already specified it the other way around. The problem with the post-compile step though, is compile time performance. It just takes time and can be more efficient to just add it once manually (or via tooling) to the code than to have a tool run on very compilation. Now that we have Roslyn, it would be so, so, so (sorry the emphasis) great to just use it as a compiler pipeline and hook up my own transformations into the compiler run. That way, I would only have to pay for the actual tree manipulation, not the assemly building etc.
Lastly, I applaud the tightrope act you are performing here. Personally, I’d love to just turn the null-checks up to eleven and opt out of non-nullability on a case-by-case basis.
Best regards, Michael
Great news! Made my day, I’ll definitely try it.
Yes Yes Yes! I currently use ReSharper’s [NotNull] and [CanBeNull] attributes on method parameters, properties and sometimes even fields. I cannot overstate how many NullReferenceExceptions that has already prevented.
It also makes working with APIs so much more convenient. If I see a method with a string? parameter I know it’s safe to pass it a potentially null argument. If I have a non-nullable parameter in a private help method, I’m confident I don’t have to null-check it and anyone who would call it with null would get a warning.
Will there be attributes to help the compiler understand common patterns?
For example, consider a method like this: bool TryGet(out string? result)
The return value indicates whether result is null or not. It would be great if the compiler could understand this as well and not create any warnings.
ReSharper provides a ContractAnnotation for this: https://blog.jetbrains.com/dotnet/2012/08/15/contract-annotations-in-resharper-7/
Essentially you put a [ContractAnnotation(“=>true,result:notnull; =>false,result:null”)] on the TryGet method and the compiler can interpret it.
There are various null-related patterns where something like this would be useful, for instance a Guard.AgainstNull(myArgument) that throws when myArgument is null, meaning the compiler doesn’t have to create warnings after the call. With ReSharper,
a [ContractAnnotation(“arg:null=>halt”)] expresses this.
I think this is a really great step forward, thanks!
Nulls are nasty
This looks great. PS. “alright” is not a word.
Hmm… https://www.merriam-webster.com/dictionary/alright 😀
Definition of alright
:all right
First Known Use: 1865
But then I think the only advantageous or realistic philosophy of language is descriptivism, not prescriptivism. =)
However, “aggresiveness” is a typo. @Mads
Well done, @jnm2. http://i.imgur.com/0mKXcg1.gif
So it is. Fixed! 😀
This is looking very good. A nice feature, with simple syntax, that will bring more safety to C# code, similar to F#.
This is a bad move. Coming from the C++ world, I always understood perfectly well what a reference type meant. Just like a pointer can be null, a reference type can be null. If you want to wrap a reference type in a NonNullable and also add some syntactic sugar to the language, great! That would be an explicit contract for a reference type.
Assigning a reference type to null means it doesn’t point to any instance of that object. This is not a hard concept.
Why are you breaking our code with this?
Just like you added Nullable to wrap value types to allow them to be nullable…you could create a NonNullable for reference types.
Give it some syntax like
string! foo = “some string”;
The string! means not nullable….come on….
Why should nullability semantics be coupled to how data is stored in memory or how they are passed as arguments to other functions?
It was a mistake to do this originally. The sooner C# moves away from this the better.
I disagree that it was a mistake. Regardless, the language is mature and now this would change how code is interpreted. No longer can we look a the code and know how reference types are handled unless we know it was compiled with this option.
This is like redefining what a reference type is now. Why redefine it? It is what it is and if the problem is that we need to ensure via some contract that a reference type can’t be nullable (which doesn’t make sense to me anyway sense it is a pointer to an object in memory not the object itself), then we *add* that to the language not redefine what a reference type means.
non-nullable reference doesn’t even make sense. Maybe they should have stuck with C++ sematics but they didn’t. It’s fine with the way it is…just add something too the language that gives clear indication that the reference type isn’t nullable. Easy.
I agree that what is known in C# as reference is in fact POINTER.
If I would be an advocate of conspiracy theories I would say that the term “reference” was chosen to just make C# look better than C++ (for marketing purposes).
Look how C++ distinguish what can be null (pointers) from what CANNOT be null:
// Equivalent of current C# code looks in C++ like this:
vector* pointerToListOfPointers;
// C# should really find a way to express THIS!
vector nonNullListOfNonNullObjects;
// or sth potentially more doable in C# – a real reference
vector nonNullListOfReferencesToNonNullObjects;
Going back to the proposition.
Warnings are not a solution.
IMHO really great developers have already found some ways to tackle with nulls in an effective way (attributes, contracts, etc. + deliberately taking care of using them). They do not need such suboptimal solution like this and will not switch to it.
Other developers that suffers from nulls every day will still not be freed from “The billion-dollar mistake” because the proposition does not solve the problem entirely.
Sorry! This forum editor has ate my code 🙂
Now I have put spaces trying to avoid this:
// Current C# code equivalent looks in C++ like this:
vector * pointerToListOfPointers;
// C# should really find a way to express THIS!
vector nonNullListOfNonNullObjects;
// or sth potentially more doable in C# – real reference
vector nonNullListOfReferencesToNonNullObjects;
Sorry again. Now I have put underscores trying to avoid this.
Haven’t found help what is a syntax of posts on MSDN blogs to include code samples.
// Current C# code equivalent looks in C++ like this:
vector__* pointerToListOfPointers;
// C# should really find a way to express THIS!
vector_ nonNullListOfNonNullObjects;
// or sth potentially more doable in C# – real reference
vector_ nonNullListOfReferencesToNonNullObjects;
I’m done. How to post code sample here?
I would really like to clean all this mess with my posts by myself.
If only forum would allow me to edit and/or deleted my posts….
@Mads Torgersen, simply feel free to delete all my post as they do not provide correct code at all.
In C++ we already have not_null. See here: https://github.com/isocpp/CppCoreGuidelines/blob/master/CppCoreGuidelines.md#Ri-nullptr
Example from the linked URL:
int length(const char* p); // we must assume that p can be nullptr
int length(not_null p); // better: we can assume that p cannot be nullptr
Why is this added to the language in A.D. 2017? Hoare “invented” null in the 1960s, but didn’t force you to reinvent it in a language made almost forty years later. You had every chance in the world to fix this as part of the language and not as a patch on the language.
Man, I wish I had your hindsight! 😉
Isn’t forty years of hindsight enough?
Apparently 40 years is enough, because they’re addressing it now!
Blame that Anders guy.
As a teacher, one of my main goal is to make students understand the fundamental difference between Value and Reference types. The current solution exposed here will be a mess.
In my experience, you first understand Value and Reference types with a simple rule Value type are manipulated by value and as such can not be null. References are “naturally” nullable.
Based on this:
– You want a nullable value? Suffix it with a ?
– You want a non-nullable reference? Suffix it with a !
And yes, this EXHIBITS the very different nature of these two beasts.
And this is a good thing.
I would worry about teaching that as the difference between reference and value types. You should illustrate how they behave different when passing them as arguments – one is a *reference* to a value. The other is a *copy* of the value. That’s the only difference that counts. Languages like F# (for example) have support for both reference and value types. But neither are nullable.
The fact that reference types are nullable in C# should best be thought of as a coincidence – not an explicit effect.
Olivier, totally agree!
This is a good step in the right direction, assuming devs pay attention to the warnings. I’d rather see some information, such as the property name in a NullReferenceException that is thrown at runtime if a member is dereferenced on a null object. Yes, it is better to handle issues at compile time rather than runtime, however, NullReferenceExceptions thrown at runtime without information on precisely what is null is more of a pain point for me.
Visual Studio now has better support for showing you what was null when a null reference exception happens in the debugger.
That doesn’t help you in production, but then again, if you turn on nullable reference types you won’t *have* so many in production. 🙂
This approach feels uncharacteristic for C#; like its a band-aid that covers up the problem imperfectly rather than a comprehensive solution. Like it has the same sort of “gotcha” warts that would have existed if generics had been implemented using Java-style type erasure by the compiler rather than drilling native support for them down into the runtime itself; or if Nullable hadn’t been given support in the runtime and was just done via compiler magic only.
Holes in the warning system because certain constructs are “too common” are a pretty big red flag that this isn’t the right approach; and aside from the mushy areas around arrays that C# inherited from Java, C# just doesn’t *do* holes in its type enforcement system.
Seeing all the negative comments I just want to add that I think you are doing the right thing, null is absolutely the exception and non null should be the default.
Thought experiment: Code Review this method…assume that Foo is a reference type…
public void PrintFooName(Foo foo)
{
Console.WriteLine(foo.Name);
}
Now depending on how the compiler is set, Foo will either be a real reference or a non-nullable reference but we don’t know that unless we know what the compiler is doing.
Now if the default behavior becomes the only behavior, then we’ll have to ask which version of the compiler was used before we know the behavior of this code.
However if we keep the behavior as it is now, we know that this method needs a null check regardless of the compiler being used.
Now let’s rewrite it with new syntax to support non-nullable reference types with an *explicit* syntax.
public void PrintFooName(Foo! foo)
{
Console.WriteLine(foo.Name);
}
Now we know several things about this code.
1. It is using new syntax which implies newer compiler.
2. It is explicit that the foo parameter is not nullable (because of the “!” or we could use “&”, e.g. Foo& to be closer to C++ concept of a reference) so there is no need for a null check.
This would not result in broken code, confusion, and changing the behavior of a fundamental concept as a reference type in C#….I can’t believe this is even being considered.
Changing the meaning (not the behaviour) will create some pain at the beginning, but typescript has already gone though it, and now is ok.
I prefer to suffer a little bit for some time than having to write Dictionary<string!,List!>! for the rest of my live.
This change does not change the behavior – there isn’t two compilers, there isn’t emitted code differences, There are just warnings and no warnings, so everything you are saying is wrong.
Pete, it is my understanding that initially this will be a compiler flag to enable. So right there, if you compile the code above with the compiler flag enabled, the behavior does change in that you’re guaranteed that foo can’t be null so no null reference checking required. But if you compile with today’s compiler/options, it can be null. But you can’t know that by looking at the code alone. That’s my point. There’s no obvious way to know that this code behaves a certain way because we don’t know how it is compiled.
But the does the behavior of the compiled code actually change? My impression is that this is _not_ a change to the actual type system, just what warnings the compiler throws. So the compiler might behave differently depending on the compiler version/options while the resulting code behaves the same way regardless, but that’s always been true.
There is no guarantee that the parameter is not null, just a strong likelihood if everyone is playing by the rules.
Jim, as the others said, this doesn’t actually change the code compiled at all. If you saw this function in an older project, you’d assume it won’t be called with null since it has no check. If you aren’t sure, you’d travel up the stack to callers to see if it is checked and if there is a bug in the code. If you want more information without doing that legwork yourself, you compile with the flag enabled. Now it says “FooCreator calls PrintFooName but its code paths don’t sure it is non-null by that time”. So you should probably fix the bug…
It isn’t about knowing what version it was compiled with, it is about you compiling it with the warnings on and seeing if you’ve made a mistake. I “think” for external libraries there will be some kind of typing information exported you can use? If not, when using external libraries you don’t control you probably should put a null check at the point of using their return value and now you’ll know the rest of your code is safe.
I think it all makes more sense if you know TypeScript. TypeScript just outputs regular JavaScript and has no special runtime checks. If an external library has a type definition file saying a function returns something with interface IFoo but it doesn’t actually return that, your code could blow up. But that isn’t any different from the docs of the library saying it has property Name on the returned object but it doesn’t. Most of the time it all works fine and saves a lot of time in preventing bugs and additional documentation.
I’m puzzled about the developers that have commented about not seeing the need for this feature. I’m extremely enthusiastic about this feature in general.
My team has been using Code Contracts since I introduced it 5-6 years ago, and a big part of the need that Code Contracts fills is around clarifying and enforcing nullability (via parameter checks, documentation, code clarity, etc). I’m been quite frustrated with CodeContracts being abandoned in the last 18 months, particularly the lack of support in VS 2017 and no official statement on the project, though it is “in the box” for VS 2015. I spent more time than I’d like figuring out how to make Code Contracts work in VS 2017 tooling, but 1 ominous obstacle is that ccrewrite crashes on portable PDBs. This means any portable PDBs in dependencies will crash ccrewrite.
While this feature doesn’t replace all the benefits of CodeContracts, it replaces the primary benefit (in theory, I haven’t used it yet) in a clearer and more productive way. If Roslyn adds support for code generation (https://github.com/dotnet/csharplang/issues/341) IMO the CC functionality could be effectively replaced by library authors.
The one complaint I have about the language design described above is the use of `!` suffix operator to mean “not nullable”. I find the `?` operator to be natural for inline null checks and `??` for null alternatives, but I don’t find `!` to be a natural way to assert/communicate not nullable. I will read it as “not” at least until it becomes mainstream and I figure out a way to not dislike it. I don’t have a better suggestion, sorry.
Correcting my post since there doesn’t seem to be a way to edit it: Nullable reference types DOES NOT replace the primary benefit of Code Contracts, but it does provide a better model for the 70% (maybe even 80%) use case.
My objection is redefining how reference types work be default. I’m totally onboard with having compiler supported non-nullable reference types but as an explicit syntax whatever that may be.
In C++ reference variables were like this Foo& so maybe use the & but the idea is to not break code nor change behavior such a fundamental thing as reference types.
I know where you’re coming from, but this seems like an ok compromise for now for me. I think it is pretty safe to say that the vast majority of code should be non-nullable with certain exceptions needed. That points toward non-null being the default and nullable being extra syntax. The trouble is not just needing a tooon of ! everywhere to specify non-null but now you also have three kinds in the system. Unspecified, specified nullable and specified non-nullable. If you get to the point in your code where everything is either ! or ? and you enforce needing them specified, then it’d make sense to just get rid of the ! again and we’re back where we started.
But actually changing the underlying IL in some way to break existing code would be a huge deal. Maybe it can happen eventually if this takes off, but would be too invasive right now. So this solution helps the syntax to make nullability clearer and helps detect a lot of bugs without such an invasive change at the IL level.
Forget pragmas, I want a VStudio option to disable this micro-managing nonsense permanently. I’m sort of appalled any professional developer has such a hard time dealing with nulls that they’d applaud this weirdness. I have a higher regard for C# than any other language I’ve used in the past 40 years, but I’m starting to dread the recent explosion of line-noise syntax, and nothing is worse than mandatory new syntax that isn’t even functionally useful. It’s mixing design-time code analysis into the language syntax itself. Ridiculous.
Somebody asked a good question earlier — does Microsoft intend to fix the entire .NET codebase? How about all the gajillions of Azure, SQL Server, Office, and other SDKs? What about the thousands and thousands of MSDN Library samples and articles, etc etc etc? Surely if the dreaded null is so threatening and dangerous, it would be terribly irresponsible of you to foist this eyesore on our codebases without addressing your own lack of explicitly stated intent.
I also see this being a maintenance problem. When you have a large code base there’s a lot of different people working on it. Some people know the code very well and others barely at all. People make changes without knowing the impact of their change, compounded by the absence of unit testing, no documentation, no mandatory code reviews, everyone being short on time. Not all teams are able to follow recommended practices, depending on what management’s focus is – is this a software company, or does the software exist only to facilitate the business in making money in some other industry? Good engineering requires budgets that permit it. I think what will happen here is someone will start modifying the code in ways where what was not null before, can be null now, or vice versa, but no one will have the resources to go through the code base to make sure all the ? and ! remain correct. I understand that the purpose of this feature is so that the compiler will alert you to what is breaking due to nullity, but it does not happen automatically – a person still has to express their intent, that intent can and will change, and it is not necessarily the case that the person making the change understands what is correct. I have been stumped many times when reading code written by someone else and I can’t figure out what they were thinking. And I can’t ask them because they work somewhere else now.
Honestly, I feel this thinking kind of misses the point. It makes it easier to deal with larger codebases instead of harder. Right now there can be no intent specified in the code, so you have to rely on outside things. Currently if a given function uses an argument of a reference type and has no null check, your only way to know if this is a bug is to check up the stack of all the callers. But with the changes it’d give a warning saying “foo class that called your function might be passing a null to it, so you should check for null before calling it”. Currently you may know your function is safe to not need an internal null check due to the callers being safe. But what if someone on another team calls it and isn’t safe? Or they may not know if the function can be called with null. So now they need to look at the body of the function to see if it accepts null (and maybe it delegates off to another call, so you have to go deeper).
As for libraries, BCL, etc. I think if it doesn’t have some specific null annotation file for it (like what TypeScript does), you could treat those as always nullable and work from there. If you know the external library never returns null, you could use the !, otherwise would check for null as always. It’d just encourage you to check at the first point that your code expects there not to be null.
I believe this new behaviour should be project property and not IDE setting. Because ultimately it is your project which either by default handles variables as nullable or non-nullable and not your entire IDE.
This way we keep current codebase intact but both new projects and old projects can utilize that non-null enforcement if explicitly desired.
I voted for the feature, but now reading the comments I am not sure the implementation is what we (who voted) wanted. Sorry for this comment as I did not help with a proposal.
What about runtime checking? From what I understood it is a compile time feature. If it is compile time only it will not prevent runtime NPEs when exposing APIs through REST on an MVC application for example.
In that aspect I miss code contracts that in my point of view should be integrated in the language syntax (see Spec Sharp) with is ability to toggle runtime enforcement.
There is no runtime change (except for attributes on method declarations).
Ooh, I missed that there is no runtime enforcement. I think that’s a major problem, and it doesn’t make sense to me, which is why it didn’t occur to ask specifically about that.
If a non-nullable property/field can be set to null (eg using reflection or serialization) then you’ll get NPEs, and all the code expects that the field can’t/wont be null.
Matt Warren: Are you sure that there is no runtime enforcement? I suppose I could test it out, but that just doesn’t make any sense…
Great stuff!
I was wondering how you would solve the problem with 3rd-party libraries that are not “nullable reference aware”. And I’m surprised that your response is basically “Fix your external library or push owner of this library to fix it.” I understand that your experience with Typescript shows that it’s not that big deal as it seems to be but in Typescript/Javascript it’s a bit easier:
1. Community embrace open-source more. It’s easier to ask owner to fix or provide fix yourself.
2. Even if you didn’t write this library you can always edit source or, more likely, .d.ts files with type definitions
3. If you use some ancient library that no one is supporting you probably wrote type definitions yourself or didn’t bother and just typed everything as “any”
And what option do I have when I use some ancient library that I only have .dll for. I know which arguments or return types can be null and which don’t. I know it from experience with working with this library. Can a consumer of this library (which is can be some other .dll library) express this additional information about nullability somehow? Or I need to use “!” everywhere in my consuming code when I use this “not null-aware library”?
You can also put a #pragma disable warning
Your point about what’s different in the TypeScript ecosystem is certainly valid. Just because the approach worked out there, doesn’t *necessarily* mean it can work for us.
I love you all and I hope you all win the lottery the day after this lands in the official compiler.
So this feature looks like regular code analyzer (or maybe code contracts static analysis?) with only difference that this one has syntax sugar for annotation. Its good to have such a tool, but… Its far not the same as having burned-into-code preconditions support. I can’t rely that some other code which is compiled without respecting these annotations won’t try to pass nulls to my code. I anyway have to manually put null checks to all public APIs.
How about at least adding a compiler switch to enable preconditions generation for public APIs based on this non-nullability support?
I think this is a good thing to get done and I like the approach.
Any idea when this will be included in Entity Framework 6?
Our programs rely so much on EF that I will not start on this, before the API generate classes and relations correctly according to the new way, when we use the edmx designer.
Ofc the int? and stuff is already there, but the missing part is when relations to objects might be null, because they are connected to a nullable field.
Hope it makes sense
Cheers.
I was initially concerned about how nullable reference types end up implemented. I must say that I like this approach and especially that non-nullable is the default. I only hope that this will be included in VB.NET as well!
@Peter,
A feature like this will have a huge impact on the .NET platform and all three of our languages will have work to do to take advantage of it. There is significant overlap between the VB and C# language design teams and we started to review what this feature would look like in VB but decided to first release this preview in C# and get as much feedback on the design before putting in more design work on the VB side. The idea being, we’ll no doubt need to make changes based on the feedback we get on the prototype so we should make sure the design is in a really good place before VB-ifying it. So you can help us get it into VB by trying it in C# and telling us what you love and what you hate so we can end up with the best design for both languages 🙂
-ADG
Let’s see how all this evolves. I’m looking forward to it.
So far I think this is an unnecessary complication added to a well-established domain. Seems like the new feature now comes to rescue me from evil nulls after 15 years of dealing with them in C#…
I’m sad that my favorite, innovative language got this after ‘Swift’
Please include ‘HasValue’ as per nullable value types
Or maybe HasReference or whatever…
How would you envisage null.HasValue working? It would effectively have to be a new property on Object that always returned true and would have to be accessed via objectUnderTest?.HasValue.
It could be implemented as an extension method on object 🙂
public static bool HasValue(this object? o) => o != null;
I think it is a valid line of thought to try to minimize the differences between nullable reference types and nullable value types. Whether this particular method is a good idea, I am not sure. I don’t like extension methods that handle null arguments, because it breaks the instance method “illusion”.
Yes, that thought struck me too. Consistency is good.
Could it be the other way around?
For example Person! means a non-nullable type while Person should remain nullable?
Basically I appreciate this as an improvement of the Expression of intent of the Code, in particular for new code. However, the loopholes, where no warning is created even though there is a Chance that a reference may be set to null might produce hard to find Errors in new Code.
A clean way treating existing code when introducing this concept would be to Change all reference declarations R to R?. Yes, changing the Code was never something a Compiler was allowed or even able to do. however: this way the semantics of the Code would, in fact, be untouched. Which I would prefer to Change the semantics of an existing Code base (is a Little bit as if laws were retro-fitted).
One Little favor (most likely you thought about that anyway) – You want to allow this:
string[] a = new string[10]; // 4 ok: too common
Please be so nice as to flag any usage like a[i] with appropriate warnings. ok – if that string[] a is the return value of some function, and not all places of the Array have been set, that might be a difficult to find loophole.
> Please be so nice as to flag any usage like a[i] with appropriate warnings.
Yes, in essence I wonder if such arrays could automatically be treated as nullable reference types?
Yeah, it’d be nice if there was more tooling around arrays. Like if var ages = int[3]; would give a warning that it should be int?[3]; instead (or at least treat the var as int?[3] after). But var ages = [12, 23, 34] or List’s ToArray() method could actually be int[3] since it knows it has to be ok. Anything in between I’m guessing is too hard to detect that all items were filled in later and now it is safe automatically.
(repost – my original comment didn’t seem to appear)
I know how I’m going to test this feature. I’m going to turn off the warning and never turn it on. Once again C# is getting a feature of limited value that was “requested” by the community. It seems like the C# team has completely lost focus in this new “design by committee” world. We’re getting features of limited useful to every day code. This feature, in particular, doesn’t solve anything. I’m still going to write the exact same code except now I have to write more code to fix the compiler warnings.
Let’s look at a couple of examples.
public void Foo ( string value ) { }
Getting a warning here solves nothing. I’m still checking for null. Why? Because the users of this code are using one of the many .NET languages that don’t understand this nonnull ref concept. So I have to code for that. Here’s another one.
public class SomeType
{
public string SomeProperty { get; set; }
}
void Foo ( SomeType value )
{
var str = value?.SomeProperty;
}
I have to check for null here as well. Why? Because the object may have been created by some other language or by serialization (which doesn’t require a constructor call). Other than adding more warnings that I have to resolve, this feature solves absolutely nothing.
I would like to request a new language feature – it’s called immutable. This feature basically dismisses the language design committee and locks the C# repo for a while so people can actually use the language without it continually changing out from under them. Fast iterations aren’t always good and this is one of them. The new features are being added so fast that they are half baked. The language, which was a simple replacement for C++, has become a nightmare to work in. It’s quickly becoming as bad as C++ to learn and use. I used to be able to know what is available by knowing the VS version. Now I have to know whether you have update 3 or 4 installed for language features. Try teaching this in a classroom which is at VS 2017 update 1 while the students at home are running VS 2017 update 4. I know because I’m in that situation right now.
“Users of this code are using one of the many .NET languages that don’t understand”. As the post stated, this improvement is for C#. Blaming C# for the lack of support in other languages is just making no sense. F# is very strong in immutability, leaving VB. Honestly, not a very strong case.
Eh, quite a lot of people aren’t writing public APIs that might be consumed by people in different .NET languages, etc. If you have a large enterprise application, most of that code is in-house and using the same language but might be used by different teams. It’d be very handy to know that method created by team C doesn’t handle null, so I should take care of that before calling it. Otherwise I have to go into the method and see how exactly it is using the value. It might even be passing it down to other things that now you have to look at. Vice versa, if a particular method has no null check in it, you have no idea if that is a bug or not without looking at all the callers and perhaps their callers. It’d be overkill to have every method of every class in your codebase that accepts a reference type have to check for null before using it. Now you have more security about if your method in the bowels of the system is being called with null or not and you know a bug hasn’t been introduced by Team B also using the method but not checking for null as it was in the previous caller. Sure you still have to check at the edges of your application and when working with external libraries but where it helps is within your own inner code IMO.
Actually, thinking about it, shouldn’t it be a tri-state thing (nullable, non-nullable, undefined)?
Any newly compiled assembly would have to be either nullable or non-nullable (per the syntax suggested). However any legacy assembly where the nullable attribute is not set is treated as undefined. And you do not generate warnings if you are consuming an undefined type.
This would allow backward compatibility, and not polluting solutions with warnings they you can’t do anything about when they are consuming a legacy assembly.
That would mirror Kotlin’s “platform types” for java interop.
https://kotlinlang.org/docs/reference/java-interop.html#null-safety-and-platform-types
Seems to work well there.
This is explicitly what Mads said they don’t want to do: they don’t want all declarations to be written as “string”, “string!” or “string?”.
I’m not too afraid of adding a simple “!” non-nullable operator because of the preferred explicitness. We already have the “var” keyword for type inference to allow retaining “pretty” looking code when the type is explicitly known.
So, that would be my preferred change for C#. Their very clever system of warnings, et al, to treat it as a two-state problem has too many potential issues. And it ensures that we need to go back and modify old code anyway. If we have to go back and modify old code anyway, why not have the non-nullability be opt-in?
No I am not suggesting new code should be allowed to have three states. New code should be only nullable or non-nullable, so there is no need for a third notation. Just do not interpret legacy assembly where the nullable flag hasn’t been defined as non-nullable. Just interpret it as a third state that doesn’t generate warnings.
Thinking about it, legacy assemblies will be around for a while. Not just because people don’t update them. Also because if you create a library you don’t really want to target the very latest version of the framework otherwise you make it incompatible to whoever is targeting an earlier version (think some big projects where changing the targeted version is a big deal).
This is not out of the question. We looked at approaches like that. We decided to try to live without them, because they do bring complexity with them.
It is absolutely possible – easy, even – to identify “pre-nullable” assemblies: Just have the compiler add an attribute to the new “post-nullable” ones, and check if it’s there. We could then have a third state that’s “classic”, “legacy”, “oblivious”, “dangerous”, or whatever we want to call it, and interpret all “old” assemblies as trafficking in that.
What do we want to do with legacy APIs, though? Avoid all warnings on the grounds that they don’t express their intent, so it wouldn’t be appropriate? Or warn *everywhere*, precisely because all the references are null unsafe, and you want to defensively guard against both nulls going in and nulls coming out? I can see arguments for both answers.
So far we’re happy to see if we can just dodge this whole question! 🙂 But we may have to revisit it.
It would be nice to be able to identify post-nullable assemblies that are known to be clean. The ones that only reference other clean assembles or perform all the right checks when referencing legacy ones.
Then after adoption increases a move to errors instead of warnings might be possible.
I already posted this as a reply to a reply to a comment… So, I’m posted here as a top-level comment, with some additional thoughts.
The proper response is to hard fork of C# that’s only everything we’ve always wanted: call the new language something brilliant like C#++. I’ll use C#++ for now.
We should get a clean C#++ that uses whatever nice things we want to borrow from TypeScript, F#, Kotlin, etc.. Non-nullability from the ground up would be one basic feature.
Another great example would be to have C#++ use F# style async/await implementation since improvements to the async/await concept were thought of after C# had already shipped it’s async/await implementation (e.g. it is now known how to implement async/await without allocation of Task classes). Or even keep the current implementation and add an alternative flavor that’s more efficient. We’ve already got all those async methods written returning Task objects… But that’s still a great place for some kind of automated convert to step in (I’ve sure JetBrains would put a converter like that in Resharper, for instance).
Lastly, it would be easier to do this and port existing C# to C#++ than to deal with all of these contrivances forced onto C#. We really do need a way of starting fresh with an awesomer flavor of C# without breaking old code or doing these kinds of incredible logistical backflips.
Implementing amazingly complicated features like this new non-nullable reference type contrivance is pushing off the real solution. Other communities introduce new languages all the time to deal with situations exactly like non-nullability. I believe we are way past time for a C#++ to hit the scene.
The nice thing is that if you guys don’t go too crazy with the new language, you can keep it in sync with new features added to C#. Moreover, since they are both .Net languages, they would be interoperable. C#++ code could call C# code, etc.
And we could follow TypeScript’s lead and have an externalizable metadata provider to wrap C# legacy assemblies with the metadata to make the C#++ compiler happy. For example, if old code ensures non-nullability, we could either add [NonNullable] attributes to our old C# as appropriate or have these defined in external XML metadata.
This is the real solution. And it will generate a huge consulting/career opportunity for developers to bring legacy code into the new, safer age of programming. And companies are the winners: getting rid of generalized nullability is way past due. And companies are already rewriting entire codebases and libraries into new languages just to get benefits like that. So, why not provide them a C# successor that requires very little code changes for accomplishing just that?
That’s a big win for MS: solves the core issues, keeps current developers on .NET, and provides a new stable foundation for marketing .NET (with C#++) as a viable solution for companies looking to address those core safety issues without jumping to another platform (e.g. Kotlin).
Regards,
For the record, in C#++, my preferred outcome would be to have non-nullability be default. And for structs and references, nullability can be imparted with the “?” modifier.
If we cannot get the momentum to create C#++, my preferred fix for C# is to have an explicit non-nullability operator “!”. Yes, it means we have to rewrite old code to opt-in. But the current solution requires a potential massive code rewrite anyway. The existence of the “var” keyword allows clean looking code where the explicit type can be inferred. So we don’t necessarily have to see “?” and “!” after every variable use — just the declarations. Moreover, we could have an automated fixup and introduce a one-time fixup for old code and turn every naked reference variable into a “?” declaration and then make the default declaration non-nullable. In that automated fixup scenario, we’d need to do things like turn default(string) into default(string?). Lastly, we would then just extend the current Nullable semantics to apply to nullable reference and struct types. I am *pretty* sure that this would all be syntactic sugar and that no CLR changes would be required (I believe the required CLR capabilities were added when Nullable was added for structs).
Moreover, the automated fixup for C# like what I described above could be two-way: if a developer selects C#vnext in the drop down, it runs the converter forward, and if the developer selects C#legacy it would could the converter backwards. The conversion logic is deterministic both directions, so that is very doable.
That’s my 2 cents: let developers opt-in to non-nullability as appropriate and make the syntax for nullable types by the same for structs and references. (And have an automated way to fixup old code. Everyone likes wizards, right?)
Thinking about this some more. Implementing my preferred design on C# might require a CLR change :/
I’m not an expert on CIL and I’m not sure the exact changes that were added to support nullable structs. But I can perceive that it might require a new CLR to enforce non-nullable reference types at runtime: e.g. non-nullable can convert implicitly to nullable (unchecked conversion), but nullable cannot implicitly convert to non-nullable (explicit checked conversion would be required). If the existing Convert and ConvertChecked CIL instructions could be used-as is with this language design, then I think it could be done without changing the CLR.
If not… the CLR would have to be changed too. However, a change like this to Convert and ConvertChecked in the CLR would make designing languages with explicit nullability easier to write for the CLR.
Since F# has explicit nullability and it runs on the current CLR, I’m guessing the current CIL instructions were sufficient to do so. However, maybe they did some inefficient CIL emission hackery to stay within the current CLR version. If that is the case, adding this feature to the CLR would make languages like F# more effective at runtime too. So, it would be worth it.
Moreover, this could be handled at the JIT level without modifying the CLR: leave CIL the way it is (e.g. use whatever mechanism for emitting CIL for non-nullable reference types that F# uses), and just have the JIT compiler recognize the pattern for reference checking and output efficient runtime code. I’m *guessing* that F#’s non-nullability feature is already done this way and that this JIT optimization (or something like it) is already implemented (either that, or the CLR already had the requisite support required to efficiently achieve F#’s implementation).
The path you are suggesting will lead to a Python 2/3 scenario, where massive amounts of existing code just breaks by failing to compile. Or, you are proposing all the corporations to embark on a massive “Upgrade to C#++” project for no apparent business value. And at some point, people will also ask for improvements to your “C#++” language, but since your repository is locked down, you now need to create C##++. No thanks to any of these outcomes.
F#’s non-nullable implementation is all at the type-system level and done at compile-time. There’s no runtime checking to stop you e.g. using reflection to “beat” the system AFAIK. It works remarkably well – I can’t remember the last time we got a null reference exception from F# code itself (it’s still possible to get it from BCL and C# libraries).
I think you overestimate the willingness of companies to rewrite old code bases to get new language features. In 37 years I nave not come across one company willing to rewrite a code base to take advantage of new language features. There always had to be an extremely compelling business reason to do a rewrite. My most recent experience with this was with a company whose entire product was written in a combination of VB6, c++/COM and c# 1.1. They refused to move forward, and for no other reason than “its worked.” The year was 2017.
Who says they have to upgrade to C# 8? I’ve contributed to OSS projects that still have the version locked at C# 6 and even C# 5, and it is 2017. But that’s their choice.
Swift ?
Have a look at Swift, which manages it pretty well, I think.
And in the same time, if you could add the let keyword (i.e. something like var const), make the parameter names part of method signatures, and allow partial definitions in different assemblies (i.e. extensions), that would be great 🙂.
I’m sad about C#. The default for nullability is plainly crazy.
Have you worked in a language that has non-nulls as default? If so, which one – and why didn’t you like it?
No language in use has non-nulls as default. Maybe some freakish languages such as Kotlin and Swift… and I just don’t give a damned s. about they, because we are talking about C#: a practical language with a HUGE codebase that will become broken after this change.
Seriously, guys, are null reference access such a big issue? I have never been worried about that kind of bugs. I can sort them without help. Yes, some help would be appreciated, but what you pretend is to turn all conventions upside down. That’s crazy.
The codebase will not get broken because these are warnings only
I hate warnings in my code.
Most languages that support non nullability have it on by default. There’s even one in .NET that works this way, F#. I wouldn’t call that (or Swift or Kotlin) “freakish”.
This is a big change for someone that has only ever used C/C++/Java/C# because it’s changing something you take for granted. I’ve tried both for years now – non null by default is way better.
I love the C# language and I respect you guys a lot but there are things I just can’t wrap my head around.
Validating arguments is the first thing we do in methods:
public void Foo(string bar, int baz)
{
if (bar == null)
throw new ArgumentNullException(nameof(bar));
if (baz < 20)
throw new ArgumentOutOfRangeException(nameof(baz), "…");
}
You said there will be no runtime checking, so the compiler of Visual Basic or any other .NET language (even the older versions of C#) without this feature can still pass null (or Nothing) here right? If this is true and I'll still need to do my runtime checking anyway, I fail to see just how useful it is to have non-nullable types.
Won't this cause more bugs if we call something non-nullable when we still can get null instances of it, like in the array example?
The people already know that instances of reference types can be null. They know they need to sanitize their arguments before using them, it's CS 101. But now you're giving them this so called non-nullable types which can actually be null.
"Reference types that can't be null but actually can" is a concept that is hard to learn, hard to teach and adds way more complexity to the language than it needs to. I believe the lack of argument validation is the underlying problem and NREs are just a symptom of it. In my opinion Code Contracts (or Spec#) was on a much better direction to solve it.
There are things people need to understand in order to write good, maintainable code and reference semantics are one of them. I wish these things were simpler but sometimes failing fast and learning something from it is better than not failing. I really find it counter-intuitive to hide the problem under the carpet where it lurks instead of teaching people that reference types can be null and arguments need to be sanitized before use.
CS 101, I think you nail the point exactly, all the requests they are getting are for folks that never made it that far, the reason they are getting what they perceive more requests for this type of thing is that professional developers esp on MS stack spend there days creating solutions not surfing github and stack overflow for code , basically Windows Division is calling the shots, the theory is app should be single page “no lines” or other distractions, and shall be created by the semi skill masses and no more investment in WPF/Real Net , UWP is the future .Net Core Standard XAML, Xamarine Forms for maC and Linux, Period. it’s “all about” the “learn by delivering” throw garbage at the wall , get user feedback, rinse and repeat scrumness and now this. the points you’ve made are just the tip of the iceberg, it’s ethier
#define CheckForNulls, which might be useful, or Sabotage IMHO.
It would be nice, and conceptually simpler, if we actually could have “reference types that can’t be null”. That is not a viable option, especially when added to an existing language. We have to settle for “reference types that shouldn’t be null”. That, I think, is a pretty accurate one-liner to describe what I call “non-nullable” reference types above.
Because people can still (knowingly or by accident) pass null to public APIs that don’t “want” it, those APIs probably still need to do argument checking. The value here is not so much to the author of the API, but to the caller: instead of getting a runtime exception, they can get a compile time warning.
We’ve talked about a syntactic shorthand for doing the argument null checking. As a strawman syntax:
public string GetFullName(Person p!)
{
…
}
Could be a shorthand for
public string GetFullName(Person p)
{
if (p is null) throw new ArgumentNullException(nameof(p));
…
}
Mads, I do like this idea of being able to manually specify the edges of code where they meet the outside with an easy to see syntax. Some people might get carried away (or misunderstand) and use it everywhere, but hopefully not.
I’d guess theoretically there could also be a slower compile mode for debugging where all non-null parameters get this runtime check?
Mads, and everybody else on the C# language design team, I want to contragulate you on the proposed “nullable reference types” approach for C# 8. I wrote the original Visual Studio User Voice Request for such a feature in 2011 [https://visualstudio.uservoice.com/forums/121579-visual-studio-ide/suggestions/2320188-add-non-nullable-reference-types-in-c]. After six years of careful evaluation, you have finally found a sensible, well-balanced, and pragmatic way to immensely improve the current mess with null references in C#.
I want to suggest two things when implementing this feature.
1) I have worked for many years in large, mixed C#/F# projects (and also VB.NET in the past). As the leading .NET language, C# has a special responsibility. Please make sure that, on the IL level, nullable reference types are implemented in a way that can be well integrated with other languages. This should not be too hard, as the major .NET languages are all maintained by Microsoft itself. What a great opportunity to work together! For instance, F# has non-nullability by default; therefore, a C# project referencing an F# assembly should take this into consideration. Maybe we need an assembly-level attribute that testifies, regardless of language, whether an assembly was compiled with [CSharpNullabilityStrictness] or not. Even better, it should be called [CliNullabilityStrictness] and become part of the CLI specification.
2) Please add the flow analysis to nullable value types as well, to keep the language symmetric.
Thanks, and keep up the good work!
+1 to this.
How would something like the code below be parsed?
void Foo(string bar) {
string? x
var c = x!=bar;
}
We need to preserve the meaning that it already has in C# today, so it would be a not-equals expression, and yield an error that x is read without being initialized.
Using “expr!” to the left of a simple assignment (or in general as an “l-value” – a target of assignment) is not useful or meaningful, and should probably be disallowed.
Parser would probably error with “; expected” on line 2.
Finally! I had proposed this idea many times! I am glad you are doing it the right way!
void M(Person p)
{
WriteLine(p.MiddleName.Length); // WARNING: may be null
WriteLine(p.MiddleName!.Length); // ok, you know best!
}
I think you should use: MiddleName?.Length
“`MiddleName?.Length“` would not be ok (it already has meaning today). That would return an “`int?“` not an “`int“`. The “`!“` operator is so you can say that you want this to be the exact same as p.MiddleName.Length, just with the compiler silencing the warning in a easy manner.
Many thanks for a very interesting post. I’m a huge fan of C# and have welcomed all the improvements over the years, but I’m not sure sure about this one. I agree with other comments that this change is uncharacteristically flawed. I for one am quite happy that references can be null, and am used to the pattern of checking for null arguements.
One specific point I’d like to check: if we are to encourage library vendors to change their API signatures, they will understandably want to be sure that they are not imposing a specific compiler version on their clients. Will a client who uses a compiler that doesn’t understand the new syntax work with a library compiled with the new syntax! I’m guessing yes as it is just a new attribute, but I thought it worth checking.
Many thanks
Wonderfully lucid post!
I fully support your design principles for this feature and I’m eagerly looking forward to using it.
Having used Resharper’s null attributes for almost 10 years I can attest to the benefit of having nullability analysis baked into the design-time environment. Over the years it has identified many issues in my code, saving the cost of countless runtime crashes. A NullReferenceException thrown from operational code is often tricky to track down, particularly when it occurs on a line with deeply chained method calls, or within a legacy code base with little input validation and insufficient precondition checks upon entering methods. A mistakenly assigned null in these circumstances can propagate far from the point of assignment making it costly to detect and fix.
Fantastic feature. Thank you!
Will this be added to VB.Net too?
(please, please, please)
this is amazing, when do you expect to release?
This is a great approach! Thank you guys for involving the community to try to get best result and great acceptance by developers.
Better still just add Code Contracts as a first class citizen. And don’t change the default way that code works. If you want something like to make a value Not Nullable then do something like string! instead of breaking all of the existing code just to make your own bias forced on the world.
It doesn’t actually break any existing code, though. The only output change at the IL level is adding an attribute which by default any code would be ignoring. This purely gives optional feedback during the compilation processing of what code is safer and what should have more null checks. It also helps provide developers documentation of the code without having to actually look at the body of a method.
While I understand that null reference types are a large enough issue that a language specific approach is useful, but I also believe an approach based on Code Contracts would be much better overall, as it would create a framework for a larger set of static analysis and warnings to be created without requiring syntactic sugar (even if such sugar is very useful in the nullability case). Example of additional analysis and possible warnings are indexers that require a >=0 value, but still have an argument type of a signed integer. Or non-empty or non-blank strings. And so on. The existing Code Contracts tool does need a lot of work, but it already had the notion of contract assemblies to annotate third party code to prevent warnings and there are techniques to infer constraints based on if-throw patterns and the like.
I understand that Code Contracts is tricky, because it has been used for static annotation as well as run-time code rewriting to enforce constraints. Many people see the second usage as paramount. But, the two usages are completely independent; One does not demand the other. All an analysis framework based on Code Contracts would need is a setup to “assume reference types are non-nullable” option to generate appropriate warnings and contract suggestions, as Code Contracts already tries to infer and suggest contracts during it’s static analysis.
At the very least, the flow analysis should respect Code Contract annotations and their usage in terms of “being smart” about null references.
Yeah I do see where you’re coming from. I haven’t had the chance to use Code Contracts much, so it’s a bit of a blind spot. This is a place where it’d be nice for some enhancements to the type system. Like defining a constructor/validation function that outputs an int of custom type PositiveInt which can still be a normal integer under the hood but can only be created through that function so you know it is safe to use later on. And maybe a custom default value or prohibition on using it for things like empty arrays being initialized. I mean technically you can work around all this by wrapping the value in a class of course, but not so great for efficiency depending on the application. Same problem with structs not being able to hide the parameter-less constructor.
Hopefully they can expand things later on, but I do think the null issue is probably the most important to start with. You can’t even do the “wrapping the value in a class” trick for this particular issue since any class could be null.
Very welcome improvements!
Will guard-clause libraries that provide null-assertions be supported?
For example, the popular Shoudly (https://github.com/shouldly/shouldly) or Ensure.That (https://github.com/danielwertheim/Ensure.That) provide guard assertion code that is often put at the top of the function, like:
EnsureArg.IsNotNullOrWhiteSpace(myString, nameof(myString));
Will the compiler be able to determine that myString would not be null hereafter?
If so, would that be done through code analysis or through attributes?
Congrats — this is a much needed, and long overdue C# ability. A few thoughts:
1) ReferenceEquals seems to complain (CS8600) when used to check for nullness, whether explicitly using “null” as an argument or passing a T? as an argument. I think it is a smoother migration path if ReferenceEquals did not treat this as an error by default.
2) While it is critical to consider the migration path of existing code, C# should also consider “green field” projects, where nulls are “forbidden” from the start.
3) Reading the announcements, it was not clear that the nullness can be specified in an interface.
4) A mechanism to define nullness for dusty deck code would be nice, similar to what can be done for CodeContracts.
5) For the documented “Known Issues”, could you add some verbiage on each item about where the language is headed>
6) Will using ? with a generic type parameter work whether or not the actual type is a reference or value/struct?
7) Is there a supported way to specify nullness in a where clause?
8) What are the planned semantics of nullness with dynamic classes?
9) Maybe give some thought to allowing events to be null even without use of ?
10) Use of Predicate in an interface definition seems to cause problems (CS0535) as it appears to be mishandled as Func
11) My personal belief is that ?. should only be used for invoking events or in finally/dispose handlers. Any chance Roslyn could add some compiler switches along these lines?
12) Are the nullness rules and inferences defined for volatile and ThreadStatic storage? What about unsafe also?
I have been using ReSharper “strict” for years, and it is nice to see C# also adopting this critical functionality. So again, congratulations 🙂
byte[] nullArray = null;
“Cannot convert null to non-nullable reference”
byte[]? nullArray = null;
This gives a compile error.
How can I express that an array can be null? There is no way to get rid of this warning.
public void LogError(string msg, [CanBeNull] Exception e);
LogError(“error”,null);
“Cannot convert null to non-nullable reference”
public void LogError(string msg, Exception? e);
This gives a compile error.
How do I indicate that e is allowed to be null?
Here is an example of the kinds of constructs you referenced. This builds and runs (on my machine 🙂 ) using VS2017 Enterprise 15.5.0 Preview 4.0, with the nullable package referenced at the top of this post. I am using NCrunch, ReSharper, GhostDoc, and several other add-ons, and building this as a .Net 4.7.1 console app. Other notes: Build->Advanced->LanguageVersion is “C# latest minor version (latest)”, ReSharper project C# Language Level is “Experimental”, and I am not treating any warnings as errors. There are numerous “false negative” intellisense errors flagged (as warned in known issues). But the app does compile and run.
#region usings
using System;
using System.Collections.Generic;
using System.Linq;
using Platform.Annotations; // my ReSharper annotation project
#endregion
namespace NullableReferenceA
{
public interface ILogErrorApi
{
#region instance public methods
void LogError ( [ NotNull ] string msg, [ CanBeNull ] Exception ? e );
#endregion
}
public class LogErrorApi : ILogErrorApi
{
#region non-public constructors
[CanBeNull]
public static byte[] ? NullArray ( [ CanBeNull ] IEnumerable ? nullable )
{
byte [] ? result = null;
if ( ! ReferenceEquals ( null, nullable ) )
{
result = nullable.ToArray ( );
}
return result;
}
#endregion
#region class public methods
#endregion
#region instance public methods
///
public void LogError ( string msg, Exception ? e )
{
Console.WriteLine ( $”{msg}: {e}” );
}
#endregion
}
internal class Program
{
#region class non-public methods
private static void Main ( [ NotNull ] [ ItemNotNull ] [ UsedImplicitly ] string [ ] args )
{
var logger = new LogErrorApi ( );
logger.LogError ( “first”, null );
logger.LogError ( “second”, new InvalidOperationException ( “third” ) );
foreach ( var sample in new [ ]
{
LogErrorApi.NullArray ( null ),
LogErrorApi.NullArray(new byte[] { 0x1, 0x2})
} )
{
logger.LogError ( “is” + ( ReferenceEquals ( null, sample) ? “” : ” not” ) + ” null”, null );
}
}
#endregion
}
}
Here is sample output:
first:
second: System.InvalidOperationException: third
is null:
is not null:
NOTE — You could also configure the project to ignore warnings CS8600,CS8604 to silence the nullable related warnings. Also, be careful with ReSharper reformat code — it does not always understand the new syntax, and can really mangle things.
I noticed a formatting issue after I posted — the IEnumerable type specification should have the generic type argument of byte. There may be other things lost in transmission, but I can zip & ship if you are interested.
The first “null reference” I fixed was an Error 429 in a 5-year old VB4/5/6 program (cant remember the details, just the pain). Before that I had dealt with “wild pointers” (actually in-initialised) in C
Fast forward a few years, I joined a team with a BIG VC++ codebase, and I was given the task of turning on GS – stack check. I spent the next month cleaning some of the 8000 odd warnings.
Reading some of the comments, It feels like people are totally focussed on their specific pain-point, and have (justifiable) concern about how features like this will affect THEIR particular class/API/method.
We (and MS) have to look at the bigger picture, “you cant please all of the people all of the time”. Nulls are a fact of life in every major codebase. A well-designed SQL server database will have null columns – DateOfBirth should most likely be non-null, DateOfDeath should be null – unless you work at a headstone manufacturer. These Person properties will need to emerge from the data layer and be used/tested in code in one layer or another. (it would be useful if SQL server and C# could agree on what the date is…)
I do agree with some of the comments about the gradual increase in complexity in C#. K&R’s original book described a clean, simple C language, then came C++, now I struggle to read some of the MACRO-ridden, pragma infested uncommented code offered as “examples”.
One suggestion I’d offer is rather than turning this option on with a compiler switch, allow us to decorate a class / assembly with some [NullCheck] attribute, so we can fix the new warnings incrementally, rather than the Big Bang. If it means taking something from resharper, fine.
(repost – my update didn’t seem to appear).
I’m aware this wont be popular, its perhaps tangential to the language changes.
System.ComponentModel.DataAnnotations (10+ years old now) allows us to decorate classes and properties with attributes like [Required]
so
[MetadataType(typeof(PersonMetaData))]
public class Person
{
public string GivenName { get; set; }
public string FamilyName { get; set; }
public DateTime DateOfBirth { get; set; }
public DateTime? DateOfDeath { get; set; }
public string Email { get; set; }
public string WebSite { get; set; }
public string Notes { get; set; }
}
public class PersonMetaData
{
[Required, Display(Description =”First Name in western cultures”,Name =”Given Name”,Prompt =”etc”)]
public string GivenName { get; set; }
[Required]
public string FamilyName { get; set; }
[Required,Range(typeof(DateTime),””,””)]
public DateTime DateOfBirth { get; set; }
public DateTime? DateOfDeath { get; set; }
[EmailAddress,Required]
public string Email { get; set; }
[Url]
public string WebSite { get; set; }
[MaxLength(2000), Display(Prompt = “watermark”, Name = “label”, GroupName = “validation group”, Description = “tooltip”), UIHint(“MultilineText”)]
public string Notes { get; set; }
}
The attributes are read by controls like asp:DynamicEntity and asp:DynamicControl ( yes webForms ! ) to create a UI that formats input, validates, checks not just for null, but also range. And its localised by resources.
If the Person is passed to an API method, then a ValidationContext can do the checks, and raise exceptions.
How about
public bool Save( [Required] Person person){
var context = new ValidationContext(person, null, null);
bool result = Validator.TryValidateObject(person, context, results, true);
}
Its more typing (4 characters – “[Re” followed by ctrl tab) but its readable , no language change required, predicable results, can be added to existing code, and no punctuation soup like ” ! ? * ??” – fine when you’re fresh but not so cool after an 8 hour coding binge.
Fur Nullable aspects, this is very similar to ReSharper’s [NotNull/CanBeNull/ItemNotNull/ItemCanBeNull]. They are pure static/compile-time specifications that are tracked through flow-analysis with ReSharper displaying messages when something may be violated. As to the representation aspects, that can already be done via domain-defined data types. There are cyclic shifts in “everything must be a domain” vs “everything must be a primitive”, and unfortunately we seem to currently be in the primitive phase.
Also, I’m not sure if the overhead associated with enforcing DataAttributes is acceptable if it is done every time an assignment is made to a decorated property. But it is worth exploring (and you could probably prototype a generic pattern to do the checking on each assignment without too much trouble).
C# would still have to change to allow attributes on anonymous declarations, casts,, locals, etc. (basically anywhere a type could be specified, the attributes should be allowed also).
SQL Server and C# do agree on what the the date is when you use the more modern Date or DateTime2.
And the default value of that is 0001-01-01 (DateTime.MinValue) – which you can use in the database too and then you don’t have null values!
I think this is a fantastic idea and the implementation makes complete sense.
I will try it out…
My remark about dates was slightly tongue-in-cheek, but to expand on that: SQL server has 6 date / time / datetime datatypes, of varying precision. Such a fixed-point type is useful for storing a date, but less useful for indexing. In my (slightly morbid) example, retrieving living people is simply dealt with if DateOfDeath is null. To search on values where its not DateTime.MinValue requires a decent index, maybe on a field with a resolution of 100 nanoseconds.
Again, taking the bigger picture, when marketing want to do a mailshot to living prospects, if the select is slow, because of the NOT Datetime.MinValue bit, they will say just select everyone, and a few grieving spouses will get the letter.
I just dont like using magic numbers to mean “Not set”. I worked on one system where an integer customer Id was set in code, and negative values represented something special. Unfortunately, the system was popular, and there was a risk of the value rolling over…
Remember my 10cents above was about a feature that’s been in the framework (not the C# language) for at least 10 years. It may not be perfect, but it works well. I haven’t benchmarked the difference between decorating with a [Required] attribute and coding a null check/raise exception. Maybe over the Christmas break 🙂
Will the compiler respect an if statement that contains string.IsNullOrEmpty(str)?
https://news.ycombinator.com/item?id=15797496
This is a definitely bad invention, and if this can be turned off, I will do it for sure in every project!
Default non-nullability vs nullability, a tough choice indeed, and a matter of personal preference. I would vote for default nullability though.
Considering:
1. Added value
2. Introduced problems
3. Added complexity to the language
4. Elegance
5. Intuitiveness
6. Compatibility with existing code bases and libraries
In general, I would prefer a compiler to be 100% right about a subset of my code, rather than being
sometimes right about all of it.
By “default nullability” I mean the “Person! p” syntax that would ensure for 100% that p is never null.
Person![] p = new Person![10] –compile error
Person![] p = new Person![1] { new Persion() } – ok
public void Do(Person! p) {
// implicit null check and null argument exception for public methods, no check for privates
}
struct A { public Person! p; } – compile error
class B { public Person! p; public B(Person! p) { this.p = p; } } – ok if B is internal, error if B is public class in a library.
We could decide that public fields cannot be non-nullable due to a multithreaded scenarios, and for public properties throw in case of access to an object that was not fully initialized.
default(B!) – provide a way to define default member of non-nullable class, similar to string.Empty
for example:
class B {
public static readonly B! default = new B(default(Person!));
}
default(string!) == string.Empty – ok
**** ADDED VALUE ****
default nullability – no added value for existing code bases. 100% proof of null safety where it is provably possible.
default non-nullability – will catch a subset of bugs on existing code bases.
**** INTRODUCED PROBLEMS ****
default nullability – none I can think of
default non-nullability – Is likely to introduce a set of bugs where null-check will be omitted, due to the expressed intent of non-nullability which will prove to be wrong.
**** ADDED COMPLEXITY TO THE LANGUAGE ****
default nullability – introduces new syntax, makes the code uglier
default non-nullability
In general non-nullability enforces new concepts and mindset upon you. You can’t choose not to deal with it one way or another. Either you will have to ignore specific warnings, or ignore the new intent you are putting into the code.
1. The notion of nullable references are confusing – since they are always suspected to be nullable.
2. The notion of “somewhat non-nullable” – as a programmer you will have to know when you can trust this non-nullability and when not. Instead of compiler taking this concerns away from you, it brings it in by default.
3. Confusingly different semantics with nullable value types.
**** ELEGANCE ****
default nullability – “?” – adds nullability to value types, “!” – adds non-nullability to reference types seems like an elegant dichotomy.
default non-nullability – elegant but fake.
**** INTUITIVENESS ****
default nullability – expression of the intent is intuitive; however, the interpretation of code correctness is not.
default non-nullability – expression of the intent is intuitive, code correctness is ensured by the compiler. Also, the compiler helps you learn and understand the limitations of the language by preventing you form using non-nullable types in cases where they cannot be proved to be non-nullable. This will if not prevent you from making mistakes, and make you aware of the cases you might not have thought of.
**** COMPATIBILITY WITH EXISTING CODE BASES AND LIBRARIES ****
default nullability – seamless, since this is a new concept did not exist in previous versions of the libraries, and behaves the same way in the new versions.
default non-nullability – confusing, you need to know the version of the compiler in order to understand the intent of the code. Nowhere in the code it is claimed that non-nullability is assumed.
Thanks for the great job of moving the language forward, hope you reconsider this decision.
I also prefer support for P!, as it opens the door to ” P ! – expected to never be null”, ” P ? – expected that it may be null sometime”, and ” P -no idea when or why it will be null or not null”. Maybe there is a way to also support “once/deferred-readonly” semantics (i.e. may be null until assigned, then never null until Dispose()).
There also is a semantic/intention difference between ” P [ 10 ] “, ” P ! [10 ]”, ” P [10] ! }, and “P ! [10] ! “. This is similar to the [NotNull] and [ItemNotNull] support in ReSharper. This gets more precise as C# approaches support for the A68 triple ref technique.
As to defaults, I could see introducing a mechanism to define the “default” value for a reference type, similar to how default constructors and field/property initializers are supported.
I strongly prefer default of not-null for API elements (return types, parameters, properties). I do not think there is serious risk of “default not-null” introducing a new set of run-time bugs — static flow analysis does a very good job of detecting null-status. Of course there are ways around this analysis (just as using reflection can defeat a lot of safety).
And a new project option (or even project type) tailored to nullness is worth considering — similar to “unsafe”, ClsCompliant, …
Thanks for your comment )
I don’t quite understand the benefit of having three states:
Person? p; // intentionally nullable
Person p; // no intent
Person! p; // intentionally non-nullable
It seems excessive and confusing to me. As well as eventually non-nullable, can you provide an example where this could be useful?
“I do not think there is serious risk of “default not-null” introducing a new set of run-time bugs”
Can’t agree.
If you still need to guard all references even non-nullable then what’s the point of non-nullability? If you start avoiding guards, all the cases when the compiler cannot guarantee non-nullability and does not provide warnings you will have bugs.
In the examples provided by Mads:
void M(Person p)
{
if (p.MiddleName != null)
{
p.ResetAllFields(); // can’t detect change
WriteLine(p.MiddleName.Length); // ok
}
}
In other words you need to be smarter than the compiler, in that case what to you need the compiler for?
In a paradoxical way it seems to me this could be yet another billion dollar mistake.
Since it will give coders confidence to avoid null checks.
Sava, your exactingly correct , the value proposition is not a technical one, it’s quite obvious,
unfortunately there a new system of (opinion based design principles) the anti pattern here is basically better to continue executing in some undefined state for poorly designed system than crash , it’s a check box “Feature” like “Open Source”, don’t worry thou , who ever is behind this is is for a rude awakening, the aren’t in the position to dictate anything anymore it’s not 1992
A benefit to supporting all 3 (!, ?, and no intent) is that it makes it more explicit. As you jump between different solutions and projects, it is easy to “forget” what semantics are applied to a project (i.e. does it default a missing specifier to nullable or not nullable?). Using ! and ? clarify things. The ReSharper analogy here is optimistic vs pessimistic (https://www.jetbrains.com/help/resharper/Code_Analysis__Value_Analysis.html ). I always prefer Pessimistic as it reports more potential issues during static analysis.
The examples which ignore volatility are not helpful. Since an async action can modify MiddleName even this is unsafe:
if (p.MiddleName != null)
WriteLine(p.MiddleName.Length); // unsafe, even though post says “ok”
The correct way to code this is using a local temporary variable to hold the value of p.Middle:
var temp = p.MiddleName;
if (temp != null) …
In general, any code that has multiple chained dereferences (p.MiddleName.Length) should be avoided.
Proper use of [Pure] (and a default of “treat unspecified as Impure) could be used to have the flow analysis treat the ResetAllFields() method as something that modifies the state of p. And again for clarity both [Pure] and [Impure] should be supported. ( https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.contracts.pureattribute?view=netframework-4.7.1 )
Migrating to never-null is not trivial, and the ripple effect can be a shock. But proper flow-analysis can take a lot of the uncertainty out of things, especially coupled with properly expressive architecture rules (such as Pure/Impure). Maybe by C# 10.0 the crossover point may be reached where the majority of code written is null-safe (we also need a uniform taxonomy covering the models and rules etc.)
After giving it some more thought, i like this interpretation:
Person? p; // Optional concept, null being a valid value to be assigned.
Person p; // Initially null, but eventually expected to become and remain not null.
cannot be assigned with null or nullable (i would make it a compile error and not warning)
Person! p; // Compiler ensures that variable is initialized as not null and remains not null. Can throw exception though, but throws it as early as possible.
Makes most sense to me.
That’s great feature. Glad to see this kind of changes to c#. Keep up!
How do you plan to handle multi-threaded code?
if (ns != null)
{
// context switch – another thread could null ns
WriteLine(ns.Length); // there should be a warning here
}
Compiler does not give any guarantees about null reference safety.
And does not take that concern away from you.
The intent of non-nullability is confusing because it is simply not true. All types are initially null,
and the compiler is good with that.
You should still write safe code and put null-checks everywhere,
the compiler will warn you in certain use cases, in others won’t.
I don’t think writing null checks “everywhere” is needed. If we ignore the malicious case of someone using reflection to get around the nullableness, then it is mainly an issue when assigning to something declared to be non-nullable. Static analysis can recognize that code is safe (i.e. ensures it does not assign a null to something not nullable), and raise an error for code that it cannot determine is safe.
Concurrent access may still require the usual safeguards, but nullableness does not really contribute to this — the types of coding required will still work with the nullableness feature.
I disagree that all types are initially null, but there definitely is a mindshift associated with “never dereference a null” design.
C:\Roslyn_Nullable_References_Preview_11152017>dir
Volume in drive C is Win10
Volume Serial Number is 2236-DFF3
Directory of C:\Roslyn_Nullable_References_Preview_11152017
12/07/2017 04:01 PM .
12/07/2017 04:01 PM ..
11/14/2017 11:07 PM 96 install.bat
12/07/2017 04:01 PM tools
11/14/2017 11:07 PM 98 uninstall.bat
12/07/2017 04:01 PM vsix
2 File(s) 194 bytes
4 Dir(s) 103,129,927,680 bytes free
C:\Roslyn_Nullable_References_Preview_11152017>install
Using VS Instance 1862b88f at “C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise”
Installing all Roslyn VSIXes
Installing vsix\Roslyn.Compilers.Extension.vsix
“C:\Roslyn_Nullable_References_Preview_11152017\tools\vsixexpinstaller\VsixExpInstaller.exe” /rootSuffix: /vsInstallDir:”C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise” “C:\Roslyn_Nullable_References_Preview_11152017\vsix\Roslyn.Compilers.Extension.vsix”
Unhandled Exception: System.IO.FileNotFoundException: Could not load file or assembly ‘Microsoft.VisualStudio.Threading, Version=15.3.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ or one of its dependencies. The system cannot find the file specified. —> System.IO.FileNotFoundException: Could not load file or assembly ‘Microsoft.VisualStudio.Threading, Version=15.5.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a’ or one of its dependencies. The system cannot find the file specified. —> System.IO.FileNotFoundException: Could not load file or assembly ‘file:///C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PrivateAssemblies\Microsoft.VisualStudio.Threading.dll’ or one of its dependencies. The system cannot find the file specified.
at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.InternalLoadFrom(String assemblyFile, Evidence securityEvidence, Byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm, Boolean forIntrospection, Boolean suppressSecurityChecks, StackCrawlMark& stackMark)
at System.Reflection.Assembly.LoadFrom(String assemblyFile)
at VsixExpInstaller.Program.c__DisplayClass16_0.b__0(Object sender, ResolveEventArgs eventArgs)
at System.AppDomain.OnAssemblyResolveEvent(RuntimeAssembly assembly, String assemblyFullName)
— End of inner exception stack trace —
at System.Reflection.RuntimeAssembly._nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.nLoad(AssemblyName fileName, String codeBase, Evidence assemblySecurity, RuntimeAssembly locationHint, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.InternalLoadAssemblyName(AssemblyName assemblyRef, Evidence assemblySecurity, RuntimeAssembly reqAssembly, StackCrawlMark& stackMark, IntPtr pPrivHostBinder, Boolean throwOnFileNotFound, Boolean forIntrospection, Boolean suppressSecurityChecks)
at System.Reflection.RuntimeAssembly.InternalLoadFrom(String assemblyFile, Evidence securityEvidence, Byte[] hashValue, AssemblyHashAlgorithm hashAlgorithm, Boolean forIntrospection, Boolean suppressSecurityChecks, StackCrawlMark& stackMark)
at System.Reflection.Assembly.LoadFrom(String assemblyFile)
at VsixExpInstaller.Program.c__DisplayClass16_0.b__0(Object sender, ResolveEventArgs eventArgs)
at System.AppDomain.OnAssemblyResolveEvent(RuntimeAssembly assembly, String assemblyFullName)
— End of inner exception stack trace —
at VsixExpInstaller.Program.c__DisplayClass16_2.g__RunProgram1()
at VsixExpInstaller.Program.Main(String[] args)
Command failed to execute: “C:\Roslyn_Nullable_References_Preview_11152017\tools\vsixexpinstaller\VsixExpInstaller.exe” /rootSuffix: /vsInstallDir:”C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise” “C:\Roslyn_Nullable_References_Preview_11152017\vsix\Roslyn.Compilers.Extension.vsix”
System.Management.Automation.RuntimeException: Command failed to execute: “C:\Roslyn_Nullable_References_Preview_11152017\tools\vsixexpinstaller\VsixExpInstaller.exe” /rootSuffix: /vsInstallDir:”C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise” “C:\Roslyn_Nullable_References_Preview_11152017\vsix\Roslyn.Compilers.Extension.vsix”
at Exec-CommandCore, C:\Roslyn_Nullable_References_Preview_11152017\tools\utils.ps1: line 39
at Exec-Console, C:\Roslyn_Nullable_References_Preview_11152017\tools\utils.ps1: line 71
at Deploy-VsixViaTool, C:\Roslyn_Nullable_References_Preview_11152017\tools\install.ps1: line 29
at , C:\Roslyn_Nullable_References_Preview_11152017\tools\install.ps1: line 41
C:\Roslyn_Nullable_References_Preview_11152017>
The preview version of vs should be the only one installed
So I read a lot of these comments and even tho I initially was like “WTF?” I understand your approach and intend… and it is good idea overall.
However as I understand from this portion: “The feature design envisions a separate compiler switch to enable warnings related to nullability.” this is implemented as compiler switch… so my question is – why did you not make that behaviour as derivative from project property? Because this would pretty much handle a lot of mentioned problems. Simply add property “references are not null” (or whatever) and if this is checked then you will launch compiler in no-null mode, leave it unchecked and compiler would work as it worked before (and if project does not have that property then it is recognized as unchecked so no issues with old projects here). Reason for this is that compiler mode used should actually be defined by project and not workstation or IDE env. When I write code I know what compiler behaviour I expect and I should get the very same results 10 years later when I compile my project again. Please do not force another generation of programmers to use outdated Visual Studios just so they can compile their legacy assemblies.
Also eventually with this approach you actually could enforce true no-null state without breaking anyone code – so string s = default(string) being string.empty and not null.
As another point – for consistency sake please add .HasValue and .Value for all ? (nullable) variables even if they are reference type.
I can see how this feature would be useful, but I am very concerned about how it will impact legacy code. I realize that it will only produce warnings for now, and the warnings are optional, but to me, that just feels like a way of easing it into the language, to where it will eventually become mandatory. And I think it goes without saying, but this a MAJOR change — reference types being nullable by default is, in my opinion, one of the defining features of C#. And yes, I believe it is a feature. A lot of code that myself (and I’m sure many others) have written is designed around the fact that reference types are nullable by default.
I have so many questions that I am not even sure where to begin…
For one, how does this play with optional arguments that are of a generic type? For instance, let’s say I have the following method:
T GetValueOrDefault(string key, T defaultValue = default(T))
Obviously since T is unconstrained, it can be either a value or a reference type. Does this generate a compiler warning? And if so, how would I write this method “properly”? I suppose I could write it like this:
T GetValueOrDefault(string key, T? defaultValue = default(T))
But doesn’t that imply that T must be a reference type? Perhaps I am reading too much into it.
And why does “string s = default(string)” generate a warning? If we are going all the way, then shouldn’t “default(string)” translate to “string.Empty”? You could use the following syntax to default for nullable reference types: “string? s = default(string?)”
I have a lot more questions, so I should probably just download the preview and try it for myself… Perhaps my opinion will change after I see it in action, but for now, I really believe that going the other way, that is, requiring that non-nullable reference types be explicitly defined, is the proper way to do things. If we were creating a new language, then sure, the “string?” syntax would be ok. In my opinion, it’s just too significant of a change to make this late in the game.
It looks like an awkward solution to far-fetched issue. First, people having problems with NullReferenceException should realize that they get this exception not because C# is bad language, but because they are incompetent developers. Second, even if you want to make thoose teenagers happy, give them some new class / extension method / attribute / whatever else, but do not touch the language. There is no excuse for changes in syntax unless you are sure that this feature cannot be implemented at BCL level. Third, you selected the worst way to implement this feature because it will be breaking change for all developers, not an option for rare F# fans.
Completely agree. Null checking is tedious, I’ll give them that. However, it’s not a real issue. The null conditional operator they added in C# 6.0 made it much easier to do null checking (and therefore, avoid NullReferenceExceptions), and it’s a change that is universily loved. In my opinion, those are the types of changes they should continue to make — not major breaking changes like this!
They claim to have received a lot of requests for this feature, but from whom? I can’t help but think that the requests are mostly coming from C# noobies who recently migrated from another language and don’t yet understand how classes function in C#. Personally, I’ve never heard anyone complain about NullReferenceExceptions in C#.
Overall, I think C# has improved a lot over the past few years, and I’ve been a big fan of many of their recent changes, but not this one… Like you said, breaking changes, especially of this magnitude, are always a bad idea, but doubly so when there are alternative ways to accomplish your goal without breaking anything! If they actually move forward with this change, then I’m going to be very concerned with the future of C# — which is sad, because I love this language.
While I still prefer language support for ! (not-nullable) and ? (nullable), this is easily one of the most value-enhancing C# changes since the introduction of generics. ReSharper NotNull/CanBeNull attributes are an intermediate step, but language change(s) to allow specifying nullability wherever a type can be specified are critical (and cannot be done solely via attributes). While “?.” is reasonable for Dispose() and Event handling, it is a wart outside of those areas — and a fairly good indication of risky code. Similarly, other language changes (lambdas, anonymous methods to name a couple) more often than not simply indicate that “this code is poorly tested”.
Reference nullability is a significant step towards safer code, as well as “designed” code instead of keyboard banging.
I agree that it has value, I just disagree with how they are choosing to implement it. I too would favor a “!” (non-nullable) and “?” (nullable) syntax. However, hypothetically, if we were rebuilding C# from scratch, then I would actually favor their approach. It’s just, at this point in the language’s history, this is far too significant of breaking change. Only warnings for now, sure, but I worry that the team may eventually decide to enforce it at the compiler level. In which case, I would be left with the following choices:
1. Update a TON of legacy code.
2. Opt-out of all future C# versions.
I would prefer neither.
As far as the “?.” operator goes, I disagree that it is an indicator code smell — at least, when it is used properly. I find that it is a great QOL enhancement, especially for Linq queries. Personally, I love the functionality that lambda expressions enable (e.g. Tasks, Linq queries), but I have seen them greatly abused by some people, so they are a love/hate for me. As far as anonymous methods go… I couldn’t agree more.
Very cool!
Has anyone found a way to get resharper to handle this without squiggly errors everywhere?
I am late to the party but why dont you introduce an option type and make compiler complain about it ? it is safer, it wont break old legacy code and it will introduce good practices.
Personally this makes me shudder a little. While it’s an admirable cause, this is an unprecedented change that is going to take some serious getting used to. Also, I’m uneasy about the approach taken with the warnings; by your own admission they are never going to catch every scenario. My concern is that if folks become too reliant on the warnings they’ll miss other edge cases, resulting in inconsistencies in their handling of nulls, and/or may just end up ignoring the warnings altogether.
C# has always been a great language but I feel the level of new features being added has gone a bit crazy in the past few years, especially the myriad of syntactic features and shortcuts that, let’s face it, we don’t *really* need. Yes they might save a line here, a few keystrokes there, but at the expensive of readability?
IMO there should definitely be an “aggressive” option to turn these warnings into errors. And that option should be on by default on the “new project” templates.
Creating a new project with c#8 should always behave like this, so that more people adopt this feature faster.
+1 I fully support your suggestion.
I want to take your attention to another point of view about existing/legacy codes. Seems you treat next situation as definitely bad: “…you’d only get safe nullable behavior going forward, as you start adding these annotations. Any existing code would benefit not at all.”
But this “going forward” case may be good. It allows to have all new code in a safe way and to improve old codes step-by-step.
Not all teams can stop all other development at big projects to make “a total null hunting”. And if they will not make such total hunting then they get thousands of warnings constantly after new compiler switch is on. So you force to choose between thousands of warnings or spend a single big piece of time for “hunting” or to switch off this warning at all. And this is really bad choosing 🙁
As a possible solution I propose to add new refactoring “Change usages of Reference Types to usages of Nullable Reference Types” into Visual Studio in the same time with “Nullable Reference Types” adding.