C# 7.3 : les nouveautés

Petite évolution de C# 7.3 qui mérite notre attention, à nous fans de C#, du framework .NET et de ses apports. Microsoft a en effet apporté une attention particulière à la performance du code en mode sécurisé, à une amélioration substantielle de fonctionnalités existantes mais aussi deux nouvelles options de compilateur qui vont ravir ceux qui aiment l’opensource.

Que devons-nous retenir ?

Amélioration des performances du code sécurisé

L’indexation des champs fixed ne nécessite plus un second fixed

Vous le savez probablement, même si on en parle peu, même si on l’utilise peu, C# nous permet la création de code en mode non sécurisé. Cette écriture est très pratique pour ceux qui travaillent sur des fonctionnalités pointues où les performances sont essentielles. L’apport de C# 7.3 est de faciliter le contexte entre code sécurisé et code non-sécurisé afin que les développeurs aient justement moins accès au code non sécurisé. En effet, moins les développeurs passent de temps en code non sécurisé, plus leur code est stabilisé et sûr.

Observer par exemple dans le code ci-dessous la différence entre un code pré-C# 7.3 et un code C# 7.3. Dans cet exemple, vous devez accéder à un élément d’un tableau d’entier à l’intérieur d’une structure « unsafe »

unsafe struct S
{
   public fixed int myFixedField[10];
}

//Avant c# 7.3
class Avant
{
   static S s = new S();

   unsafe public void MaMethod()
   {
      fixed (int* ptr = s.myFixedField)
      {
         int p = ptr[5];
      }
   }
}

//Apres c# 7.3
class Apres
{
   static S s = new S();

   unsafe public void MaMethod()
   {
      int p = s.myFixedField[5];
   }
}

Avec C# 7.3, il n’y a plus besoin d’épingler avec fixed un pointeur complémentaire vers l’élément ; il y a a toutefois toujours besoin d’un contexte unsafe.

L’utilisation de fixed est étendue

L’instruction fixed, qui se limitait à un nombre limite de types est étendu à tout type contenant une méthode GetPinnableReference() qui retoure un ref T ou ref readonly T. L’instruction fixed est par exemple utilisable avec System.Span<T>.

Initialiseurs sur stackalloc

A l’image des initialiseurs sur les tableaux « classiques », il est possible d’utiliser la syntaxe suivante avec stackalloc (allocation d’un bloc mémoire sur la pile)

int* pArr = stackalloc int[3] {1, 2, 3};
int* pArr2 = stackalloc int[] {1, 2, 3};
Span arr = stackalloc [] {1, 2, 3};

Réaffectation des variables locales ref

Très pratique, les variables locales ref peuvent être réaffectées ; ce qui était auparavant impossible.

ref MaStructure  a = ref Structure1;
a = ref Structure2; //réassignation

Quelques nouveautés

Comparaisons et différences sur les tuples

Tous d’abord les tuples prennent désormais en charge les comparaisons et les différences. Je vous invite vivement à lire ce document Microsoft, mais voici en résumé le comportement : les tuples peuvent se comparer grâce à une comparaison dans l’ordre des membres de gauche à ceux de droites, les comparaisons s’arrêtant dès qu’une paire n’est pas égale.

var left = (a: 5, b: 10);
var right = (a: 5, b: 10);
Console.WriteLine(left == right); // affiche 'true'

De nombreuses choses se passent de façon transparente, notamment des conversions implicites comme dans l’exemple ci-dessous où la comparaison de int à long se fait implicitement

(long a, int b) longFirst = (5, 10);
(int a, long b) longSecond = (5, 10);
Console.WriteLine(longFirst == longSecond); // Affiche vrai

Des conversions « lifted », grosso modo des conversions qui gère le cas des comparaisons de type Nullable s’opèrent également

var left = (a: 5, b: 10);
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Vrai

 Les contraintes « where » sur les génériques sont améliorées

Qu’il est pratique ce « where » sur les génériques ; il permet de spécifier l’héritage mais également s’il est possible de créer une instance comme dans l’exemple suivant où le type T doit implémenter IComparable mais également avoir un constructeur accessible sans paramètre.

public class MyGenericClass where T : IComparable, new()
{
// La ligne suivante est impossible sans la contrainte new():
T item = new T();
}

Vous connaissez et probablement utilisez déjà les contraintes de type struct ou class comme dans l’exemple suivant.

class MyClass<T, U>
where T : class
where U : struct
{ }

Ils sont en revanche nouveaux grâce à C# 7.3, ce sont les contraintes de type Enum, Delegate ou MulticastDelegate .

public class UsingEnum where T : System.Enum { }

public class UsingDelegate where T : System.Delegate { }

public class Multicaster where T : System.MulticastDelegate { }

Avec C# 7.3 est égaleemnt ajouté les types non managés, c’est à dire les type qu’on ne sont pas un type référence et ne contiennent aucun champ de type référencé à tous les niveaux d’imbrication

class UnManagedWrapper
where T : unmanaged
{ }

Cibles d’attribut

La cible d’un attribut est l’entité à laquelle il s’applique. Les cibles peuvent être assembly, module, field, event, method, param, propery, return ou type. Il est désormais possible d’écrire :

[field: InformationSurLeFieldAttribute]
public int Couleur { get; set; }
{ }

Nouvelles options du compilateur

Signature publique/opensource

Il est désormais possible de signer un assembly avec une clé publique grâce à

-publicsign. 

L’assembly est ainsi identifié comme « signée » (grâce à un bit dans l’assembly) mais provient d’une clé publique. Ceci permet la génération d’assembly à partir de clé publique opensource.

pathmap

L’option

pathmap

 contrôle le chemin des sources écrits dans les fichiers PDB ou CallerFilePathAttribute. Il est ainsi possible d’indiquer au compilateur de remplacer les chemins sources de l’environnement de build par ceux mappés.

Cet article s’est focalisé sur les améliorations les plus parlantes, la liste complète de toutes les fonctionnalités est disponible  dans la documentation Microsoft.