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

StackOverflow https://stackoverflow.com/questions/32664

  •  09-06-2019
  •  | 
  •  

Pregunta

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

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

soy consciente de la where palabra clave, pero no puedo encontrar una interfaz para solo estos tipos,

Algo como:

static bool IntegerFunction<T>(T value) where T : INumeric 
¿Fue útil?

Solución

C# no admite esto.Hejlsberg ha explicado los motivos por los que no se ha implementado la función en una entrevista con Bruce Eckel:

Y no está claro que la complejidad añadida justifique el pequeño rendimiento que se obtiene.Si algo que desea hacer no es compatible directamente con el sistema de restricciones, puede hacerlo con un patrón de fábrica.Podrías tener un Matrix<T>, por ejemplo, y en eso Matrix Le gustaría definir un método de producto escalar.Eso, por supuesto, significa que, en última instancia, debes entender cómo multiplicar dos. Ts, pero no se puede decir eso como una restricción, al menos no si T es int, double, o float.Pero lo que podrías hacer es tener tu Matrix tomar como argumento un Calculator<T>, y en Calculator<T>, tiene un método llamado multiply.Vas a implementar eso y se lo pasas al Matrix.

Sin embargo, esto conduce a un código bastante complicado, donde el usuario tiene que proporcionar su propia Calculator<T> implementación, para cada T que quieren utilizar.Siempre y cuando no tenga que ser extensible, es decir.si solo desea admitir un número fijo de tipos, como int y double, puedes salirte con la tuya 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 proporcionar sus propios tipos personalizados, deberá abrir esta implementación para que el usuario pueda proporcionar sus propios tipos. Calculator instancias.Por ejemplo, para crear una instancia de una matriz que utiliza una implementación de coma flotante decimal personalizada, DFP, tendrías que escribir este código:

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

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

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

Otros consejos

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

En este código de muestra, demostraré un ejemplo muy simple de cómo se puede utilizar el potente motor de plantillas para hacer lo que el compilador hace prácticamente entre bastidores con los genéricos.

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

Para hacer esto:

  • Crear un nuevo Plantilla de texto archivo llamado GenericNumberMethodTemplate.tt.
  • Elimine el código generado automáticamente (conservará la mayor parte, pero parte no es necesaria).
  • 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 todo.Ya terminaste.

Al guardar este archivo, se 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 tus main mé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();
        }
    }
}

enter image description here

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 código en varios lugares, lo que haría que la aplicación fuera difícil de mantener.

Este no es el caso en absoluto aquí:Si desea un cambio, simplemente puede cambiar la plantilla (¡una única 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 en la 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 está muy bien.

Descargo de responsabilidad:Esta muestra ha sido fuertemente influenciada por Metaprogramación en .NET por Kevin Hazzard y Jason Bock, Manning Publications.

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

Yo iría más allá 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

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

Podrías 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 sobre los operadores o verificando los tipos), pero eso pierde la ventaja de tener el genérico en primer lugar.

Solución alternativa mediante 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. Marco CityLizard 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/.Ver CityLizard.Policy.I estructura.

Esta pregunta es un poco frecuente, por lo que la publicaré como wiki (ya que publiqué cosas similares antes, pero esta es anterior);de todos modos...

¿Qué versión de .NET estás usando?Si está utilizando .NET 3.5, entonces tengo un 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 de 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 su rendimiento.

Algunos antecedentes adicionales sobre por qué esto es complicado son aquí.

Quizás también quieras saber que dynamic (4.0) también resuelve este problema indirectamente, es decir,

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

Desafortunadamente, en este caso solo puedes especificar struct en la cláusula where.Parece extraño que no puedas especificar Int16, Int32, etc.específicamente, pero estoy seguro de que hay alguna razón de implementación profunda subyacente a la decisión de no permitir tipos de valor en una cláusula Where.

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

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

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

Probablemente lo más cerca que puedas hacer es

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

No estoy seguro si podrías 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 simplemente tener sobrecargas para cada tipo? La lista es muy corta y posiblemente ocuparía menos memoria.

No hay forma de restringir las plantillas a 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 tipos se evalúan en tiempo de compilación, por lo que el compilador eliminará las declaraciones if.El compilador también elimina conversiones espurias.Entonces algo se resolvería en el compilador para

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

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

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

Puedes encontrar el código fuente aquí: https://codereview.stackexchange.com/questions/26022/improvement-requested-for-generic-calculator-and-generic-number

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

Si lo único que quieres son números enteros, no uses un genérico, que no sea genérico;o mejor aún, rechazar cualquier otro tipo marcando su tipo.

¿Cuál es el objetivo 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 por usted.

static bool IntegerFunction(Int64 value) { }

Si su función se encuentra en una ruta crítica para el rendimiento (muy poco probable, en mi opinión), podría proporcionar sobrecargas para todas las funciones necesarias.

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

Yo usaría uno genérico que puedas 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;
}

Esta limitación me afectó cuando intenté sobrecargar operadores para tipos genéricos;Dado que no había ninguna restricción "INumeric", y por muchas otras razones que la buena gente de stackoverflow está feliz de brindar, 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 en tiempo de ejecución 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 dynamic son

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

Todavía no existe una "buena" solución para esto.Sin embargo, puede limitar significativamente el argumento de tipo para descartar muchos errores de ajuste para su hipotética restricción 'INumeric', como Haacked ha mostrado anteriormente.

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

Los tipos primitivos numéricos de .NET no comparten ninguna interfaz común que permita su uso para cálculos.Sería posible definir sus propias interfaces (p. ej. ISignedWholeNumber) que realizaría tales operaciones, definiría estructuras que contengan un único Int16, Int32, etc.e implementar esas interfaces, y luego tener métodos que acepten tipos genéricos restringidos a ISignedWholeNumber, pero tener que convertir valores numéricos a 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 delegados estáticos para Int64 GetInt64(T value), T FromInt64(Int64 value), bool TryStoreInt64(Int64 value, ref T dest).El constructor de clases podría codificarse para cargar delegados para tipos conocidos y posiblemente usar Reflection para probar si el tipo T implementa métodos con los nombres y firmas adecuados (en caso de que sea algo así como una estructura que contiene un Int64 y representa un número, pero tiene una costumbre ToString() método).Este enfoque perdería las ventajas asociadas con la verificación de tipos en tiempo de compilación, pero aun así lograría evitar operaciones de boxeo y cada tipo sólo tendría que ser "verificado" una vez.Después de eso, las operaciones asociadas con ese tipo serían reemplazadas por un envío delegado.

Si está utilizando .NET 4.0 y posterior, puede utilizar dinámica como argumento del método y comprobar en tiempo de ejecución que el pasado dinámica El tipo de argumento es numérico/entero.

Si el tipo de aprobado dinámica es no tipo numérico/entero y luego lanza una excepción.

Un ejemplo corto El código 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, tendrá que ajustar el dinámica con una estructura/clase pública cuya sobrecarga público Los constructores aceptan argumentos solo de los tipos deseados y dan el nombre apropiado a la estructura/clase.

Tiene sentido que el envuelto dinámica es siempre privado miembro 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 tendrás que definir e implementar público métodos y/u operadores que trabajan 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 especial/único constructor que acepta 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 ejemplo largo El código 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 utilizar dinámica en tu código debes Añadir referencia a Microsoft.CSharp

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

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

Entonces, en lugar de tener el muy 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 de double a int u otros si es necesario, pero no podrás utilizar ComputeSomething con double y int en el mismo programa.

¿Pero por qué no reemplazar todos? double a int ¿entonces?Debido a que es posible que su método desee utilizar un double si la entrada es double o int.El alias le permite saber exactamente qué variable usa el dinámica tipo.

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

Nuevamente, como mucha gente, analicé las restricciones y se me ocurrieron un montón de interfaces que debían admitir.Sin embargo, a) no era 100% hermético yb), cualquiera que mirara por primera vez esta larga lista de limitaciones quedaría inmediatamente muy confundido.

Entonces, mi enfoque fue 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 maneja explícitamente el tipo que quería manejar; en mi opinión, el código es limpio y explícito, p.

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
}

No existe una única interfaz o clase base que todos hereden (que no sean heredadas también por otras clases), por lo que la respuesta simple es no.

Sin embargo, me pregunto por qué esto es un problema.¿Qué desea hacer dentro de su clase IntegerFunction que solo se puede hacer con números enteros?

Creo que estás malinterpretando los genéricos.Si la operación que intenta realizar sólo es válida 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 le convierta 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 según el tamaño real de int que se pasa a la función, creo que debería reconsiderar seriamente incluso intentar hacer lo que está haciendo.Si tienes que engañar al lenguaje, deberías pensar un poco más en lo que estás tratando de lograr que en cómo hacer lo que quieres.

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

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top