In this post, we are going to look at the list of new features arriving with C# 11 next week. If anything changes, I'll update the post.
As you may know, these features are brought along with NET 7, which we’ll take an in-depth look at in another post.
Index
1 - required keyword
Let's start with one of the changes that affects me the most. Although it might seem small, it's actually significant because, thanks to this change, we can have records with nullable enabled as objects inside the Options pattern. Up until now, if you had a record as an object for the pattern, you had to disable nullable in that file.
This is no longer the case, as the required keyword covers that need and you no longer have to disable nullable.
public record EjemploRequired
{
public required string ApiKey { get; init; }
}
This example without the required keyword in C# 10 would give you a warning or even an error if you have warnings set as errors.
Personally, I think init
should imply required
. But it’s not the end of the world to add one more keyword.
- I might even create a dedicated video for this feature.
2 - List patterns
In recent versions of the language, we've seen several improvements in pattern matching and now it's the turn for list pattern matching. We have two main changes:
Now you can do array is [1,2,3]
and it will return true or false.
int[] ejemploArray = new int[] { 1, 2, 3, 4, 5, 6, 7 };
bool result = ejemploArray is [1, 2, 3];
Additionally, you can use ..
to indicate ranges, which is a nice addition and useful for developers coming from other languages used to this functionality in their native environments.
var resultado = ejemploArray switch
{
[1, .., 7] => 1,
[1, 2, 3] => 1,
[2, 3] => 2,
_ => throw new NotImplementedException(),
};
However, I imagine this functionality is not 100% complete yet, since for example you can't use it with multidimensional arrays yet.
3 - file access modifier
With the arrival of C# 11, the Microsoft team is including a new access modifier. This time, it’s the file
modifier, which gives visibility only within the file where it's declared.
It was initially created for those using source generators to prevent naming collisions, but as someone who creates libraries constantly, I find it very useful as well.
public class CualquerCasoDeuso
{
public void Execute()
{
FileScope fileScope = new();
fileScope.Execute();
}
}
file class FileScope
{
public void Execute()
{
Console.WriteLine("este metodo solo se puede llamar desde el mismo fichero");
}
}
Just like other access modifiers, the only difference is it's called file
and it can't be used outside the same file.
4 - Changes in the string type in C# 11
In this version, we have several changes related to string
or text in general, so I'll group them all here.
4.1 - Raw string literals
Until now in C#, if we wanted to add strings as-is in the code, we couldn’t really do it directly. In the following example, all the spaces on the left and right would be trimmed.
Not only that, but you couldn’t easily use variables, well, you could, but it was a bit messy.
With the arrival of raw string literals, this problem is solved; you can specify the $
character to indicate you want variables, and you can add more than one if you want multiple {
characters in your code to indicate a variable spot.
string stringLiteral = """
esto es
un ejemplo de string literal:
con diferentes niveles de interpolacion
""";
string valor1 = "test";
string stringLiteralPura = $$$"""
esto es
un ejemplo de string literal pura:
donde podemos poner variables: {{{valor1}}}
""";
If you add another $
, you'll need to use another set of { }
to group the variable.
4.2 - UTF-8 string literals
If you add u8
to the end of a string, you can store that literal as a byte
format, either in a span<byte>
or a byte[]
array.
byte[] resultado = "Esto es una frase de ejemplo"u8.ToArray();
A couple of things to note: these strings are literals at runtime, not at compile time, which has two implications in C#:
- You can't use them with variables; i.e., you can’t String.Format them
- They can't be used as default parameter values.
4.3 - Changes in string interpolation
Now, anything you put inside {
and }
will be evaluated as C#, which allows line breaks and if I'm not mistaken, pattern matching as in switch expressions introduced in C#9.
int variable = 1;
string example = $"El valor de ejemplo es: {variable switch
{
> 1 => "el valor es 1",
_ => "valor no registrado",
}}";
5 - ref fields inside ref structs
Now you can create a member with the ref
keyword inside a ref struct
:
public ref struct EjemploRef
{
public ref int valor;
public int Getvalor()
{
if (System.Runtime.CompilerServices.Unsafe.IsNullRef(ref valor))
{
throw new InvalidOperationException("El valor no ha sido inizializdo");
}
return valor;
}
}
Personally, I never needed to do anything like this, and I can't think of a use-case right now.
By the way, before we leave structs, from now on, structs will initialize to their default values as part of the constructor, ensuring everything is properly initialized by the compiler.
internal struct StructValorPorDefecto
{
private int Valor { get; set; } //initialized to 0 by default
public StructValorPorDefecto()
{
}
}
6 - Support for Generic Math
In C# 10, Microsoft introduced Generic Math, but the community felt several features were still missing.
Microsoft listened and implemented a series of changes so that developers can take advantage of operators and static APIs by making them static virtual and using generics.
Personally, I think for 99.9% of people, this change won’t matter to their daily work, but for that remaining 0.1%, it’s a game changer. I’m in the 99.9%.
7 - Generic attributes in C#
Another change I think I’ll use quite often is generic attributes, basically, you can now pass the generic T
to your attribute:
Previously, you couldn’t do this. Yes, you could pass the type as a constructor parameter, but it was clunky. Now it’s much more elegant and understandable:
public class GenericAttribute<T> : Attribute
{
}
internal class AtributosGenericos
{
[GenericAttribute<string>]
public void Example() { }
}
One thing to note is you can’t pass the generic type from the calling class to the attribute: when you specify the attribute, you must specify the type for the attribute.
You also can't use the dynamic
type, nullable types, or tuples
. The types Object
and ValueTuple<T, U>
are allowed.
internal class AtributosGenericosEj2<T>
{
//this does not compile
[GenericAttribute<T>]
public void Example() { }
}
8 - Null parameter checking
As some of you may know, we've already covered ways to check for null values in C# on this blog. In that post, I mentioned parameter null checking, which consists of adding the "bang" character !
(exclamation mark) after the parameter name, well, actually two !!
.
Even though it was included in one of the preview releases, in the end, the Microsoft team decided to remove it from this release due to community feedback essentially saying they didn’t like the idea.
And I agree, I don't like it either. For example, if the bang were in the data type, I might get it, but on the variable name, it doesn’t make sense. We'll see what they come up with in the future, because the idea is good and I hope they find something that works for most people.
//Example (does not compile)
public void Example(int valorNoNulo!!)
{
}
If there is any problem you can add a comment bellow or contact me in the website's contact form