简体   繁体   English

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

[英]Arithmetic operator overloading for a generic class in C#

Given a generic class definition like给定一个通用的 class 定义,例如

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

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 Operators by our very own Marc Gravell and Jon Skeet. 我建议您通过我们自己的Marc Gravell和Jon Skeet阅读Generic Operators It explains why this is such a difficult problem and what can be done to work around it. 它解释了为什么这是一个如此困难的问题,以及可以采取哪些措施来解决它。

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

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; 因为运算符被声明为静态方法,所以没有所有数值类型实现的IMath或类似的等效接口; 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. 相反,有直接的IL方法。 [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). [强调我的]为了使情况更加复杂,Nullable <>要求“提升运算符”的概念,其中内部“T”描述适用于可空类型的运算符 - 但这是作为语言特征实现的,并且是不是由运行时提供的(使反射更有趣)。

I think the best you'd be able to do is use IConvertible as 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应该让你足够接近,并允许你进行操作。

In C# 4.0 you can use dynamic to get round this limitation. 在C#4.0中,您可以使用动态来绕过此限制。 I had a look at your code, and managed to produce a working (albeit cutdown) version: 我查看了你的代码,并设法生成了一个工作(虽然削减)版本:

 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! 请享用!

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

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

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

How about this friends (using the RTTI and the object class) 这个朋友怎么样(使用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;
    }
}

I just did this after looking here. 我看完之后才这样做了。 The Vector4<T> class contains 4 numbers/axis of type T with the usual vector math. Vector4 <T>类包含4个数字/轴类型T和通常的矢量数学。 Just add 2 implicit ops to convert to and from Decimal. 只需添加2个隐式操作即可转换为Decimal和来自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! 像你们一样,我希望有一个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)));
}

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 ),但这不会给你你想要的语法

If I had to do something like this I'd probably approach it along the lines of 如果我不得不做这样的事情,我可能会按照这样的方式来做

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

The end result is a bit like the end result of the dynamic approach, which will in fact end up internally doing something like this but with a bit more overhead, and should work for any T that is either an arithmetic primitive or has a + operator defined for it. 最终的结果有点像dynamic方法的最终结果,实际上最终会在内部执行类似这样的操作,但会有更多的开销,并且应该适用于任何T算术原语或具有+运算符的T为它定义。 The one case that the dynamic approach would handle differently is that it would work for string while this wouldn't. dynamic方法处理不同的一种情况是它可以用于string而这不会。 Whether that's a good or bad thing depends on the use case but if it was needed string could be special-cased. 这是好事还是坏事取决于用例,但如果需要, string可以是特殊的。

C# 11 / .NET 7 gives us this: 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;
    }
}

Unfortunately, this is not possible as there is not an IArithmetic (as you said) interface defined for integers. 不幸的是,这是不可能的,因为没有为整数定义的IArithmetic (如你所说)接口。 You can wrap those primitive types in classes that do implement such an interface. 您可以将这些原始类型包装在实现此类接口的类中。

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's an article about that. 是一篇关于此的文章。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM