Existe uma restrição que restringe meu método genérico a tipos numéricos?

364

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 wherepalavra - chave, mas não consigo encontrar uma interface apenas para esses tipos,

Algo como:

static bool IntegerFunction<T>(T value) where T : INumeric 
Corin Blaikie
fonte
2
Atualmente, existem várias propostas de C # que permitiriam isso, mas o AFAIK nenhuma delas está além de explorações / discussões preliminares. Consulte Exploração: formas e extensões , Exploração: funções, interfaces de extensão e membros de interface estática , Champion "Classes de tipo (também conhecidas como conceitos, restrições genéricas estruturais)" e Proposta: Tipos genéricos devem oferecer suporte a operadores
Chris Yungmann

Respostas:

140

C # não suporta isso. Hejlsberg descreveu os motivos para não implementar o recurso em uma entrevista com Bruce Eckel :

E não está claro que a complexidade adicionada valha o pequeno rendimento que você obtém. Se algo que você deseja fazer não é diretamente suportado no sistema de restrição, você pode fazê-lo com um padrão de fábrica. Você poderia ter um Matrix<T>, por exemplo, e Matrixgostaria de definir um método de produto com pontos. Isso, claro, isso significa que você finalmente precisa entender como multiplicar dois Ts, mas você não pode dizer que como um constrangimento, pelo menos não se Té int, doubleou float. Mas o que você poderia fazer é ter sua Matrixtomada como um argumento a Calculator<T>, e em Calculator<T>, tem um método chamado multiply. Você vai implementar isso e passa para o arquivo Matrix.

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 um Tque deseja usar. Contanto que ele não precise ser extensível, ou seja, se você quiser apenas suportar um número fixo de tipos, como inte double, poderá usar uma interface relativamente simples:

var mat = new Matrix<int>(w, h);

( 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 Calculatorinstâncias. Por exemplo, para instanciar uma matriz que usa uma implementação de ponto flutuante decimal personalizada DFP, você teria que escrever este código:

var mat = new Matrix<DFP>(DfpCalculator.Instance, w, h);

… 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 .

Konrad Rudolph
fonte
25
btw, MiscUtil fornece uma classe genérica que faz exatamente isso; Operator/ Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell
11
@ Mark: bom comentário. No entanto, só para esclarecer, não acho que Hejlsberg estava se referindo à geração de código como uma solução para o problema, como você faz no Operator<T>código (desde que a entrevista foi dada muito antes da existência da Expressionsestrutura, mesmo que alguém pudesse uso do curso Reflection.Emit) - e eu estaria realmente interessado em sua solução alternativa.
219 Konrad Rudolph
@ Konrad Rudolph: Penso que esta resposta a uma pergunta semelhante explica a solução alternativa de Hejlsberg. A outra classe genérica é feita abstrata. Como requer que você implemente a outra classe genérica para cada tipo que você deseja oferecer suporte, isso resultará em código duplicado, mas significa que você só pode instanciar a classe genérica original com um tipo suportado.
Ergwun
14
Eu não concordo com a frase de Heijsberg "Então, em certo sentido, os modelos C ++ são realmente sem tipo ou vagamente digitados. Enquanto os genéricos C # são fortemente digitados". Isso é realmente Marketing BS para promover C #. A digitação forte / fraca não tem a ver com a qualidade dos diagnósticos. Caso contrário: descoberta interessante.
Sebastian Mach
100

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:

  • Crie um novo arquivo de modelo de texto chamado GenericNumberMethodTemplate.tt .
  • Remova o código gerado automaticamente (você manterá a maioria, mas alguns não são necessários).
  • Adicione o seguinte trecho:
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>

<# Type[] types = new[] {
    typeof(Int16), typeof(Int32), typeof(Int64),
    typeof(UInt16), typeof(UInt32), typeof(UInt64)
    };
#>

using System;
public static class MaxMath {
    <# foreach (var type in types) { 
    #>
        public static <#= type.Name #> Max (<#= type.Name #> val1, <#= type.Name #> val2) {
            return val1 > val2 ? val1 : val2;
        }
    <#
    } #>
}

É isso aí. Você terminou agora.

Salvar este arquivo o compilará automaticamente neste arquivo de origem:

using System;
public static class MaxMath {
    public static Int16 Max (Int16 val1, Int16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int32 Max (Int32 val1, Int32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int64 Max (Int64 val1, Int64 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt16 Max (UInt16 val1, UInt16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt32 Max (UInt32 val1, UInt32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt64 Max (UInt64 val1, UInt64 val2) {
        return val1 > val2 ? val1 : val2;
    }
}

No seu mainmétodo, você pode verificar se possui certeza no tempo de compilação:

namespace TTTTTest
{
    class Program
    {
        static void Main(string[] args)
        {
            long val1 = 5L;
            long val2 = 10L;
            Console.WriteLine(MaxMath.Max(val1, val2));
            Console.Read();
        }
    }
}

insira a descrição da imagem aqui

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:

<#@ import namespace="TheNameSpaceYouWillUse" #>
<#@ assembly name="$(TargetPath)" #>

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 .

Jeroen Vannevel
fonte
Isso é bem legal, mas seria possível modificar essa solução para fazer com que os métodos aceitem algum tipo genérico Tque seja ou herda das várias IntXclasses? 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 das IntXXclasses.
Zachary Kniebel
11
@ZacharyKniebel: os IntXXtipos 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 como Xe Yé um filho dele X, por definição, qualquer pessoa Ypoderá passar para esse método como um substituto de seu tipo de base.
Jeroen Vannevel
11
Esta solução alternativa usando políticas stackoverflow.com/questions/32664/… usa T4 para gerar classes.
Sergey Shandar
2
+1 para esta solução, pois preserva a eficiência operacional dos tipos integrais integrados, diferentemente das soluções baseadas em políticas. Chamar operadores CLR internos (como Adicionar) por meio de um método adicional (possivelmente virtual) pode afetar seriamente o desempenho se usado muitas vezes (como em bibliotecas matemáticas). E como o número de tipos integrais é constante (e não pode ser herdado de), você só precisa gerar novamente o código para correção de erros.
Attila Klenik,
11
Muito legal e eu estava prestes a começar a usá-lo, lembrei-me de como sou dependente do Resharper para refatoração e você não pode renomear refatorador através do modelo T4. Não é crítico, mas vale a pena considerar.
Bradgonesurfing
86

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

static bool GenericFunction<T>(T value) 
    where T : operators( +, -, /, * )

Ou até

static bool GenericFunction<T>(T value) 
    where T : Add, Subtract

Infelizmente, você só possui interfaces, classes base e as palavras-chave struct(deve ser do tipo valor), class(deve ser do tipo referência) e new()(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.

Keith
fonte
2
Eu me pergunto se você já viu o apoio da MiscUtil para os operadores genéricos ... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell
10
Sim - Jon Skeet me apontou para eles há mais algum tempo (mas após a resposta deste ano) - eles são uma ideia inteligente, mas eu ainda gostaria do apoio adequado das restrições.
1311 Keith
11
Espere, where T : operators( +, -, /, * )é legal c #? Desculpem esta questão de novato.
Kdbanman
@kdbanman Acho que não. Keith está dizendo que o C # não suporta o que o OP está perguntando e está sugerindo que devemos ser capazes de fazer where T : operators( +, -, /, * ), mas não podemos.
AMTerp
62

Solução alternativa usando políticas:

interface INumericPolicy<T>
{
    T Zero();
    T Add(T a, T b);
    // add more functions here, such as multiplication etc.
}

struct NumericPolicies:
    INumericPolicy<int>,
    INumericPolicy<long>
    // add more INumericPolicy<> for different numeric types.
{
    int INumericPolicy<int>.Zero() { return 0; }
    long INumericPolicy<long>.Zero() { return 0; }
    int INumericPolicy<int>.Add(int a, int b) { return a + b; }
    long INumericPolicy<long>.Add(long a, long b) { return a + b; }
    // implement all functions from INumericPolicy<> interfaces.

    public static NumericPolicies Instance = new NumericPolicies();
}

Algoritmos:

static class Algorithms
{
    public static T Sum<P, T>(this P p, params T[] a)
        where P: INumericPolicy<T>
    {
        var r = p.Zero();
        foreach(var i in a)
        {
            r = p.Add(r, i);
        }
        return r;
    }

}

Uso:

int i = NumericPolicies.Instance.Sum(1, 2, 3, 4, 5);
long l = NumericPolicies.Instance.Sum(1L, 2, 3, 4, 5);
NumericPolicies.Instance.Sum("www", "") // compile-time error.

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 .

Sergey Shandar
fonte
Eu tive problemas com esse padrão quando há menos argumentos de função que parâmetros genéricos. Aberto stackoverflow.com/questions/36048248/…
xvan
alguma razão para usar struct? e se eu usar a classe singleton e alterar a instância para public static NumericPolicies Instance = new NumericPolicies();e adicionar esse construtor private NumericPolicies() { }.
M.kazem Akhgary
@ M.kazemAkhgary você pode usar o singleton. Eu prefiro struct. Em teoria, ele pode ser otimizado pelo compilador / CLR porque a estrutura não contém informações. No caso de singleton, você ainda passará uma referência, o que pode adicionar pressão adicional ao GC. Outra vantagem é que struct não pode ser nulo :-).
Sergey Shandar
Eu diria que você encontrou uma solução muito inteligente, mas a solução é muito limitada para mim: eu ia usá-la T Add<T> (T t1, T t2), mas Sum()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.
Tobias Knauss
16

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 (como DateTime + 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 é,

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
Marc Gravell
fonte
14

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: -

static bool IntegerFunction<T>(T value) where T : struct {
  if (typeof(T) != typeof(Int16)  &&
      typeof(T) != typeof(Int32)  &&
      typeof(T) != typeof(Int64)  &&
      typeof(T) != typeof(UInt16) &&
      typeof(T) != typeof(UInt32) &&
      typeof(T) != typeof(UInt64)) {
    throw new ArgumentException(
      string.Format("Type '{0}' is not valid.", typeof(T).ToString()));
  }

  // Rest of code...
}

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.

ljs
fonte
13
+1, no entanto, // Rest of code...pode não ser compilado se depender das operações definidas pelas restrições.
Nick
11
Convert.ToIntXX (value) pode ajudar a compilar "// Rest of code" - pelo menos até que o tipo de retorno de IntegerFunction também seja do tipo T, e você será processado. :-p
yoyo
-1; isso não funciona pelo motivo indicado pelo @Nick. No momento em que você tenta executar operações aritméticas // Rest of code...como , value + valueou value * value, você tem um erro de compilação.
precisa
13

Provavelmente, o mais próximo que você pode fazer é

static bool IntegerFunction<T>(T value) where T: struct

Não tenho certeza se você poderia fazer o seguinte

static bool IntegerFunction<T>(T value) where T: struct, IComparable
, IFormattable, IConvertible, IComparable<T>, IEquatable<T>

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.

Haacked
fonte
6

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 .

class SomeGeneric<T> where T : unmanaged
{
//...
}

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:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal ou bool
  • Qualquer tipo de enumeração
  • Qualquer tipo de ponteiro
  • Qualquer tipo de estrutura definido pelo usuário que contém apenas campos de tipos não gerenciados e, no C # 7.3 e anterior, não é um tipo construído (um tipo que inclui pelo menos um argumento de tipo)

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):

    class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
    {
    //...
    }
Vlad Novakovsky
fonte
Bom, mas não o suficiente ... Por exemplo, DateTimecai sob unmanaged, IComparable, IEquatable<T>restrição ..
Adam Calvet Bohl 23/04
Eu sei, mas você pode ir mais longe, dependendo das circunstâncias e adicionar interfaces adicionais. não gerenciado permite manter essa lista mais curta. Acabei de mostrar a abordagem, aproximação usando não gerenciado. Para a maioria dos casos, isso é suficiente
Vlad Novakovsky
4

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.

    class Something<TCell>
    {
        internal static TCell Sum(TCell first, TCell second)
        {
            if (typeof(TCell) == typeof(int))
                return (TCell)((object)(((int)((object)first)) + ((int)((object)second))));

            if (typeof(TCell) == typeof(double))
                return (TCell)((object)(((double)((object)first)) + ((double)((object)second))));

            return second;
        }
    }

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

        internal static int Sum(int first, int second)
        {
            return first + second;
        }
Rob Deary
fonte
Obrigado por fornecer uma solução empírica!
zsf222
Não é o mesmo que criar o mesmo método para cada tipo?
Luis
3

Criei uma pequena funcionalidade de biblioteca para resolver esses problemas:

Ao invés de:

public T DifficultCalculation<T>(T a, T b)
{
    T result = a * b + a; // <== WILL NOT COMPILE!
    return result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Should result in 8.

Você pode escrever:

public T DifficultCalculation<T>(Number<T> a, Number<T> b)
{
    Number<T> result = a * b + a;
    return (T)result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.

Você pode encontrar o código fonte aqui: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number

Martin Mulder
fonte
2

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.

Martin Marconcini
fonte
2

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 {...

dmihailescu
fonte
2

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:

using System;
public class InvalidArgumentException : Exception
{
    public InvalidArgumentException(string message) : base(message) {}
}
public class InvalidArgumentTypeException : InvalidArgumentException
{
    public InvalidArgumentTypeException(string message) : base(message) {}
}
public class ArgumentTypeNotIntegerException : InvalidArgumentTypeException
{
    public ArgumentTypeNotIntegerException(string message) : base(message) {}
}
public static class Program
{
    private static bool IntegerFunction(dynamic n)
    {
        if (n.GetType() != typeof(Int16) &&
            n.GetType() != typeof(Int32) &&
            n.GetType() != typeof(Int64) &&
            n.GetType() != typeof(UInt16) &&
            n.GetType() != typeof(UInt32) &&
            n.GetType() != typeof(UInt64))
            throw new ArgumentTypeNotIntegerException("argument type is not integer type");
        //code that implements IntegerFunction goes here
    }
    private static void Main()
    {
         Console.WriteLine("{0}",IntegerFunction(0)); //Compiles, no run time error and first line of output buffer is either "True" or "False" depends on the code that implements "Program.IntegerFunction" static method.
         Console.WriteLine("{0}",IntegerFunction("string")); //Also compiles but it is run time error and exception of type "ArgumentTypeNotIntegerException" is thrown here.
         Console.WriteLine("This is the last Console.WriteLine output"); //Never reached and executed due the run time error and the exception thrown on the second line of Program.Main static method.
    }

É 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:

using System;
public struct Integer
{
    private dynamic value;
    private Integer(dynamic n) { this.value = n; }
    public Integer(Int16 n) { this.value = n; }
    public Integer(Int32 n) { this.value = n; }
    public Integer(Int64 n) { this.value = n; }
    public Integer(UInt16 n) { this.value = n; }
    public Integer(UInt32 n) { this.value = n; }
    public Integer(UInt64 n) { this.value = n; }
    public Integer(Integer n) { this.value = n.value; }
    public static implicit operator Int16(Integer n) { return n.value; }
    public static implicit operator Int32(Integer n) { return n.value; }
    public static implicit operator Int64(Integer n) { return n.value; }
    public static implicit operator UInt16(Integer n) { return n.value; }
    public static implicit operator UInt32(Integer n) { return n.value; }
    public static implicit operator UInt64(Integer n) { return n.value; }
    public static Integer operator +(Integer x, Int16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int64 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt64 y) { return new Integer(x.value + y); }
    public static Integer operator -(Integer x, Int16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int64 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt64 y) { return new Integer(x.value - y); }
    public static Integer operator *(Integer x, Int16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int64 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt64 y) { return new Integer(x.value * y); }
    public static Integer operator /(Integer x, Int16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int64 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt64 y) { return new Integer(x.value / y); }
    public static Integer operator %(Integer x, Int16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int64 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt64 y) { return new Integer(x.value % y); }
    public static Integer operator +(Integer x, Integer y) { return new Integer(x.value + y.value); }
    public static Integer operator -(Integer x, Integer y) { return new Integer(x.value - y.value); }
    public static Integer operator *(Integer x, Integer y) { return new Integer(x.value * y.value); }
    public static Integer operator /(Integer x, Integer y) { return new Integer(x.value / y.value); }
    public static Integer operator %(Integer x, Integer y) { return new Integer(x.value % y.value); }
    public static bool operator ==(Integer x, Int16 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int16 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int32 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int32 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int64 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int64 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt16 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt16 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt32 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt32 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt64 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt64 y) { return x.value != y; }
    public static bool operator ==(Integer x, Integer y) { return x.value == y.value; }
    public static bool operator !=(Integer x, Integer y) { return x.value != y.value; }
    public override bool Equals(object obj) { return this == (Integer)obj; }
    public override int GetHashCode() { return this.value.GetHashCode(); }
    public override string ToString() { return this.value.ToString(); }
    public static bool operator >(Integer x, Int16 y) { return x.value > y; }
    public static bool operator <(Integer x, Int16 y) { return x.value < y; }
    public static bool operator >(Integer x, Int32 y) { return x.value > y; }
    public static bool operator <(Integer x, Int32 y) { return x.value < y; }
    public static bool operator >(Integer x, Int64 y) { return x.value > y; }
    public static bool operator <(Integer x, Int64 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt16 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt16 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt32 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt32 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt64 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt64 y) { return x.value < y; }
    public static bool operator >(Integer x, Integer y) { return x.value > y.value; }
    public static bool operator <(Integer x, Integer y) { return x.value < y.value; }
    public static bool operator >=(Integer x, Int16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Integer y) { return x.value >= y.value; }
    public static bool operator <=(Integer x, Integer y) { return x.value <= y.value; }
    public static Integer operator +(Int16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator -(Int16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator *(Int16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator /(Int16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator %(Int16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int64 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt64 x, Integer y) { return new Integer(x % y.value); }
    public static bool operator ==(Int16 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int16 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int32 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int32 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int64 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int64 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt16 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt16 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt32 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt32 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt64 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt64 x, Integer y) { return x != y.value; }
    public static bool operator >(Int16 x, Integer y) { return x > y.value; }
    public static bool operator <(Int16 x, Integer y) { return x < y.value; }
    public static bool operator >(Int32 x, Integer y) { return x > y.value; }
    public static bool operator <(Int32 x, Integer y) { return x < y.value; }
    public static bool operator >(Int64 x, Integer y) { return x > y.value; }
    public static bool operator <(Int64 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt16 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt16 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt32 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt32 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt64 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt64 x, Integer y) { return x < y.value; }
    public static bool operator >=(Int16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int64 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt64 x, Integer y) { return x <= y.value; }
}
public static class Program
{
    private static bool IntegerFunction(Integer n)
    {
        //code that implements IntegerFunction goes here
        //note that there is NO code that checks the type of n in rum time, because it is NOT needed anymore 
    }
    private static void Main()
    {
        Console.WriteLine("{0}",IntegerFunction(0)); //compile error: there is no overloaded METHOD for objects of type "int" and no implicit conversion from any object, including "int", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer(0))); //both compiles and no run time error
        Console.WriteLine("{0}",IntegerFunction("string")); //compile error: there is no overloaded METHOD for objects of type "string" and no implicit conversion from any object, including "string", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer("string"))); //compile error: there is no overloaded CONSTRUCTOR for objects of type "string"
    }
}

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
2

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.

public static T Sum(T[] items)
{
    T sum = Number.Zero<T>();
    foreach (T item in items)
    {
        sum = Number.Add(sum, item);
    }
    return sum;
}
public static T SumAlt(T[] items)
{
    // implicit conversion to Number<T>
    Number<T> sum = Number.Zero<T>();
    foreach (T item in items)
    {
        // operator support
        sum += item;
    }
    // implicit conversion to T
    return sum;
}
TylerBrinkley
fonte
1

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ê.

static bool IntegerFunction(Int64 value) { }

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.

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }
dbkk
fonte
11
Eu trabalho muito com métodos numéricos. Às vezes, quero números inteiros e, às vezes, ponto flutuante. Ambos possuem versões de 64 bits ideais para a velocidade de processamento. A conversão entre essas é uma péssima idéia, pois há perdas em cada sentido. Embora eu costumo usar duplos, às vezes acho melhor usar números inteiros por causa de como eles são usados ​​em outros lugares. Mas seria muito bom quando estou escrevendo um algoritmo para fazê-lo uma vez e deixar a decisão de tipo de acordo com os requisitos da instância.
VoteCoffee
1

Eu usaria um genérico que você poderia lidar externamente ...

/// <summary>
/// Generic object copy of the same type
/// </summary>
/// <typeparam name="T">The type of object to copy</typeparam>
/// <param name="ObjectSource">The source object to copy</param>
public T CopyObject<T>(T ObjectSource)
{
    T NewObject = System.Activator.CreateInstance<T>();

    foreach (PropertyInfo p in ObjectSource.GetType().GetProperties())
        NewObject.GetType().GetProperty(p.Name).SetValue(NewObject, p.GetValue(ObjectSource, null), null);

    return NewObject;
}
Marc Roussel
fonte
1

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

public struct Foo<T>
{
    public T Value{ get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + RHS.Value; };
    }
}

Eu resolvi esse problema usando a digitação em tempo de execução dinâmico .net4.

public struct Foo<T>
{
    public T Value { get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + (dynamic)RHS.Value };
    }
}

As duas coisas sobre o uso dynamicsão

  1. Atuação. Todos os tipos de valor são colocados em caixa.
  2. Erros de tempo de execução. Você "vence" o compilador, mas perde a segurança do tipo. Se o tipo genérico não tiver o operador definido, uma exceção será lançada durante a execução.
pomeroy
fonte
1

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 único Int16, Int32etc. 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ática bool Available {get;};e delegados estáticos para Int64 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 tipo Timplementa métodos com nomes e assinaturas apropriados (no caso, é algo como uma estrutura que contém um Int64e representa um número, mas possui um ToString()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.

supercat
fonte
@KenKin: IConvertible fornece um meio pelo qual qualquer número inteiro pode ser adicionado a outro tipo de número inteiro para produzir, por exemplo, um Int64resultado, 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 .
Supercat
1

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

public static string DoSomething(this int input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this decimal input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this double input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this string input, ...) => DoSomethingHelper(input, ...);

private static string DoSomethingHelper<T>(this T input, ....)
{
    // complex logic
}
DrGriff
fonte
0

Se tudo o que você deseja é usar um tipo numérico , considere criar algo semelhante a um alias no C ++ comusing .

Então, em vez de ter o muito genérico

T ComputeSomething<T>(T value1, T value2) where T : INumeric { ... }

você pode ter

using MyNumType = System.Double;
T ComputeSomething<MyNumType>(MyNumType value1, MyNumType value2) { ... }

Isso pode permitir que você vá facilmente de ou doublepara intoutros, se necessário, mas não poderá usar ComputeSomethingcom doublee intno mesmo programa.

Mas por que não substituir tudo doublepara intentão? Porque seu método pode querer usar a doublese a entrada é doubleou int. O alias permite que você saiba exatamente qual variável usa o tipo dinâmico .

user276648
fonte
0

O tópico é antigo, mas para futuros leitores:

Esse recurso está intimamente relacionado ao Discriminated Unionsqual 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:

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...
Arman Ebrahimpour
fonte
-11

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.

Tom Welch
fonte
10
Considere uma classe Histograma <T>. Faz sentido deixar que ele use um parâmetro genérico, para que o compilador possa otimizá-lo para bytes, ints, duplos, decimais, BigInt, ... mas, ao mesmo tempo, você precisa impedir que você possa criar um, por exemplo, Histograma <Hashset >, porque - falando com Tron - não computa. (literalmente :))
sunside
15
Você é quem entende mal dos genéricos. A metaprogramação não está apenas operando em valores que poderiam ser de qualquer tipo possível , é para operar em tipos que se ajustam a várias restrições .
perfil completo de Jim Balter