在C#中为泛型类重载算术运算符

35

假设有一个通用的类定义如下:

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'约束?


12
作为一门语言,C# 的一个巨大缺点就是泛型算术运算的问题。似乎泛型算术运算是一个巨大难题。参考链接:http://social.msdn.microsoft.com/Forums/en-US/csharpgeneral/thread/1b7a80ea-34f3-428c-a469-9be1ec6e60ac、http://www.codeproject.com/KB/cs/genericnumerics.aspx而在 C++ 中则不存在这个问题。 - Triynko
4
C# 的泛型和 C++ 的模板不同(我认为模板是一种编译器宏)。C# 的泛型在运行时解析,而 C++ 的模板在编译时解析,这就是为什么 C++ 编译器可以解析这些操作符(它可以在编译时验证模板类型是否实现了该操作符),而 C# 不能的原因。 - oɔɯǝɹ
C# 11 / .NET 7 给我们带来了这个新特性:"... where T : INumber<T>" ... - undefined
13个回答

17

我认为你最好使用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); }
}

然而,这并不能阻止某些人传递字符串或日期时间。因此,您可能需要进行一些手动检查 - 但是使用IConvertible应该足以让您接近目标,并允许您执行操作。


是的,但那样你就必须为每种类型单独做。这有点hacky,没错,但应该可以覆盖所有东西。 - Daniel Schaffer
9
啊,停止删除你的评论 :D。 - Daniel Schaffer
实际上,我可以通过让运算符返回 double 来避免装箱,因为使用运算符的算术表达式可以根据需要处理强制转换(而不需要装箱)。在任何情况下,我都不必将其转换回 ConstrainedNumber<T>,直到赋值点,在这里限制才有影响。 - Triynko
就性能而言,我不担心ToDouble。它只是调用Convert.ToDouble(int x)的静态类型特定重载,该重载仅返回x,因为它是隐式转换!由于我们使用“where”将T限制为IConvertible,所以这是一个编译时检查,因此ToDouble是一个标准调用。 - Triynko
很抱歉,但是这段代码是否能编译通过呢:public static operator T +(T x, T y) where T: IConvertible?T应该是类的泛型参数,并且这两个参数中至少有一个必须是封闭类的类型,而不是T,即ConstrainedNumber<T>。如果您成功地使非泛型类的运算符重载成为泛型的,我会非常感兴趣听听您的经验。 - Stelios Adamantidis
显示剩余5条评论

15
很遗憾,目前没有办法限制泛型参数为整数类型(编辑:我猜“算术类型”可能是一个更好的词,因为这不仅仅适用于整数)。
能够像这样做一些事情会很好:
where T : integral // or "arithmetical" depending on how pedantic you are

或者

where T : IArithmetic

我建议你阅读我们自己的Marc Gravell和Jon Skeet所写的通用运算符。它解释了为什么这是一个如此棘手的问题,以及可以采取哪些方法来解决它。
.NET 2.0在.NET世界中引入了泛型,为现有问题提供了许多优雅的解决方案。泛型约束可以用于限制类型参数为已知接口等,以确保访问功能 - 或者对于简单的相等/不相等测试,Comparer.Default和EqualityComparer.Default单例实现了IComparer和IEqualityComparer(例如,允许我们对元素进行排序,而无需了解有关所讨论的“T”的任何信息)。
尽管如此,当涉及到操作符时,仍然存在一个巨大的差距。因为操作符被声明为静态方法,所以没有IMath或类似的等效接口,所有数值类型都实现了该接口;而且,操作符的灵活性使得以有意义的方式实现这一点非常困难。更糟糕的是,许多原始类型的操作符甚至不存在作为操作符;而是直接使用IL方法。[强调我的]为了使情况更加复杂,Nullable<>要求“提升操作符”的概念,其中内部的“T”描述了适用于可空类型的操作符 - 但这是作为一种语言特性实现的,并不由运行时提供(使反射变得更加有趣)。

4
实际上,那篇文章几乎完全是由Marc Gravell撰写的,他还编写了MiscUtil中的助手类。 - Jon Skeet
1
实际上,所有数字类型都实现了IConvertible接口,而没有IMath或类似的等效接口。对于所有数字类型(byte、sbyte、short、ushort、int、uint、long、ulong、float、double、decimal),这会在编译时通过调用IConvertible.ToDecimal(内部Convert.ToDecimal)在我的算术重载中产生简单的隐式或显式转换。十进制类型足够精确,可以存储所有其他类型,包括Int64(约20个有效数字)。在支持硬件十进制算术的平台上,这是完美的。 - Triynko
现在已经存在这样的界面:https://stackoverflow.com/a/74796391/14637 - undefined

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

享受吧!


2
给未来读者的提示:将值类型强制转换为 dynamic 会导致装箱分配,如果你的代码对性能很关键,这可能会(或将)产生影响。 - Stelios Adamantidis

6

C# 11 / .NET 7 给我们带来了这个:

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

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


1
一个有趣的方法,但它看起来更适合于C#编译器,而不是C#程序。它似乎是手动构建IArithmetic<T>类及其实现(例如IntArithmetic:IArithmetic<int>等)的自动化版本,这将提供编译时检查。 - Triynko
1
到目前为止,我已经几次后悔通用类型约束不够灵活。有两个问题:一是指定方法签名,例如通过隐式接口映射,这将是很好的(但我理解这方面存在的技术挑战)。第二,更多针对基元类型等的BCL接口。 - Lucero
确实,这是一种克服某些限制的方法。但是,除非与像PostSharp这样的工具集结合使用以直接注入“好”的代码并抱怨无效组合,否则它们显然无法产生编译时检查。 - Lucero

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

1

这样怎么样,朋友们(使用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;
    }
}

2
这应该被称为“类型转换的恐怖”。 - NucS

1

目前没有可用的约束条件,但是有一种解决问题的方法:

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

现在有一些限制可用:https://stackoverflow.com/a/74796391/14637 - undefined

1

在这里看过之后,我刚刚完成了这个。Vector4<T>类包含类型为T的4个数字/轴,具有通常的向量数学。只需添加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>(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))); }

0

.Net泛型目前不支持指示支持运算符的操作。

这是一个常被请求的功能。

可以通过半工作方式解决(请参见MiscUtils),但这无法给您所需的语法。


这个答案已经不再正确,请参考:https://stackoverflow.com/a/74796391/14637 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接