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

时间:2009-04-16 16:33:20

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

给出类似

的泛型类定义
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 个答案:

答案 0 :(得分:15)

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

能够做到这样的事情会很高兴:

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

where T : IArithmetic

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

  

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

     

尽管如此,仍有   在运营商方面存在很大差距。   因为运算符被声明为   静态方法,没有IMath   或类似的等效接口   所有数字类型实现;和   的确,运营商的灵活性   会让这很难做到   有意义的方式更糟糕的是:很多   原始类型的运算符不会   甚至作为运营商存在;而是在那里   是直接的IL方法。 [强调我的]使   情况更复杂,   可空&LT;&GT;要求的概念   “解除运营商”,内在的地方   “T”描述了适用的运算符   到可空类型 - 但这是   实现为语言功能,和   运行时没有提供(制作   反思更有趣)。

答案 1 :(得分:15)

我认为你能做的最好的事情就是使用IConvertible作为约束,并执行以下操作:

 public static operator T +(T x, T y)
    where T: IConvertible
{
    var type = typeof(T);
    if (type == typeof(String) ||
        type == typeof(DateTime)) throw new ArgumentException(String.Format("The type {0} is not supported", type.FullName), "T");

    try { return (T)(Object)(x.ToDouble(NumberFormatInfo.CurrentInfo) + y.ToDouble(NumberFormatInfo.CurrentInfo)); }
    catch(Exception ex) { throw new ApplicationException("The operation failed.", ex); }
}

这不会阻止某人传递String或DateTime,所以你可能想要做一些手动检查 - 但IConvertible应该让你足够接近,并允许你进行操作。

答案 2 :(得分:12)

在C#4.0中,您可以使用dynamic来解决此限制。我查看了你的代码,并设法生成了一个有效的(虽然是缩减版):

 public class ConstrainedNumber<T> where T : struct, IComparable, IComparable<T>, IEquatable<T>
    {
        private T _value;

        public ConstrainedNumber(T value)
        {
            _value = value;
        }

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

还有一个小测试程序:

class Program
{
    static void Main(string[] args)
    {
        ConstrainedNumber<int> one = new ConstrainedNumber<int>(10);
        ConstrainedNumber<int> two = new ConstrainedNumber<int>(5);
        var three = one + two;
        Debug.Assert(three == 15);
        Console.ReadLine();
    }
}

享受!

答案 3 :(得分:4)

不,这不起作用。但是有一些关于如何解决问题的建议。我做了以下(使用网上不同来源的一些想法):

public delegate TResult BinaryOperator<TLeft, TRight, TResult>(TLeft left, TRight right);

/// <summary>
/// Provide efficient generic access to either native or static operators for the given type combination.
/// </summary>
/// <typeparam name="TLeft">The type of the left operand.</typeparam>
/// <typeparam name="TRight">The type of the right operand.</typeparam>
/// <typeparam name="TResult">The type of the result value.</typeparam>
/// <remarks>Inspired by Keith Farmer's code on CodeProject:<br/>http://www.codeproject.com/KB/cs/genericoperators.aspx</remarks>
public static class Operator<TLeft, TRight, TResult> {
    private static BinaryOperator<TLeft, TRight, TResult> addition;
    private static BinaryOperator<TLeft, TRight, TResult> bitwiseAnd;
    private static BinaryOperator<TLeft, TRight, TResult> bitwiseOr;
    private static BinaryOperator<TLeft, TRight, TResult> division;
    private static BinaryOperator<TLeft, TRight, TResult> exclusiveOr;
    private static BinaryOperator<TLeft, TRight, TResult> leftShift;
    private static BinaryOperator<TLeft, TRight, TResult> modulus;
    private static BinaryOperator<TLeft, TRight, TResult> multiply;
    private static BinaryOperator<TLeft, TRight, TResult> rightShift;
    private static BinaryOperator<TLeft, TRight, TResult> subtraction;

    /// <summary>
    /// Gets the addition operator + (either native or "op_Addition").
    /// </summary>
    /// <value>The addition operator.</value>
    public static BinaryOperator<TLeft, TRight, TResult> Addition {
        get {
            if (addition == null) {
                addition = CreateOperator("op_Addition", OpCodes.Add);
            }
            return addition;
        }
    }

    /// <summary>
    /// Gets the modulus operator % (either native or "op_Modulus").
    /// </summary>
    /// <value>The modulus operator.</value>
    public static BinaryOperator<TLeft, TRight, TResult> Modulus {
        get {
            if (modulus == null) {
                modulus = CreateOperator("op_Modulus", OpCodes.Rem);
            }
            return modulus;
        }
    }

    /// <summary>
    /// Gets the exclusive or operator ^ (either native or "op_ExclusiveOr").
    /// </summary>
    /// <value>The exclusive or operator.</value>
    public static BinaryOperator<TLeft, TRight, TResult> ExclusiveOr {
        get {
            if (exclusiveOr == null) {
                exclusiveOr = CreateOperator("op_ExclusiveOr", OpCodes.Xor);
            }
            return exclusiveOr;
        }
    }

    /// <summary>
    /// Gets the bitwise and operator &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);

答案 4 :(得分:2)

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

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

答案 5 :(得分:2)

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

基类

public class Arithmetic<T>
{
    protected static readonly Func<T, T, T> OP_ADD;
    protected static readonly Func<T, T, T> OP_MUL;
    protected static readonly Func<T, T, T> OP_SUB;
    /* Define all operators you need here */

    static Arithmetic()
    {
        Arithmetic<Single>.OP_ADD = (x, y) => x + y;
        Arithmetic<Single>.OP_MUL = (x, y) => x * y;
        Arithmetic<Single>.OP_SUB = (x, y) => x - y;

        Arithmetic<Double>.OP_ADD = (x, y) => x + y;
        Arithmetic<Double>.OP_MUL = (x, y) => x * y;
        Arithmetic<Double>.OP_SUB = (x, y) => x - y;

        /* This could also be generated by a tool */
    }
}

用法

public class Vector2<T> : Arithmetic<T>
{
    public T X;
    public T Y;

    public static Vector2<T> operator +(Vector2<T> a, Vector2<T> b)
    {
        return new Vector2<T>()
        {
            X = OP_ADD(a.X, b.X),
            Y = OP_ADD(a.Y, b.Y)
        };
    }
    public static Vector2<T> operator -(Vector2<T> a, Vector2<T> b)
    {
        return new Vector2<T>()
        {
            X = OP_SUB(a.X, b.X),
            Y = OP_SUB(a.Y, b.Y)
        };
    }
    public static Vector2<T> operator *(Vector2<T> a, Vector2<T> b)
    {
        return new Vector2<T>()
        {
            X = OP_MUL(a.X, b.X),
            Y = OP_MUL(a.Y, b.Y)
        };
    }
}

答案 6 :(得分:1)

.Net泛型中目前没有支持表明运营商受支持。

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

它可以半合作(参见MiscUtils)但这不会给你你想要的语法

答案 7 :(得分:1)

我看完之后就这样做了。 Vector4&lt; T&gt; class包含4个数字/ T类型的T,带有通常的矢量数学。只需添加2个隐式操作即可转换为Decimal和来自Decimal。这可能与你将会得到的一样简洁,但正如你所指出的那样,它比你需要的更精确,更重。和你们一样,我希望有一个INumeric或者什么东西!


public static Vector4<T> operator +(Vector4<T> a, Vector4<T> b)
{
    Vector4<Decimal> A = a;
    Vector4<Decimal> B = b;

    var result = new Vector4<Decimal>(A.X + B.X, A.Y + B.Y, A.Z + B.Z, A.W + B.W);

    return result;
}

public static implicit operator Vector4<Decimal>(Vector4<T> v)
{
    return new Vector4<Decimal>(
        Convert.ToDecimal(v.X), 
        Convert.ToDecimal(v.Y), 
        Convert.ToDecimal(v.Z), 
        Convert.ToDecimal(v.W));
}

public static implicit operator Vector4<T>(Vector4<Decimal> v)
{
    return new Vector4<T>(
        (T)Convert.ChangeType(v.X, typeof(T)), 
        (T)Convert.ChangeType(v.Y, typeof(T)), 
        (T)Convert.ChangeType(v.Z, typeof(T)), 
        (T)Convert.ChangeType(v.W, typeof(T)));
}

答案 8 :(得分:0)

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

答案 9 :(得分:0)

我已经看到了一些涉及表达式树的潜在解决方案,其中运算符表达式是手动创建的。

它并不完美,因为你失去了编译时验证,但它可能会为你做到这一点。

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

答案 10 :(得分:0)

这个朋友怎么样(使用RTTI和对象类)

class MyMath
{
    public static T Add<T>(T a, T b) where T: struct
    {
        switch (typeof(T).Name)
        {
            case "Int32":
                return (T) (object)((int)(object)a + (int)(object)b);
            case "Double":
                return (T)(object)((double)(object)a + (double)(object)b);
            default:
                return default(T);
        }
    }
}

class Program
{
    public static int Main()
    {
        Console.WriteLine(MyMath.Add<double>(3.6, 2.12));
        return 0;
    }
}

答案 11 :(得分:0)

如果我必须做这样的事情,我可能会按照

的方式处理
public class ConstrainedNumber<T>
{
    private T Value { get; }
    public ConstrainedNumber(T value)
    {
        Value = value;
    }

    private static Func<ConstrainedNumber<T>, ConstrainedNumber<T>, T> _addFunc; // Cache the delegate
    public static ConstrainedNumber<T> operator+(ConstrainedNumber<T> left, ConstrainedNumber<T> right)
    {
        var adder = _addFunc;
        if (adder == null)
        {
            ParameterExpression lhs = Expression.Parameter(typeof(ConstrainedNumber<T>));
            ParameterExpression rhs = Expression.Parameter(typeof(ConstrainedNumber<T>));
            _addFunc = adder = Expression.Lambda<Func<ConstrainedNumber<T>, ConstrainedNumber<T>, T>>(
                Expression.Add(
                    Expression.Property(lhs, nameof(Value)),
                    Expression.Property(lhs, nameof(Value))
                    ),
                lhs,
                rhs).Compile();
        }
        return new ConstrainedNumber<T>(adder(left, right));
    }
}

最终结果有点像dynamic方法的最终结果,实际上最终会在内部执行类似这样的操作,但会有更多的开销,并且应该适用于任何T这可以是算术原语,也可以为其定义+运算符。 dynamic方法处理方式不同的一种情况是,它适用于string而不是string。这是好事还是坏事取决于用例,但如果需要> cat /etc/redhat-release Red Hat Enterprise Linux Server release 6.7 (Santiago) 可能是特殊情况。