Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Você define indexadores quando instâncias de uma classe ou struct podem ser indexadas como uma matriz ou outra coleção. O valor indexado pode ser definido ou recuperado sem especificar explicitamente um tipo ou membro da instância. Os indexadores se assemelham a propriedades , exceto que seus acessadores usam parâmetros.
O exemplo a seguir define uma classe genérica com get
e set
métodos de acessador para atribuir e recuperar valores.
namespace Indexers;
public class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
// Define the indexer to allow client code to use [] notation.
public T this[int i]
{
get => arr[i];
set => arr[i] = value;
}
}
O exemplo anterior mostra um indexador de leitura/gravação. Ele contém os accessores get
e set
. Você pode definir indexadores só de leitura como um membro com corpo de expressão, conforme mostrado nos exemplos a seguir.
namespace Indexers;
public class ReadOnlySampleCollection<T>(params IEnumerable<T> items)
{
// Declare an array to store the data elements.
private T[] arr = [.. items];
public T this[int i] => arr[i];
}
A get
palavra-chave não é usada, =>
introduz a expressão corpo.
Os indexadores habilitam propriedades indexadas : propriedades referenciadas usando um ou mais argumentos. Esses argumentos fornecem um índice em alguma coleção de valores.
- Os indexadores permitem que os objetos sejam indexados de forma semelhante às matrizes.
- Um
get
acessador retorna um valor. Umset
acessador atribui um valor. - A
this
palavra-chave define o indexador. - A
value
palavra-chave é o argumento para oset
acessador. - Os indexadores não exigem um valor de índice inteiro; Cabe a você definir o mecanismo de pesquisa específico.
- Os indexadores podem estar sobrecarregados.
- Os indexadores podem ter um ou mais parâmetros formais, por exemplo, ao acessar uma matriz bidimensional.
- Você pode declarar
partial
indexadores empartial
tipos.
Você pode aplicar quase tudo o que aprendeu trabalhando com propriedades a indexadores. A única exceção a essa regra são as propriedades implementadas automaticamente. O compilador nem sempre pode gerar o armazenamento correto para um indexador. Você pode definir vários indexadores em um tipo, desde que as listas de argumentos para cada indexador sejam exclusivas.
Usos de indexadores
Você define indexadores em seu tipo quando sua API modela alguma coleção. Seu indexador não precisa mapear diretamente para os tipos de coleção que fazem parte da estrutura principal do .NET. Os indexadores permitem que você forneça a API que corresponde à abstração do seu tipo sem expor os detalhes internos de como os valores dessa abstração são armazenados ou calculados.
Matrizes e vetores
Seu tipo pode modelar uma matriz ou um vetor. A vantagem de criar seu próprio indexador é que você pode definir o armazenamento para essa coleção para atender às suas necessidades. Imagine um cenário em que seu tipo modela dados históricos que são muito grandes para serem carregados na memória de uma só vez. Você precisa carregar e descarregar seções da coleção com base no uso. O exemplo a seguir modela esse comportamento. Ele informa quantos pontos de dados existem. Ele cria páginas para armazenar seções dos dados sob demanda. Ele remove páginas da memória para abrir espaço para páginas necessárias para solicitações mais recentes.
namespace Indexers;
public record Measurements(double HiTemp, double LoTemp, double AirPressure);
public class DataSamples
{
private class Page
{
private readonly List<Measurements> pageData = new ();
private readonly int _startingIndex;
private readonly int _length;
public Page(int startingIndex, int length)
{
_startingIndex = startingIndex;
_length = length;
// This stays as random stuff:
var generator = new Random();
for (int i = 0; i < length; i++)
{
var m = new Measurements(HiTemp: generator.Next(50, 95),
LoTemp: generator.Next(12, 49),
AirPressure: 28.0 + generator.NextDouble() * 4
);
pageData.Add(m);
}
}
public bool HasItem(int index) =>
((index >= _startingIndex) &&
(index < _startingIndex + _length));
public Measurements this[int index]
{
get
{
LastAccess = DateTime.Now;
return pageData[index - _startingIndex];
}
set
{
pageData[index - _startingIndex] = value;
Dirty = true;
LastAccess = DateTime.Now;
}
}
public bool Dirty { get; private set; } = false;
public DateTime LastAccess { get; set; } = DateTime.Now;
}
private readonly int _totalSize;
private readonly List<Page> pagesInMemory = new ();
public DataSamples(int totalSize)
{
this._totalSize = totalSize;
}
public Measurements this[int index]
{
get
{
if (index < 0) throw new IndexOutOfRangeException("Cannot index less than 0");
if (index >= _totalSize) throw new IndexOutOfRangeException("Cannot index past the end of storage");
var page = updateCachedPagesForAccess(index);
return page[index];
}
set
{
if (index < 0) throw new IndexOutOfRangeException("Cannot index less than 0");
if (index >= _totalSize) throw new IndexOutOfRangeException("Cannot index past the end of storage");
var page = updateCachedPagesForAccess(index);
page[index] = value;
}
}
private Page updateCachedPagesForAccess(int index)
{
foreach (var p in pagesInMemory)
{
if (p.HasItem(index))
{
return p;
}
}
var startingIndex = (index / 1000) * 1000;
var newPage = new Page(startingIndex, 1000);
addPageToCache(newPage);
return newPage;
}
private void addPageToCache(Page p)
{
if (pagesInMemory.Count > 4)
{
// remove oldest non-dirty page:
var oldest = pagesInMemory
.Where(page => !page.Dirty)
.OrderBy(page => page.LastAccess)
.FirstOrDefault();
// Note that this may keep more than 5 pages in memory
// if too much is dirty
if (oldest != null)
pagesInMemory.Remove(oldest);
}
pagesInMemory.Add(p);
}
}
Você pode seguir esta linguagem de design para modelar qualquer tipo de coleção onde há boas razões para não carregar todo o conjunto de dados em uma coleção na memória. Observe que a Page
classe é uma classe aninhada privada que não faz parte da interface pública. Esses detalhes são ocultos dos usuários dessa classe.
Dicionários
Outro cenário comum é quando você precisa modelar um dicionário ou um mapa. Este cenário é quando seu tipo armazena valores com base em chave, possivelmente chaves de texto. Este exemplo cria um dicionário que mapeia argumentos de linha de comando para expressões lambda que gerenciam essas opções. O exemplo a seguir mostra duas classes: uma ArgsActions
que mapeia uma opção da linha de comando para um System.Action delegado e uma ArgsProcessor
que utiliza o ArgsActions
para executar cada Action quando encontra essa opção.
namespace Indexers;
public class ArgsProcessor
{
private readonly ArgsActions _actions;
public ArgsProcessor(ArgsActions actions)
{
_actions = actions;
}
public void Process(string[] args)
{
foreach (var arg in args)
{
_actions[arg]?.Invoke();
}
}
}
public class ArgsActions
{
readonly private Dictionary<string, Action> _argsActions = new();
public Action this[string s]
{
get
{
Action? action;
Action defaultAction = () => { };
return _argsActions.TryGetValue(s, out action) ? action : defaultAction;
}
}
public void SetOption(string s, Action a)
{
_argsActions[s] = a;
}
}
Neste exemplo, a coleção ArgsAction
corresponde de perto à coleção subjacente. O get
determina se uma determinada opção está configurada. Em caso afirmativo, ele retorna o Action associado a essa opção. Se não, devolve um Action que não faz nada. O acessador público não inclui um set
acessador. Em vez disso, o design está usando um método público para definir opções.
Mapas Multidimensionais
Você pode criar indexadores que usam vários argumentos. Além disso, esses argumentos não são restritos a serem do mesmo tipo.
O exemplo a seguir mostra uma classe que gera valores para um conjunto de Mandelbrot. Para mais informações sobre a matemática por trás do conjunto, leia este artigo. O indexador usa dois duplos para definir um ponto no plano X, Y. O get
acessador calcula o número de iterações até que um ponto seja determinado como não estando no conjunto. Quando o número máximo de iterações é atingido, o ponto está no conjunto e o valor maxIterations da classe é retornado. (As imagens geradas por computador popularizadas para o conjunto de Mandelbrot definem cores para o número de iterações necessárias para determinar que um ponto está fora do conjunto.)
namespace Indexers;
public class Mandelbrot(int maxIterations)
{
public int this[double x, double y]
{
get
{
var iterations = 0;
var x0 = x;
var y0 = y;
while ((x * x + y * y < 4) &&
(iterations < maxIterations))
{
(x, y) = (x * x - y * y + x0, 2 * x * y + y0);
iterations++;
}
return iterations;
}
}
}
O conjunto de Mandelbrot define valores em cada coordenada (x,y) para valores de números reais. Isso define um dicionário que pode conter um número infinito de valores. Portanto, não há armazenamento por trás do conjunto. Em vez disso, essa classe calcula o valor para cada ponto quando o código chama o get
acessador. Não há armazenamento subjacente usado.
Resumindo
Você cria indexadores sempre que tiver um elemento semelhante a uma propriedade em sua classe, onde essa propriedade representa não um único valor, mas sim um conjunto de valores. Um ou mais argumentos identificam cada item individual. Esses argumentos podem identificar exclusivamente qual item do conjunto deve ser referenciado. Os indexadores estendem o conceito de propriedades, onde um membro é tratado como um item de dados de fora da classe, mas como um método interno. Os indexadores permitem que os argumentos encontrem um único item em uma propriedade que representa um conjunto de itens.
Você pode acessar a pasta de exemplo para indexadores. Para obter instruções de download, consulte Exemplos e Tutoriais.
Especificação da linguagem C#
Para obter mais informações, consulte Indexadores na especificação da linguagem C#. A especificação da linguagem é a fonte definitiva para a sintaxe e o uso do C#.