33 votes

Surcharge d'opérateur arithmétique pour une classe générique en C #

Donné une définition de classe générique comme

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

Comment puis-je définir des opérateurs arithmétiques pour elle?

Le suivant ne compile pas, parce que l'opérateur " + "ne peut pas être appliquée à des types" T " et "T":

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

Le générique de type "T", est contraint à l' " où " mot-clé comme vous pouvez le voir, mais j'ai besoin d'une contrainte pour le nombre de types qui ont des opérateurs arithmétiques (IArithmetic?).

"T" sera une primitive numéro de type int, float, etc. Est-il un " où " contrainte pour ces types?

17voto

Daniel Schaffer Points 14707

Je pense que le mieux que vous puissiez faire est d'utiliser IConvertible comme contrainte et de faire quelque chose comme:

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

Cela n'empêchera cependant pas quelqu'un de passer une chaîne ou une date, alors vous voudrez peut-être faire une vérification manuelle - mais IConvertible devrait vous rapprocher suffisamment et vous permettre de faire l'opération.

15voto

Andrew Hare Points 159332

Malheureusement, il n'existe aucun moyen de contraindre un paramètre générique d'un type intégral (Edit: je suppose que "arithmétique de type" pourrait être un meilleur mot que cela ne concerne pas seulement les entiers).

Il serait agréable d'être en mesure de faire quelque chose comme ceci:

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

ou

where T : IArithmetic

Je voudrais vous suggérer de lire les Opérateurs Génériques par notre propre Marc Gravel et Jon Skeet. Il explique pourquoi c'est un problème difficile et ce qui peut être fait de travailler autour d'elle.

.NET 2.0 a introduit des génériques dans le .NET monde, qui a ouvert la porte pour beaucoup d'élégance des solutions existantes problèmes. Contraintes génériques peuvent être utilisé pour restreindre le type d'arguments connu des interfaces, etc, afin d'assurer l'accès à la fonctionnalité ou pour un simple l'égalité/inégalité des tests de la Comparer.Par défaut et EqualityComparer.Par défaut les singletons de mettre en œuvre IComparer et IEqualityComparer respectivement (ce qui nous permet de trier les éléments pour exemple, sans avoir à connaître rien sur les "T" en question).

Avec tout cela, cependant, il ya encore un grand écart quand il s'agit pour les opérateurs. Parce que les opérateurs sont déclarées comme les méthodes statiques, il n'y a pas de IMath ou similaire équivalent interface tous les types numériques mise en œuvre; et en effet, la flexibilité des opérateurs qui pourrait la rendre très difficiles à faire dans un de façon significative. Pire: beaucoup de les opérateurs sur les types primitifs ne sont pas même exister en tant qu'opérateurs; au lieu de cela, il y direct IL des méthodes. [c'est moi qui souligne] Pour rendre l' la situation encore plus complexe, Nullable<> exigences du concept de "levé les opérateurs", où l'intérieure "T" décrit les opérateurs applicables pour le type nullable - mais ce n'est mis en œuvre comme un langage, et n'est pas fourni par le moteur d'exécution (décisions la réflexion encore plus de plaisir).

13voto

RichardOD Points 19942

En C # 4.0, vous pouvez utiliser dynamic pour contourner cette limitation. J'ai jeté un coup d'œil à votre code et j'ai réussi à produire une version fonctionnelle (quoique réduite):

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

Et un petit programme de test pour l'accompagner:

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

Prendre plaisir!

4voto

Lucero Points 38928

Non, cela ne fonctionne pas. Mais il y a quelques suggestions sur la façon de résoudre le problème. J'ai fait ce qui suit (en utilisant quelques idées de différentes sources sur le net):

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

Utilisez comme ceci: int i = Operator.Addition (3, 5);

2voto

mbillard Points 15829

Il n'y a pas de contraintes disponibles pour cela, mais il existe un moyen de contourner le problème:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X