C# Recent Features
These notes are about newish features in C#, not the basics.
Here's the C# version history. I've written up the parts of it that I care about.
The last version of C# I had used when I started writing this was 3.5.
Once again, C# seems to have spanked Java (which has just about gotten around to a naff version of LINQ) in terms of development velocity.
Duck Typing
The dynamic keyword does run-time duck typing. I'm surprised they put this in.
This is done using the first one we find of:
- COM IDispatch
- IDynamicMetaObjectProvider
- Reflection
We should probably implement IDynamicMetaObjectProvider in most cases to make things faster? But performance benchmark it first.
Not to be confused with var, which is type inference.
Covariant and Contravariant Type Parameters
In generics, we can now use the out and in keywords, like Thing<out X>
Technical Term | C# Term | Java Term |
---|---|---|
Covariance | out | extends |
Contravariance | in | super |
If we don't specify these, our type is invariant.
COM
The ref keyword (call by reference) is now optional when calling COM methods.
It will magically create a variable and refer to it for you.
There is also a thing called Embedded Interop. This means you don't have to ship so much code when you use COM.
Caller Info
You can put one of these attributes on a string typed method parameter, and default that parameter's value to null
:
- CallerMemberName
- CallerFilePath
- CallerLineNumber
When the method is called, the default will be overridden with some magic reflection info.
A useful debugging and tracing tool.
Async and Await
Built on the Task<T>
type.
When you declare a method (or lambda), you can mark it with the async keyword.
When you call an async method, you use the await keyword. You can only use this inside a method which is itself declared async.
Await is what actually passes control over. Behind the scenes, it will cleverly sort out a reactor loop, and yield execution whenever it comes to the await keyword.
This is useful for I/O operations, like waiting for a database, a network, or another process. However, you can use the same pattern together with the Task Parallel Library (Java equivalent is Fork/Join) if you actually just want to do some CPU work in parallel.
Useful static methods on the Task class:
Task.WhenAll(tasks)
- returns a Task which is complete when all the tasks are complete.
Task.WhenAny(tasks)
- returns a Task which is complete when the first task completes.
Task.Delay()
- returns a Task which is complete later.
Task.Run(task)
- run the Task on another thread.
Overall, using async and await is still going to turn the control flow of your program inside-out.
As of C# 7.1, you can also declare your Main method to be async and have it return a Task
.
TODO Exceptions with Async and Await
Exceptions will end up on the Task object itself.
It appears that the await keyword does some exception handling magic for us though?
Look into this some more — it's usually a massive pain with this sort of thing.
Progress Reports
There is an IProgress<T>
interface, which is useful for providing notifications on how far along a task is.
Your asynchronous method should accept this as a parameter.
Cancellation
Asynchronous methods may accept a CancellationToken
as a parameter.
Async void
If you are declaring a method which has no return type, you can't use Task<T>
. You need to write async void
.
This will really mess up your exception handling. Avoid it if you can.
Imports
Added the using static keyword. Not clear if it works with enums — I should check.
Exceptions
You can now put a guard condition on your catch blocks. These are called exception filters.
try { Whatever(); } catch (Exception ex) when (MyCondition()) { // do some stuff }
This is useful for debugging, if you have break on uncaught exceptions turned on.
Additionally, you can now throw exceptions as part of an expression (for example, inside a ternary).
Methods
Defaults
You can specify a default value when declaring your method parameters. When calling a method, you can call those parameters by name in any order (after the positional arguments).
It looks just like every other language which has this feature.
Lambda Syntax
Method declarations can now also use the lambda => syntax for brevity. Since C# 7.0, you can do use this syntax for constructors and finalizers too.
Scope Functions
You can now define functions inside a method, to restrict them to the scope. They are compiled to private methods, so they still has access to this
.
Pass By Reference
If a method has out parameters, you can now declare them inside the method callsite. They then become available in the scope of your calling method. This saves some lines of code, but could look confusing sometimes. You can also pass _
as a discard value for out parameters.
DoSomething(out string result); Console.WriteLine(result);
There are some extra keywords relating to passing by reference and returning references.
in
- this method parameter is passed by reference, but not modified.
ref
- this method's return value is a reference.
ref readonly
- this method's return value is a reference which you can't modify.
When you return a reference, you have to write return ref myReturnValue
. When you call a method which returns a reference, you must store the result in a ref variable.
ref var thing = ref SomeRefReturningMethod();
The main point of the above is for improving the performance and readability of mathematical code where you don't want to have lots of allocations.
To help with this, we also have some extra keywords when declaring structs:
readonly struct
- this struct can't be changed, when used as a method parameter should be an in parameter.
ref struct
- this struct will always be stack allocated.
Properties
When you declare a property, you can now set its initial value. This works even if it's read-only.
The value must be statically computable.
public String Thing {get;} = "Oy oy " + "savaloy!";
Like methods, property getters can now use the lambda => syntax.
Null Propagation
There's a new ?. operator, which is like the normal . operator, but with a null check first.
// This long winded way of writing things var thing = x == null ? x : x.whatever; // Is the same as this new syntax var thing2 = x?.whatever;
For method calls, you need to use the Invoke() method.
var thing = x.?MyMethod.?Invoke();
Strings
We can do quick string formatting with a new kind of interpolated string literal.
$"String greeting = Hello, {someExpressionToInsert, minCharacters, formatString}!";
There's also now a nameof function. This is actually a compile time thing rather than a real function call, and turns a variable, type or function into a string.
Dictionary Initializer
Wrap each key, value pair in some braces.
I could have sworn this existed before?
var thing = new Dictionary<string, int> { {"x", 0}, {"y", 1} };
Tuples
C# now has tuples, which are mostly written like in every other language.
You can give the fields of the tuple names by using an association syntax var thing = (x: x, y: y)
.
As of C# 7.1, these field names are inferred anyway if you write var thing = (x, y)
.
It also has simple destructuring. To make this work, you type need to implement the Deconstruct method. The parameters you're outputting should be out parameters, obviously.
You can add destructuring to a type you don't control using extension methods.
You can use _
as a discard value when destructuring.
Access Modifiers
There's now the combination keyword private protected (containing class or derived types which are in the same assembly).
This compares to protected (derived types) and protected internal (classes in same assembly or derived types).
This seems a bit pointless to me actually.
Pattern Matching
The switch statement has been extended to understand about type and structure.
switch(thing) { case null: // matched null break; case 0: // matched some other value break; case int val: // matched on a type break; case string s when s > "hello": // matched on a type and condition break; default: // happens last break; }
The is statement has also been extended. It can match on value and type, but doesn't get the extra condition (which you can just put on with &&
anyway).