在C#中的generics类的算术运算符重载

给定一个通用的类定义

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

我怎样才能为它定义算术运算符?

以下不编译,因为“+”运算符不能应用于types“T”和“T”:

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

genericstypes'T'受到'where'关键字的约束,正如你所看到的,但是我需要一个具有算术运算符(IAalgorithm)的数字types的约束。

'T'将是一个原始数字types,如int,float等。这种types是否存在“where”约束?

我认为你能够做的最好的事情就是使用IConvertible作为约束,并且做一些事情:

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

这不会阻止某人传递string或date时间,所以你可能想要做一些手动检查 – 但IConvertible应该让你足够接近,并允许你做这个操作。

不幸的是,没有办法约束generics参数是一个整型( 编辑:我想“算术types”可能是一个更好的词,因为这不仅仅是整数)。

能够做到这样的事情会很好:

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

要么

 where T : IArithmetic 

我build议你阅读我们自己的Marc Gravell和Jon Skeet的generics操作符 。 这解释了为什么这是一个如此困难的问题,可以做些什么来解决这个问题。

.NET 2.0将generics引入了.NET世界,为现有问题提供了许多优雅的解决scheme。 generics约束可用于限制types参数到已知接口等,以确保访问function – 或者对于简单的相等/不等式testing,Comparer.Default和EqualityComparer.Default单例分别实现IComparer和IEqualityComparer(允许我们为实例,而不必知道有关“T”的问题)。

尽pipe如此,运营商仍然存在很大差距。 因为运算符被声明为静态方法,所以没有IMath或类似的所有数值types实现的接口。 事实上,运营商的灵活性将会使这项工作变得非常困难。 更糟的是: 原始types中的许多运算符甚至不存在运算符; 相反,有直接的IL方法。 为了使情况更加复杂,Nullable <>要求“提升运算符”的概念,其中内部“T”描述适用于可空types的运算符 – 但是这是作为语言特征实现的不是由运行时提供(使reflection更有趣)。

在C#4.0中,您可以使用dynamic来获取此限制。 我看了一下你的代码,并设法产生一个工作(虽然缩减)的版本:

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

和一个小testing程序一起去吧:

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

请享用!

不,这不行。 但是如何解决这个问题还有一些build议。 我做了以下(使用网上不同来源的一些想法):

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

像这样使用:int i = Operator.Addition(3,5);

没有可用的约束,但有一种方法来解决这个问题:

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

如果你没有使用太多的types作为generics参数,并且想要进行编译时检查,那么你可以使用类似于Lucero解决scheme的解决scheme。

基础类

 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 */ } } 

用法

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

.Net的generics没有当前的支持来表明运营商是受支持的。

这是一个经常要求的function。

它可以半工作(见MiscUtils ),但是这不会给你你想要的语法

看了这里之后我就做了这个。 Vector4 <T>类包含4个数字/ T型轴,并具有通常的vectormath运算。 只需添加2个隐式操作来转换为和从十进制。 这可能与你将要得到的一样冗长,但是正如你所指出的那样,更精确,因此比它所需要的更重。 像你们,我希望有一个INumeric或什么的!

public static Vector4<T> operator +(Vector4<T> a, Vector4<T> b) { Vector4<Decimal> A = a; Vector4<Decimal> B = b; var result = new Vector4<Decimal>(AX + BX, AY + BY, AZ + BZ, AW + BW); return result; } public static implicit operator Vector4<Decimal>(Vector4<T> v) { return new Vector4<Decimal>( Convert.ToDecimal(vX), Convert.ToDecimal(vY), Convert.ToDecimal(vZ), Convert.ToDecimal(vW)); } public static implicit operator Vector4<T>(Vector4<Decimal> v) { return new Vector4<T>( (T)Convert.ChangeType(vX, typeof(T)), (T)Convert.ChangeType(vY, typeof(T)), (T)Convert.ChangeType(vZ, typeof(T)), (T)Convert.ChangeType(vW, typeof(T))); }
public static Vector4<T> operator +(Vector4<T> a, Vector4<T> b) { Vector4<Decimal> A = a; Vector4<Decimal> B = b; var result = new Vector4<Decimal>(AX + BX, AY + BY, AZ + BZ, AW + BW); return result; } public static implicit operator Vector4<Decimal>(Vector4<T> v) { return new Vector4<Decimal>( Convert.ToDecimal(vX), Convert.ToDecimal(vY), Convert.ToDecimal(vZ), Convert.ToDecimal(vW)); } public static implicit operator Vector4<T>(Vector4<Decimal> v) { return new Vector4<T>( (T)Convert.ChangeType(vX, typeof(T)), (T)Convert.ChangeType(vY, typeof(T)), (T)Convert.ChangeType(vZ, typeof(T)), (T)Convert.ChangeType(vW, typeof(T))); } 

这个朋友怎么样(使用RTTI和对象类)

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

不幸的是,这是不可能的,因为没有一个IArithmetic (如你所说)为整数定义的接口。 你可以把这些原始types封装在实现这种接口的类中。

我已经看到一些涉及expression式树的潜在解决scheme,其中运算符expression式是手动创build的。

这不是完美的,因为你失去了编译时validation,但它可能会为你做的伎俩。

这里有一篇关于这个的文章。

如果我不得不做这样的事情,我可能会沿着它的方向走

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

最终的结果有点像dynamic方法的最终结果,实际上最终会在内部做这样的事情,但是会有更多的开销,并且应该适用于任何一个算术基元或者有一个+运算符的T为它定义。 dynamic方法处理不同的一种情况是,它可以用于string而不会。 不pipe是好还是坏都取决于用例,但是如果需要的话, string可以是特殊的。