Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Os membros virtuais estáticos da interface permitem definir interfaces que incluem operadores sobrecarregados ou outros membros estáticos. Depois de definir interfaces com membros estáticos, você poderá usar essas interfaces como restrições para criar tipos genéricos que usam operadores ou outros métodos estáticos. Mesmo que você não crie interfaces com operadores sobrecarregados, você provavelmente se beneficiará desse recurso e das classes matemáticas genéricas habilitadas pela atualização de idioma.
Neste tutorial, você aprenderá como:
- Definir interfaces com membros estáticos.
- Use interfaces para definir classes que implementam interfaces com operadores definidos.
- Crie algoritmos genéricos que dependem de métodos de interface estática.
Pré-requisitos
- O .NET SDK mais recente
- Editor do Visual Studio Code
- O DevKit C#
Métodos de interface abstrato estáticos
Vamos começar com um exemplo. O método a seguir retorna o ponto médio de dois double números:
public static double MidPoint(double left, double right) =>
(left + right) / (2.0);
A mesma lógica funcionaria para qualquer tipo numérico: int, , short, longfloatdecimalou qualquer tipo que represente um número. Você precisa ter um meio de usar os operadores + e /, e de definir um valor para 2. Você pode usar a System.Numerics.INumber<TSelf> interface para gravar o método anterior como o seguinte método genérico:
public static T MidPoint<T>(T left, T right)
where T : INumber<T> => (left + right) / T.CreateChecked(2); // note: the addition of left and right may overflow here; it's just for demonstration purposes
Qualquer tipo que implemente a INumber<TSelf> interface deve incluir uma definição para operator +, e para operator /. O denominador é definido por T.CreateChecked(2) para criar o valor 2 para qualquer tipo numérico, forçando o denominador a ser do mesmo tipo que os dois parâmetros.
INumberBase<TSelf>.CreateChecked<TOther>(TOther) cria uma instância do tipo do valor especificado e gera um OverflowException caso o valor fique fora do intervalo representável. (Essa implementação tem o potencial de estouro se left e right ambos são valores grandes o suficiente. Há algoritmos alternativos que podem evitar esse possível problema.)
Você define membros abstratos estáticos em uma interface usando a sintaxe familiar: você adiciona os modificadores static e abstract a qualquer membro estático que não forneça uma implementação. O exemplo a seguir define uma IGetNext<T> interface que pode ser aplicada a qualquer tipo que substitua operator ++:
public interface IGetNext<T> where T : IGetNext<T>
{
static abstract T operator ++(T other);
}
A restrição que o argumento de tipo T implementa IGetNext<T> garante que a assinatura do operador inclua o tipo contido ou seu argumento de tipo. Muitos operadores impõem que seus parâmetros devem corresponder ao tipo definido ou ser um parâmetro de tipo restrito a implementar o tipo que o contém. Sem essa restrição, o ++ operador não pôde ser definido na IGetNext<T> interface.
Você pode criar uma estrutura que cria uma cadeia de caracteres 'A' em que cada incremento adiciona outro caractere à cadeia de caracteres usando o seguinte código:
public struct RepeatSequence : IGetNext<RepeatSequence>
{
private const char Ch = 'A';
public string Text = new string(Ch, 1);
public RepeatSequence() {}
public static RepeatSequence operator ++(RepeatSequence other)
=> other with { Text = other.Text + Ch };
public override string ToString() => Text;
}
Em geral, você pode criar qualquer algoritmo em que queira definir ++ como "produzir o próximo valor desse tipo". O uso dessa interface produz código e resultados claros:
var str = new RepeatSequence();
for (int i = 0; i < 10; i++)
Console.WriteLine(str++);
O exemplo anterior produz a seguinte saída:
A
AA
AAA
AAAA
AAAAA
AAAAAA
AAAAAAA
AAAAAAAA
AAAAAAAAA
AAAAAAAAAA
Este pequeno exemplo demonstra a motivação para esse recurso. Você pode usar a sintaxe natural para operadores, valores constantes e outras operações estáticas. Você pode explorar essas técnicas ao criar vários tipos que dependem de membros estáticos, incluindo operadores sobrecarregados. Defina as interfaces que correspondem aos recursos de seus tipos e declare o suporte desses tipos para a nova interface.
Matemática genérica
O cenário motivador para permitir métodos estáticos, incluindo operadores, em interfaces é dar suporte a algoritmos matemáticos genéricos . A biblioteca de classes base do .NET 7 contém definições de interface para muitos operadores aritméticos e interfaces derivadas que combinam muitos operadores aritméticos em uma INumber<T> interface. Vamos aplicar esses tipos para criar um Point<T> registro que possa usar qualquer tipo numérico para T. Você pode mover o ponto usando os operadores XOffset e YOffset com o operador +.
Comece criando um novo aplicativo de Console usando dotnet new ou o Visual Studio.
A interface pública para o Translation<T> e Point<T> deveria ser como o seguinte código:
// Note: Not complete. This won't compile yet.
public record Translation<T>(T XOffset, T YOffset);
public record Point<T>(T X, T Y)
{
public static Point<T> operator +(Point<T> left, Translation<T> right);
}
Você usa o tipo record tanto para o tipo Translation<T> quanto para o tipo Point<T>: ambos armazenam dois valores e representam armazenamento de dados em vez de um comportamento sofisticado. A implementação de operator + ficaria como o seguinte código:
public static Point<T> operator +(Point<T> left, Translation<T> right) =>
left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };
Para que o código anterior seja compilado, você precisa declarar que T dá suporte à IAdditionOperators<TSelf, TOther, TResult> interface. Essa interface inclui o operator + método estático. Ele declara três parâmetros de tipo: um para o operando esquerdo, um para o operando à direita e outro para o resultado. Alguns tipos implementam + para diferentes tipos de operando e de resultado. Adicione uma declaração de que o argumento de tipo, T implementa IAdditionOperators<T, T, T>:
public record Point<T>(T X, T Y) where T : IAdditionOperators<T, T, T>
Depois de adicionar essa restrição, sua Point<T> classe poderá usar o + operador de adição. Adicione a mesma restrição à Translation<T> declaração:
public record Translation<T>(T XOffset, T YOffset) where T : IAdditionOperators<T, T, T>;
A IAdditionOperators<T, T, T> restrição impede que um desenvolvedor, ao usar sua classe, crie um Translation com um tipo que não satisfaça a restrição para a adição a um ponto. Você adicionou as restrições necessárias ao parâmetro de tipo para Translation<T> e Point<T>, de modo que esse código funcione. Você pode testar adicionando código como o seguinte acima das declarações de Translation e Point em seu arquivo de Program.cs :
var pt = new Point<int>(3, 4);
var translate = new Translation<int>(5, 10);
var final = pt + translate;
Console.WriteLine(pt);
Console.WriteLine(translate);
Console.WriteLine(final);
Você pode tornar esse código mais reutilizável declarando que esses tipos implementam as interfaces aritméticas apropriadas. A primeira alteração a ser executada é declarar que Point<T, T> implementa a IAdditionOperators<Point<T>, Translation<T>, Point<T>> interface. O Point tipo usa tipos diferentes para operandos e o resultado. O Point tipo já implementa um operator + com essa assinatura, portanto, adicionar a interface à declaração é tudo o que você precisa:
public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
where T : IAdditionOperators<T, T, T>
Por fim, ao executar a adição, é útil ter uma propriedade que defina o valor de identidade aditiva para esse tipo. Há uma nova interface para esse recurso: IAdditiveIdentity<TSelf,TResult>. A tradução de {0, 0} é a identidade aditiva: o ponto resultante é o mesmo que o operando à esquerda. A IAdditiveIdentity<TSelf, TResult> interface define uma propriedade somente leitura, AdditiveIdentity que retorna o valor da identidade. O Translation<T> precisa de algumas mudanças para implementar essa interface:
using System.Numerics;
public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
public static Translation<T> AdditiveIdentity =>
new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);
}
Há algumas mudanças aqui, então vamos percorrê-las uma a uma. Primeiro, você declara que o Translation tipo implementa a IAdditiveIdentity interface:
public record Translation<T>(T XOffset, T YOffset) : IAdditiveIdentity<Translation<T>, Translation<T>>
Em seguida, você pode tentar implementar o membro da interface, conforme mostrado no seguinte código:
public static Translation<T> AdditiveIdentity =>
new Translation<T>(XOffset: 0, YOffset: 0);
O código anterior não é compilado, pois 0 depende do tipo. A resposta: Use IAdditiveIdentity<T>.AdditiveIdentity para 0. Suas restrições agora devem incluir que T implemente IAdditiveIdentity<T>. Isso resulta na seguinte implementação:
public static Translation<T> AdditiveIdentity =>
new Translation<T>(XOffset: T.AdditiveIdentity, YOffset: T.AdditiveIdentity);
Agora que você adicionou essa restrição Translation<T>, você precisa adicionar a mesma restrição a Point<T>:
using System.Numerics;
public record Point<T>(T X, T Y) : IAdditionOperators<Point<T>, Translation<T>, Point<T>>
where T : IAdditionOperators<T, T, T>, IAdditiveIdentity<T, T>
{
public static Point<T> operator +(Point<T> left, Translation<T> right) =>
left with { X = left.X + right.XOffset, Y = left.Y + right.YOffset };
}
Este exemplo ofereceu uma visão de como as interfaces de matemática genérica se compõem. Você aprendeu a:
- Escreva um método que dependesse da interface
INumber<T>para que esse método pudesse ser usado com qualquer tipo numérico. - Crie um tipo que dependa das interfaces de adição para implementar um tipo que dê suporte apenas a uma operação matemática. Esse tipo declara seu suporte para essas mesmas interfaces para que ela possa ser composta de outras maneiras. Os algoritmos são escritos usando a sintaxe mais natural dos operadores matemáticos.
Experimente esses recursos e registre comentários. Você pode usar o item de menu Enviar Comentários no Visual Studio ou criar um novo problema no repositório roslyn no GitHub. Crie algoritmos genéricos que funcionam com qualquer tipo numérico. Crie algoritmos usando essas interfaces em que o argumento de tipo implementa apenas um subconjunto de recursos semelhantes a números. Mesmo que você não crie novas interfaces que usem essas funcionalidades, você poderá experimentá-las usando-as em seus algoritmos.