C# 8 - Index et Range

Au moment de l'écriture de cet article C# 8 n'est pas encore sorti en finale mais est disponible en preview (preview-5 actuellement, vous avez le lien de téléchargement en bas de l'article). Cette version va amener beaucoup de nouveautés (commme d'habitude !! ) et combiné avec la disponibilté prochaine de dotnet core 3 qui semble offrir un runtime encore plus performant, vous n'aurez plus d'excuse pour ne pas utiliser C# 8 sur dotnet core 3.

Dans ce premier post de la série nous allons aborder une nouvelle syntaxe et deux nouveaux types (Range et Index). Ces opérateurs permettent de selectionner un élement ou un intervalle d'un Array. Ils peuvent s'appliquer à des Array, Span<T> ou ReadOnlySpan<T>. Ce type d'opération est très utilisé en Python dans la manipulation des listes et sont une de ses grandes forces, et c'est avec beaucoup de plaisir que je les vois arriver en C#.

Les Index

Un index permet de récupérer une valeur par sa position dans un Array. Nous avons l'habitude de les utiliser depuis très longtemps (on se le redit entre nous, les indices commencent toujours à zéro pour désigner le premier élément).

int[] fibs = {1, 1, 2, 3, 5, 8};
var premier = fibs[0];
var second = fibs[1];

Par contre si nous voulions récuperer les deux derniers éléments nous devrions récuperer la longueur de la collection et faire un peu d'arithmétique.

int[] fibs = {1, 1, 2, 3, 5, 8};
var longueur = fibs.Length;
var dernier = fibs[longueur - 1];
var avant_dernier = fibs[longueur - 2];

Ce n'est pas très compliqué mais ce n'est pas élégant. Le nouveau type Index va simplifier un peu ce code grâce à l'opérateur ^. En C# 8 nous pouvons réécrire le code précedent :

int[] fibs = {1, 1, 2, 3, 5, 8};
var dernier = fibs[^1];
var avant_dernier = fibs[^2];

C'est plus simple à lire et à écrire, on fait moins d'erreurs et surtout on va pouvoir utiliser les Index dans les Range comme nous le verrons plus loin. ^ (opérateur hat ou chapeau) se lit 'depuis la fin' de la collection. Attention ^0 désigne l'élément suivant le dernier élément de notre liste

Voici une petite correspondance entre les valeurs et leurs index (depuis le début ou depuis la fin)

int[] fibs = {  // depuis le début    // depuis la fin
  1,            //    0                     ^6
  1,            //    1                     ^5
  2,            //    2                     ^4
  3,            //    3                     ^3
  5,            //    4                     ^2
  8             //    5                     ^1
  };

Je vous propose un petit exemple d'utilisation. Nous allons calculer le prochain élément de la suite de Fibonacci.


int fibo_next_original(int[] fibs)
{
  if (fibs.Length < 2) return 1;
  return fibs[fibs.Length - 2] + fibs[fibs.Length - 1]
}

int fibo_next_original(int[] fibs)
{
  if (fibs.Length < 2) return 1;
  return fibs[^2] + fibs[^1]
}

Nous voyons que c'est plus clair et surtout plus proche de la définition des traitements que l'on doit faire sur des tableaux.

J'en profite pour également pour montrer que l'opérateur ^ crée une structure System.Index qui peut donc être utilisée comme une variable régulière.

var index1 = new Index(1, fromEnd: true);
Index index2 = -1;
var index3 = ^1;

var dernier_element = fibs[index1];     // contient le dernier élément de notre liste

Les Range

Les Range vont s'appuyer sur les Index précédents pour nous permettre de spécifier un intervalle d'Index. Si vous avez compris la section précédente, ils sont assez simples à prendre en main et sont très utiles. Attention les intervalles que nous allons définir sont toujours du type [i, j[, ce qui signifie que l'élement d'index i est inclus et que l'élément d'index j est exclus.

Par exemple:

int[] fibs = {1, 1, 2, 3, 5, 8};
var deux_premiers = fibs[0..2]      // contient donc les éléments d'indice 0, 1 uniquement
var deux_derniers = fibs[^2..^0];   // contient donc les éléments d'indice -2, -1 uniquement

Nous voyons qu'il est très facile d'exprimer à l'aide de ces Index un intervalle. L'opérateur .. permet de créer un nouveau type Range qui va nous permettre d'extraire une sous-liste des valeurs d'un tableau.

Un exemple d'application serait pour le calcul des valeurs de la suite de Fibonnacci, la procédure suivante (je sais que ce n'est pas la version la plus optimale, si cela vous intéresse je pourrai écrire un post là-dessus prochainement) nous permet de calculer la prochaine valeur.

int  fibo_next_range(int[] fibs)
{
  if (fibs.Length < 2) return 1;
  return fibs[^2..^1].Sum();
}

Dernier point concernant les Range, Microsoft a ajouté la possibilité d'exprimer deux concepts que l'on emploie très souvent comme montré dans les exemples suivants:

  • du début jusqu'à l'indice I '..I'
  • de l'indice I jusqu'à la fin 'I..'
var les_4_premiers =  fibs[..4];      // Ici on omet l'indice 0, mais est équivalent à 0..4
var les_2_premiers =  fibs[^2..];     // Ici on omet l'index ^0

Pour plus d'informations vous pouvez aller lire un peu code, c'est relativement simple donc c'est l'occasion d'essayer :). A vous maintenant.

Liens