C#'s Best features you might not be using

dotnet www.youtube.com

Brief Summary

Bill Wagner discusses underutilized C# features that can improve code readability and productivity. He emphasizes the importance of adopting these features gradually to avoid disrupting ongoing projects. The presentation covers pattern matching, file-scoped namespaces, init and required properties, string literals, records, and collection expressions, providing practical examples and usage scenarios for each.

  • Pattern matching simplifies complex conditional logic.
  • File-scoped namespaces reduce code nesting.
  • init and required properties enhance data encapsulation.
  • String literals and interpolated strings improve string manipulation.
  • Records streamline data-centric type definitions.
  • Collection expressions simplify collection initialization.

Pattern Matching

Pattern matching offers a declarative approach to replace complex if-else and switch statements. It enhances code readability by clearly mapping inputs to outputs. The compiler provides warnings for incomplete or unreachable patterns, ensuring logical correctness. Using pattern matching can simplify code with multiple nested if statements by making the logic more transparent. A common mistake involves using and, not, and or without proper parentheses, which can lead to unexpected behavior due to operator precedence. The compiler will soon issue warnings for such cases to improve code correctness.

File-Scoped Namespaces

File-scoped namespaces reduce code nesting by eliminating an entire level of indentation. This feature simplifies the structure of files, making code more readable and providing more horizontal space for logic. While it introduces whitespace diffs, these can be managed by disabling whitespace diffs during code reviews to focus on the actual changes. It is recommended to apply this feature incrementally during routine code modifications rather than performing a sweeping change across the entire codebase.

Init and Required Properties

The init and required keywords enhance property behavior, aligning them more closely with design intentions. The required keyword ensures that a property is set during object initialization, with the compiler issuing a warning if it is not. The init keyword creates a read-only property that can only be set during object initialization using an object initializer, preventing subsequent modifications. This combination allows for clear intent expression regarding data immutability and initialization constraints.

String Literals

C# offers several ways to handle strings, including basic strings with escape characters, verbatim strings (using @ to reduce escape sequences), and raw string literals (using triple quotes """). Raw string literals are particularly useful for JSON and other formats with many quotes, improving readability. Interpolated strings (using $) allow embedding expressions directly within strings. Recent enhancements support newlines and spacing within interpolated expressions. Multiple dollar signs can be used to handle nested curly braces in formats like JSON, providing flexibility and clarity in string construction.

Records

Records, defined using the record keyword, are designed to hold data and automatically generate methods for value-based equality (Equals, GetHashCode, etc.). Records can be either classes or structs. The with keyword enables non-destructive mutation, creating a copy of a record with specified properties modified. Using the record declaration on a struct type improves equality implementation by allowing the compiler to generate optimized code based on known fields.

Collection Expressions

Collection expressions simplify collection initialization using bracket syntax. The compiler can optimize memory usage by allocating collections on the stack when appropriate, such as when passing them as arguments to methods. This feature eliminates the need for explicit new declarations and stack allocation, improving code conciseness and efficiency.

Summarize any YouTube video in seconds

Get summaries like this one for any video, in your language, with the free Summ app.

Download on the Apple Store
Get it on Google Play

Transcript

Full transcript of “C#'s Best features you might not be using”. Click a timestamp to open the video at that moment. Need another video? Use the YouTube transcript generator.

0:00 [Music] I'm Bill Wagner and I'm here to talk about some of the recent things that we've been doing doing in C not all Cutting Edge C 13 things as Dustin and mads did yesterday in their presentation but some of the features that have been baking for a while they've been in in a few versions and if you're not using them you really should be adapting some of these today just to make your code base a little bit easier to read keep a lot of things in place where you can look at it and and understand what code is doing a little bit better a little bit easier and to slowly start integrating those you know and if you're like me and you've been using C for a while it's really really easy to just

0:46 just get in these habits where all we do is we just keep doing the same thing because it's always worked it's been working forever you know and that's one of the things with C where we have a very very high bar for backwards compatibility so anything that happens that you know we've added new features to the language throughout all these different versions everything that you knew from whenever you first started using C that all still works but we keep adding these features and we keep looking at things because we want you to be more productive and because we are building different programs than we did you know when C was first invented you know you just heard Scott and Matt talk about Aspire and containers and building Cloud native

1:31 things and and so on and Azure functions and all these so we're doing different things than we did 20 years ago so I'm going to look at six different things that we've been working on over the past several releases and talk about where you should be integrating those into your codebase today and also talk about how to do it in a way that isn't quite so disruptive right because you know we're all shipping projects we're all shipping product or application we have to get delivered so you know you can't just sit there and stop and say I'm just going to you know take the next month and just upgrade my codebase to use the latest language features especially because it all works so we're going to go over pattern matching bioscope name spaces in it required some different things with

2:16 properties and the story around properties we're going to look at string literals and a lot of the things we're doing with strings we're going to talk about records and collection Expressions okay so now let's let's start with pattern matching pattern matching is a way to declaratively write what we historically have written with if then else imperative statements or switch statements and things moving back and forth and it's like if you do this do that and you know check this and if this was true and now we're inside another block and we have more ifs and so on and pretty soon you end up with this you know fireman's ladder in and out and things and you've got to like walk through each statement to figure out for multiple variables what what does it really mean so now let's let's just look

3:02 at some code so I'm going to start with just play around with uh some patterns I'm going to talk about the thing I've added right here in a little bit but let's just say I wanted to write a a Boolean function and I wanted to write a nand gate so I'm going to make a nand function and that's going to take two bulls A and B and if you look okay the compiler is using C language to just build it but want to use pattern matching so I can see how those inputs map to an output so I'm going to say I've got two inputs here I've got a comma B and if you look okay that once again co-pilot just wrote this for me if both A and B are true then the

3:47 output of an endgate is false otherwise it's true now if I want I can expand this out I can actually say you know what if it's true and false well then it's going to be true and now you see I start to get some things here I've got a green squiggle under my switch and what it says for me this is a warning from the compiler that says you know you didn't handle all possible input values right I've got two booleans so there's four input values I've only got two there if I write the others in in as well and then it says okay now that goes away I have handled all the possible inputs of this one one is false the others are true now what co-pilot wrote for me originally is it said all right so since

4:35 these three are all the same I can say well I just don't care anything here and it should return true okay um now I can also put the comma there because we saw for pattern matching there's times you might want to rearrange these and another thing with pattern matching is if I were to take this and move this one up above of now you see I get an error and the error I get is that this one will never be reachable because up here it's already been handled since that discard pattern says oh it's going to match anything so it's already matched anything so as I write the logic for a pattern anytime I do this switch expression there are two things that are

5:21 always going to be true on it first of all if my switch arms don't match every possible input sequence I'm going to get a warning and say that you know it's possible that the inputs fall right through and you never you didn't match anything and then the output is to throw an exception and it's also true if I end up putting something in place in one of these patterns and it actually takes over everything for a later pattern well then I'm going to get an error that says you know this this pattern you've put down here is never going to be reached and because it's never going to be reached well that code never executes so we're going to tell you that's probably an error in your code that kind of logic from the compiler helps make sure that your logic

6:08 in terms of I've got these multiple inputs and here's what should happen make sure that that's a little bit more correct and if you look at and you read this either in this form or you know in the full form if I go back to that version you can read this and see exactly what the output is based on the input so a lot of times what we'll find is if you've got code in your codebase that does any number of these multiple ifs this and nested ifs and nested ifs inside the else you can look at that as there's probably two or three or four inputs and maybe there's a pattern matching way to make that a little bit more clear as to what happens and

6:53 there's a tutorial on learn that goes through and takes a rather larger large system and refact s all all kinds of if then else logic into some patterns and you can experiment with that yourself now the other one I'm going to add here is something that we've discovered by seeing some error reports where people using pattern matching are making I'm going to say a common mistake because a lot of people are making it so we're going to address it in an upcoming build of um visual studio so if you look at this pattern I'm saying okay okay if it's a number between 1 and 10 and I want to see if it's not a perfect square so I've written a pattern that looks perfectly normal and says all right if number is

7:39 not four or nine it's not a perfect square it's one two 3 five you know whatever else so therefore this should run and if I pass it in four if I pass it in nine it's going to return false otherwise it should return true but when you run this you'll find that there's an interesting mistake in it and it's a it's one that we've seen in in a number of code bases when we look at and we've had error reports and questions on the docs as well so if you look it comes in here and it says the only one it says is not a perfect square is four this logic says nine is a perfect square and the reason is when you start using patterns with and not an or they bind in a certain order and what

8:27 this pattern actually does is that so it says it's not four or it's nine right now the compiler doesn't warn on this but soon in an upcoming build it's going to start issuing a warning if you don't have the parenthesis here because the nine will never match it's effectively been um subsumed by the not four and what you really wanted here is you wanted something like this where it's not four or nine now obviously see you can add parentheses in in your code anytime you're using and not nor in uh some of your patterns and it can increase the clarity uh the rules in the language are clear is that not binds

9:14 first followed by and followed by or which is the same thing we do on the um um primary Expressions the exclamation point the double Amper sand and the double vertical bars but again a good recommend is if you start building those conjunctions and compound patterns go ahead and add the parentheses anyway it's guarantees that your codee's going to be correct and the developers that follow you can read it and understand exactly what you're putting together so again pattern matching really good way to just simplify your code and express the intent of what you were doing and when you the slides come out later if you click on this link then that will take you to the tutorial that walks through a good half hour exercise on

10:01 using pattern matching now one of the things that we just like just to make your code look a little bit better is file scop name spaces so that's just going to remove an entire level of nesting for you so all you have here and I'm using visual studio because we get some tool tips and the comp uh ID will do this for me so if I have this this particular file come on you if I just put a semi or semic in there if you watch what happened there it removed the two curly braces and out dented everything in the file by four by one tab I still have the light bulb if I really hate this for some reason I can convert it back and then I get the screwdriver and I convert it back this way again

10:51 now we really like this just because it gives you that extra horizontal space you know to talk about your code and to have it work you give you a little bit more space for your logic and it just gets rid of a little bit of clutter now this does introduce you know wh space diffs on every line in your file so what we've been doing even on our team as we've been converting code and starting to use this feature is we go in and you know when we make the pr that made some some of these changes we tell reviewers turn off Whit space div and then the diff will only show the added semicolon and the removed braces you know for that particular change and it makes the pr review easy makes the code review easy and then that

11:36 that comment stays so if someone's going through history later you know they don't suddenly get that wall of diffs and they can actually see what's really going on um and again this isn't a feature I would go through and sweep your codebase for when you're making changes it's one of those things that just Mak stuff look a little bit tidier okay and we got the analyzers and fixators that update it if if you want to update your entire solution you could apply the same thing and just run it through your whole whole repository and with that one PR say hide whites space diff and then you can move on now in it and required are two new keywords relatively new that we have added into properties to make properties

12:21 act more the way we really wanted them to all the way along for design so let's look at some things with some properties in here so if I were to take let's see we'll play with this one a little bit I think we can probably pop one in here so let's just make a new class where I'm going to say inside here I'm going to have a public string property for a name and might say it that way so you can see Kathleen de Dollard helped create some of these demos originally so that's our public get set property well now as a string if I don't set this now and I don't give it any initial value I now have a warning because that

13:08 warning says you know you might not have set this and you said it's a non-nullable value or notable reference type so it's supposed to be set either in a Constructor or someplace so well let's change this now well we'll leave that as a set for the moment but now let's say this is required so now what's going to happen is if any place else in the code somebody creates a new object of this type and doesn't set this property in a property initializer they'll now get a compiler warning the compiler are saying that you didn't set this required property but now of course by doing that it's now has to be get set we have to be able to set it anywhere and I don't

13:56 really want it changing over time so now we have init now what a nit does is it creates effectively a readon property however callers when they construct an object of this type they can then set the property but only as part of an object initializer so you can set the property once but you can't do set it again you can't change it after it's been created but you still get to use that convenience of and it the object initializer syntax to set the properties so from there you get to start and makes it more clear what your intent is I intended this to act like a property it should act like data you should be able to set it using an object

14:45 initializer but nobody gets to change it afterward and now that helps here in terms of now even inside this class because now it's an init property if I were to write some other method here um and just take a string and we'll say it's new name and if I were to try to implement that and say well I want to just say name equals new name well the compiler once again now is going to give me an error here even inside the class it now says Nope you said this was in it only so you cannot set it after in anything outside of a Constructor okay so what you've been able to do here now is with a property you can now make it read only inside or

15:32 outside the class you can give calling code the ability to use object initializers by saying it's a required property and using in it instead of making it truly read only and so on and that makes it just easier to work with any of this code to express what your intent is on any of the data members in any classes you decide so again we're trying to just make it e to work with properties all the way along now strings as we start using Json and data more and more and more we have more different ways that we're dealing with string data especially now with you know we've seen binary formatters going away serialization using binary formats

16:18 is can be dangerous there are security risks there so Jason is now becoming more and more of a way that we transfer data back and forth or strings in general using some other format so let's look at what those features do now since c 1 we've had your basic strings that you can see on the screen here we've got quotes backslashes for anything that would be an escape character double backslashes if it's supposed to be um supposed to have the backslash in it it's got backs slash n which well it's not quite right on Windows it would be car turn Line Feed and so on we also have btim strings so if you put the Amper sign in front of it well then

17:03 you can get rid of some of the Escape characters you know you notice I still need double quotes here around this one or but I can use only my single backslashes now if you look at Raw strings are really a nice feature especially with something like Jason if you look at this Jason string is all of the element names or field names are quoted strings those that happen to be string values they also surrounded by quotes to try to write this using an earlier format you end up with a whole lot of double quotes everywhere backslash quotes or some you know it's

17:49 it's just very very hard to read so what R string literals do is now if you start a string with three quotes or more then the string starts on the next line and you end it with three uh quotes on the ending line and whichever one matches you know if I were to start it with four which is perfectly legal if you notice Visual Studio automatically added a fourth on the closing one they have to match so you know four five six whatever as long as it is three or more the reason it was done that way is is it's possible you could have a string that contains three repeated quotes and

18:35 we didn't want to make that impossible then once you have that discussion well what about four what about five and pretty soon you reach this point where we're just not going to pick the number but anything more than three starts it and now the actual string output starts with this column here where you can see that Visual Studio has added the the highlighter there legally it starts on the last the starting character of the closing quote white space here is ignored and any of the white space from column one up to the underlining with these brackets is ignored that looks like raw string now what we could do here once we have these we also now have interpolated string

19:23 examples so now as you were trying to build strings if you've ever used two string historically you would have open curly brace zero open curly brace one and so on and then Expressions at the end of it and they get filled in in the right order and you've got to kind of parse back and forth between the length of the string and which argument is next interpolated strings allow you to just put an expression inside those curly braces inside the string starts with a dollar sign or dollar sign and the Amper sand if you want to do verbatim and then the expression inside those curly braces is replaced as part of constructing the string recently we also

20:09 added the ability that as long as you're inside the C sh part you could have new lines or any other spacing in there and it it just works okay for something as simple as just a variable it's not that commonly used but you may end up with something where you've got a link query something like that and you could format that all the way up and down and and make it look the way you want it to and if you look you can do that here with things an interpolated string for Json so now I can have the interpolated values come in on the Json string and if you notice here they're surrounded by double brackets the reason they're surrounded by double brackets is Jason uses a

20:56 single bracket for starting an object so I need more than one bracket so using the same trick we use for the Triple quotes if I have multiple dollar signs it takes that many curly braces to start an interpolated expression so since I have two dollar signs takes two curly braces to start an expression two curly braces to end it so it's very easy with Jason and the same rules apply for an interpolated Jas and string or an interpolated raw string literal as apply for just a raw string literal in quotes if I were to have three or four or five dollar signs it would be three or four or five repeated um curly race characters and they parse from the

21:43 inside so if I were to accidentally have a typo here and there's a third um curly race at the close the outside one becomes part of the text string and you can see that in the way it's colored right so combination of RW string literals interpolated RW string literals interpolated strings in general give us a lot more expressiveness in working with strings and converting objects into string data and back and forth so that's what we're doing with strings all right next we come up to records now there's a lot of really good things about records let's go over into to discuss

22:29 those so if I make a record we get something like customer record okay now given we only have half an hour I'm not going to pop it into sharp laab but if you go to sharp. and you put this in what you'll see is that it generates a very large number of methods for you it generates equals it generates a two string it generates GI hash code it generates not equal it generates an overrides of equal it generates a hidden copy Constructor that will get used if you make a derived record from customer like maybe it's a business customer or a retail customer and all of that gets generated by saying it's a

23:14 record and what that does a the record keyword on either a class or a struct says that the primary purpose of this type is holding data so I want the compiler to generate all the code correctly to do value based equality and to treat this as a value based type even though in the case of a class it is a reference type but everything in terms of equality is based on the values that it holds now and when we put those together well then we get a record that has has those extra methods and another thing that happens with records or with structs is we now have the wi keyword that now says all right if I had created

24:00 a a customer and now if I want to modify something make a non-destructive mutation I say if I take whatever was in X my first record and now with and then set any number of properties it will be a copy of X with the new values for the properties set that I said in the width clause and I could do more than one if I wanted to could say first name equals whatever right that's they can set any or all of them okay works the same as it does for strs and another big advantage of using the record Declaration on a struct type

24:46 so if you think about how equality is implemented in a struct the compiler and the runtime have to do some weird things to look at all the values maybe use some reflection maybe do some other comparisons and see and that's going to slow it down whereas when you say it's a record struct the compiler knows exactly what fields are part of that record and it just generates the code for that record type because it already knows what the fields are okay so that's adding records into your types primary Constructors we've seen this a little bit already in terms of a record we have a primary Constructor here where at the end of the type

25:31 name we just introduce the properties that we want now in the case of a record that will create public properties for each of those values so in a record primary Constructor parameters become public properties now in the case of a nonrecord type what will happen is those primary Constructor par parameters become parameters that are in scope throughout the Declaration of the class so you can use them in any of the methods in the class just like any other parameter so often times if you're doing something with dependency injection you can pass the dependencies as primary Constructor

26:17 parameters and they're automatically available in everywhere in the class you don't need to declare a separate field technically the compiler generates one for you and a closure and and the implementation is not specified exactly but that's what it does do so that you can access those anywhere and then finally collection Expressions which you saw a little bit yesterday so we'll go through those only somewhat briefly and in that anytime you're initializing a collection or creating a collection that you're going to pass to a method or what have you if you see I've now got some tips underneath any of these

27:02 declarations it's going to say you know you can use a collection expression instead I don't have to declare new I don't have to add that extra syntax to remembers it curly braces what goes there array. empty same thing can happen there's a simplified collection expression for that anytime you want the empty collection it's just open close bracket if I want to do this with the bite array I can do the same thing here we now you see I don't have to assign it to a variable or assign it to a collection type if right by array which is right down right there takes an array of bytes okay fine got an array of bytes I can initialize

27:48 lists if you notice I can also use the stack Alec if it's a date time um excuse me I can also use a stack Alec if I'm setting into a span sorry I misspoke there so and it will stack out like the the span if we want to put it yeah into a span same thing here we don't need to state that so why are these last two significant if you think about what's going on here especially in this second one I'm sending three bytes as a collection to this other method I don't need to declare stack Alec if I use the collection expression because the compiler will go up this is being

28:35 used as the parameter to that method as the argument to that method I will allocate these three things on the stack and pass them right to it so by using a collection expression we've enabled the compiler to say I know how I want to get this this um this bits of data to that method I'm going to do it in the most memory efficient and time efficient way possible so in the case of where it's it's being assigned to a span I can put it on the stack it's just going to be then part of the Span in the case where I'm passing it to a method it can be a stack Alex structure and then that is just pointed to for the the method call so those things become faster but you don't have to remember all that and

29:21 think of how you're using it and jump through hoops to get there if you just use the collection expression compiler will do it for you okay and then finally I'm just going to show one quick demo of this is that you can use see if I remember which file this one was in but if you can use a um put it up there no I can't remember where we can put a tuple declaration as an alias so I can say if I I were to add a using and I am not seeing that one right now and I think we're actually out of time but that is one of the ones that we can use with the using directives you can say

30:07 using and give it a tuple type equals name I think it's the other way around actually name equals Tuple type and then you can get this slow growup story from I have some data it's discrete values I find I'm using these things together it's a tuple make it an alias it's got a name and then I can slowly replace that with a type so as you've probably heard several times today if you don't have net9 go ahead and get it and places that are resources to get C guide is where we have all of our docs for C including what's new for a few versions back um including C 13 github.com Donnet C Lang is where C design happens

30:53 and github.com umet rosin is where the compiler development happens so those are places where you would want to keep track of what's going on if you want to participate in the community on Building C talking about what the features are looking at bugs or any other feedback on anything else in there and with that I am going to say thank you

Share

More video summaries