 Hey, I'm Matt Sorgerson, and I get to design C-Sharp for a living. So that's pretty cool. I'll be doing the first of these two sessions about C-Sharp 8. The other session is actually going to be Bill Wagner, who writes about C-Sharp for a living. So we're both deeply involved there. There's a bunch of new features in C-Sharp 8. So that's why we cheated a little bit here and got ourselves two of these short sessions. But this first one here is going to be devoted exclusively to null. So this is going to be 25 minutes talking about nothing essentially. Null is one of these things that we've learned to live with, but we aren't really super happy. It's been an object going to programming forever. Null is actually about 53 or 54 years old. The problem with it is that it poses as an object, you can always get null. But you never know where it's coming from, and eventually you accidentally de-reference it and it'll blow up. So it's like this black hole, you circle it once you're beyond the event horizon, you get the gist here. So I'm going to switch into Visual Studio and we're going to blow some things up. So here's an absolutely innocent looking little program. I create a person, I call a method, and I print out the result of that method. That method gets the length of the person's middle name and returns it. So it's absolutely straightforward code, nothing to see here would pass any code review. But of course, if we run it, then we wait for a while, and then we get a nice little explosion, because it turns out that middle name was null, but we were de-referencing it. At least now debugger tools can tell us what it was that was null, but it doesn't tell us where the null came from, why is it there, should it have been there, was it my fault, was it the API's fault, the person API that I was calling. That's all really muddled and unclear. So that is what the feature is for that we call Nullable Reference Types in C-Sharp 8. It's one of those features that is about telling you new things about your existing programs. It's giving you new warnings where your behavior isn't what we could call null-safe. It's attempting to catch places where you do things that are likely to lead to null-reference exceptions. Since it's adding new warnings, that's like a breaking change. If all of a sudden we start adding warnings to existing code, so we don't do that automatically, it's a feature that has to be turned on. The most sweeping way of turning it on, I'm going to turn it on here for the Person Library. Well, I'm going to go into Solution Explorer and turn it on. I just go into the project file. That's this thing. I pre-added this thing. There's Nullable Element here and it's set to Disable. I'm not going to enable the feature, but it's disabled by default. You have to go do this manually. Now that I do that, let's go look at this Person Class. It has straightforward auto-properties and it has this constructor I was calling with a first and a last name. What we see now is that now it gets a warning after I turn the feature on, and the warning says that the non-nullable property middle name is uninitialized. So what does that mean? Well, it means that essentially, it means that apparently now it considers middle name to be non-nullable. In other words, reference types as we know and love them are now considered to be non-nullable. They're not supposed to have null in them, and it catches the fact that since it comes pre-initialized to null, we don't change that to something non-null. We could of course initialize it, kind of cheekily we could initialize it to null, and then that warning would go away. It goes away, but now we get another warning saying, hey, you can't assign null in there. Nice try. So we're faced with this design question actually. This force is a design question that we probably should have asked ourselves earlier, but had no way of expressing in the language. Should these strings actually be null or not? Is it part of the API to handle it there null or is it not? Now, by default, the feature is saying they're not. They're not supposed to be null. But what if I want them to be null? What if it makes sense for middle names to be null? Actually, it probably makes sense for all of these to be null, but we're just going to do the middle name today. Symmetry, exercise for the reader. But I can put a question mark on string, and it's now a nullable reference type, which is the name of the feature as well. I'm signaling that middle names can be string or null. They can actually be null. I'm signaling this in my public API, and I absolve myself from having to make sure it's not null, and I get no more warnings than the API here. So now I've come clean with my API. I've told the consumers what's null and what isn't, and now if I go back to the program here as a consumer of the API, I now get a warning saying, hey, this thing here, you're de-referencing it even though it might be a null reference. Well, why might it be a null reference? Well, that's because the local variable is actually now inferred to be of type string question mark because the middle name probably returns string question mark, nullable string, okay? So we're propagating this potential nullness through the system, and now I get a chance. So now the API is saying, hey, it's your problem to deal with nulls, but I'm also getting help from the language dealing with the nulls. I get advanced warning instead of luckily catching it maybe from an exception that gets thrown before I accidentally ship to customers. So what can I do? Well, what are the usual things I would do about a null? I should just do the same things here. So if middle is null, this is how I would usually write it, you know, return zero, and what you see is that the warning goes away now. Actually, a little aside here, as of patterns in C-sharp seven, I prefer to say middle is null because that checks for null even when you've overwritten the equality operator, which wouldn't necessarily work otherwise. So if the middle is null, return zero, otherwise return middle.length. Now the warning went away, and that's because the compiler understands that if we get to this point in the code, at this point, middle will not be null. You just checked, and if it wasn't null, you went down a different branch. So that's doing essentially what we call a flow analysis, which is potentially pretty advanced. It will follow the different branches of your code and will track in all these different places where nulls may be and where they may not be. It's kind of similar to definite assignment analysis, which is already in the language, but again, tracking the state of a variable as you go through. And actually, it's a bit smarter than just tracking the locals and parameters here. If we go and sort of cut out the middle man, if you will, inline the temporary variable, and we use person.middleName directly twice. You see that there's also no warning, but remove the if here just to see. You get a warning on person.middleName, potentially being null, but if I check that property of that parameter, then we track the state of that as well. So we track chains of properties and fields. This is not actually entirely safe. If you think about it, the, somebody, if another thread is running, they could change the middle name between the test and the de-reference here, or I could call some innocuous-looking method here that actually goes and changes the middle name behind the scenes or whatnot, and the compiler can't see it. So this is a bit dangerous, but the way we think about the nullable feature is it gives you warnings. Warnings are places where we warn you if we're pretty certain that you have a problem. And there's a lot of existing code out there that looks like this, that works just like this. And if we were to flag all of that with this feature, it would be useless. It would be impossible for you to upgrade your existing code to use a nullable feature. So we give you a pass on this probably safe thing, and there are a couple of other places where, you know, the feature isn't ironclad. It will let through things that could potentially be wrong and not give you warnings there. It's not a guarantee against null-reference exceptions. It's just gonna find you most of them. So that's good to know. But if you want to avoid that kind of situation, you can write code that's sort of correct by construction. You can try to do that. We had that local variable before. That's a good way of doing that. You can also use things like the question.operator that, oh, not there, but here. That checks for null and only does the dereference if the thing wasn't null. Now of course we get an in question mark, but then we can use the null call lessing operator to take any nulls that come out of that and turn them into zero. So that's correct by construction in the sense that you only read the variable once and you don't depend on its state remaining the same from check to consumption. And if you go back to this version here, another couple of ways that you can be correct by construction if we just go and, well actually let's look at what happens if you also had a nullable person coming in. Now we have two potentially nullable things. We could use the person question mark dot here in the condition and that actually checks that the person is not null and the middle name is not null. And sure enough, you don't get any warnings down here. The compiler tracks that they're both not null. Whereas if that check hadn't been there both would have been a problem. That's actually two sets of squiggles on top here. So let's go and invert, oh, wrong button. Let's go and invert the condition here. I've got an invert if. And talk about what if I wanna check that something isn't null rather than is null. So the new pattern matching, the new features that are added to pattern matching actually help a bit here. I don't have to write it like this with a not and a parenthesis here. Instead, there's a new pattern in town that I can use to check that something is not null. It looks like this. So it sort of means is object. Think about is an object, is a thing, is something. So think about this as meaning something. That means that if that thing is something then go down the true branch. So what's nice about that is we can actually name the something. So there's optionally you can give it a name and this pattern here, this something pattern. We don't actually call it that but I'll get back to that in a second. We give the something a name that can be used in the true branch where it isn't null and then we could say middle.length here. And again, we get no warnings because we are in the true branch. We could also go a step further. What this actually is is a degenerate form of a new pattern, a recursive pattern called a property pattern. So a property pattern is one where you can drill, we check for null but then you can drill down into properties of the object that we're currently matching on and then we can apply further patterns at the next level down. So the string here has a length and I could just use a var pattern to grab that length into a local variable and I could actually just return that local variable. So just return the length and now I don't need the middle anymore. So I'm using pattern matching here now to dig in and get the, in case everything is not null, I get the right thing out and return it. Of course, now I could easily turn this into a conditional expression and it starts getting nice and you can make it an expression-bodied method as well. So you can play with this but the upshot is you can use these newfangled pattern-based methods of null checking if you want to and these features interoperate very well. The pattern matching, the question dot operator and the nullable reference types but if you just use the old fashioned if that's perfectly fine too and we will check your code as best we can. So that's sort of the upshot, that's the core of the nullable reference types language feature. You can, you get warnings when you put null into non-nullable reference types, you can then annotate things to become nullable reference types and then you get warnings when you de-reference those without a check as in our code here. Now there are a couple of refinements that we've added to the feature we found as we were annotating the core libraries for instance that there are places where you kind of want to know better than the compiler or help the compiler a little bit. There's actually a feature that we used to call the Dammit operator but we're not allowed to. It's the bang, the PostFix exclamation mark here which means I know better than what you think about this expression here. So you might think that middle is null but I as a programmer know better so just shut up with your warnings. I think we call it the null ignoring operator or something instead. It's not exclamation mark dot there. Those aren't a single operator. The exclamation mark is its own operator and so you can just say I know better. That would be really bad to do here because and usually it's bad to do because usually you don't know better. You just think you do. So let's not use that one. But then there are ways in which certain helper methods for instance they have special behavior around nulls that it will be really nice to communicate. So before when I said if something wasn't null here or something was null, I relied on the fact that the compiler could see that there was a null check but what if that null check is inside of a helper method? For instance, what if we use string dot is null or empty to check whether this string is, whether this string should just return zero as its length. Let's do that for a second. What we see is, well, we see that the if string dot is null or empty. Oh, I should pass it now or an argument. Of course, if the middle name is not null or empty, return zero and what you see now is that the warning goes away. So somehow the compiler knows that this method, that when that method returns false, then the thing is not null. And the way it knows it is that it's annotated with a special attribute. So we have a handful of attributes that signal special null behavior that you can put on members and parameters and so on. In this case, it's an attribute on the string question mark type to value parameter here that says that this thing is not null when the result of the method is false. And there's a little set of like 10 of them you can read in the docs what they are. They can use to refine your APIs if they have special null behavior. And we do that a lot around the core libraries where a lot of utility methods are. I think it's probably gonna be less common in most other code actually. But here and there it's really useful to tweak the nullability behavior that the compiler sees in the public API. So those are a couple of ways of tweaking. Now, I mentioned a couple times existing code and when you have a feature that when you turn it on for existing code, it lights up with all these warnings for potentially a large project for instance. It's not really feasible to convert it all over in one go. I think most people would find it a little daunting. You get like 10,000 warnings on a large project and go and fix them all. And so you wanna do a piecemeal. And the way you can do a piecemeal is that you can actually turn the nullable feature on and off at a file level as well. So I can say nullable as a new directive in C-sharp. I can say nullable, disable. And now for the rest of this file, the nullable feature is disabled. Now, you can see I also get a warning on the question mark here because when I disable a feature, I don't get to use nullable reference types in my code. I could say, let's say that I wanted to, I wanted to annotate my library and give my consumers good nullable annotations, but I don't have the time to fix all the warnings that show up in my internal implementations of it. I could disable just the warnings and let's actually, let's introduce a, that's fine. I can disable the warnings and now the annotations are still allowed, but warnings are turned off. I can also, conversely, I could disable just annotations and now I still get the warnings but the public API is not affected. And of course, I can do this not just at the top of the file, I can do it anywhere in the file. So now the warnings or the annotations rather are enabled until I get down here and now after that I'm no longer allowed to, say, put a question mark. Now I start getting a warning for that. So this is a way that you can take chunks of, like, file by file or even chunk by chunk of your files and get the nullable warnings and annotations handled for those bit by bit. You don't have to look at all the warnings at once. Speaking of adoption, I think that another feature, a feature that actually or a property of this feature that is a little reminiscent of when we did async is that in order to make use of the feature, to make good use of the feature as like an end user and app writer or something like that, you really rely on the libraries that you have a dependency on to adopt this feature also because if you interact with a library that didn't have the nullable feature on, then you won't get any warnings from interacting with that library. And only later when they do get around to adding these annotations, you start getting warnings maybe for any null unsafe use you have of that library. And so there's a bit of sort of an ecosystem adoption thing where this language feature, it's useful in and of itself, but it certainly gets more and more useful. The more of the world starts adopting it. So in order to talk about that, let's go back to slides quickly here. So essentially what we want to suggest, and this of course is up to you out there, but we kind of want to suggest that if you're a library author, and that includes Microsoft by the way, as a big time library author, if you're a library author, maybe consider the time between now and .NET 5, which is slated for November of next year, treat that as the nullable adoption phase. This is the time where it would be a really good idea for you to adopt null in your public API and the surface area that your clients consume. And then on the flip side, if you're a client of these APIs, expect there to be some churn in that period of time. So expect that you won't get nullable annotations from your favorite libraries tomorrow. It might be whenever it's natural for them next to ship a version, when they've had time to go through and do the work and roll out normally. So let's give everyone a breather and think about the next year and a bit as the nullable adoption phase where there will be some churn and if you adopt this as a client, you should be okay with your library sort of gradually coming online and therefore warnings gradually may be popping up in your code. That might still be the best way for you to go. That's certainly how I will go. But if you don't want that churn, maybe wait until.NET 5 before you turn the nullable feature on as a client and then hopefully most of the ecosystem has settled and you get the finished nullable annotations for most of the libraries. And this goes for Microsoft as well. We haven't adopted these for all our libraries because we also kind of want to learn from the adoption phase and expand a knowledge just here and there. And it's also a lot of work just to go and update the entire .NET BCL. And so we've updated the core libraries with nullable annotations, but we actually consider them in preview a little bit that we will try to keep them stable, but we will change, if we did something wrong, if we thought about something wrong or just messed up, we want to reserve the right to fix those up until .NET 5. So that's just a way that we can kind of soften the blow of something that introduces new warnings and existing code and not making it a big, sort of green squiggle jungle for everyone out there. I also, for library writers, a lot of library writers don't just target .NET Core 3.0, right, you want reach and you want to target .NET Stana 2.0 maybe, so you can get .NET Framework and that broad set of things that you can target that way. And so we're cognizant of that and what we recommend to do, say if you're using Visual Studio, is multi-target between .NET Core 3.0 and .NET Stana 2.0. That means you can't turn on C-Sharp 8 in the UI of Visual Studio, for instance, because lots of C-Sharp 8 things won't work properly when you do that, but if you consider yourself an expert, you can go and manually do it in the project file and that's probably the best way to go as you try to both, and this should work just fine, that you try to both get the nullable annotations that are in .NET Core 3.0 and get the warnings for those, but you also want to generate code that reaches all of .NET Stana 2.0. So this is how we recommend that you go. And if you want to check out more things, there are blog posts out there that have come out over the last couple of years around it, and the docs are already excellent around nullable reference types, so here's a set of links that you can follow and get the docs. And with that, I want to turn it over to you out there and your questions, and we've got five minutes, so shoot away.