sobreescritura sobrecarga sencillos poo operadores metodos ejemplos constructores c# generics operator-overloading math primitive-types

c# - sencillos - sobrecarga de operadores poo



Sobrecarga de operadores aritméticos para una clase genérica en C# (12)

Dada una definición de clase genérica como

public class ConstrainedNumber<T> : IEquatable<ConstrainedNumber<T>>, IEquatable<T>, IComparable<ConstrainedNumber<T>>, IComparable<T>, IComparable where T:struct, IComparable, IComparable<T>, IEquatable<T>

¿Cómo puedo definir operadores aritméticos para ello?

Lo siguiente no se compila, porque el operador ''+'' no se puede aplicar a los tipos ''T'' y ''T'':

public static T operator +( ConstrainedNumber<T> x, ConstrainedNumber<T> y) { return x._value + y._value; }

El tipo genérico ''T'' está restringido con la palabra clave ''where'' como puede ver, pero necesito una restricción para los tipos de números que tienen operadores aritméticos (¿IArithmetic?).

''T'' será un tipo de número primitivo como int, float, etc. ¿Existe una restricción ''where'' para dichos tipos?


¿Qué tal esto amigos (usando el RTTI y la clase de objeto)

class MyMath { public static T Add<T>(T a, T b) where T: struct { switch (typeof(T).Name) { case "Int32": return (T) (object)((int)(object)a + (int)(object)b); case "Double": return (T)(object)((double)(object)a + (double)(object)b); default: return default(T); } } } class Program { public static int Main() { Console.WriteLine(MyMath.Add<double>(3.6, 2.12)); return 0; } }


Acabo de hacer esto después de mirar aquí. La clase Vector4 <T> contiene 4 números / ejes de tipo T con el vector math habitual. Simplemente agregue 2 operaciones implícitas para convertir ay desde Decimal. Probablemente sea tan poco detallado como sea posible, pero como usted señala, es más preciso y, por lo tanto, más pesado de lo que debe ser. Al igual que ustedes, ¡ojalá hubiera un INumeric o algo!

public static Vector4<T> operator +(Vector4<T> a, Vector4<T> b) { Vector4<Decimal> A = a; Vector4<Decimal> B = b; var result = new Vector4<Decimal>(A.X + B.X, A.Y + B.Y, A.Z + B.Z, A.W + B.W); return result; } public static implicit operator Vector4<Decimal>(Vector4<T> v) { return new Vector4<Decimal>( Convert.ToDecimal(v.X), Convert.ToDecimal(v.Y), Convert.ToDecimal(v.Z), Convert.ToDecimal(v.W)); } public static implicit operator Vector4<T>(Vector4<Decimal> v) { return new Vector4<T>( (T)Convert.ChangeType(v.X, typeof(T)), (T)Convert.ChangeType(v.Y, typeof(T)), (T)Convert.ChangeType(v.Z, typeof(T)), (T)Convert.ChangeType(v.W, typeof(T))); }


Creo que lo mejor que puedes hacer es usar IConvertible como restricción y hacer algo como:

public static operator T +(T x, T y) where T: IConvertible { var type = typeof(T); if (type == typeof(String) || type == typeof(DateTime)) throw new ArgumentException(String.Format("The type {0} is not supported", type.FullName), "T"); try { return (T)(Object)(x.ToDouble(NumberFormatInfo.CurrentInfo) + y.ToDouble(NumberFormatInfo.CurrentInfo)); } catch(Exception ex) { throw new ApplicationException("The operation failed.", ex); } }

Sin embargo, eso no impedirá que alguien transmita un String o DateTime, por lo que es posible que desee realizar una comprobación manual, pero IConvertible debe acercarse lo suficiente y permitirle realizar la operación.


Desafortunadamente no hay forma de restringir un parámetro genérico para que sea un tipo integral ( Edición: supongo que "tipo aritmético" podría ser una palabra mejor, ya que esto no se aplica solo a los enteros).

Sería bueno poder hacer algo como esto:

where T : integral // or "arithmetical" depending on how pedantic you are

o

where T : IArithmetic

Le sugiero que lea Operadores genéricos por nuestro propio Marc Gravell y Jon Skeet. Explica por qué este es un problema tan difícil y qué se puede hacer para evitarlo.

.NET 2.0 introdujo los genéricos en el mundo .NET, lo que abrió la puerta a muchas soluciones elegantes para los problemas existentes. Las restricciones genéricas se pueden usar para restringir los argumentos de tipo a interfaces conocidas, etc., para garantizar el acceso a la funcionalidad; o para simples pruebas de igualdad / desigualdad los Singletons Comparer.Default y EqualityComparer.Default implementan IComparer e IEqualityComparer respectivamente (lo que nos permite ordenar elementos para ejemplo, sin tener que saber nada sobre la "T" en cuestión).

Con todo esto, sin embargo, todavía hay una gran brecha en lo que respecta a los operadores. Debido a que los operadores se declaran como métodos estáticos, no existe una interfaz IMath o equivalente similar que implementen todos los tipos numéricos; y de hecho, la flexibilidad de los operadores haría que esto fuera muy difícil de hacer de una manera significativa. Peor aún: muchos de los operadores en tipos primitivos ni siquiera existen como operadores; en cambio, hay métodos IL directos. [énfasis mío] Para hacer que la situación sea aún más compleja, Nullable <> exige el concepto de "operadores levantados", donde la "T" interior describe los operadores aplicables al tipo que admite nulo, pero esto se implementa como una función de idioma y es no proporcionado por el tiempo de ejecución (haciendo que la reflexión sea aún más divertida).


Desafortunadamente, esto no es posible ya que no hay una IArithmetic (como dijiste) definida para enteros. Puede envolver esos tipos primitivos en clases que implementen tal interfaz.


En C # 4.0 puedes usar dynamic para solventar esta limitación. Eché un vistazo a su código y logré producir una versión funcional (aunque sea de reducción):

public class ConstrainedNumber<T> where T : struct, IComparable, IComparable<T>, IEquatable<T> { private T _value; public ConstrainedNumber(T value) { _value = value; } public static T operator +(ConstrainedNumber<T> x, ConstrainedNumber<T> y) { return (dynamic)x._value + y._value; } }

Y un pequeño programa de prueba para ir con eso:

class Program { static void Main(string[] args) { ConstrainedNumber<int> one = new ConstrainedNumber<int>(10); ConstrainedNumber<int> two = new ConstrainedNumber<int>(5); var three = one + two; Debug.Assert(three == 15); Console.ReadLine(); } }

¡Disfrutar!


He visto algunas soluciones potenciales que implican árboles de expresión, donde la expresión del operador se crea manualmente.

No es perfecto porque pierdes la verificación en tiempo de compilación, pero podría ser el truco para ti.

aquí hay un artículo sobre eso.


No existe soporte actual en los genéricos de .Net para indicar que los operadores son compatibles.

Esta es una característica frecuentemente solicitada.

Puede ser semi trabajado (ver MiscUtils ) pero esto no te dará la sintaxis que deseas


No hay restricciones disponibles para eso, pero hay una forma de evitar el problema:

public static T operator -(T foo, T bar) { return (T)System.Convert.ChangeType( System.Convert.ToDecimal(foo) - System.Convert.ToDecimal(bar), typeof(T)); }


No, esto no funciona. Pero hay algunas sugerencias sobre cómo resolver el problema. Hice lo siguiente (usando algunas ideas de diferentes fuentes en la red):

public delegate TResult BinaryOperator<TLeft, TRight, TResult>(TLeft left, TRight right); /// <summary> /// Provide efficient generic access to either native or static operators for the given type combination. /// </summary> /// <typeparam name="TLeft">The type of the left operand.</typeparam> /// <typeparam name="TRight">The type of the right operand.</typeparam> /// <typeparam name="TResult">The type of the result value.</typeparam> /// <remarks>Inspired by Keith Farmer''s code on CodeProject:<br/>http://www.codeproject.com/KB/cs/genericoperators.aspx</remarks> public static class Operator<TLeft, TRight, TResult> { private static BinaryOperator<TLeft, TRight, TResult> addition; private static BinaryOperator<TLeft, TRight, TResult> bitwiseAnd; private static BinaryOperator<TLeft, TRight, TResult> bitwiseOr; private static BinaryOperator<TLeft, TRight, TResult> division; private static BinaryOperator<TLeft, TRight, TResult> exclusiveOr; private static BinaryOperator<TLeft, TRight, TResult> leftShift; private static BinaryOperator<TLeft, TRight, TResult> modulus; private static BinaryOperator<TLeft, TRight, TResult> multiply; private static BinaryOperator<TLeft, TRight, TResult> rightShift; private static BinaryOperator<TLeft, TRight, TResult> subtraction; /// <summary> /// Gets the addition operator + (either native or "op_Addition"). /// </summary> /// <value>The addition operator.</value> public static BinaryOperator<TLeft, TRight, TResult> Addition { get { if (addition == null) { addition = CreateOperator("op_Addition", OpCodes.Add); } return addition; } } /// <summary> /// Gets the modulus operator % (either native or "op_Modulus"). /// </summary> /// <value>The modulus operator.</value> public static BinaryOperator<TLeft, TRight, TResult> Modulus { get { if (modulus == null) { modulus = CreateOperator("op_Modulus", OpCodes.Rem); } return modulus; } } /// <summary> /// Gets the exclusive or operator ^ (either native or "op_ExclusiveOr"). /// </summary> /// <value>The exclusive or operator.</value> public static BinaryOperator<TLeft, TRight, TResult> ExclusiveOr { get { if (exclusiveOr == null) { exclusiveOr = CreateOperator("op_ExclusiveOr", OpCodes.Xor); } return exclusiveOr; } } /// <summary> /// Gets the bitwise and operator &amp; (either native or "op_BitwiseAnd"). /// </summary> /// <value>The bitwise and operator.</value> public static BinaryOperator<TLeft, TRight, TResult> BitwiseAnd { get { if (bitwiseAnd == null) { bitwiseAnd = CreateOperator("op_BitwiseAnd", OpCodes.And); } return bitwiseAnd; } } /// <summary> /// Gets the division operator / (either native or "op_Division"). /// </summary> /// <value>The division operator.</value> public static BinaryOperator<TLeft, TRight, TResult> Division { get { if (division == null) { division = CreateOperator("op_Division", OpCodes.Div); } return division; } } /// <summary> /// Gets the multiplication operator * (either native or "op_Multiply"). /// </summary> /// <value>The multiplication operator.</value> public static BinaryOperator<TLeft, TRight, TResult> Multiply { get { if (multiply == null) { multiply = CreateOperator("op_Multiply", OpCodes.Mul); } return multiply; } } /// <summary> /// Gets the bitwise or operator | (either native or "op_BitwiseOr"). /// </summary> /// <value>The bitwise or operator.</value> public static BinaryOperator<TLeft, TRight, TResult> BitwiseOr { get { if (bitwiseOr == null) { bitwiseOr = CreateOperator("op_BitwiseOr", OpCodes.Or); } return bitwiseOr; } } /// <summary> /// Gets the left shift operator &lt;&lt; (either native or "op_LeftShift"). /// </summary> /// <value>The left shift operator.</value> public static BinaryOperator<TLeft, TRight, TResult> LeftShift { get { if (leftShift == null) { leftShift = CreateOperator("op_LeftShift", OpCodes.Shl); } return leftShift; } } /// <summary> /// Gets the right shift operator &gt;&gt; (either native or "op_RightShift"). /// </summary> /// <value>The right shift operator.</value> public static BinaryOperator<TLeft, TRight, TResult> RightShift { get { if (rightShift == null) { rightShift = CreateOperator("op_RightShift", OpCodes.Shr); } return rightShift; } } /// <summary> /// Gets the subtraction operator - (either native or "op_Addition"). /// </summary> /// <value>The subtraction operator.</value> public static BinaryOperator<TLeft, TRight, TResult> Subtraction { get { if (subtraction == null) { subtraction = CreateOperator("op_Subtraction", OpCodes.Sub); } return subtraction; } } private static BinaryOperator<TLeft, TRight, TResult> CreateOperator(string operatorName, OpCode opCode) { if (operatorName == null) { throw new ArgumentNullException("operatorName"); } bool isPrimitive = true; bool isLeftNullable; bool isRightNullable = false; Type leftType = typeof(TLeft); Type rightType = typeof(TRight); MethodInfo operatorMethod = LookupOperatorMethod(ref leftType, operatorName, ref isPrimitive, out isLeftNullable) ?? LookupOperatorMethod(ref rightType, operatorName, ref isPrimitive, out isRightNullable); DynamicMethod method = new DynamicMethod(string.Format("{0}:{1}:{2}:{3}", operatorName, typeof(TLeft).FullName, typeof(TRight).FullName, typeof(TResult).FullName), typeof(TResult), new Type[] {typeof(TLeft), typeof(TRight)}); Debug.WriteLine(method.Name, "Generating operator method"); ILGenerator generator = method.GetILGenerator(); if (isPrimitive) { Debug.WriteLine("Primitives using opcode", "Emitting operator code"); generator.Emit(OpCodes.Ldarg_0); if (isLeftNullable) { generator.EmitCall(OpCodes.Call, typeof(TLeft).GetMethod("op_Explicit", BindingFlags.Public|BindingFlags.Static), null); } IlTypeHelper.ILType stackType = IlTypeHelper.EmitWidening(generator, IlTypeHelper.GetILType(leftType), IlTypeHelper.GetILType(rightType)); generator.Emit(OpCodes.Ldarg_1); if (isRightNullable) { generator.EmitCall(OpCodes.Call, typeof(TRight).GetMethod("op_Explicit", BindingFlags.Public | BindingFlags.Static), null); } stackType = IlTypeHelper.EmitWidening(generator, IlTypeHelper.GetILType(rightType), stackType); generator.Emit(opCode); if (typeof(TResult) == typeof(object)) { generator.Emit(OpCodes.Box, IlTypeHelper.GetPrimitiveType(stackType)); } else { Type resultType = typeof(TResult); if (IsNullable(ref resultType)) { generator.Emit(OpCodes.Newobj, typeof(TResult).GetConstructor(new Type[] {resultType})); } else { IlTypeHelper.EmitExplicit(generator, stackType, IlTypeHelper.GetILType(resultType)); } } } else if (operatorMethod != null) { Debug.WriteLine("Call to static operator method", "Emitting operator code"); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldarg_1); generator.EmitCall(OpCodes.Call, operatorMethod, null); if (typeof(TResult).IsPrimitive && operatorMethod.ReturnType.IsPrimitive) { IlTypeHelper.EmitExplicit(generator, IlTypeHelper.GetILType(operatorMethod.ReturnType), IlTypeHelper.GetILType(typeof(TResult))); } else if (!typeof(TResult).IsAssignableFrom(operatorMethod.ReturnType)) { Debug.WriteLine("Conversion to return type", "Emitting operator code"); generator.Emit(OpCodes.Ldtoken, typeof(TResult)); generator.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle", new Type[] {typeof(RuntimeTypeHandle)}), null); generator.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] {typeof(object), typeof(Type)}), null); } } else { Debug.WriteLine("Throw NotSupportedException", "Emitting operator code"); generator.ThrowException(typeof(NotSupportedException)); } generator.Emit(OpCodes.Ret); return (BinaryOperator<TLeft, TRight, TResult>)method.CreateDelegate(typeof(BinaryOperator<TLeft, TRight, TResult>)); } private static bool IsNullable(ref Type type) { if (type.IsGenericType && (type.GetGenericTypeDefinition() == typeof(Nullable<>))) { type = type.GetGenericArguments()[0]; return true; } return false; } private static MethodInfo LookupOperatorMethod(ref Type type, string operatorName, ref bool isPrimitive, out bool isNullable) { isNullable = IsNullable(ref type); if (!type.IsPrimitive) { isPrimitive = false; foreach (MethodInfo methodInfo in type.GetMethods(BindingFlags.Static|BindingFlags.Public)) { if (methodInfo.Name == operatorName) { bool isMatch = true; foreach (ParameterInfo parameterInfo in methodInfo.GetParameters()) { switch (parameterInfo.Position) { case 0: if (parameterInfo.ParameterType != typeof(TLeft)) { isMatch = false; } break; case 1: if (parameterInfo.ParameterType != typeof(TRight)) { isMatch = false; } break; default: isMatch = false; break; } } if (isMatch) { if (typeof(TResult).IsAssignableFrom(methodInfo.ReturnType) || typeof(IConvertible).IsAssignableFrom(methodInfo.ReturnType)) { return methodInfo; // full signature match } } } } } return null; } } internal static class IlTypeHelper { [Flags] public enum ILType { None = 0, Unsigned = 1, B8 = 2, B16 = 4, B32 = 8, B64 = 16, Real = 32, I1 = B8, // 2 U1 = B8|Unsigned, // 3 I2 = B16, // 4 U2 = B16|Unsigned, // 5 I4 = B32, // 8 U4 = B32|Unsigned, // 9 I8 = B64, //16 U8 = B64|Unsigned, //17 R4 = B32|Real, //40 R8 = B64|Real //48 } public static ILType GetILType(Type type) { if (type == null) { throw new ArgumentNullException("type"); } if (!type.IsPrimitive) { throw new ArgumentException("IL native operations requires primitive types", "type"); } if (type == typeof(double)) { return ILType.R8; } if (type == typeof(float)) { return ILType.R4; } if (type == typeof(ulong)) { return ILType.U8; } if (type == typeof(long)) { return ILType.I8; } if (type == typeof(uint)) { return ILType.U4; } if (type == typeof(int)) { return ILType.I4; } if (type == typeof(short)) { return ILType.U2; } if (type == typeof(ushort)) { return ILType.I2; } if (type == typeof(byte)) { return ILType.U1; } if (type == typeof(sbyte)) { return ILType.I1; } return ILType.None; } public static Type GetPrimitiveType(ILType iLType) { switch (iLType) { case ILType.R8: return typeof(double); case ILType.R4: return typeof(float); case ILType.U8: return typeof(ulong); case ILType.I8: return typeof(long); case ILType.U4: return typeof(uint); case ILType.I4: return typeof(int); case ILType.U2: return typeof(short); case ILType.I2: return typeof(ushort); case ILType.U1: return typeof(byte); case ILType.I1: return typeof(sbyte); } throw new ArgumentOutOfRangeException("iLType"); } public static ILType EmitWidening(ILGenerator generator, ILType onStackIL, ILType otherIL) { if (generator == null) { throw new ArgumentNullException("generator"); } if (onStackIL == ILType.None) { throw new ArgumentException("Stack needs a value", "onStackIL"); } if (onStackIL < ILType.I8) { onStackIL = ILType.I8; } if ((onStackIL < otherIL) && (onStackIL != ILType.R4)) { switch (otherIL) { case ILType.R4: case ILType.R8: if ((onStackIL&ILType.Unsigned) == ILType.Unsigned) { generator.Emit(OpCodes.Conv_R_Un); } else if (onStackIL != ILType.R4) { generator.Emit(OpCodes.Conv_R8); } else { return ILType.R4; } return ILType.R8; case ILType.U8: case ILType.I8: if ((onStackIL&ILType.Unsigned) == ILType.Unsigned) { generator.Emit(OpCodes.Conv_U8); return ILType.U8; } if (onStackIL != ILType.I8) { generator.Emit(OpCodes.Conv_I8); } return ILType.I8; } } return onStackIL; } public static void EmitExplicit(ILGenerator generator, ILType onStackIL, ILType otherIL) { if (otherIL != onStackIL) { switch (otherIL) { case ILType.I1: generator.Emit(OpCodes.Conv_I1); break; case ILType.I2: generator.Emit(OpCodes.Conv_I2); break; case ILType.I4: generator.Emit(OpCodes.Conv_I4); break; case ILType.I8: generator.Emit(OpCodes.Conv_I8); break; case ILType.U1: generator.Emit(OpCodes.Conv_U1); break; case ILType.U2: generator.Emit(OpCodes.Conv_U2); break; case ILType.U4: generator.Emit(OpCodes.Conv_U4); break; case ILType.U8: generator.Emit(OpCodes.Conv_U8); break; case ILType.R4: generator.Emit(OpCodes.Conv_R4); break; case ILType.R8: generator.Emit(OpCodes.Conv_R8); break; } } } }

Úselo así: int i = Operator.Addition (3, 5);


Si no está utilizando muchos tipos que se usan como argumento genérico y quiere tener verificaciones en tiempo de compilación, entonces puede usar una solución que sea similar a la solución de Lucero.

Clase base

public class Arithmetic<T> { protected static readonly Func<T, T, T> OP_ADD; protected static readonly Func<T, T, T> OP_MUL; protected static readonly Func<T, T, T> OP_SUB; /* Define all operators you need here */ static Arithmetic() { Arithmetic<Single>.OP_ADD = (x, y) => x + y; Arithmetic<Single>.OP_MUL = (x, y) => x * y; Arithmetic<Single>.OP_SUB = (x, y) => x - y; Arithmetic<Double>.OP_ADD = (x, y) => x + y; Arithmetic<Double>.OP_MUL = (x, y) => x * y; Arithmetic<Double>.OP_SUB = (x, y) => x - y; /* This could also be generated by a tool */ } }

Uso

public class Vector2<T> : Arithmetic<T> { public T X; public T Y; public static Vector2<T> operator +(Vector2<T> a, Vector2<T> b) { return new Vector2<T>() { X = OP_ADD(a.X, b.X), Y = OP_ADD(a.Y, b.Y) }; } public static Vector2<T> operator -(Vector2<T> a, Vector2<T> b) { return new Vector2<T>() { X = OP_SUB(a.X, b.X), Y = OP_SUB(a.Y, b.Y) }; } public static Vector2<T> operator *(Vector2<T> a, Vector2<T> b) { return new Vector2<T>() { X = OP_MUL(a.X, b.X), Y = OP_MUL(a.Y, b.Y) }; } }


Si tuviera que hacer algo como esto, probablemente lo abordaría según las líneas de

public class ConstrainedNumber<T> { private T Value { get; } public ConstrainedNumber(T value) { Value = value; } private static Func<ConstrainedNumber<T>, ConstrainedNumber<T>, T> _addFunc; // Cache the delegate public static ConstrainedNumber<T> operator+(ConstrainedNumber<T> left, ConstrainedNumber<T> right) { var adder = _addFunc; if (adder == null) { ParameterExpression lhs = Expression.Parameter(typeof(ConstrainedNumber<T>)); ParameterExpression rhs = Expression.Parameter(typeof(ConstrainedNumber<T>)); _addFunc = adder = Expression.Lambda<Func<ConstrainedNumber<T>, ConstrainedNumber<T>, T>>( Expression.Add( Expression.Property(lhs, nameof(Value)), Expression.Property(lhs, nameof(Value)) ), lhs, rhs).Compile(); } return new ConstrainedNumber<T>(adder(left, right)); } }

El resultado final es un poco como el resultado final del enfoque dynamic , que de hecho terminará haciendo algo como esto pero con un poco más de sobrecarga, y debería funcionar para cualquier T que sea una primitiva aritmética o que tenga un operador + definido para ello. El único caso que el enfoque dynamic manejaría de manera diferente es que funcionaría para una string mientras que esto no funcionaría. Si eso es bueno o malo depende del caso de uso, pero si fuera necesario, la string podría tener una envoltura especial.