Alguém pode me dizer se existe uma maneira com os genéricos limitar apenas um argumento de tipo genérico T
:
Int16
Int32
Int64
UInt16
UInt32
UInt64
Conheço a where
palavra - chave, mas não consigo encontrar uma interface apenas para esses tipos,
Algo como:
static bool IntegerFunction<T>(T value) where T : INumeric
c#
generics
constraints
Corin Blaikie
fonte
fonte
Respostas:
C # não suporta isso. Hejlsberg descreveu os motivos para não implementar o recurso em uma entrevista com Bruce Eckel :
No entanto, isso leva a um código bastante complicado, em que o usuário precisa fornecer sua própria
Calculator<T>
implementação, para cada umT
que deseja usar. Contanto que ele não precise ser extensível, ou seja, se você quiser apenas suportar um número fixo de tipos, comoint
edouble
, poderá usar uma interface relativamente simples:( Implementação mínima em um GitHub Gist. )
No entanto, assim que você desejar que o usuário possa fornecer seus próprios tipos personalizados, é necessário abrir essa implementação para que o usuário possa fornecer suas próprias
Calculator
instâncias. Por exemplo, para instanciar uma matriz que usa uma implementação de ponto flutuante decimal personalizadaDFP
, você teria que escrever este código:… E implemente todos os membros para
DfpCalculator : ICalculator<DFP>
.Uma alternativa, que infelizmente compartilha as mesmas limitações, é trabalhar com classes de política, conforme discutido na resposta de Sergey Shandar .
fonte
Operator
/Operator<T>
; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.htmlOperator<T>
código (desde que a entrevista foi dada muito antes da existência daExpressions
estrutura, mesmo que alguém pudesse uso do cursoReflection.Emit
) - e eu estaria realmente interessado em sua solução alternativa.Considerando a popularidade desta pergunta e o interesse por trás dessa função, fico surpreso ao ver que ainda não há resposta envolvendo o T4.
Neste código de exemplo, demonstrarei um exemplo muito simples de como você pode usar o poderoso mecanismo de modelagem para fazer o que o compilador praticamente faz nos bastidores com genéricos.
Em vez de passar por obstáculos e sacrificar a certeza no tempo de compilação, você pode simplesmente gerar a função desejada para cada tipo que você gosta e usá-la adequadamente (no momento da compilação!).
Para fazer isso:
É isso aí. Você terminou agora.
Salvar este arquivo o compilará automaticamente neste arquivo de origem:
No seu
main
método, você pode verificar se possui certeza no tempo de compilação:Vou chegar à frente de uma observação: não, isso não é uma violação do princípio DRY. O princípio DRY existe para impedir que as pessoas dupliquem o código em vários locais, o que tornaria difícil a manutenção do aplicativo.
Este não é o caso aqui: se você deseja alterar, basta alterar o modelo (uma única fonte para toda a sua geração!) E pronto.
Para usá-lo com suas próprias definições personalizadas, adicione uma declaração de namespace (verifique se é a mesma em que você definirá sua própria implementação) ao código gerado e marque a classe como
partial
. Depois, adicione estas linhas ao seu arquivo de modelo para que ele seja incluído na eventual compilação:Sejamos honestos: isso é bem legal.
Isenção de responsabilidade: este exemplo foi fortemente influenciado pela metaprogramação no .NET por Kevin Hazzard e Jason Bock, Manning Publications .
fonte
T
que seja ou herda das váriasIntX
classes? Gosto dessa solução porque economiza tempo, mas, para resolver 100% do problema (apesar de não ser tão bom como se o C # tivesse suporte para esse tipo de restrição, interno), cada um dos métodos gerados ainda deve ser genérico, para que eles podem retornar um objeto de um tipo que herda de uma dasIntXX
classes.IntXX
tipos são estruturas, o que significa que não suportam herança em primeiro lugar . E mesmo que isso aconteça, aplica-se o princípio de substituição de Liskov (que você pode conhecer pelo idioma SOLID): se o método for definido comoX
eY
é um filho deleX
, por definição, qualquer pessoaY
poderá passar para esse método como um substituto de seu tipo de base.Não há restrição para isso. É um problema real para quem deseja usar genéricos para cálculos numéricos.
Eu iria além e diria que precisamos
Ou até
Infelizmente, você só possui interfaces, classes base e as palavras-chave
struct
(deve ser do tipo valor),class
(deve ser do tipo referência) enew()
(deve ter o construtor padrão)Você pode agrupar o número em outra coisa (semelhante a
INullable<T>
) como aqui no projeto de código .Você pode aplicar a restrição no tempo de execução (refletindo para os operadores ou verificando tipos), mas isso perde a vantagem de ter o genérico em primeiro lugar.
fonte
where T : operators( +, -, /, * )
é legal c #? Desculpem esta questão de novato.where T : operators( +, -, /, * )
, mas não podemos.Solução alternativa usando políticas:
Algoritmos:
Uso:
A solução é segura em tempo de compilação. O CityLizard Framework fornece versão compilada para o .NET 4.0. O arquivo é lib / NETFramework4.0 / CityLizard.Policy.dll.
Também está disponível no Nuget: https://www.nuget.org/packages/CityLizard/ . Consulte a estrutura CityLizard.Policy.I .
fonte
struct
? e se eu usar a classe singleton e alterar a instância parapublic static NumericPolicies Instance = new NumericPolicies();
e adicionar esse construtorprivate NumericPolicies() { }
.T Add<T> (T t1, T t2)
, masSum()
só funciona quando ele pode recuperar seu próprio tipo de T a partir de seus parâmetros, o que não é possível quando incorporado em outra função genérica.Esta pergunta é um pouco frequente, então eu estou postando isso como wiki (desde que eu postei algo semelhante antes, mas esse é mais antigo); de qualquer forma...
Qual versão do .NET você está usando? Se você estiver usando o .NET 3.5, eu tenho uma implementação de operadores genéricos no MiscUtil (gratuito etc).
Isso tem métodos como
T Add<T>(T x, T y)
e outras variantes para aritmética em diferentes tipos (comoDateTime + TimeSpan
).Além disso, isso funciona para todos os operadores embutidos, levantados e sob medida, e armazena em cache o delegado para desempenho.
Alguns antecedentes adicionais sobre por que isso é complicado está aqui .
Você também pode querer saber que
dynamic
(4.0) também resolve esse problema indiretamente - isto é,fonte
Infelizmente, você só pode especificar struct na cláusula where nesta instância. Parece estranho que você não possa especificar Int16, Int32, etc. especificamente, mas tenho certeza de que há algum motivo profundo de implementação subjacente à decisão de não permitir tipos de valor em uma cláusula where.
Eu acho que a única solução é fazer uma verificação de tempo de execução que, infelizmente, impede que o problema seja detectado no momento da compilação. Isso seria algo como: -
O que é um pouco feio, eu sei, mas pelo menos fornece as restrições necessárias.
Também examinaria possíveis implicações de desempenho para essa implementação, talvez haja uma maneira mais rápida.
fonte
// Rest of code...
pode não ser compilado se depender das operações definidas pelas restrições.// Rest of code...
como ,value + value
ouvalue * value
, você tem um erro de compilação.Provavelmente, o mais próximo que você pode fazer é
Não tenho certeza se você poderia fazer o seguinte
Para algo tão específico, por que não apenas sobrecargas para cada tipo, a lista é tão curta e possivelmente teria menos espaço na memória.
fonte
A partir do C # 7.3, você pode usar uma aproximação mais próxima - a restrição não gerenciada para especificar que um parâmetro de tipo é um tipo não gerenciado sem ponteiro e não nulo .
A restrição não gerenciada implica a restrição struct e não pode ser combinada com as restrições struct ou new ().
Um tipo é um tipo não gerenciado, se for um dos seguintes tipos:
Para restringir ainda mais e eliminar os tipos de ponteiro e definidos pelo usuário que não implementam IComparable, adicione IComparable (mas o enum ainda é derivado de IComparable, portanto, restrinja o enum adicionando IEquatable <T>, você poderá ir mais longe, dependendo das circunstâncias e adicionar interfaces adicionais. não gerenciado permite manter esta lista mais curta):
fonte
DateTime
cai sobunmanaged, IComparable, IEquatable<T>
restrição ..Não há como restringir modelos a tipos, mas você pode definir ações diferentes com base no tipo. Como parte de um pacote numérico genérico, eu precisava de uma classe genérica para adicionar dois valores.
Observe que os typeofs são avaliados em tempo de compilação, portanto, as instruções if seriam removidas pelo compilador. O compilador também remove lançamentos espúrios. Então, algo resolveria no compilador para
fonte
Criei uma pequena funcionalidade de biblioteca para resolver esses problemas:
Ao invés de:
Você pode escrever:
Você pode encontrar o código fonte aqui: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number
fonte
Eu queria saber o mesmo que samjudson, por que apenas para números inteiros? e se for esse o caso, convém criar uma classe auxiliar ou algo assim para armazenar todos os tipos que você deseja.
Se tudo o que você deseja são números inteiros, não use um genérico, que não é genérico; ou, melhor ainda, rejeite qualquer outro tipo, verificando seu tipo.
fonte
Ainda não existe uma solução 'boa' para isso. No entanto, você pode restringir significativamente o argumento de tipo para descartar muitos erros de interpretação para sua restrição hipotética 'INumeric', como o Haacked mostrou acima.
static bool IntegerFunction <T> (valor T) em que T: IComparable, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...
fonte
Se você estiver usando o .NET 4.0 e posterior, basta usar dinâmico como argumento de método e verificar, em tempo de execução, se a dinâmica passada tipo de argumento é do tipo numérico / inteiro.
Se o tipo da dinâmica passada não for numérico tipo / inteiro em seguida, jogá exceção.
Um exemplo de código curto que implementa a ideia é algo como:
É claro que essa solução funciona apenas em tempo de execução, mas nunca em tempo de compilação.
Se você deseja uma solução que sempre funcione em tempo de compilação e nunca em tempo de execução, será necessário agrupar a dinâmica com uma estrutura / classe pública cujos construtores públicos sobrecarregados aceitam argumentos apenas dos tipos desejados e forneçam o nome apropriado à estrutura / classe.
Faz sentido que a dinâmica agrupada seja sempre privada membro da classe / estrutura e seja o único membro da estrutura / classe e o nome do único membro da estrutura / classe seja "valor".
Você também terá que definir e implementar métodos públicos e / ou operadores que funcionem com os tipos desejados para o membro dinâmico privado da classe / estrutura, se necessário.
Também faz sentido que a struct / classe tenha um construtor especial / exclusivo que aceite dinâmico como argumento que inicializa seu único membro dinâmico privado chamado "valor", mas o modificador desse construtor é privado, é claro.
Quando a classe / estrutura estiver pronta, defina o tipo de função Integer do argumento para ser a classe / estrutura que foi definida.
Um exemplo de código longo que implementa a ideia é algo como:
Observe que, para usar dinâmico em seu código, você deve adicionar referência ao Microsoft.CSharp
Se a versão do .NET framework estiver abaixo / abaixo / menor que 4.0 e dinâmico não for definido nessa versão, será necessário usar o objeto e fazer a conversão para o tipo inteiro, o que é um problema, por isso recomendo que você use em pelo menos .NET 4.0 ou mais recente, se você puder, para usar dinâmico em vez de objeto .
fonte
Infelizmente, o .NET não fornece uma maneira de fazer isso nativamente.
Para solucionar esse problema, criei a biblioteca OSS Genumerics, que fornece a maioria das operações numéricas padrão para os seguintes tipos numéricos integrados e seus equivalentes nulos, com a capacidade de adicionar suporte para outros tipos numéricos.
sbyte
,byte
,short
,ushort
,int
,uint
,long
,ulong
,float
,double
,decimal
, EBigInteger
O desempenho é equivalente a uma solução específica do tipo numérico, permitindo criar algoritmos numéricos genéricos eficientes.
Aqui está um exemplo do uso do código.
fonte
Qual é o objetivo do exercício?
Como as pessoas já apontaram, você pode ter uma função não genérica que pega o item maior e o compilador converterá automaticamente ints menores para você.
Se sua função estiver no caminho crítico de desempenho (IMO muito improvável), você poderá fornecer sobrecargas para todas as funções necessárias.
fonte
Eu usaria um genérico que você poderia lidar externamente ...
fonte
Essa limitação me afetou quando tentei sobrecarregar os operadores para tipos genéricos; como não havia restrição "INUMÉRICA" e por várias outras razões pelas quais as pessoas boas no stackoverflow têm prazer em fornecer, as operações não podem ser definidas em tipos genéricos.
Eu queria algo como
Eu resolvi esse problema usando a digitação em tempo de execução dinâmico .net4.
As duas coisas sobre o uso
dynamic
sãofonte
Os tipos numéricos .NET primitivos não compartilham nenhuma interface comum que permita que eles sejam usados para cálculos. Seria possível definir suas próprias interfaces (por exemplo
ISignedWholeNumber
), que iria realizar tais operações, definem as estruturas que contêm um únicoInt16
,Int32
etc. e implementar essas interfaces, e depois ter métodos que aceitam tipos genéricos constrangidos aISignedWholeNumber
, mas ter que converter valores numéricos para seus tipos de estrutura provavelmente seria um incômodo.Uma abordagem alternativa seria definir classe estática
Int64Converter<T>
com uma propriedade estáticabool Available {get;};
e delegados estáticos paraInt64 GetInt64(T value)
,T FromInt64(Int64 value)
,bool TryStoreInt64(Int64 value, ref T dest)
. O construtor da classe pode ser codificado para carregar delegados para tipos conhecidos e, possivelmente, usar o Reflection para testar se o tipoT
implementa métodos com nomes e assinaturas apropriados (no caso, é algo como uma estrutura que contém umInt64
e representa um número, mas possui umToString()
método personalizado ). Essa abordagem perderia as vantagens associadas à verificação de tipo em tempo de compilação, mas ainda assim conseguiria evitar operações de boxe e cada tipo teria que ser "verificado" apenas uma vez. Depois disso, as operações associadas a esse tipo seriam substituídas por um envio de delegado.fonte
Int64
resultado, mas não fornece um meio pelo qual, por exemplo, um número inteiro de tipo arbitrário pode ser incrementado para gerar outro número inteiro do mesmo tipo .Eu tive uma situação semelhante em que precisava lidar com tipos e seqüências numéricas; parece um pouco bizarro, mas lá vai você.
Mais uma vez, como muitas pessoas, observei as restrições e criei várias interfaces que elas tinham que suportar. No entanto, a) não era 100% estanque eb), qualquer pessoa nova que visse essa longa lista de restrições ficaria imediatamente muito confusa.
Portanto, minha abordagem foi colocar toda a minha lógica em um método genérico sem restrições, mas tornar esse método genérico privado. Eu então o expus com métodos públicos, um deles explicitamente manipulando o tipo que eu queria manipular - na minha opinião, o código é limpo e explícito, por exemplo
fonte
Se tudo o que você deseja é usar um tipo numérico , considere criar algo semelhante a um alias no C ++ com
using
.Então, em vez de ter o muito genérico
você pode ter
Isso pode permitir que você vá facilmente de ou
double
paraint
outros, se necessário, mas não poderá usarComputeSomething
comdouble
eint
no mesmo programa.Mas por que não substituir tudo
double
paraint
então? Porque seu método pode querer usar adouble
se a entrada édouble
ouint
. O alias permite que você saiba exatamente qual variável usa o tipo dinâmico .fonte
O tópico é antigo, mas para futuros leitores:
Esse recurso está intimamente relacionado ao
Discriminated Unions
qual não está implementado em C # até o momento. Encontrei seu problema aqui:https://github.com/dotnet/csharplang/issues/113
Esse problema ainda está aberto e o recurso foi planejado para
C# 10
Portanto, ainda temos que esperar um pouco mais, mas, após a liberação, você pode fazer o seguinte:
fonte
Eu acho que você está entendendo mal genéricos. Se a operação que você está tentando executar é boa apenas para tipos de dados específicos, você não está fazendo algo "genérico".
Além disso, como você deseja permitir que a função funcione nos tipos de dados int, não será necessário uma função separada para cada tamanho específico. Simplesmente usar um parâmetro no maior tipo específico permitirá ao programa elevar automaticamente os tipos de dados menores para ele. (ou seja, a passagem de um Int16 será convertida automaticamente em Int64 ao chamar).
Se você estiver executando operações diferentes com base no tamanho real de int sendo passado para a função, acho que você deveria reconsiderar seriamente mesmo tentando fazer o que está fazendo. Se você precisa enganar a linguagem, deve pensar um pouco mais sobre o que está tentando realizar, em vez de como fazer o que deseja.
Caso contrário, um parâmetro do tipo Objeto poderá ser usado e, em seguida, você deverá verificar o tipo do parâmetro e executar a ação apropriada ou lançar uma exceção.
fonte