¿Hay alguna restricción que restrinja mi método genérico a los tipos numéricos?


364

¿Alguien puede decirme si hay una manera con los genéricos para limitar un argumento de tipo genérico Ta solo:

  • Int16
  • Int32
  • Int64
  • UInt16
  • UInt32
  • UInt64

Soy consciente de la wherepalabra clave, pero no puedo encontrar una interfaz solo para estos tipos,

Algo como:

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

Respuestas:


140

C # no es compatible con esto. Hejlsberg ha descrito las razones para no implementar la función en una entrevista con Bruce Eckel :

Y no está claro que la complejidad agregada valga el pequeño rendimiento que obtienes. Si algo que desea hacer no se admite directamente en el sistema de restricción, puede hacerlo con un patrón de fábrica. Podría tener un Matrix<T>, por ejemplo, y en el que Matrixle gustaría definir un método de producto de puntos. Eso, por supuesto, eso significa que en última instancia tiene que entender cómo multiplicar dos Ts, pero no se puede decir que como una limitación, al menos no si Tes int, doubleo float. Pero lo que podría hacer es tener su Matrixtoma como argumento a Calculator<T>, y en Calculator<T>, tener un método llamado multiply. Vas a implementar eso y lo pasas al Matrix.

Sin embargo, esto lleva a un código bastante complicado, donde el usuario debe proporcionar su propia Calculator<T>implementación, para cada uno Tque quiera usar. Siempre que no tenga que ser extensible, es decir, si solo desea admitir un número fijo de tipos, como inty double, puede salirse con una interfaz relativamente simple:

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

( Implementación mínima en un GitHub Gist. )

Sin embargo, tan pronto como desee que el usuario pueda suministrar sus propios tipos personalizados, debe abrir esta implementación para que el usuario pueda suministrar sus propias Calculatorinstancias. Por ejemplo, para crear una instancia de una matriz que utiliza una implementación personalizada de coma flotante decimal DFP, tendría que escribir este código:

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

... e implementar todos los miembros para DfpCalculator : ICalculator<DFP>.

Una alternativa, que desafortunadamente comparte las mismas limitaciones, es trabajar con clases de políticas, como se discutió en la respuesta de Sergey Shandar .


25
por cierto, MiscUtil proporciona una clase genérica que hace exactamente esto; Operator/ Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell

1
@ Mark: buen comentario. Sin embargo, para ser claros, no creo que Hejlsberg se refiera a la generación de código como una solución al problema como lo hace usted en el Operator<T>código (ya que la entrevista se dio mucho antes de la existencia del Expressionsmarco, aunque uno podría uso del curso Reflection.Emit), y estaría realmente interesado en su solución.
Konrad Rudolph

@Konrad Rudolph: Creo que esta respuesta a una pregunta similar explica la solución de Hejlsberg. La otra clase genérica se hace abstracta. Como requiere que implemente la otra clase genérica para cada tipo que desee admitir, dará como resultado un código duplicado, pero significa que solo puede crear una instancia de la clase genérica original con un tipo compatible.
Ergwun

14
No estoy de acuerdo con la frase de Heijsberg "Entonces, en cierto sentido, las plantillas de C ++ en realidad no están tipificadas, o están sueltas. Mientras que los genéricos de C # están fuertemente escritos". Eso es realmente Marketing BS para promover C #. La tipificación fuerte / débil no tiene que ver con la calidad de los diagnósticos. De lo contrario: interesante encontrar.
Sebastian Mach

100

Teniendo en cuenta la popularidad de esta pregunta y el interés detrás de tal función, me sorprende ver que todavía no hay una respuesta con T4.

En este código de muestra, demostraré un ejemplo muy simple de cómo puede usar el poderoso motor de plantillas para hacer lo que el compilador hace entre bastidores con genéricos.

En lugar de pasar por aros y sacrificar la certeza de tiempo de compilación, simplemente puede generar la función que desee para cada tipo que desee y usarla en consecuencia (¡en tiempo de compilación!).

Para hacer esto:

  • Cree un nuevo archivo de plantilla de texto llamado GenericNumberMethodTemplate.tt .
  • Elimine el código generado automáticamente (conservará la mayor parte, pero no es necesario).
  • Agregue el siguiente fragmento:
<#@ 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;
        }
    <#
    } #>
}

Eso es. Ya terminaste.

Guardar este archivo lo compilará automáticamente en este archivo fuente:

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;
    }
}

En su mainmétodo, puede verificar que tiene certeza en tiempo de compilación:

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

ingrese la descripción de la imagen aquí

Me adelantaré a un comentario: no, esto no es una violación del principio DRY. El principio DRY está ahí para evitar que las personas dupliquen el código en varios lugares que podrían hacer que la aplicación sea difícil de mantener.

Este no es el caso en este caso: si desea un cambio, simplemente puede cambiar la plantilla (¡una sola fuente para toda su generación!) Y listo.

Para usarlo con sus propias definiciones personalizadas, agregue una declaración de espacio de nombres (asegúrese de que sea la misma que definirá su propia implementación) a su código generado y marque la clase como partial. Luego, agregue estas líneas a su archivo de plantilla para que se incluya en la compilación final:

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

Seamos honestos: esto es genial.

Descargo de responsabilidad: esta muestra ha sido fuertemente influenciada por Metaprogramming en .NET por Kevin Hazzard y Jason Bock, Manning Publications .


Esto es bastante bueno, pero ¿sería posible modificar esta solución para que los métodos acepten algún tipo genérico Tque sea o herede de las diversas IntXclases? Me gusta esta solución porque ahorra tiempo, pero para que resuelva el problema al 100% (a pesar de no ser tan agradable como si C # tuviera soporte para este tipo de restricción, incorporado) cada uno de los métodos generados debería ser genérico para que pueden devolver un objeto de un tipo que hereda de una de las IntXXclases.
Zachary Kniebel

1
@ZacharyKniebel: los IntXXtipos son estructuras, lo que significa que no admiten la herencia en primer lugar . E incluso si lo hiciera, entonces se aplica el principio de sustitución de Liskov (que quizás conozcas por el lenguaje SOLID): si el método se define como Xy Yes un hijo de Xentonces, por definición, cualquiera Ydebería poder pasar a ese método como un sustituto de su tipo base
Jeroen Vannevel

1
Esta solución alternativa que utiliza políticas stackoverflow.com/questions/32664/… sí utiliza T4 para generar clases.
Sergey Shandar

2
+1 para esta solución, ya que conserva la eficiencia operativa de los tipos integrales integrados, a diferencia de las soluciones basadas en políticas. Llamar a los operadores CLR integrados (como Agregar) a través de un método adicional (posiblemente virtual) puede afectar gravemente el rendimiento si se usa muchas veces (como en las bibliotecas matemáticas). Y dado que el número de tipos integrales es constante (y no se puede heredar), solo necesita regenerar el código para corregir errores.
Attila Klenik

1
Muy bien y estaba a punto de comenzar a usarlo, luego recordé cuán dependiente de Resharper soy para refactorizar y no se puede cambiar el nombre de refactor a través de la plantilla T4. No es crítico pero vale la pena considerarlo.
bradgonesurfing

86

No hay restricción para esto. Es un problema real para cualquiera que quiera usar genéricos para cálculos numéricos.

Yo iría más lejos y diría que necesitamos

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

O incluso

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

Desafortunadamente, solo tiene interfaces, clases base y las palabras clave struct(debe ser de tipo valor), class(debe ser de tipo de referencia) y new()(debe tener un constructor predeterminado)

Puede envolver el número en otra cosa (similar a INullable<T>) como aquí en codeproject .


Puede aplicar la restricción en tiempo de ejecución (reflexionando para los operadores o comprobando los tipos), pero eso pierde la ventaja de tener el genérico en primer lugar.


2
Me pregunto si has visto el soporte de MiscUtil para operadores genéricos ... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell

10
Sí, Jon Skeet me señaló algo diferente hace un tiempo (pero después de la respuesta de este año): son una idea inteligente, pero todavía me gustaría un apoyo de restricción adecuado.
Keith

1
Espera, ¿ where T : operators( +, -, /, * )es legal C #? Perdón por la pregunta de novato.
kdbanman

@kdbanman, no lo creo. Keith dice que C # no admite lo que OP está pidiendo, y sugiere que deberíamos poder hacer where T : operators( +, -, /, * ), pero no podemos.
AMTerp

62

Solución 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.

La solución es segura en tiempo de compilación. CityLizard Framework proporciona una versión compilada para .NET 4.0. El archivo es lib / NETFramework4.0 / CityLizard.Policy.dll.

También está disponible en Nuget: https://www.nuget.org/packages/CityLizard/ . Vea la estructura CityLizard.Policy.I .


Tuve problemas con este patrón cuando hay menos argumentos de función que parámetros genéricos. Abierto stackoverflow.com/questions/36048248/…
xvan

alguna razón por qué usar struct? ¿Qué pasa si uso singleton-class en su lugar y cambio la instancia a public static NumericPolicies Instance = new NumericPolicies();y luego agrego este constructor private NumericPolicies() { }?
M.kazem Akhgary

@ M.kazemAkhgary puede usar el singleton. Prefiero la estructura. En teoría, puede ser optimizado por el compilador / CLR porque la estructura no contiene información. En el caso de singleton, aún pasará una referencia, lo que puede agregar presión adicional sobre GC. Otra ventaja es que la estructura no puede ser nula :-).
Sergey Shandar

Iba a decir que encontraste una solución muy inteligente, pero la solución es demasiado limitada para mí: iba a usarla T Add<T> (T t1, T t2), pero Sum()solo funciona cuando puede recuperar su propio tipo de T de sus parámetros, lo que no es posible cuando está incrustado en otra función genérica.
Tobias Knauss

16

Esta pregunta es un poco una pregunta frecuente, así que estoy publicando esto como wiki (ya que he publicado similar antes, pero esta es una más antigua); de todas formas...

¿Qué versión de .NET estás usando? Si está utilizando .NET 3.5, entonces tengo una implementación de operadores genéricos en MiscUtil (gratis, etc.).

Esto tiene métodos como T Add<T>(T x, T y), y otras variantes para la aritmética en diferentes tipos (como DateTime + TimeSpan).

Además, esto funciona para todos los operadores incorporados, elevados y personalizados, y almacena en caché al delegado para el rendimiento.

Aquí hay algunos antecedentes adicionales sobre por qué esto es complicado .

También es posible que desee saber que dynamic(4.0) resuelve este problema indirectamente también, es decir

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect

14

Desafortunadamente, solo puede especificar struct en la cláusula where en esta instancia. Parece extraño que no pueda especificar Int16, Int32, etc. específicamente, pero estoy seguro de que hay una razón de implementación profunda que subyace a la decisión de no permitir tipos de valor en una cláusula where.

Supongo que la única solución es hacer una verificación de tiempo de ejecución que desafortunadamente evita que el problema se resuelva en el momento de la compilación. Eso sería algo así 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...
}

Lo cual es un poco feo, lo sé, pero al menos proporciona las restricciones requeridas.

También analizaría las posibles implicaciones de rendimiento para esta implementación, tal vez haya una forma más rápida.


13
+1, sin embargo, // Rest of code...puede no compilarse si depende de las operaciones definidas por las restricciones.
Nick

1
Convert.ToIntXX (valor) podría ayudar a hacer la compilación "// Resto del código", al menos hasta que el tipo de retorno de IntegerFunction también sea del tipo T, entonces estás enganchado. :-p
yoyo

-1; esto no funciona por la razón dada por @Nick. En el momento en que intentas realizar cualquier operación aritmética en // Rest of code...like value + valueo value * value, tienes un error de compilación.
Mark Amery

13

Probablemente lo más cerca que puedes hacer es

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

No estoy seguro si podría hacer lo siguiente

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

Para algo tan específico, por qué no solo tener sobrecargas para cada tipo, la lista es tan corta y posiblemente tenga menos huella de memoria.


6

Comenzando con C # 7.3, puede usar una aproximación más cercana : la restricción no administrada para especificar que un parámetro de tipo es un tipo no administrado sin puntero ni anulable .

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

La restricción no administrada implica la restricción de estructura y no se puede combinar con las restricciones de estructura o new ().

Un tipo es un tipo no administrado si es alguno de los siguientes tipos:

  • sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal o bool
  • Cualquier tipo de enumeración
  • Cualquier tipo de puntero
  • Cualquier tipo de estructura definida por el usuario que contenga solo campos de tipos no administrados y, en C # 7.3 y anteriores, no es un tipo construido (un tipo que incluye al menos un argumento de tipo)

Para restringir aún más y eliminar el puntero y los tipos definidos por el usuario que no implementan IComparable, agregue IComparable (pero enum todavía se deriva de IComparable, por lo tanto, restrinja la enumeración agregando IEquatable <T>, puede ir más allá dependiendo de sus circunstancias y agregar interfaces adicionales. no administrado permite mantener esta lista más corta):

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

Agradable, pero no lo suficiente ... Por ejemplo, DateTimecae bajo unmanaged, IComparable, IEquatable<T>restricción ..
Adam Calvet Bohl

Lo sé, pero puedes ir más lejos dependiendo de tus circunstancias y agregar interfaces adicionales. no administrado permite mantener esta lista más corta. Acabo de mostrar el enfoque, aproximación usando no administrado. Para la mayoría de los casos esto es suficiente
Vlad Novakovsky

4

No hay forma de restringir las plantillas a los tipos, pero puede definir diferentes acciones según el tipo. Como parte de un paquete numérico genérico, necesitaba una clase genérica para agregar dos 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;
        }
    }

Tenga en cuenta que los typeofs se evalúan en tiempo de compilación, por lo que el compilador eliminaría las declaraciones if. El compilador también elimina los moldes espurios. Entonces, algo se resolvería en el compilador para

        internal static int Sum(int first, int second)
        {
            return first + second;
        }

¡Gracias por proporcionar una solución empírica!
zsf222

¿No es lo mismo que crear el mismo método para cada tipo?
Luis

3

Creé una pequeña funcionalidad de biblioteca para resolver estos problemas:

En vez 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.

Podrías escribir:

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.

Puede encontrar el código fuente aquí: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number


2

Me preguntaba lo mismo que samjudson, ¿por qué solo a enteros? y si ese es el caso, es posible que desee crear una clase auxiliar o algo así para contener todos los tipos que desee.

Si todo lo que quiere son enteros, no use un genérico, que no sea genérico; o mejor aún, rechace cualquier otro tipo marcando su tipo.


2

No hay una solución "buena" para esto todavía. Sin embargo, puede reducir significativamente el argumento de tipo para descartar muchos errores para su hipotética restricción 'INumeric' como ha mostrado Haacked anteriormente.

static bool IntegerFunction <T> (valor T) donde T: IComparable, IFormattable, IConvertible, IComparable <T>, IEquatable <T>, struct {...


2

Si está utilizando .NET 4.0 y versiones posteriores, puede usar dinámico como argumento de método y verificar en tiempo de ejecución que el tipo de argumento dinámico pasado es de tipo numérico / entero.

Si el tipo de dinámica pasada no es numérico / entero, entonces arroje una excepción.

Un ejemplo de código breve que implementa la idea es 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.
    }

Por supuesto, esta solución funciona solo en tiempo de ejecución pero nunca en tiempo de compilación.

Si desea una solución que siempre funcione en tiempo de compilación y nunca en tiempo de ejecución, entonces deberá envolver la dinámica con una estructura / clase pública cuyos constructores públicos sobrecargados acepten argumentos de los tipos deseados únicamente y den el nombre apropiado a la estructura / clase.

Tiene sentido que la dinámica envuelta siempre sea un miembro privado de la clase / estructura y es el único miembro de la estructura / clase y el nombre del único miembro de la estructura / clase es "valor".

También deberá definir e implementar métodos públicos y / u operadores que trabajen con los tipos deseados para el miembro dinámico privado de la clase / estructura si es necesario.

También tiene sentido que la estructura / clase tenga un constructor especial / único que acepte la dinámica como argumento que inicializa su único miembro dinámico privado llamado "valor", pero el modificador de este constructor es privado, por supuesto.

Una vez que la clase / estructura esté lista, defina el tipo de argumento de IntegerFunction para que sea esa clase / estructura que se ha definido.

Un código largo de ejemplo que implementa la idea es 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"
    }
}

Tenga en cuenta que para usar Dynamic en su código, debe Agregar referencia a Microsoft.CSharp

Si la versión de .NET Framework está por debajo / debajo / menos de 4.0 y la dinámica no está definida en esa versión, entonces tendrá que usar object en su lugar y realizar la conversión al tipo entero, lo cual es un problema, por lo que le recomiendo que lo use en menos .NET 4.0 o más reciente si puede para poder usar dinámico en lugar de objeto .


2

Desafortunadamente .NET no proporciona una forma de hacerlo de forma nativa.

Para solucionar este problema, creé la biblioteca OSS Genumerics, que proporciona la mayoría de las operaciones numéricas estándar para los siguientes tipos numéricos integrados y sus equivalentes anulables con la capacidad de agregar soporte para otros tipos numéricos.

sbyte, byte, short, ushort, int, uint, long, ulong, float, double, decimal, YBigInteger

El rendimiento es equivalente a una solución específica de tipo numérico que le permite crear algoritmos numéricos genéricos eficientes.

Aquí hay un ejemplo del uso del 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;
}

1

¿Cuál es el punto del ejercicio?

Como la gente ya señaló, podría tener una función no genérica que tome el elemento más grande, y el compilador convertirá automáticamente entradas más pequeñas para usted.

static bool IntegerFunction(Int64 value) { }

Si su función está en una ruta crítica de rendimiento (muy poco probable, IMO), podría proporcionar sobrecargas para todas las funciones necesarias.

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }

1
Trabajo mucho con métodos numéricos. A veces quiero enteros y a veces quiero coma flotante. Ambos tienen versiones de 64 bits que son óptimas para la velocidad de procesamiento. La conversión entre estos es una idea terrible ya que hay pérdidas en cada sentido. Si bien tiendo a usar dobles, a veces me parece mejor usar números enteros debido a cómo se usan en otros lugares. Pero sería muy bueno cuando estoy escribiendo un algoritmo para hacerlo una vez y dejar la decisión de tipo según los requisitos de la instancia.
VoteCoffee

1

Usaría uno genérico que podría manejar 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;
}

1

Esta limitación me afectó cuando intenté sobrecargar operadores para tipos genéricos; Como no existía una restricción "INumeric", y por muchas otras razones que las buenas personas en stackoverflow están felices de proporcionar, las operaciones no se pueden definir en tipos genéricos.

Quería 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; };
    }
}

He solucionado este problema utilizando la escritura dinámica .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 };
    }
}

Las dos cosas sobre el uso dynamicson

  1. Actuación. Todos los tipos de valor se encuadran.
  2. Errores de tiempo de ejecución. Usted "vence" al compilador, pero pierde la seguridad del tipo. Si el tipo genérico no tiene definido el operador, se lanzará una excepción durante la ejecución.

1

Los tipos primitivos numéricos .NET no comparten ninguna interfaz común que les permita ser utilizados para los cálculos. Sería posible definir sus propias interfaces (por ejemplo ISignedWholeNumber) que realizar tales operaciones, definir estructuras que contienen una sola Int16, Int32, etc. y aplicar esas interfaces, y luego tener métodos que aceptan tipos genéricos limitados a ISignedWholeNumber, pero tener que convertir los valores numéricos para sus tipos de estructura probablemente sería una molestia.

Un enfoque alternativo sería definir la clase estática Int64Converter<T>con una propiedad estática bool Available {get;};y los delegados estáticas para Int64 GetInt64(T value), T FromInt64(Int64 value), bool TryStoreInt64(Int64 value, ref T dest). El constructor de la clase podría usar un código rígido para cargar delegados para tipos conocidos, y posiblemente usar Reflection para probar si el tipo Timplementa métodos con los nombres y firmas adecuados (en caso de que sea algo así como una estructura que contiene un Int64y representa un número, pero tiene Un ToString()método personalizado ). Este enfoque perdería las ventajas asociadas con la verificación de tipos en tiempo de compilación, pero aún así lograría evitar las operaciones de boxeo y cada tipo solo tendría que "verificarse" una vez. Después de eso, las operaciones asociadas con ese tipo se reemplazarían con un despacho de delegado.


@KenKin: IConvertible proporciona un medio por el cual cualquier entero podría agregarse a otro tipo de entero para producir, por ejemplo, un Int64resultado, pero no proporciona un medio por el cual, por ejemplo, un entero de tipo arbitrario podría incrementarse para producir otro entero del mismo tipo .
supercat

1

Tuve una situación similar en la que necesitaba manejar cadenas y tipos numéricos; Parece una mezcla un poco extraña, pero ahí lo tienes.

Nuevamente, como muchas personas, miré las restricciones y se me ocurrieron varias interfaces que tenía que soportar. Sin embargo, a) no era 100% hermético yb), cualquiera que mirara esta larga lista de restricciones estaría inmediatamente muy confundido.

Entonces, mi enfoque era poner toda mi lógica en un método genérico sin restricciones, pero hacer que ese método genérico sea privado. Luego lo expuse con métodos públicos, uno que explícitamente maneja el tipo que quería manejar: en mi opinión, el código es limpio y explícito, por ejemplo

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
}

0

Si todo lo que quiere es usar un tipo numérico , podría considerar crear algo similar a un alias en C ++ con using.

Entonces, en lugar de tener el genérico

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

podrías tener

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

Eso podría permitirle pasar fácilmente doublea intu otros si es necesario, pero no podría usar ComputeSomethingcon doubley inten el mismo programa.

Pero, ¿por qué no reemplazar todo doublepara intentonces? Porque su método puede querer usar a doublesi la entrada es doubleo int. El alias le permite saber exactamente qué variable usa el tipo dinámico .


0

El tema es antiguo pero para futuros lectores:

Esta característica está estrechamente relacionada con lo Discriminated Unionsque no está implementado en C # hasta ahora. Encontré su problema aquí:

https://github.com/dotnet/csharplang/issues/113

Este problema aún está abierto y la función se ha planificado para C# 10

Así que todavía tenemos que esperar un poco más, pero después de la liberación puedes hacerlo de esta manera:

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...

-11

Creo que no entiendes los genéricos. Si la operación que está intentando realizar solo es buena para tipos de datos específicos, entonces no está haciendo algo "genérico".

Además, dado que solo desea permitir que la función funcione en tipos de datos int, no debería necesitar una función separada para cada tamaño específico. Simplemente tomar un parámetro en el tipo específico más grande permitirá que el programa emita automáticamente los tipos de datos más pequeños. (es decir, pasar un Int16 se convertirá automáticamente a Int64 al llamar).

Si está realizando diferentes operaciones en función del tamaño real de int que se pasa a la función, creo que debería reconsiderar seriamente incluso tratar de hacer lo que está haciendo. Si tiene que engañar al lenguaje, debe pensar un poco más sobre lo que está tratando de lograr en lugar de cómo hacer lo que quiere.

Si falla todo lo demás, se podría usar un parámetro de tipo Objeto y luego deberá verificar el tipo del parámetro y tomar las medidas apropiadas o lanzar una excepción.


10
Considere una clase Histograma <T>. Tiene sentido dejar que tome un parámetro genérico, por lo que el compilador puede optimizarlo para bytes, ints, dobles, decimal, BigInt, ... pero al mismo tiempo debe evitar que pueda crear un, por ejemplo, Histograma <Hashset >, porque - hablando con Tron - no computa. (literalmente :))
sol

15
Eres el que entiende mal los genéricos. La metaprogramación no solo opera en valores que podrían ser de cualquier tipo posible , es para operar en tipos que se ajustan a varias restricciones .
Jim Balter
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.