【问题标题】:Arithmetic operator overloading for a generic class in C#C#中泛型类的算术运算符重载
【发布时间】:2010-10-19 21:47:53
【问题描述】:

给定一个像

这样的通用类定义
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' 约束?

【问题讨论】:

标签: c# generics operator-overloading math primitive-types


【解决方案1】:

我认为您能做的最好的事情就是使用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 应该让您足够接近,并允许您执行操作。

【讨论】:

  • 我实际上将它更改为 Double,因为它是最大且精度最高的数字类型,然后它被装箱回 T 是什么。这应该可行,但我不保证它的性能:D
  • 是的,但是您必须针对每种类型专门进行此操作。这有点老套,是的,但它应该可以为您提供所有内容。
  • Argh 停止删除你的 cmets :D
  • 好的! +不涉及编写新代码。 +原始数字类型都使用 IConvertible。 +子类始终将“T”指定为原始数字,因此没有错误风险。 + where 约束不涉及对接口的强制转换,因此“ToDouble”与非泛型一样快,并且 ToDouble 实现通常返回“this”。
  • 对匆忙发表的评论感到抱歉。我正在分析这个答案的利弊。如果使用 ConstrainNumber 的子类始终将 T 指定为数字,则不需要类型检查和错误处理,这是一个巨大的优势。我看到的唯一缺点是所需的最高精度算术和装箱。
【解决方案2】:

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

如果能做这样的事情就好了:

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 方法。 [强调我的] 情况更加复杂, Nullable 需要的概念 “解除运营商”,其中内部 “T”描述适用的运算符 到可空类型 - 但这是 实现为语言功能,以及 不是由运行时提供的(使 反思更有趣)。

【讨论】:

  • 那篇文章几乎完全由 Marc Gravell 撰写,事实上,他还在 MiscUtil 中编写了帮助类。
  • "...没有 IMath 或所有数字类型都实现的类似等效接口。"实际上,所有数字类型都实现了 IConvertible,对于所有数字类型(byte、sbyte、short、ushort、int、uint、long、ulong、float、double、decimal),它都会在编译时通过以下方式进行简单的隐式或显式转换在我的算术重载中调用 IConvertible.ToDecimal(内部为 Convert.ToDecimal)。十进制类型足够精确,可以存储所有其他类型,包括 Int64(约 20 个有效数字)。这在硬件中支持十进制算术的平台上是完美的。
【解决方案3】:

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

享受吧!

【讨论】:

  • 给未来读者的说明,将值类型转换为 dynamic 会导致装箱分配,如果您的代码对性能至关重要,这可能(将会?)产生影响。
【解决方案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);

【讨论】:

  • 一种有趣的方法,但看起来它属于 C# 编译器,而不是 C# 程序。看起来这是手动构建 IArithmetic 类的自动化版本,以及它的实现,例如 IntArithmetic:IArithmetic 等,它们将提供编译时检查。
  • 到目前为止,我已经多次后悔泛型类型约束没有更灵活。两件事:指定方法签名,例如通过隐式接口映射,会很棒(但我理解这方面的技术挑战)。其次,为原语等提供更多 BCL 接口。
  • (cmets 太短 ;) ) 事实上,这是一种克服这些限制的方法。但他们显然无法进行编译时检查,除非与 PostSharp 等工具集结合直接注入“好”代码并抱怨无效组合。
【解决方案5】:

没有可用的限制,但有办法解决这个问题:

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

【讨论】:

    【解决方案6】:

    如果您没有使用太多用作泛型参数的类型并且想要进行编译时检查,那么您可以使用类似于 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)
            };
        }
    }
    

    【讨论】:

      【解决方案7】:

      .Net 泛型目前不支持表示支持运算符。

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

      它可以半解决(见MiscUtils),但这不会给你你想要的语法

      【讨论】:

        【解决方案8】:

        我只是在看了这里之后才这样做的。 Vector4 类包含 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))); }

        【讨论】:

          【解决方案9】:

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

          【讨论】:

          • 这应该叫做'casting gore'
          【解决方案10】:

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

          【讨论】:

            【解决方案11】:

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

            它并不完美,因为您丢失了编译时验证,但它可能会为您解决问题。

            here's 一篇关于此的文章。

            【讨论】:

              【解决方案12】:

              如果我不得不做这样的事情,我可能会按照以下方式处理它

              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 可以是特殊情况。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-05-01
                • 2020-03-10
                • 1970-01-01
                • 2015-05-23
                • 2016-01-25
                • 2017-06-07
                • 2017-04-28
                相关资源
                最近更新 更多