C# 8 incorporou os conceitos de índices e intervalos. Para isso, depende do uso de dois novos tipos: Index e Range (ambas suportadas pelo .Net standard 2.1) e dois novos operadores (..
e ^
)
O benefício é uma sintaxe mais limpa, elegante e concisa para manipular sequências.
using System; using static System.Console; static class Program { static void Main() { const string name = "Elemar Junior"; WriteLine(name.Substring(0, 6)); // Elemar WriteLine(name[..6]); WriteLine(name.Substring(7, 6)); // Junior WriteLine(name[^6..]); } }
No código acima, quatro novas strings
são geradas, duas usando os novos conceitos, duas usando métodos tradicionais.
Importante destacar que os operadores oferecidos pelo C# são apenas açucares sintáticos que, durante o processo de compilação, são substituídos por código instanciando as estruturas adequadas.
using System; using static System.Console; static class Program { static void Main() { const string name = "Elemar Junior"; WriteLine(name[new Range(0, 6)]); WriteLine(name[new Range(Index.FromEnd(6), Index.FromEnd(0))]); } }
Os operadores definidos na linguagem suportam tanto valores constantes quanto variáveis. Além disso, tanto instâncias de Index quando de Range conseguem expor seus parâmetros.
using System; using System.Linq; public class Program { readonly int[] sequence = Sequence(1000); public static void Main() { new Program().Run(); } public void Run() { for (var start = 0; start < sequence.Length; start += 100) { Range r = start..(start + 10); var (min, max, average) = MovingAverage(sequence, r); Console.WriteLine($"From {r.Start} to {r.End}: tMin: {min},tMax: {max},tAverage: {average}"); } for (var start = 0; start < sequence.Length; start += 100) { Range r = ^(start + 10)..^start; var (min, max, average) = MovingAverage(sequence, r); Console.WriteLine($"From {r.Start} to {r.End}: tMin: {min},tMax: {max},tAverage: {average}"); } } static (int min, int max, double average) MovingAverage(int[] subSequence, Range range) { var s = subSequence[range]; return ( subSequence[range].Min(), subSequence[range].Max(), subSequence[range].Average() ); } static int[] Sequence(int count) => Enumerable.Range(0, count).Select(x => (int)(Math.Sqrt(x) * 100)).ToArray(); }
Caso desejemos que um tipo possa ser explorado através de índices e intervalos, devemos garantir que ele atenda as seguintes exigências:
- Ter um atributo de dimensão (
Count
ouLength
); - Ser acessível através de uma propriedade de índice que aceite um
int
como parâmetro, para suportar índices; - Prover uma implementação para um método
Slice
, que retorne um array e receba um parâmetro indicando início do intervalo e outro com a quantidade de elementos, para suportar intervalos
O exemplo abaixo mostra uma implementação, meramente ilustrativa, não otimizada, para a sequência de Fibonacci que aceita índices e intervalos (em um cenário real, faria mais sentido manter um array com a sequência pré-calculada).
using System; public class Program { public static void Main() { var fibonacci = new FibonacciSequence(20); var sample = fibonacci[..10]; foreach (var element in sample) { Console.WriteLine(element); } } } public class FibonacciSequence { public int Length { get; } public FibonacciSequence(int length) { Length = length; } public int this[int index] { get { if (index == 0) return 0; if (index == 1) return 1; int a = 0, b = 1, c = 0; for (var i = 2; i <= index; i++) { c = a + b; a = b; b = c; } return c; } } public int[] Slice(int start, int length) { var result = new int[length]; for (var i = 0; i < length; i++) { result[i] = this[start + i]; } return result; } }
Índices e intervalos, na prática, apenas melhoram a legibilidade do código e não promovem nenhum ganho (nem prejuízo) de performance.