¿Alguien puede decirme si hay una manera con los genéricos para 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 solo para estos tipos,
Algo como:
static bool IntegerFunction<T>(T value) where T : INumeric
c#
generics
constraints
Corin Blaikie
fuente
fuente
Respuestas:
C # no es compatible con esto. Hejlsberg ha descrito las razones para no implementar la función en una entrevista con Bruce Eckel :
Sin embargo, esto lleva a un código bastante complicado, donde el usuario debe proporcionar su propia
Calculator<T>
implementación, para cada unoT
que quiera usar. Siempre que no tenga que ser extensible, es decir, si solo desea admitir un número fijo de tipos, comoint
ydouble
, puede salirse con una interfaz relativamente simple:( 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
Calculator
instancias. Por ejemplo, para crear una instancia de una matriz que utiliza una implementación personalizada de coma flotante decimalDFP
, tendría que escribir este código:... 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 .
fuente
Operator
/Operator<T>
; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.htmlOperator<T>
código (ya que la entrevista se dio mucho antes de la existencia delExpressions
marco, aunque uno podría uso del cursoReflection.Emit
), y estaría realmente interesado en su solución.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:
Eso es. Ya terminaste.
Guardar este archivo lo compilará automáticamente en este archivo fuente:
En su
main
método, puede verificar que tiene certeza en tiempo de compilación: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: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 .
fuente
T
que sea o herede de las diversasIntX
clases? 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 lasIntXX
clases.IntXX
tipos 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 comoX
yY
es un hijo deX
entonces, por definición, cualquieraY
debería poder pasar a ese método como un sustituto de su tipo baseNo 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
O incluso
Desafortunadamente, solo tiene interfaces, clases base y las palabras clave
struct
(debe ser de tipo valor),class
(debe ser de tipo de referencia) ynew()
(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.
fuente
where T : operators( +, -, /, * )
es legal C #? Perdón por la pregunta de novato.where T : operators( +, -, /, * )
, pero no podemos.Solución alternativa usando políticas:
Algoritmos
Uso:
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 .
fuente
struct
? ¿Qué pasa si uso singleton-class en su lugar y cambio la instancia apublic static NumericPolicies Instance = new NumericPolicies();
y luego agrego este constructorprivate NumericPolicies() { }
?T Add<T> (T t1, T t2)
, peroSum()
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.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 (comoDateTime + 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 decirfuente
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: -
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.
fuente
// Rest of code...
puede no compilarse si depende de las operaciones definidas por las restricciones.// Rest of code...
likevalue + value
ovalue * value
, tienes un error de compilación.Probablemente lo más cerca que puedes hacer es
No estoy seguro si podría hacer lo siguiente
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.
fuente
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 .
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:
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):
fuente
DateTime
cae bajounmanaged, IComparable, IEquatable<T>
restricción ..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.
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
fuente
Creé una pequeña funcionalidad de biblioteca para resolver estos problemas:
En vez de:
Podrías escribir:
Puede encontrar el código fuente aquí: /codereview/26022/improvement-requested-for-generic-calculator-and-generic-number
fuente
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.
fuente
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 {...
fuente
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:
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:
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 .
fuente
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.
fuente
¿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.
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.
fuente
Usaría uno genérico que podría manejar externamente ...
fuente
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
He solucionado este problema utilizando la escritura dinámica .net4.
Las dos cosas sobre el uso
dynamic
sonfuente
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 solaInt16
,Int32
, etc. y aplicar esas interfaces, y luego tener métodos que aceptan tipos genéricos limitados aISignedWholeNumber
, 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áticabool Available {get;};
y los delegados estáticas paraInt64 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 tipoT
implementa métodos con los nombres y firmas adecuados (en caso de que sea algo así como una estructura que contiene unInt64
y representa un número, pero tiene UnToString()
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.fuente
Int64
resultado, 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 .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
fuente
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
podrías tener
Eso podría permitirle pasar fácilmente
double
aint
u otros si es necesario, pero no podría usarComputeSomething
condouble
yint
en el mismo programa.Pero, ¿por qué no reemplazar todo
double
paraint
entonces? Porque su método puede querer usar adouble
si la entrada esdouble
oint
. El alias le permite saber exactamente qué variable usa el tipo dinámico .fuente
El tema es antiguo pero para futuros lectores:
Esta característica está estrechamente relacionada con lo
Discriminated Unions
que 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:
fuente
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.
fuente