What's New in C# 14
By dotnet
Summary
## Key takeaways - **Null-Conditional Assignment Operator**: C# 14 enables ?., the null-conditional operator, for assignments like person?.PropertyChanged += Handler, skipping the block if null to avoid warnings without explicit checks. [01:59], [02:22] - **Lambda Params Ignore Modifiers**: Lambda expressions no longer require explicit parameter types when using out or ref modifiers, with a code fix to remove them automatically. [02:56], [03:23] - **field Keyword in Auto-Properties**: Use the new 'field' keyword in auto-property setters to access the backing field without declaring it manually, ideal for small logic like OnNameChanged. [05:06], [05:35] - **Extension Blocks for Members**: Extension blocks lift 'this' parameter and generics to the top, enabling extension properties, methods, and more in a scalable way inside static classes. [09:42], [10:52] - **Static Extension Methods**: Parameterless extension blocks add discoverable static methods like int.Range() on types, using generic math constraints for broad applicability. [17:42], [19:57] - **Extension Operators & Compound**: C# 14 introduces extension operators like times for IEnumerable<T> and user-defined compound assignment operators like *= that modify in-place and return void. [20:13], [23:18]
Topics Covered
- Null-conditional assignments eliminate boilerplate checks
- Field keyword unlocks auto-property backing fields
- Extension blocks enable properties and operators
- Static extensions boost method discoverability
- Compound operators mutate collections in-place
Full Transcript
[music] Hey everyone. Uh, my name is Dustin
Hey everyone. Uh, my name is Dustin Campbell and I'm here to show you what's new in C 14. Um, I apologize that it's just me, but my regular co-presenter
Matt Sterson is on on vacation and we'll hopefully, you know, see him soon. Um,
but yeah, so we're talking about what's new in C1 14. This talk will be the same as it has been in the past in the sense that we're going right to demo. Okay,
it's just going to be code uh for the most part. I I don't like looking at a
most part. I I don't like looking at a lot of slides when I'm talking about language features. I want to use them. I
language features. I want to use them. I
want to see them. So, first up, we're just going to do a couple of warm-ups um of what's here. Uh there a couple of nice little features and we'll start right here. Um just just kind of some
right here. Um just just kind of some some some appetizers, some tasters. So
looking at this program, uh I've got a uh a person class. You know, it's typical, but it uh it has a warning here because, you know, nullable reference types. It's telling me that this could
types. It's telling me that this could possibly be a null reference. And uh
that's bad. Kind of annoying. So if I want to uh get rid of that though, I I've got to like add a null check, right? So if person is not null, then
right? So if person is not null, then you know, then we'll hook up the event.
And of course, you know, then it just moves the warning around, right? So,
it's this is one of these features that's like I I appreciate it, but when I've got an assignment like this where I've got code where I'm, you know, I'm dotting into something and then I'm
assigning it, we don't really we haven't really had a way to to kind of get around that easily to perform that null check. And part of that is because in C
check. And part of that is because in C 6, we chose not to make the question dot operator. that's been around since then
operator. that's been around since then uh work for assignments. We just
thought, you know, hey, it'd be a little confusing at the time, but that was a long time ago. I mean, we're we're eight releases past that. So, yeah, with C#
14, you can use question in uh assignments like this. And the reason we thought it would be consume confusing is because when you do that, that means if it's null, then this entire block won't
run. You know, nothing will happen here.
run. You know, nothing will happen here.
And so you might end up with something where there's a side effect uh that isn't observed anymore that doesn't occur. But um that is you know that's
occur. But um that is you know that's you know we just don't think that's as confusing as it used to be. Um so there you go. You can use question dot now uh
you go. You can use question dot now uh for assignments. Um we call the null
for assignments. Um we call the null conditional assignment operator. The
next feature I want to show just again it's a taster of just kind of small things that we do when you know there's lots of things to kind of go clean up and make a little easier. Um, and that's this lambda expression right here. Um,
an annoyance with lambda expressions sometimes is that, you know, I I love simple lambda parameters where I don't have to state the parameter type, but there are so many cases and they we've
reduced a lot of them, but there's so many cases where I do have to state the parameter type. And one of those is when
parameter type. And one of those is when modifiers are used. And in this case, you know, we have an out uh modifier on the success parameter. And in this release, you know what? Let's just not
in fact there's a there's a code fix to fix it for you. There's a suggestion and a fix. So often I see these you know I
a fix. So often I see these you know I usually don't see an out like this but often you'll see a ref where it's like you know there's some sort of you know refstruct that's building something up
and is being passed to a to as a in a delegate um and you use it in a lambda expression. So we're seeing that pattern
expression. So we're seeing that pattern more and more and it felt like the right time to go and just say you know let's just make this easier and clean this up.
All right next one. So this is a feature we've shown a number of times. This is
field access in auto properties. Now
we've shown a number of times because it it's been a challenge to kind of get this out there and deployed in in in kind of the right way. Um but looking at this code uh and I'll explain why that
is. Uh looking at this code, we have
is. Uh looking at this code, we have really a C# 10 property. This is the same code you would have written a long time ago, right? In C in the C# 10 days.
Um, in C# 3, we introduced auto properties and they're great because I don't have to declare a field, but there's this big cliff, right, with an auto property. Um, in an auto property,
auto property. Um, in an auto property, if I do need to do something small, then like in the setter for this in this example, we're going to call an on name
changed method. Um, if I do if I want to
changed method. Um, if I do if I want to do that now, I have to kind of fall off this cliff and write the whole property and declare a field. Um, and it's it's just kind of a it's it's always felt
kind of icky. So, in this this release, we're finally releasing the field access and autopopies feature. And here's what that looks like. So, I'd really like to
delete that field. Okay. So, I I can do that and we can make this, you know, a getter only auto property. However, what
do we do in the setter if I want to still do that? And actually the the C# the warning from the compiler is telling me, yeah, I need to use field. Okay,
there's a new keyword that gives me access to that backing field. So here I can set it and now it's the same as it was before. Essentially, you don't have
was before. Essentially, you don't have the backing field if you were assigning it elsewhere in code, but if it's just only there to support this property, then you can switch over to the field
property uh to the to the field access.
Now the reason this was a long time coming and finally released is um because it's it's a breaking change. Um
you can have a field called field um and it's not weird, right? Databases
famously have fields. Um and so this is a breaking change to have this keyword.
And so what we're recommending in this case is that you know you need to be able to disambiguate it. and you e either say um and copilot was trying to help me out there and and spoiling
spoiling the fun. I can say this dot and and disamiguate that this is a field access just like I would in any other code or because this is a keyword I can
escape it with an at symbol like I can with other other keywords. Okay. Um and
that is that is how it works. So, and of course I'm getting a warning here because now I'm talking to the field, but this is a uh uh getter only, you know, property. So, it was talking to a
know, property. So, it was talking to a different backing field. So, I need to come in here and make sure I'm doing the same thing. There we go. So, that's how
same thing. There we go. So, that's how this works. It's not too bad. Um, and I
this works. It's not too bad. Um, and I really love uh I love getting rid of this sort of thing. I think this is uh this has been a long time coming and I'm glad it's here in C# 14. Now, one thing I've been using these for, I've been
using them a lot lately. Uh I just want to want to share these work great for like lazily initialized properties, right? So if I want to say do something
right? So if I want to say do something like you know put out a we'll say like an I readon list of person um like maybe maybe there's a like a a collection of
relatives here or something and then I want to make that a getter only property. Um I can do so with the field
property. Um I can do so with the field keyword or I can say oh field keyword.
Yeah. Okay. And then if it's null and we use the null conditional assignment operator I can just assign it to an empty I read only list using a collection expression there. And now and
you can see that in the the uh the tool tip tells me that hey relatives field might be null here. And so we handle that and we initialize it with an empty empty list. So, this is kind of a cool
empty list. So, this is kind of a cool way to kind of do that sort of thing.
Um, and I like I've been using it a ton for that. So, all right. So, that's your
for that. So, all right. So, that's your warm-up stuff. Let's get to kind of some
warm-up stuff. Let's get to kind of some meat and potatoes, some some real nitty-gritty things. And let's talk
nitty-gritty things. And let's talk about extension members. Um, extension
members is something we've been talking about for a while, but it's also gone through some radically different designs. um we started with it uh you
designs. um we started with it uh you know honestly as soon as extension methods were kind of you know ready to go out before it was even released I know that the C# language design team
was already talking about well how do we do extension properties and this is now like you know many years ago this was back in C 3 that we put extension methods out there and you know extension
methods were they were this nice little feature for that was really in support of query expressions link query expressions um and they became this kind
of powerful massive popular feature on their own with in you know large swaths of ecosystem and libraries to kind of built upon uh extension methods. So
we've been looking at this for a while because the requests keep coming in how do we get extension properties? How do
we get extension this and that and extension everything? Um and so in this
extension everything? Um and so in this release we have kind of started that process and come up with a design that we think works. The reason it's taken so long is that it's been difficult to come
up with a design that scales to other kinds of members right methods are you know not hard to do in one sense because they they have all these extra features member methods get you can declare
generic type parameters on them and you can have you have parameter lists so an extension method I have a place to put that this parameter right here right so this is a an extension method it kind of
looks like link's you know first uh extension method but That means that like other members need to have these features too, right? You need to be able to have a if I want to have an extension
property, how do I declare an extension property with generic type parameters or where do I put that that this parameter because it doesn't have a parameter
list? Um, and so that's always been the
list? Um, and so that's always been the problem. And so in this release, we are
problem. And so in this release, we are introducing a feature we call extension blocks. Now extension blocks, I'm going
blocks. Now extension blocks, I'm going to I'm going to show what that looks like here. I'm going to copy this bit.
like here. I'm going to copy this bit.
This is the important bit of your extension method. Um, and I'm going to
extension method. Um, and I'm going to copy it up here and I'm going to take and say extension and then I'm going to put it in braces.
I'll remove that this keyword.
And there we go. That's an extension block. And what we're doing is we're
block. And what we're doing is we're saying we're taking the important bits that kind of make this method an extension method and moving them up to the top. Okay. Um, and so then I don't
the top. Okay. Um, and so then I don't need the this parameter anymore because it's it's declared up in the extension block. Okay. And in fact, I've got the
block. Okay. And in fact, I've got the parameter name source right there to be used down here. I don't need the generic type parameter because again it's declared in the extension block. All
right. And then the last bit and the reason there's an error on this source parameter is because it doesn't need to be static, right? And so what we've done
now is we've taken essentially this you know fancy static method which is what an extension method kind of is you know that we can call with instance syntax
and we have now changed the declarations so that the extra bits that are important uh that kind of across the members go up here and then this is the
signature that you would use in the instance syntax. Okay. So like this
instance syntax. Okay. So like this method right here, we're calling first and that's still calling this first right down here. Um, and I can I can show that in IntelliSense
just like so. So if I go here, you'll see that uh oops first shows right up and it's got kind of a little funkier parameter uh info to let you know what's going on there. Uh
to let you know that this is a little bit different. And in fact link is
bit different. And in fact link is actually still here linked to objects system.link. Uh so you know there are
system.link. Uh so you know there are actually the overloads for link there.
Um but here's ours and that's pretty cool now and we tried to make this so that it would always generate that same static method you would have written
under the hood. This is inside a static class and this is kind of a a container that will contain different extension members and under the hood. So under the hood it's but under the hood it's still
generating static methods into that static class. Right? Right? The
static class. Right? Right? The
containers really there may be some some metadata but it's not important from that language perspective of using C it's really you can think of them as all just kind of being on the on the static
classes as they were before. So that
means that when you need to disambiguate and you can see we might need to disambiguate here in in in in some cases because we have a link method and we have our my so off of I innumerable we
also have first right the link and we also have the one off my innumerable. So
if we want to disambiguate, we call into the static class just like we would with uh a regular extension method. And you
can see here here's that static method.
Okay? And it that's how you and dismbiguate. And so it's exactly like
dismbiguate. And so it's exactly like using an extension method before. We
just, you know, make you write a bunch more code instead.
I'm I'm joking. Of course, that's that's not really true. We it it starts to build up and add and really start to reduce code um that was there before.
Let's try let's I mean let's try another one. We'll see if I take select and I
one. We'll see if I take select and I move that in here as well. Well, okay.
Then I don't need again I don't need the this parameter and we can just do that.
I don't need the T source generic type parameter.
Okay. And um it doesn't need to be static. Whoops. And I delete the wrong
static. Whoops. And I delete the wrong bit of code. There we go.
And again, now we have, you can see that the extension block is doing a lot of extra work for us here. We've got the T source declared top. We got the parameter. Um, now we've split the type
parameter. Um, now we've split the type parameters though. Select gets to
parameters though. Select gets to declare T result. It has select is a method that has two type parameters, one for T source and one for T result. But
in the end, we still generate essentially the same thing that we would have generated before. And so you can see here that there's actually in parameter info there is a select method
with both generic type parameters that we kind of smooshed together. Um and so that's that's what that looks like. All
right. But the whole point of this was to be able to add extension members, right? Um that this is a scalable syntax
right? Um that this is a scalable syntax that we can add other members into. by
lifting up those generic type parameters, lifting up, you know, the the this parameter, now they can be taken advantage of by other members. So
making a property is easy. I just, you know, turn this into a property and we can put get around there and there we go. Now up top, this is still this that
go. Now up top, this is still this that just reverted to calling the link method, but we don't have to. We can go and there's our extension property.
Okay. and extension properties. Of
course, they can have setters um right here. And you can, you know, you could
here. And you can, you know, you could set that if you if you so if you so desire. Um little weird. We're getting a
desire. Um little weird. We're getting a little weird here. Uh but the thing about the setter is and this is this is kind of kind of reveals like kind of the the edges of what an extension really
is. is and what it's really is under the
is. is and what it's really is under the hood is that it's a static that is taking an instance of something and manipulating it or you know being able to call methods or doing something with
it. Um and so we don't really have a way
it. Um and so we don't really have a way in here to generate things that are like state on that instance that comes in. So
when you know the source that comes in here at the very top in the extension block where that's declared when it's when it comes into the property we don't have a way to say put some field on that
and so instance fields are not supported uh and probably will not be that also means that you know autopropies uh fieldbacked auto properties uh they won't work in extensions either. You can
you can write setters and you can store it on the side if you want. Um but
that's uh that's something that isn't isn't there today. Now um if I uh carry on with this, I want to talk about like the disambiguation. How do we
the disambiguation. How do we disambiguate a property? Right? Because
we have first but I've got a setter and a getter. And what we do for this for
a getter. And what we do for this for for disambiguation is we actually generate and expose to you um the methods that the compiler already would have generated for a property. you know,
properties are actually sugar around two a getter and a setter method, right? And
so, um, and so we expose those so that they can just be called, um, as they would be. And so, there's that. All
would be. And so, there's that. All
right.
Now, that's what we've kind of talked about um, in the past. I also want to talk about one other thing. Now, we've
looked at instance extensions, right?
Now, um, but uh, there's other stuff to look at. So, let's uh, let's let's
look at. So, let's uh, let's let's shrink this down a bit.
And on here we also have just a regular static method, a range method. And uh
this range method is is useful and it's useful in link. I mean where I can type innumerable.range that's very useful. Um
innumerable.range that's very useful. Um
but it's not very discoverable, right? I
need to know that in link there's this linked objects. There's this innumerable
linked objects. There's this innumerable class that I can dot into and there happens to be this helper method, this helper range method. Um and I can do the same thing here, right? I can call my innumerable.range because it's just a
innumerable.range because it's just a static method, right? Um, but I'd really like to be able to call that in a more discoverable way. That's what's great
discoverable way. That's what's great about extension methods, extension properties now is that I can press dot and get them on an instance and and get them in IntelliSense and I can discover
them. Well, if I want to do that with uh
them. Well, if I want to do that with uh a static method, it's it's also simple with an extension block. So suppose I want to say I want to dot into I
innumerable of int. Well, I could write an extension for innumerable of int without declaring a parameter.
Okay. And again it disambiguates because it's on my innumerable. But I can also type I innumerable event and that works
just as well now. All right, that's pretty cool. I could um let's see. Let's
pretty cool. I could um let's see. Let's
uh that also means though I wish though that like if I want to write this for other types of innumerabables now I'm kind of like I'm kind of stuck right I got to write a whole bunch of these. Um
but you know we can declare generic type parameters and uh and so I could say well I've got generic type parameter let's let's let's let it take a t and be really nice if I could return that
innumerable t and then well the starting value is going to have to be of type t whatever that is. And down here I see there's an error and that's because I can't apply plus+ to you know operator
to type T. But we you know int net 7 we add this generic math thing. So if I constrain to I number of t well now it
works and that's pretty cool. So now I innumerable of range works. Um but also uh inubable of int.range works and I could also say inubable of long will
also work. And that's now part of that
also work. And that's now part of that is because the one here actually, you know, this is treated as an int32, long as an in64 and in32 fits inside of an
in64. So this works. Okay. But if I were
in64. So this works. Okay. But if I were to change this to an unsigned int. Well,
now now it doesn't work, right? And it's
because this has that whole negative range that isn't part of of an unsigned int. So in order to address that um I
int. So in order to address that um I can come in here and we can just say like let's call it like range from one and uh we'll take off that first
parameter do a start here.
Okay. And uh we'll call it up here range from one there. And now it works fine. And now I
there. And now it works fine. And now I can use it with anything. Okay. And it
should work just fine. One last step though. This isn't as discoverable as I
though. This isn't as discoverable as I want. It's weird to type inable. Why do
want. It's weird to type inable. Why do
I have to type in numeral? That's not
where I want it. I want to be able to say like int.range.
And so we can do that. We can just get rid of this.
Do it like this. And there we go. Okay.
All right. That's cool. I dig this.
Let's go a little step further. Okay.
We've looked at extension static extension methods. Um but another thing
extension methods. Um but another thing that's been requested a lot are operators. Extension operators. Um and
operators. Extension operators. Um and
uh in this release in C# 14, we've got them. So imagine we wanted to write an
them. So imagine we wanted to write an operator like you know I wanted to write an operator for for um in fact for T here where it's an I number but I wanted to use the times operator. Say I wanted
to treat range as this as this vector like if I wanted to treat an innumerable as a vector and I I wanted to apply a scaler across it for example. So I could say let's make an ional t that is well
let's make a static operator um sorry operator I always mix up where these keywords go in operators and we'll call for times and that's going to be on an I
innumerable of t and we'll call that a vector and then I'm going to pass in my scalar that I am simply going to do a select
and then multiply my scaler across All right. So now let's let's go ahead
All right. So now let's let's go ahead and say loop through all this and write it to the console and we should see okay this is just
going to print out our 1 to 20. But now
if I come in here and I say add a scaler multiply at times 10 that calls into our generic operator right. Um, here's
another one. And this is this is also something else that's new in C# 14, which is compound assignment operators.
Um, which are intended for kind of types where it might be expensive to create a whole new type and you want to kind of modify a type in place. And so I'm going
to type this up very quickly. Um, and
we'll say another extension of T. And
we're going to operate on an array where t is an i number of t. And then finally, I'm going to declare my compound assignment operator. And these are a
assignment operator. And these are a little weird because they're not static like regular operator overloads. They're
instance members, and they return void because you're updating the object in place uh instead of, you know, producing a brand new object like you would from a normal operator overload. So, we're
going to type this in. We're going to say operator, and I'm going to do a very similar, you know, operation. We're
going to do it. We're going to scale a vector. Okay. So, let's go ahead and
vector. Okay. So, let's go ahead and loop through the array.
Oops.
Typing a for loop has never taken so long. And let's go ahead and just scale
long. And let's go ahead and just scale each element. Oops.
each element. Oops.
scale. There we go. All right. And now I can take this and we can say let's make a vector out of this. We'll call it, you know, we just call to array. Okay. And
then I can call times equals six. And
that will call our compound assignment operator down there.
Run this and we'll see that now should be scaling up by 42. So that is kind of the the the the nuts and bolts and big things in C# 14. I'm going to switch
back to slides real quick. Um there's
actually a lot that's new in C# 14. Uh
and a few things that we didn't look at partial events and constructors are you know as we continue to fill out that partial story especially for source generator scenarios. Um you know a nice
generator scenarios. Um you know a nice little feature around unbound types in uh name of but uh yeah extension members extension operators even userdefined
compound assignment operators. This is
cool stuff. C# 14 I hope um will be useful to you if you want to stay kind of apprised of things and how to keep up in C. Um there's the what's new in C
in C. Um there's the what's new in C site over at learn.microsoft. Um, this
is a great place to find out, you know, exactly what's new. And, uh, in in every .NET preview, these get kind of updated over time as we as we're developing the language. And these kind of go in in
language. And these kind of go in in levels of kind of your engagement and how much you even want to engage in this. The language feature status,
this. The language feature status, that's on Ros's GitHub page, uh, GitHub repo. That's really a, uh, a place where
repo. That's really a, uh, a place where you can see kind of like an engineering schedule of how language features are being implemented and who's working on them and what the status is. And then
finally, there's the C# language design repo. And that's a place where our
repo. And that's a place where our design happens in the open. Um, if
you're really interested in that sort of thing, you can read the notes. Um, you
can see the the disputes and the agreements and the votes and what we're doing next. Um, and uh, yeah, with that,
doing next. Um, and uh, yeah, with that, uh, thanks, uh, thanks for checking this out.
Loading video analysis...