A couple of years ago I began an XMPP Component Framework in C#, but after a few days I had to put it on hold. One thing led to another, and it’s been on hold ever since. I’m finally returning to it. C#, however, has had some significant improvements since then, I want to start again from scratch leveraging the latest C# improvements.
Before beginning, with the release of C# 8 imminent, I decided to re-visit the changes since version 6. Here are some consolidated notes mainly for myself, I hope they may be useful to you in the way of a brief summary, or syntax reminder. It’s kind of a checklist of changes. I was suprised by how much I had overlooked. This awesome language just keeps getting better.
This post is much longer than I had anticipated, and I got a bit lazy at the end with the examples. I’ll revist it from time to time with more notes and clearer examples.
I’m working through the documents available here, which are more detailed.
This post begins with an index, click on a link to jump to specific contents. A list of a few good C# books is next, followed by the actual content.
Index of Changes
- Read-only auto-properties
- Expression-bodied function members
- Using static
- Null-conditional operators
- String interpolation
- Exception filters
- The nameof expression
- Await in catch and finally blocks
- Initialize associate collections using indexers
- Extension Add methods in collection initializers
- out variables
- Pattern matching
- Ref locals and returns
- Local functions
- More expression-bodied members
- Throw expressions
- Generalized async return types
- Numerical litersal syntax improvements
- in modifier on parameters
- ref readonly modifier on mrthod returns
- The readonly struct declaration
- The ref struct declaration
- Non-trailing named arguments
- Leading underscores in numeric literals
- private protected access modifier
- Conditional ref expressions
- Indexing fixed fields does not require pinning
- ref local variables may be reassigned
- stackalloc arrays support initializers
- More types support the fixed statement
- Enhanced generic constraints
- Tuples support == and !=
- Attach attributes to the backing fields for auto-implemented properties
- Extend expression variables in initializers
- switch expressions
- Property patterns
- Tuple patterns
- Positional patterns
- using declarations
- Static local functions
- Disposable ref structs
- Nullable reference types
- Asynchronous streams
- Indices and ranges
Some great C# books
- C# in Depth, Fourth Edition
- Essential C# 7.0, 6th Edition
- More Effective C# (Covers C# 7.0) 50 Specific Ways to Improve Your C#, 2nd Edition
- Effective C# (Covers C# 6.0) 50 Specific Ways to Improve Your C#, 3rd Edition
- C# 5.0 Unleashed
- C# Programming Language (Covering C# 4.0), The, 4th Edition
- CLR via C#, 4th Edition
- Effective C# (Covers C# 4.0): 50 Specific Ways to Improve Your C#, Second Edition
- More Effective C# (Covers C# 3.0): 50 Specific Ways to Improve Your C#
- Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries
- C# 7.0 Pocket Reference: Instant Help for C# 7.0 Programmers
- C# 7.0 in a Nutshell: The Definitive Reference
- Annotated C# Standard 1st Edition
- Pro C# 7: With .NET and .NET Core 8th ed. Edition
- Adaptive Code: Agile coding with design patterns and SOLID principles (Developer Best Practices) 2nd Edition
- Patterns, Principles, and Practices of Domain-Driven Design
- Professional C# 7 and .NET Core 2.0 7th Edition
A concise way of declaring a read-only property, no need to specify a backing field:
As it is read-only you will need to initialize it either in a constructor:
or via an initializer:
Instead of writing a function, or a getter in a read-only property, you can use an expression:
Enables you to import the static methods of a class, and nested types.
In this example, you don’t need to say Console.WriteLine as we imported the static methods of Console with our using statement. The using statement is at the top of the file as per normal, outside of the class.
This operator will return null if the variable is null, or the value of the variable.
You can chain this operator:
You can use this in combination with the null coalescing operator to return a default value:
You can also use this operator to invoke methods:
Instead of inserting arguments into a string via placeholders:
You can now insert the arguments (and expressions) directly into the string. You must prefix the string with $. The arguments are specified within braces
All the previous formatting options still work.
This feature allows you to catch an exception when a specific criteria is met:
the when keyword is used to specify your criteria.
Evaluates to the name of a symbol, for example nameof(lastName) results in a string value of “lastName”.
Useful in method preconditions, or in XAML.
You can now use await in catch and finally expressions. Often used in logging errors:
A new way of initializing dictionaries, essentially a list of [key]=value
This feature enables you to use an extension method for the Add method required for collection initialization. Useful if you have a collection with a different method name for adding items, the extension method can simply wrap this method.
Previously you had to declare an out variable that was passed to a method, this is no longer necessary:
This section is pretty much lifted from the microsoft site
You can create a tuple by assigning a value to each member, and optionally providing semantic names to each of the members of the tuple:
In a tuple assignment, you can also specify the names of the fields on the right-hand side of the assignment:
There may be times when you want to unpackage the members of a tuple that were returned from a method. You can do that by declaring separate variables for each of the values in the tuple. This unpackaging is called deconstructing the tuple:
You can also provide a similar deconstruction for any type in .NET. You write a Deconstruct method as a member of the class. That Deconstruct method provides a set of out arguments for each of the properties you want to extract. Consider this Point class that provides a deconstructor method that extracts the X and Y coordinates:
You can extract the individual fields by assigning a Point to a tuple:
You can learn more in depth about tuples in the tuples article.
You can ignore deconstructed tuple values you are not interested in by using the discards varaiable ( _ ), for example here we are only interested in the pop1 and pop2 values:
For more information, see Discards.
C# 7.0 introduced basic support for pattern matching as found in functional languages. The switch statement has been modified so you can switch on types now, as well as criteria expressed by the when keyword. This example from the microsoft site demonstrates these new features:
You can learn more about pattern matching in Pattern Matching in C#.
You can now return references. In this example, a matrix is received as a parameter and searched, if the correct value is found, a reference to the found value is returned:
This enables the receiver to modify the value in the matrix – if the receiving variable is a reference variable, i.e. ref int i = … Notice in the example that the function signature contains the ref keyword, as well as the return statment.
The addition of ref locals and ref returns enables algorithms that are more efficient by avoiding copying values, or performing dereferencing operations multiple times.
For more information, see the ref keyword article.
You can now declare a method within a method.
Todo: demonstrate usage with lambda
You can now implement as expressions the following:
- get|set accessors on properties
- get|set accessors on indexers
You can now use exceptions in expressions, for example:
Async method return types are nolonger limited to Task and void. The ValueTask Type (nuget package: System.Threading.Tasks.Extensions) is now available. This featutre is useful in performance critical code:
In addition to a binary literal (prefixed by 0b), a new digit seperator has been added to help with the readability of large numbers:
You can now use aync in your main method:
You can more simply express default value expressions:
The names of tuple elements can be inferred from the variables used to initialize the tuple:
The argument is passed by reference but not modified by the called method.
Returns its value by reference but doesn’t allow writes to that object.
Indicates that a struct is immutable and should be passed as an in parameter to its member methods.
Indicates a struct accesses managed memory directly and must always be stack allocated.
Method calls may now use named arguments that precede positional arguments when those named arguments are in the correct positions. For more information see Named and optional arguments.
You can now use the digit separator as the first character of a literal value.
Limits access to derived types declared in the same assembly. See access modifiers.
The conditional expression may produce a ref result instead of a value result.
You can access fixed fields without pinning:
For more information, see the article on the fixed statement.
You can reassign ref local variables:
For more information, see the article on ref returns and ref locals.
You can use initializers on stackalloc arrays:
For more information, see the stackalloc statement article in the language reference.
Any type that contains a GetPinnableReference() method that returns a ref T or ref readonly T may be fixed. For more information, see the fixed statement article in the language reference.
You can now specify the type System.Enum or System.Delegate as base class constraints for a type parameter. You can also use the new unmanaged constraint, to specify that a type parameter must be an unmanaged type. See the articles on where generic constraints and constraints on type parameters.
You can specify a backing field for an auto-implemented property via an attribute:
You can now use the out variable declarations in:
- field initializers
- property initializers
- constructor initializers
- query classes
It enables code such as:
Switch expressions enable you to use a more concise expression syntax:
The property pattern enables you to match on properties of the object examined:
Tuple patterns allow you to switch based on multiple values expressed as a tuple:
When a Deconstruct method is accessible, you can use positional patterns to inspect properties of the object and use those properties for a pattern:
A using declaration is a variable declaration preceded by the using keyword. It tells the compiler that the variable being declared should be disposed at the end of the enclosing scope:
You can now add the static modifier to local functions to ensure that local function doesn’t capture (reference) any variables from the enclosing scope:
To enable a ref struct to be disposed, it must have an accessible void Dispose() method. You can learn more about the feature in the overview of nullable reference types.
Inside a nullable annotation context, any variable of a reference type is considered to be a nonnullable reference type. If you want to indicate that a variable may be null, you must append the type name with the ? to declare the variable as a nullable reference type.
Starting with C# 8.0, you can create and consume streams asynchronously:
Ranges and indices provide a succinct syntax for specifying subranges in an array, Span, or ReadOnlySpan.
- You specify from the end using the ^ operator
- You can specify a range with the range operator: ..
You can omit the beginning operand or the end as appropriate:
You can also declare ranges as variables: