Event is over
Event is over

Buy tickets for the next conference .NET fwdays'24 conference!

What’s Next in C#

Join Mads on a tour of upcoming language features in C#. While still very much in the works, C# 12 is starting to take shape. We touch on some of the ways, big and small, that C# is striving to make your life easier in the coming years.

Mads Torgersen
Microsoft
  • Mads is the lead designer of C#. He has been a PM at Microsoft since 2005.
  • GitHub, Twitter

Talk transcription

Welcome to the last talk of the session for you. The first for me. It's early morning in Seattle, but there's no bad time to be talking about C-Sharp. So here we go. I brought a few things to show for the next recording. This is the version of C-Sharp, C-Sharp 12. Before we just dive in and do some Visual Studio fun, let me just show you a few places that you can go and follow along as we approach the launch of C-Sharp 12 and beyond.

So here's C-Sharp's documentation site. It's actually great. There's all kinds of stuff in here if you want to follow what's new. There's a whole section here that's already starting to accumulate C-Sharp 12 things. Over here, for instance, if you're wondering why we keep adding so many things to C-Sharp after more than 20 years, what are we thinking? How are we thinking about this? The C-Sharp language strategy is a good, concise description of our philosophy for how we evolve our different languages, including C-Sharp. So I recommend you go and take a look there. Let me see if I can get the screen sharing thing out of the way. Let me move this window a little bit and click on the other tab.

So here's the official repo for C-Sharp. The design of C-Sharp is open source. The implementation is also open source in a different GitHub repo. But all the artifacts that we use for designing C-Sharp are actually shared inside and outside of Microsoft. We have several participants, both in the design and implementation of C-Sharp, that do not work for Microsoft. And everything happens here. In this repo, this is where new language proposals come in. They typically start here as discussions. People will discuss an idea, a problem in C-Sharp. If an idea has legs, we will ask people to write it up more formally and put it up as an issue here. So you can see there are various proposals here. Members of the C-Sharp design team can champion a proposal, meaning that the C-Sharp design team should look at it. Here's one that I put up recently.

And eventually, we will discuss them in language design meetings. So if we look here under code, the language design meetings are documented as well. You can go in and find the notes from every single meeting. And that is where we capture our thoughts about any given proposal or idea or problem with a feature that we discuss in here. So you can see the previous here. Here's the agenda for upcoming meetings. You can see the previous meetings here. Go and take a look at what they were talking about for collection literals and get quite a detailed discussion. And this is great for us as well as we evolve the language and come back later and say, why didn't we do it this other way? Why did we drop this idea? Why did we make this choice? We have the rationale documented here. So anyone can come in and participate here as well. It's a great way to influence the C-Sharp language and get into the mindset.

But I bet most of you are eager to just hear about, well, what are you actually going to do in C-Sharp 12? And it's really starting to take shape. Obviously, it's still a work in progress. So anything I show you here might be different by the time we ship the actual language features. I'm not showing every feature that will end up being there. Also, some of these might be pushed further into the future. But I'll give you a picture of our thinking at least. And if you are asking questions along the way, I'll pause here and there and ask if there are any questions about a particular feature. And I'll do my best to stay on track with time. So I'm going to start you out with some code that already works in C-Sharp 11. There are some newish features in here. You might not all be super familiar with everything if you haven't kept up with the latest. But, you know, I'll point out the things that are necessary to understanding.

So the first thing I'm going to talk about is a tiny little feature that is just essentially removing a small limitation in C-Sharp that's been there forever. When you have using aliases in C-Sharp, for some reason, the language has always been limited to allowing you to put a name here. So you couldn't just say 'decimal' because it's a keyword. But we just opened that up. You can say types anyway. You can say types elsewhere. And actually, this one is just sort of cosmetic, right? You could have said the other thing all the time. But it also means that some types that are best expressed or that can only be expressed with special syntax can now be put here.

So you could put a tuple, for instance, if it was 'decimal, string'. You can put a tuple type here. And you can give that. You can use tuple name. You can put a tuple, you know, name, colon, and all of that. Apparently, I can't. But oh, sorry. No, that's not how you put it. I just I'm just pretending I know C-Sharp. You know, I don't really. So you can put 'string name, decimal grade', something like that, right? You can even have, I wonder if you can have a pointer to a tuple. No, you can't because it's generic. You can even have pointers, right? So you could say 'decimal*'. Oh, sorry. Again, why? Why don't I go use some other language? Oh, it's because I can only use them in an unsafe context. But you can actually make using aliases in unsafe context, and then things work. So just cleaning that one up a little bit so that people don't get surprised by what they can't see. It's not really like an addition, so much as a removal of a limitation. Now, this should really just be 'decimal'. Otherwise, the rest of the demo doesn't work. Are there any questions about this one actually out there? We could take that quickly. Nope. All right. Let's move on to a bigger feature. So primary constructors is what we call it. In a couple of versions ago, we added the record feature to C sharp.

And as part of records, we allow something that we call primary constructors, which is that you can put constructor parameters directly on a record class or record struct. And that primarily does is essentially add constructor parameters directly to the class. But when it's a record, we additionally do a thing where we automatically generate a property for those parameters, if there isn't already a property there. So you can see them. The ID and name and things are in here as things that become public properties.

Now, we've thought for a long time that primary constructors would actually be useful for every kind of project. So in C-Sharp 12, we're going to allow any class to have primary constructors. However, the difference is that it's not records, are a very specific kind of thing. They're meant to represent data, and they're meant to have public data exposed. But not all classes are like that. It's very common to want to have constructor parameters that are not exposed as part of the public state of a given class or struct. So for the rest of the classes, you have to do your own exposing of the parameters. Let's actually rename these to lowercase because they're just parameters now. So this one should be named with a lowercase, and this one should be. I don't know why it's sluggish here today.

And it should be lowercase. They're really just parameters. So what does that mean? Well, you can still use scope here, but as we saw, nothing is exposed. So I have to write my own properties, and I can do that in any usual way. I could make 'name' an auto property, get set here. But we could make 'ID' a computed property. Public ID, meaning that it has a getter. With them. And it does not have its backing storage here. It has a getter that's just computed. The shorthand way of doing that is, you know, as an expression-bodied getter here. So I can choose whether and how to expose my parameters, including not exposing them at all. So, for instance, is something that you might do if you think about things like dependency injection, which is common to do with constructors. You get something in that is like your logger or your helper class that you're parameterized with. That's none of the class's clients' business. That's an internal detail for the class to use. So you might wonder. So that's a constructor generated for.

For these parameters. And you might wonder, how am I able to access ID from inside the body of a property after the constructor has finished running? That's essentially what we call capture, and that's what you already know from lambda expressions, for instance, that use surrounding parameters and locals after the enclosing method has already finished running. We simply capture, hold on to whatever values are necessary to keep around for later. So while 'name' is really just a constructor parameter, 'ID' is actually captured as part of the state of the object. So even though you don't see a field here, it's implicitly there.

That's essentially primary constructors. You can see this. Another primary constructor. Another constructor declared here. And other constructors always have to go through the primary constructor directly or indirectly to make sure the primary constructor always gets called so that these, the captured parameters or the parameters in general are always in scope for the body to use. That's primary constructors. I want to see if there's a question on primary constructors. Go ahead, saggy. No, I said that. No, no, no, no, no questions. Yeah. Okay. All right. Maybe we just save the questions for the end. Give you sure a little time to think about it. Okay. So the next thing is a little more. I mean, primary constructors, you can say it's just a shorthand for something that you could have written already. Just make some patterns of coding a lot more convenient. You don't have to declare so much storage and stuff behind the scenes.

The next thing, though, is something that is quite a lot more annoying in C-Sharp today. You see that I'm passing in an array parameter for my primary constructor. I'm passing in an array here. I'm passing in an empty array here. If I, let's say I changed my mind. I would prefer this to be a list. So I could say 'List'. Now, because I'm using a different collection type, the way that I'm creating the collections to pass in no longer works. Right. So now I have to say something else. Maybe I can say 'new'. I don't know. I could say 'new List { }' and used in a collection initializer. We actually allow you to do an implicit new nowadays, so you don't have to say the type that makes it shorter, but it's still different. And for the empty, maybe there's a, maybe there's a list that empty, no, there isn't even a non-generic list here. Maybe it's 'List.Empty'. Let's see, no, there isn't, there actually isn't an empty method for lists. So we have to create, maybe we just create a new list and that's the empty one. Yeah, that seems to work, but again, I can figure this out that ways, but why does it have to be different? And what if I want an even different collection type, then, then what do I do?

What if I want something that you can't initialize today? So in C-Sharp 12, we want to add a common syntax for collection literals, like for creating a new list or new collection of elements of any collection type. And that's the syntax that we use for that is square brackets. So now you can just put this and the empty one, unsurprisingly, it looks like this and that will work for the list of grades that I have here. And it will also actually work for something like immutable array for which collection initializes don't work today because they depend on mutation and immutable collections can't be mutated. That's the whole point, right? But collection literals will work for those as well.

And of course, they will work for the original type we had here for arrays as well. So we have this. Yeah. So we have a shared syntax for creating collections. And it's not just that the compiler has a long list of collections it knows about that it'll create. It's also we are making it so that there is a pattern that you can follow when you write your own collection types that will then plug into this. Even if you don't, you know, a pattern that does not rely on the thing being mutated. So this is another one of those language features that is open to everyone to explore. You can write your own collections so that in such a way that they can be created with collection literals, even if they're not mutable. So we think this is going to clean a lot of C-Sharp code up. This is also just nicer, right? You know, it's just nicer to look at. It unifies a whole bunch of different things. And the plans currently for collection initializes is that we allow them will allow them also to have a spread functionality. So you could say something like, let's say I wanted to steal my colleague Dustin's grades. I think I have a second I think I have a very aged Dustin here, I can steal his grades. And so that. So the dot dot syntax here is a spread operator, right? It takes all the elements in another collection and spread them in as elements in this collection. If you're thinking about where the syntax is coming from, why are we using this syntax?

You can look down here at list patterns that we added recently to C-Sharp as well. And you can see that there's a kind of like a clear pattern. Parallel between these two. It's actually, we didn't add much new for pattern matching this time around, but it's a general principle and pattern matching that we try to make patterns that we match on to sort of like deconstruct an incoming value. We try to make them look similar to the syntax that we use to construct the value. So there's this dualism between constructing and deconstructing. And usually for patterns. The pattern comes later. But for collection literals, it's the other way around. There were already patterns for deconstructing lists or collections, as you can see here. And then, you know, they can have single-element things. And there's even the dot dot syntax to match out all the remaining elements of a given list pattern. And that's completely symmetrical to how the collection literals look. So now we have the symmetry as well. Questions? Not yet. Either this is so easy that nobody has any questions or it's a little hard to chew. I will move on to the next feature I want to show. So let me see. Just remind myself. There's one thing that we've wanted to do for quite a while as well. Sometimes in C sharp, we are very reluctant to add a new feature because it introduces what we call a cliff. You can fall over a cliff where we introduce a very easy syntax for something.

And then as soon as that easy syntax doesn't quite do the thing you want, you have to fall off the cliff and use the complicated syntax. And that creates kind of like a break in the experience, if you will. And one of the things that we knew it had a cliff. But we added it anyway, all the way back in C sharp three, I think is auto properties. We've allowed a few more things over time for auto properties, but essentially the problem with auto properties, they are awesome until you want to do just the tiniest little bit of extra work in one of the accessors or and then you fall off a cliff and you have to manually declare a backing field and both of the accessors. And we want to do that. We want to smooth out that experience so that if I want to change something a little bit for an auto property, I can, in fact, go in and let's say, define one of one of the accessors myself. Let's say that when I set it, I just I want to.

Just trim the input before I put it into the backing field. The way that we allow that is we're going to introduce a field variable. Okay. So 'field' is going to be a special name. You see, this doesn't work yet. I don't have this in my Visual Studio yet. We will introduce a field to represent the backing field of this auto property. And I can then, if I just wanted to do what is always done, I can use the 'value' special parameter and just assign it in. But in this case, I want to just, you know, I want to just trim the white space off of it.

So now the idea is that this would now be allowed. Just, you know. I can still leave the getter implemented by the compiler, or I can implement that myself as well if I want to. And I can also but I still get the backing field. Even if I implement both of them, let's say that I implemented the getter as well. Maybe just returns the field. This is still an auto property, even though it has all of its accessors implemented because it uses the field. OK. Special variable inside it tells the compiler, oh, no, this is still a this is still an auto property. Still generate the backing field. It's just that you are you're accessing explicitly. So in this case, even if you implement both of them, you still get the benefit of not having to declare your own backing field. And in fact, it's probably going to be very rare that you need to declare your own backing field when this feature comes into C sharp. Because. Usually, you're going to want the backing storage to be to have the same type as the auto property. Right. So you are the cases where you really need to declare your own back backing storage is really for situations where there's a significant difference in type or, you know, that the state is actually represented some other way.

This feels like a nice advancement in C sharp. Right. But it does have a problem. And so I think with four minutes to go before we go into questions, I'm going to let me just talk a little bit about that problem. So the problem is that. Referencing something called 'field' inside of an auto property that's already legal C sharp. Right. That could be another field up here called 'field'. Let's say that there was maybe it was even a string. Right. String 'field'. And now there's this references that. So there are cases where this new feature would cause old code to mean something new to change its meaning. It's not going to be super common, but it's going to exist. There's going to be code out there. 'Field' is not as a weird word to use in your in your code, certainly if your variable names and so on are English language. So. There are various ways that we could solve that problem so that the 'field' keyword would not be a breaking change in C sharp. We could make it so that if there's a field there called 'field', there's something already called 'field'. Well, it just means what it already means today. But that's. That's a strange effect, right? Think about the new language after this is like many years from now, somebody adds something out here called 'field'. And all of a sudden. Their auto property stops working correctly that you know that that introduces this kind of surprises. We've done that. Too many times in C sharp where we've added a feature and made it more complicated than it has to be just so that some code that could already exist will continue to to exist.

Think about the 'var.' Think about 'name of,' think about the underscore for discards. Yeah. And those have more complicated rules than they have to just so that we can't break the language. So one of the things we're thinking about and we're thinking about it hard enough and seriously enough that it might cause us to postpone the field feature here and it and make it not get into C sharp 12 so we can get this right is. Should we. Change. A little bit how we think about breaking changes in C sharp we've noticed that other programming languages around us they have actually more permissive approaches to breaking changes than we do. We think that it's a great quality of C sharp that we don't go around breaking your code all the time and we're not going to do that. But when there's this language feature that has a right design that is slightly breaking or a wrong design that's going to confuse people forever. Um. Or cause like weird error states. Maybe we should do the right design, you know, and then how can we do that in such a way that people are. Kept relatively safe when they upgrade to the next C sharp.

That's something that we're looking at right now and it's still like working work in progress. But if we can do that, then there's a chance that we can. We can. Do this kind of slight breaking changes and and have maybe even clean up some of the. Existing things like var and like underscore so that they don't have these the confusing behavior that they have today. We have evidence that the that underscore in particular the discard the rules for when a thing is a variable and when it's a discard are so confusing. That people aren't using it nearly as much as they could. They're like I can't tell if I am introducing a discard right now or I'm declaring a new variable using an existing variable. If we could fix that with a slight breaking change and then warn people appropriately that there's a breaking change about to happen to their code, then maybe that's a better a better point to be at. So I wanted to share that thinking with you as well because that's an ongoing thing that we are focused on. So with that, I've spent my half hour. I can certainly talk a little longer if there aren't any questions, but I think this is a good time. To switch and see. If there are if there are questions now.

Sign in
Or by mail
Sign in
Or by mail
Register with email
Register with email
Forgot password?