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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/756954/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-05 00:01:17  来源:igfitidea点击:

Arithmetic operator overloading for a generic class in C#

c#genericsoperator-overloadingmathprimitive-types

提问by Triynko

Given a generic class definition like

给定一个通用类定义,如

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

How can I define arithmetic operators for it?

如何为其定义算术运算符?

The following does not compile, because the '+' operator cannot be applied to types 'T' and 'T':

以下内容无法编译,因为“+”运算符不能应用于类型“T”和“T”:

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

The generic type 'T' is constrained with the 'where' keyword as you can see, but I need a constraint for number types that have arithmetic operators (IArithmetic?).

如您所见,泛型类型“T”受“where”关键字约束,但我需要对具有算术运算符(IArithmetic?)的数字类型进行约束。

'T' will be a primitive number type such as int, float, etc. Is there a 'where' constraint for such types?

“T”将是原始数字类型,例如 int、float 等。此类类型是否有“where”约束?

采纳答案by Daniel Schaffer

I think the best you'd be able to do is use IConvertibleas a constraint and do something like:

我认为您能做的最好的事情就是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); }
}

That won't stop someone from passing in a String or DateTime though, so you might want to do some manual checking - but IConvertible should get you close enough, and allow you to do the operation.

不过,这不会阻止某人传入 String 或 DateTime,因此您可能需要进行一些手动检查 - 但 IConvertible 应该让您足够接近,并允许您进行操作。

回答by Andrew Hare

Unfortunately there is no way to constrain a generic parameter to be an integral type (Edit:I guess "arithmetical type" might be a better word as this does not pertain to just integers).

不幸的是,没有办法将泛型参数限制为整数类型(编辑:我猜“算术类型”可能是一个更好的词,因为这不仅仅与整数有关)。

It would be nice to be able to do something like this:

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

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

or

或者

where T : IArithmetic

I would suggest that you read Generic Operatorsby our very own Marc Gravell and Jon Skeet. It explains why this is such a difficult problem and what can be done to work around it.

我建议您阅读我们自己的 Marc Gravell 和 Jon Skeet 的Generic Operators。它解释了为什么这是一个如此困难的问题以及可以做些什么来解决它。

.NET 2.0 introduced generics into the .NET world, which opened the door for many elegant solutions to existing problems. Generic constraints can be used to restrict the type-arguments to known interfaces etc, to ensure access to functionality - or for simple equality/inequality tests the Comparer.Default and EqualityComparer.Default singletons implement IComparer and IEqualityComparer respectively (allowing us to sort elements for instance, without having to know anything about the "T" in question).

With all this, though, there is still a big gap when it comes to operators. Because operators are declared as static methods, there is no IMath or similar equivalent interface that all the numeric types implement; and indeed, the flexibility of operators would make this very hard to do in a meaningful way. Worse: many of the operators on primitive types don't even exist as operators; instead there are direct IL methods.[emphasis mine] To make the situation even more complex, Nullable<> demands the concept of "lifted operators", where the inner "T" describes the operators applicable to the nullable type - but this is implemented as a language feature, and is not provided by the runtime (making reflection even more fun).

.NET 2.0 将泛型引入 .NET 世界,为现有问题的许多优雅解决方案打开了大门。通用约束可用于将类型参数限制为已知接口等,以确保对功能的访问 - 或者对于简单的相等/不等式测试,Comparer.Default 和 EqualityComparer.Default 单例分别实现 IComparer 和 IEqualityComparer(允许我们对元素进行排序)例如,无需了解有关“T”的任何信息)。

尽管如此,在运营商方面仍然存在很大差距。因为运算符被声明为静态方法,所以没有所有数字类型都实现的 IMath 或类似的等效接口;事实上,运营商的灵活性会使这很难以有意义的方式完成。更糟糕的是:许多原始类型的运算符甚至不作为运算符存在;相反,有直接的 IL 方法。[强调我的] 为了使情况更加复杂,Nullable<> 需要“提升运算符”的概念,其中内部“T”描述适用于可空类型的运算符 - 但这是作为语言特性实现的,并且是运行时不提供(使反射更有趣)。

回答by Mehrdad Afshari

Unfortunately, this is not possible as there is not an IArithmetic(as you said) interface defined for integers. You can wrap those primitive types in classes that do implement such an interface.

不幸的是,这是不可能的,因为没有IArithmetic(如您所说)为整数定义的接口。您可以将这些原始类型包装在实现此类接口的类中。

回答by Lucero

No, this does not work. But there are some suggestions on how to solve the problem. I did the following (using some ideas from different sources on the 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;
            }
        }
    }
}

Use like this: int i = Operator.Addition(3, 5);

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

回答by ShuggyCoUk

There is no current support in .Net generics to indicate that operators are supported.

.Net 泛型当前不支持指示支持运算符。

This is an oft requested feature.

这是一个经常要求的功能。

It can be semi worked around (see MiscUtils) but this will not give you the syntax you desire

它可以半解决(请参阅 MiscUtils),但这不会为您提供所需的语法

回答by Denis Troller

I have seen some potential solutions involving expression trees, where the operator expression is created manually.

我见过一些涉及表达式树的潜在解决方案,其中操作符表达式是手动创建的。

It's not perfect because you lose compile-time verification, but it might do the trick for you.

它并不完美,因为您失去了编译时验证,但它可能对您有用。

here'san article about that.

是一篇关于此的文章。

回答by mbillard

There aren't constraints available for that but there is a way to get around the problem:

没有可用的限制,但有一种方法可以解决这个问题:

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

回答by George R

I just did this after looking here. The Vector4<T> class contains 4 numbers/axis of type T with the usual vector math. Just add 2 implicit ops to convert to and from Decimal. This is probably as un-verbose as you're going to get, but as you point out, more precise and thus heavier than it needs to be. Like you guys, I wish there was an INumeric or something!

我只是在看这里之后才这样做的。Vector4<T> 类包含 4 个 T 类型的数字/轴,带有通常的向量数学。只需添加 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)));
}

回答by RichardOD

In C# 4.0 you can use dynamicto get round this limitation. I had a look at your code, and managed to produce a working (albeit cutdown) version:

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

And a little test program to go with it:

还有一个小测试程序来配合它:

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

Enjoy!

享受!

回答by Felix K.

If you are not using to much types which are used as generic argument and want to have compile-time checks, then you can use a solution which is similar to Lucero's solution.

如果您不使用太多用作泛型参数的类型并希望进行编译时检查,那么您可以使用类似于 Lucero 的解决方案的解决方案。

Base class

基类

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

Usage

用法

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