给出类似
的泛型类定义public class ConstrainedNumber<T> :
IEquatable<ConstrainedNumber<T>>,
IEquatable<T>,
IComparable<ConstrainedNumber<T>>,
IComparable<T>,
IComparable where T:struct, IComparable, IComparable<T>, IEquatable<T>
如何为它定义算术运算符?
以下内容无法编译,因为'+'运算符不能应用于类型'T'和'T':
public static T operator +( ConstrainedNumber<T> x, ConstrainedNumber<T> y)
{
return x._value + y._value;
}
如您所见,泛型类型'T'受'where'关键字约束,但我需要对具有算术运算符的数字类型进行约束(IArithmetic?)。
'T'将是一个原始数字类型,如int,float等。这些类型是否存在'where'约束?
答案 0 :(得分:15)
不幸的是,没有办法将泛型参数约束为整数类型(编辑:我猜“算术类型”可能是一个更好的词,因为这不仅仅是整数)。 / p>
能够做到这样的事情会很高兴:
where T : integral // or "arithmetical" depending on how pedantic you are
或
where T : IArithmetic
我建议您阅读我们自己的Marc Gravell和Jon Skeet的Generic Operators。它解释了为什么这是一个如此困难的问题,可以采取哪些措施来解决它。
.NET 2.0将泛型引入 .NET世界,打开了大门 现有许多优雅的解决方案 问题。通用约束可以是 用于将类型参数限制为 已知的接口等,以确保访问 功能 - 或简单 平等/不平等测试 Comparer.Default和 EqualityComparer.Default 单身人士实施IComparer和 IEqualityComparer分别为 (允许我们对元素进行排序 例如,不必知道 关于“T”的任何事情。)
尽管如此,仍有 在运营商方面存在很大差距。 因为运算符被声明为 静态方法,没有IMath 或类似的等效接口 所有数字类型实现;和 的确,运营商的灵活性 会让这很难做到 有意义的方式更糟糕的是:很多 原始类型的运算符不会 甚至作为运营商存在;而是在那里 是直接的IL方法。 [强调我的]使 情况更复杂, 可空&LT;&GT;要求的概念 “解除运营商”,内在的地方 “T”描述了适用的运算符 到可空类型 - 但这是 实现为语言功能,和 运行时没有提供(制作 反思更有趣)。
答案 1 :(得分:15)
我认为你能做的最好的事情就是使用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或DateTime,所以你可能想要做一些手动检查 - 但IConvertible应该让你足够接近,并允许你进行操作。
答案 2 :(得分:12)
在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;
}
}
还有一个小测试程序:
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();
}
}
享受!
答案 3 :(得分:4)
不,这不起作用。但是有一些关于如何解决问题的建议。我做了以下(使用网上不同来源的一些想法):
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 & (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 << (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 >> (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);
答案 4 :(得分:2)
没有可用的限制,但有办法解决问题:
public static T operator -(T foo, T bar)
{
return (T)System.Convert.ChangeType(
System.Convert.ToDecimal(foo)
-
System.Convert.ToDecimal(bar),
typeof(T));
}
答案 5 :(得分:2)
如果您没有使用很多用作泛型参数的类型并希望进行编译时检查,那么您可以使用类似于Lucero解决方案的解决方案。
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 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)
};
}
}
答案 6 :(得分:1)
答案 7 :(得分:1)
我看完之后就这样做了。 Vector4&lt; T&gt; class包含4个数字/ T类型的T,带有通常的矢量数学。只需添加2个隐式操作即可转换为Decimal和来自Decimal。这可能与你将会得到的一样简洁,但正如你所指出的那样,它比你需要的更精确,更重。和你们一样,我希望有一个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>(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)));
}
答案 8 :(得分:0)
不幸的是,这是不可能的,因为没有为整数定义IArithmetic
(如你所说)接口。您可以将这些原始类型包装在实现此类接口的类中。
答案 9 :(得分:0)
答案 10 :(得分:0)
这个朋友怎么样(使用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;
}
}
答案 11 :(得分:0)
如果我必须做这样的事情,我可能会按照
的方式处理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
而不是string
。这是好事还是坏事取决于用例,但如果需要> cat /etc/redhat-release
Red Hat Enterprise Linux Server release 6.7 (Santiago)
可能是特殊情况。