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 (
CountouLength); - Ser acessível através de uma propriedade de índice que aceite um
intcomo 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.