有人能告诉我是否有一种方法可以用泛型将泛型类型参数T限制为:

Int16 Int32 Int64 UInt16 UInt32 UInt64

我知道哪里关键字,但不能找到只有这些类型的接口,

喜欢的东西:

static bool IntegerFunction<T>(T value) where T : INumeric 

这是没有限制的。对于任何想要使用泛型进行数值计算的人来说,这都是一个真正的问题。

更进一步说,我们需要

static bool GenericFunction<T>(T value) 
    where T : operators( +, -, /, * )

甚至

static bool GenericFunction<T>(T value) 
    where T : Add, Subtract

不幸的是,你只有接口,基类和关键字struct(必须是值类型),class(必须是引用类型)和new()(必须有默认构造函数)

你可以把数字包装在其他东西(类似于INullable<T>),就像这里的codeproject。


您可以在运行时应用该限制(通过对操作符进行反射或检查类型),但这首先就失去了使用泛型的优势。


也许你能做的就是

static bool IntegerFunction<T>(T value) where T: struct

不确定你是否能做到以下几点

static bool IntegerFunction<T>(T value) where T: struct, IComparable
, IFormattable, IConvertible, IComparable<T>, IEquatable<T>

对于如此特定的内容,为什么不为每种类型设置重载呢?列表很短,而且可能占用更少的内存。


我想知道和samjudson一样,为什么只对整数有效?如果是这样的话,你可能想要创建一个helper类或者类似的东西来保存你想要的所有类型。

如果你想要的只是整数,不要使用泛型,那不是泛型;或者更好的是,通过检查其类型来拒绝任何其他类型。


不幸的是,在这种情况下,只能在where子句中指定struct。不能具体指定Int16、Int32等,这看起来确实很奇怪,但我相信,在决定不允许在where子句中使用值类型的基础上,有一些深层的实现原因。

我想唯一的解决方案是执行运行时检查,这不幸地阻止了在编译时拾取问题。大概是这样的:-

static bool IntegerFunction<T>(T value) where T : struct {
  if (typeof(T) != typeof(Int16)  &&
      typeof(T) != typeof(Int32)  &&
      typeof(T) != typeof(Int64)  &&
      typeof(T) != typeof(UInt16) &&
      typeof(T) != typeof(UInt32) &&
      typeof(T) != typeof(UInt64)) {
    throw new ArgumentException(
      string.Format("Type '{0}' is not valid.", typeof(T).ToString()));
  }

  // Rest of code...
}

我知道这有点难看,但至少提供了所需的约束条件。

我还将研究此实现可能的性能影响,也许有更快的方法。


这个练习的意义是什么?

正如人们已经指出的,你可以让一个非泛型函数取最大的项,编译器会自动为你转换较小的整型。

static bool IntegerFunction(Int64 value) { }

如果您的函数处于性能关键的路径上(在我看来,这是不太可能的),您可以为所有需要的函数提供重载。

static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }

十多年后,这个特性终于出现在。net 7中。最通用的接口是INumber<TSelf>而不是innumeric(在系统中。数字名称空间),它不仅包含整数类型。要只接受整数类型,可以考虑使用IBinaryInteger<TSelf>。以你的原型,神秘的IntegerFunction为例:

static bool IntegerFunction<T>(T value) where T : IBinaryInteger<T> {
    return value > T.Zero;
}
Console.WriteLine(IntegerFunction(5));         // True
Console.WriteLine(IntegerFunction((sbyte)-5)); // False
Console.WriteLine(IntegerFunction((ulong)5));  // True

下面的答案(现在已经过时了)是作为一个历史的角度。

c#不支持这一点。在接受Bruce Eckel的采访时,Hejlsberg描述了没有实现该功能的原因:

And it's not clear that the added complexity is worth the small yield that you get. If something you want to do is not directly supported in the constraint system, you can do it with a factory pattern. You could have a Matrix<T>, for example, and in that Matrix you would like to define a dot product method. That of course that means you ultimately need to understand how to multiply two Ts, but you can't say that as a constraint, at least not if T is int, double, or float. But what you could do is have your Matrix take as an argument a Calculator<T>, and in Calculator<T>, have a method called multiply. You go implement that and you pass it to the Matrix.

然而,这会导致相当复杂的代码,用户必须为他们想要使用的每个T提供自己的Calculator<T>实现。只要它不需要是可扩展的,也就是说,如果你只想支持固定数量的类型,比如int和double,你可以使用一个相对简单的接口:

var mat = new Matrix<int>(w, h);

(GitHub Gist中的最小实现。)

然而,一旦您希望用户能够提供他们自己的自定义类型,您就需要打开这个实现,以便用户能够提供他们自己的Calculator实例。例如,要实例化一个使用自定义十进制浮点数实现DFP的矩阵,你必须编写以下代码:

var mat = new Matrix<DFP>(DfpCalculator.Instance, w, h);

实现DfpCalculator的所有成员:ICalculator<DFP>。

正如Sergey Shandar的回答中所讨论的那样,另一种选择(不幸的是,它也有同样的局限性)是使用策略类。


这个问题有点像常见问题,所以我在维基上发布了这个(因为我之前发布过类似的问题,但这是一个较老的问题);无论如何……

你用的是什么版本的。net ?如果你使用的是。net 3.5,那么我在MiscUtil中有一个通用操作符实现(免费等)。

它有T Add<T>(T x, T y)等方法,以及不同类型上的其他算术变体(如DateTime + TimeSpan)。

此外,这适用于所有内置、提升和定制操作符,并缓存委托以获得性能。

这里有一些关于为什么这很棘手的额外背景。

你可能还想知道动态(4.0)也间接地解决了这个问题。

dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect

我会使用一个通用的,你可以处理外部…

/// <summary>
/// Generic object copy of the same type
/// </summary>
/// <typeparam name="T">The type of object to copy</typeparam>
/// <param name="ObjectSource">The source object to copy</param>
public T CopyObject<T>(T ObjectSource)
{
    T NewObject = System.Activator.CreateInstance<T>();

    foreach (PropertyInfo p in ObjectSource.GetType().GetProperties())
        NewObject.GetType().GetProperty(p.Name).SetValue(NewObject, p.GetValue(ObjectSource, null), null);

    return NewObject;
}

当我试图重载泛型类型的操作符时,这个限制影响了我;由于没有“innumeric”约束,以及stackoverflow上的优秀人员乐于提供的一系列其他原因,操作不能在泛型类型上定义。

我想要的是

public struct Foo<T>
{
    public T Value{ get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + RHS.Value; };
    }
}

我已经使用。net4动态运行时类型解决了这个问题。

public struct Foo<T>
{
    public T Value { get; private set; }

    public static Foo<T> operator +(Foo<T> LHS, Foo<T> RHS)
    {
        return new Foo<T> { Value = LHS.Value + (dynamic)RHS.Value };
    }
}

关于使用动态的两件事是

的性能。所有值类型都被装箱。 运行时错误。你“打败”了编译器,但失去了类型安全。如果泛型类型没有定义操作符,则会在执行期间抛出异常。


使用策略的解决方案:

interface INumericPolicy<T>
{
    T Zero();
    T Add(T a, T b);
    // add more functions here, such as multiplication etc.
}

struct NumericPolicies:
    INumericPolicy<int>,
    INumericPolicy<long>
    // add more INumericPolicy<> for different numeric types.
{
    int INumericPolicy<int>.Zero() { return 0; }
    long INumericPolicy<long>.Zero() { return 0; }
    int INumericPolicy<int>.Add(int a, int b) { return a + b; }
    long INumericPolicy<long>.Add(long a, long b) { return a + b; }
    // implement all functions from INumericPolicy<> interfaces.

    public static NumericPolicies Instance = new NumericPolicies();
}

算法:

static class Algorithms
{
    public static T Sum<P, T>(this P p, params T[] a)
        where P: INumericPolicy<T>
    {
        var r = p.Zero();
        foreach(var i in a)
        {
            r = p.Add(r, i);
        }
        return r;
    }

}

用法:

int i = NumericPolicies.Instance.Sum(1, 2, 3, 4, 5);
long l = NumericPolicies.Instance.Sum(1L, 2, 3, 4, 5);
NumericPolicies.Instance.Sum("www", "") // compile-time error.

该解决方案是编译时安全的。CityLizard框架提供了。net 4.0的编译版本。文件名为“lib/NETFramework4.0/ citylizad . policy .dll”。

它也可以在Nuget: https://www.nuget.org/packages/CityLizard/。参见CityLizard.Policy.I结构。


目前还没有“好的”解决方案。但是,您可以显著地缩小类型参数,以排除许多与您假设的“INumeric”约束不匹配的情况,如上面haacks所示。

static bool IntegerFunction<T>(T值)where T: IComparable, iformatable, IConvertible, IComparable<T>, IEquatable<T>, struct {…


. net数字基元类型不共享任何允许它们用于计算的公共接口。可以定义自己的接口(例如ISignedWholeNumber)来执行这样的操作,定义包含单个Int16、Int32等的结构并实现这些接口,然后有方法接受受ISignedWholeNumber约束的泛型类型,但是必须将数值转换为你的结构类型可能是一个麻烦。

An alternative approach would be to define static class Int64Converter<T> with a static property bool Available {get;}; and static delegates for Int64 GetInt64(T value), T FromInt64(Int64 value), bool TryStoreInt64(Int64 value, ref T dest). The class constructor could use be hard-coded to load delegates for known types, and possibly use Reflection to test whether type T implements methods with the proper names and signatures (in case it's something like a struct which contains an Int64 and represents a number, but has a custom ToString() method). This approach would lose the advantages associated with compile-time type-checking, but would still manage to avoid boxing operations and each type would only have to be "checked" once. After that, operations associated with that type would be replaced with a delegate dispatch.


我创建了一个小库功能来解决这些问题:

而不是:

public T DifficultCalculation<T>(T a, T b)
{
    T result = a * b + a; // <== WILL NOT COMPILE!
    return result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Should result in 8.

你可以这样写:

public T DifficultCalculation<T>(Number<T> a, Number<T> b)
{
    Number<T> result = a * b + a;
    return (T)result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.

你可以在这里找到源代码:https://codereview.stackexchange.com/questions/26022/improvement-requested-for-generic-calculator-and-generic-number


考虑到这个问题的受欢迎程度和这样一个函数背后的兴趣,我很惊讶地看到,还没有涉及T4的答案。

在这个示例代码中,我将演示一个非常简单的示例,说明如何使用强大的模板引擎来完成编译器在幕后使用泛型所做的工作。

你可以简单地为你喜欢的每种类型生成你想要的函数,并相应地使用它(在编译时!),而不是通过循环和牺牲编译时的确定性。

为了做到这一点:

创建一个新的名为GenericNumberMethodTemplate.tt的文本模板文件。 删除自动生成的代码(您将保留大部分代码,但有些代码不需要)。 添加以下片段:

<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>

<# Type[] types = new[] {
    typeof(Int16), typeof(Int32), typeof(Int64),
    typeof(UInt16), typeof(UInt32), typeof(UInt64)
    };
#>

using System;
public static class MaxMath {
    <# foreach (var type in types) { 
    #>
        public static <#= type.Name #> Max (<#= type.Name #> val1, <#= type.Name #> val2) {
            return val1 > val2 ? val1 : val2;
        }
    <#
    } #>
}

就是这样。你现在完成了。

保存这个文件会自动编译成这个源文件:

using System;
public static class MaxMath {
    public static Int16 Max (Int16 val1, Int16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int32 Max (Int32 val1, Int32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static Int64 Max (Int64 val1, Int64 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt16 Max (UInt16 val1, UInt16 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt32 Max (UInt32 val1, UInt32 val2) {
        return val1 > val2 ? val1 : val2;
    }
    public static UInt64 Max (UInt64 val1, UInt64 val2) {
        return val1 > val2 ? val1 : val2;
    }
}

在main方法中,你可以验证你是否具有编译时确定性:

namespace TTTTTest
{
    class Program
    {
        static void Main(string[] args)
        {
            long val1 = 5L;
            long val2 = 10L;
            Console.WriteLine(MaxMath.Max(val1, val2));
            Console.Read();
        }
    }
}

我先说一句:不,这并没有违反DRY原则。DRY原则的存在是为了防止人们在多个地方复制代码,从而导致应用程序变得难以维护。

这里的情况完全不同:如果您想要更改,那么您只需更改模板(对于您的所有生成都是一个单一的源代码!),然后就完成了。

为了将它与您自己的自定义定义一起使用,请向生成的代码添加一个名称空间声明(确保它与您将定义自己的实现的名称空间声明相同),并将该类标记为partial。然后,将这些行添加到你的模板文件中,这样它就会被包含在最终的编译中:

<#@ import namespace="TheNameSpaceYouWillUse" #>
<#@ assembly name="$(TargetPath)" #>

说实话:这太酷了。

免责声明:这个示例受到了Kevin Hazzard和Jason Bock, Manning Publications在。net中的元编程的严重影响。


没有办法将模板限制为类型,但是可以根据类型定义不同的操作。作为泛型数值包的一部分,我需要一个泛型类来添加两个值。

    class Something<TCell>
    {
        internal static TCell Sum(TCell first, TCell second)
        {
            if (typeof(TCell) == typeof(int))
                return (TCell)((object)(((int)((object)first)) + ((int)((object)second))));

            if (typeof(TCell) == typeof(double))
                return (TCell)((object)(((double)((object)first)) + ((double)((object)second))));

            return second;
        }
    }

请注意,typeofs是在编译时计算的,因此if语句将被编译器删除。编译器还会删除虚假的类型转换。因此,在编译器中会解析为

        internal static int Sum(int first, int second)
        {
            return first + second;
        }

如果你使用的是。net 4.0或更高版本,那么你可以使用dynamic作为方法参数,并在运行时检查传递的动态参数类型是数字/整数类型。

如果传递的动态类型不是数字/整数类型,则抛出异常。

实现这一想法的简短代码示例如下:

using System;
public class InvalidArgumentException : Exception
{
    public InvalidArgumentException(string message) : base(message) {}
}
public class InvalidArgumentTypeException : InvalidArgumentException
{
    public InvalidArgumentTypeException(string message) : base(message) {}
}
public class ArgumentTypeNotIntegerException : InvalidArgumentTypeException
{
    public ArgumentTypeNotIntegerException(string message) : base(message) {}
}
public static class Program
{
    private static bool IntegerFunction(dynamic n)
    {
        if (n.GetType() != typeof(Int16) &&
            n.GetType() != typeof(Int32) &&
            n.GetType() != typeof(Int64) &&
            n.GetType() != typeof(UInt16) &&
            n.GetType() != typeof(UInt32) &&
            n.GetType() != typeof(UInt64))
            throw new ArgumentTypeNotIntegerException("argument type is not integer type");
        //code that implements IntegerFunction goes here
    }
    private static void Main()
    {
         Console.WriteLine("{0}",IntegerFunction(0)); //Compiles, no run time error and first line of output buffer is either "True" or "False" depends on the code that implements "Program.IntegerFunction" static method.
         Console.WriteLine("{0}",IntegerFunction("string")); //Also compiles but it is run time error and exception of type "ArgumentTypeNotIntegerException" is thrown here.
         Console.WriteLine("This is the last Console.WriteLine output"); //Never reached and executed due the run time error and the exception thrown on the second line of Program.Main static method.
    }

当然,这个解决方案只能在运行时工作,而不能在编译时工作。

如果你想要一个总是在编译时工作而不在运行时工作的解决方案,那么你必须用一个公共结构/类来包装动态,它的重载公共构造函数只接受所需类型的参数,并给结构/类适当的名称。

被包装的动态总是类/结构的私有成员,它是结构/类的唯一成员,结构/类的唯一成员的名字是“value”,这是有意义的。

如果需要,还必须定义和实现公共方法和/或操作符,这些方法和/或操作符用于类/结构的私有动态成员的所需类型。

同样有意义的是,结构/类有特殊的/唯一的构造函数,它接受dynamic作为参数,初始化它唯一的私有动态成员“value”,但这个构造函数的修饰符当然是私有的。

类/结构准备好后,将参数的IntegerFunction类型定义为已定义的类/结构。

实现这个想法的长代码示例如下:

using System;
public struct Integer
{
    private dynamic value;
    private Integer(dynamic n) { this.value = n; }
    public Integer(Int16 n) { this.value = n; }
    public Integer(Int32 n) { this.value = n; }
    public Integer(Int64 n) { this.value = n; }
    public Integer(UInt16 n) { this.value = n; }
    public Integer(UInt32 n) { this.value = n; }
    public Integer(UInt64 n) { this.value = n; }
    public Integer(Integer n) { this.value = n.value; }
    public static implicit operator Int16(Integer n) { return n.value; }
    public static implicit operator Int32(Integer n) { return n.value; }
    public static implicit operator Int64(Integer n) { return n.value; }
    public static implicit operator UInt16(Integer n) { return n.value; }
    public static implicit operator UInt32(Integer n) { return n.value; }
    public static implicit operator UInt64(Integer n) { return n.value; }
    public static Integer operator +(Integer x, Int16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, Int64 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt16 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt32 y) { return new Integer(x.value + y); }
    public static Integer operator +(Integer x, UInt64 y) { return new Integer(x.value + y); }
    public static Integer operator -(Integer x, Int16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, Int64 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt16 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt32 y) { return new Integer(x.value - y); }
    public static Integer operator -(Integer x, UInt64 y) { return new Integer(x.value - y); }
    public static Integer operator *(Integer x, Int16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, Int64 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt16 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt32 y) { return new Integer(x.value * y); }
    public static Integer operator *(Integer x, UInt64 y) { return new Integer(x.value * y); }
    public static Integer operator /(Integer x, Int16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, Int64 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt16 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt32 y) { return new Integer(x.value / y); }
    public static Integer operator /(Integer x, UInt64 y) { return new Integer(x.value / y); }
    public static Integer operator %(Integer x, Int16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, Int64 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt16 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt32 y) { return new Integer(x.value % y); }
    public static Integer operator %(Integer x, UInt64 y) { return new Integer(x.value % y); }
    public static Integer operator +(Integer x, Integer y) { return new Integer(x.value + y.value); }
    public static Integer operator -(Integer x, Integer y) { return new Integer(x.value - y.value); }
    public static Integer operator *(Integer x, Integer y) { return new Integer(x.value * y.value); }
    public static Integer operator /(Integer x, Integer y) { return new Integer(x.value / y.value); }
    public static Integer operator %(Integer x, Integer y) { return new Integer(x.value % y.value); }
    public static bool operator ==(Integer x, Int16 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int16 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int32 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int32 y) { return x.value != y; }
    public static bool operator ==(Integer x, Int64 y) { return x.value == y; }
    public static bool operator !=(Integer x, Int64 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt16 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt16 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt32 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt32 y) { return x.value != y; }
    public static bool operator ==(Integer x, UInt64 y) { return x.value == y; }
    public static bool operator !=(Integer x, UInt64 y) { return x.value != y; }
    public static bool operator ==(Integer x, Integer y) { return x.value == y.value; }
    public static bool operator !=(Integer x, Integer y) { return x.value != y.value; }
    public override bool Equals(object obj) { return this == (Integer)obj; }
    public override int GetHashCode() { return this.value.GetHashCode(); }
    public override string ToString() { return this.value.ToString(); }
    public static bool operator >(Integer x, Int16 y) { return x.value > y; }
    public static bool operator <(Integer x, Int16 y) { return x.value < y; }
    public static bool operator >(Integer x, Int32 y) { return x.value > y; }
    public static bool operator <(Integer x, Int32 y) { return x.value < y; }
    public static bool operator >(Integer x, Int64 y) { return x.value > y; }
    public static bool operator <(Integer x, Int64 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt16 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt16 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt32 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt32 y) { return x.value < y; }
    public static bool operator >(Integer x, UInt64 y) { return x.value > y; }
    public static bool operator <(Integer x, UInt64 y) { return x.value < y; }
    public static bool operator >(Integer x, Integer y) { return x.value > y.value; }
    public static bool operator <(Integer x, Integer y) { return x.value < y.value; }
    public static bool operator >=(Integer x, Int16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Int64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, Int64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt16 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt16 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt32 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt32 y) { return x.value <= y; }
    public static bool operator >=(Integer x, UInt64 y) { return x.value >= y; }
    public static bool operator <=(Integer x, UInt64 y) { return x.value <= y; }
    public static bool operator >=(Integer x, Integer y) { return x.value >= y.value; }
    public static bool operator <=(Integer x, Integer y) { return x.value <= y.value; }
    public static Integer operator +(Int16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(Int64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt16 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt32 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator +(UInt64 x, Integer y) { return new Integer(x + y.value); }
    public static Integer operator -(Int16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(Int64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt16 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt32 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator -(UInt64 x, Integer y) { return new Integer(x - y.value); }
    public static Integer operator *(Int16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(Int64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt16 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt32 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator *(UInt64 x, Integer y) { return new Integer(x * y.value); }
    public static Integer operator /(Int16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(Int64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt16 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt32 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator /(UInt64 x, Integer y) { return new Integer(x / y.value); }
    public static Integer operator %(Int16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(Int64 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt16 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt32 x, Integer y) { return new Integer(x % y.value); }
    public static Integer operator %(UInt64 x, Integer y) { return new Integer(x % y.value); }
    public static bool operator ==(Int16 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int16 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int32 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int32 x, Integer y) { return x != y.value; }
    public static bool operator ==(Int64 x, Integer y) { return x == y.value; }
    public static bool operator !=(Int64 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt16 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt16 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt32 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt32 x, Integer y) { return x != y.value; }
    public static bool operator ==(UInt64 x, Integer y) { return x == y.value; }
    public static bool operator !=(UInt64 x, Integer y) { return x != y.value; }
    public static bool operator >(Int16 x, Integer y) { return x > y.value; }
    public static bool operator <(Int16 x, Integer y) { return x < y.value; }
    public static bool operator >(Int32 x, Integer y) { return x > y.value; }
    public static bool operator <(Int32 x, Integer y) { return x < y.value; }
    public static bool operator >(Int64 x, Integer y) { return x > y.value; }
    public static bool operator <(Int64 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt16 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt16 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt32 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt32 x, Integer y) { return x < y.value; }
    public static bool operator >(UInt64 x, Integer y) { return x > y.value; }
    public static bool operator <(UInt64 x, Integer y) { return x < y.value; }
    public static bool operator >=(Int16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(Int64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(Int64 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt16 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt16 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt32 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt32 x, Integer y) { return x <= y.value; }
    public static bool operator >=(UInt64 x, Integer y) { return x >= y.value; }
    public static bool operator <=(UInt64 x, Integer y) { return x <= y.value; }
}
public static class Program
{
    private static bool IntegerFunction(Integer n)
    {
        //code that implements IntegerFunction goes here
        //note that there is NO code that checks the type of n in rum time, because it is NOT needed anymore 
    }
    private static void Main()
    {
        Console.WriteLine("{0}",IntegerFunction(0)); //compile error: there is no overloaded METHOD for objects of type "int" and no implicit conversion from any object, including "int", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer(0))); //both compiles and no run time error
        Console.WriteLine("{0}",IntegerFunction("string")); //compile error: there is no overloaded METHOD for objects of type "string" and no implicit conversion from any object, including "string", to "Integer" is known.
        Console.WriteLine("{0}",IntegerFunction(new Integer("string"))); //compile error: there is no overloaded CONSTRUCTOR for objects of type "string"
    }
}

注意,为了在你的代码中使用动态,你必须添加引用到微软。CSharp

如果. net框架的版本低于/低于/小于4.0,并且动态在该版本中未定义,那么你将不得不使用对象来代替,并将其转换为整数类型,这很麻烦,所以我建议你至少使用。net 4.0或更新版本,如果可以的话,这样你就可以使用动态而不是对象。


如果你只想使用一个数字类型,你可以考虑用using在c++中创建类似于别名的东西。

因此,我们不再使用非常通用的

T ComputeSomething<T>(T value1, T value2) where T : INumeric { ... }

你可以

using MyNumType = System.Double;
T ComputeSomething<MyNumType>(MyNumType value1, MyNumType value2) { ... }

如果需要,这可能允许您轻松地将double类型转换为int类型或其他类型,但您不能在同一个程序中使用带有double类型和int类型的computessomething。

但为什么不把所有double都换成int呢?因为你的方法可能想要使用double类型不管输入是double型还是int型。别名允许您确切地知道哪个变量使用了动态类型。


我有一个类似的情况,我需要处理数字类型和字符串;看起来有点奇怪,但就是这样。

和许多人一样,我研究了约束条件,提出了一堆它必须支持的接口。然而,第一,它并不是100%无懈可击的;第二,任何新看到这一长串约束条件的人都会立刻感到困惑。

所以,我的方法是把我所有的逻辑放到一个没有约束的泛型方法中,但是让这个泛型方法是私有的。然后我用公共方法公开它,其中一个方法显式地处理我想处理的类型——在我看来,代码是干净而显式的,例如。

public static string DoSomething(this int input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this decimal input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this double input, ...) => DoSomethingHelper(input, ...);
public static string DoSomething(this string input, ...) => DoSomethingHelper(input, ...);

private static string DoSomethingHelper<T>(this T input, ....)
{
    // complex logic
}

不幸的是,. net并没有提供一种本地的方法。

为了解决这个问题,我创建了OSS库generatics,它为以下内置数字类型及其可空等价提供了大多数标准数字操作,并能够添加对其他数字类型的支持。

sbyte、byte、short、ushort、int、uint、long、ulong、float、double、decimal、BigInteger

其性能相当于特定于数值类型的解决方案,允许您创建高效的通用数值算法。

下面是一个代码使用示例。

public static T Sum(T[] items)
{
    T sum = Number.Zero<T>();
    foreach (T item in items)
    {
        sum = Number.Add(sum, item);
    }
    return sum;
}
public static T SumAlt(T[] items)
{
    // implicit conversion to Number<T>
    Number<T> sum = Number.Zero<T>();
    foreach (T item in items)
    {
        // operator support
        sum += item;
    }
    // implicit conversion to T
    return sum;
}

从c# 7.3开始,您可以使用更接近的非托管约束来指定类型形参是非指针、非空的非托管类型。

class SomeGeneric<T> where T : unmanaged
{
//...
}

非托管约束意味着结构约束,并且不能与结构或new()约束组合。

如果是以下类型之一,则该类型为非托管类型:

Sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal或bool 任何enum类型 任何指针类型 任何只包含非托管类型字段的用户定义结构类型,在c# 7.3及更早版本中,都不是构造类型(至少包含一个类型参数的类型)。

为了进一步限制和消除没有实现IComparable的指针和用户定义类型add IComparable(但enum仍然派生自IComparable,因此通过添加IEquatable < T >来限制enum,您可以根据您的情况进一步添加额外的接口。Unmanaged可以让这个列表更短):

    class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
    {
    //...
    }

但是这并没有阻止DateTime实例化。


话题老了,但对未来的读者来说:

这个特性与歧视联盟紧密相关,而歧视联盟目前还没有在c#中实现。我发现它的问题在这里:

https://github.com/dotnet/csharplang/issues/113

这个问题仍然没有解决,并且已经计划在c# 10中推出新特性

所以我们仍然需要等待一段时间,但在释放之后,你可以这样做:

static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...

这个约束存在于。net 7中。

查看这篇。net博客文章和实际文档。

从。net 7开始,你可以使用接口如INumber和IFloatingPoint来创建程序,例如:

using System.Numerics;

Console.WriteLine(Sum(1, 2, 3, 4, 5));
Console.WriteLine(Sum(10.541, 2.645));
Console.WriteLine(Sum(1.55f, 5, 9.41f, 7));

static T Sum<T>(params T[] numbers) where T : INumber<T>
{
    T result = T.Zero;

    foreach (T item in numbers)
    {
        result += item;
    }

    return result;
}

innumber在系统中。数字名称空间。

还有诸如IAdditionOperators和IComparisonOperators这样的接口,因此您可以通用地使用特定的操作符。


所有数值类型都是实现IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable的结构体。然而,DateTime也是如此。

所以这个泛型扩展方法是可能的:

public static bool IsNumeric<T>(this T value) where T : struct, IComparable, IComparable<T>, IConvertible, IEquatable<T>, IFormattable =>
  typeof(T) != typeof(DateTime);

但是对于实现这些接口的结构体,它将失败,例如:

public struct Foo : IComparable, IComparable<Foo>, IConvertible, IEquatable<Foo>, IFormattable { /* ... */ }

这个非泛型的替代方法性能较差,但保证可以工作:

public static bool IsNumeric(this Type type) =>
  type == typeof(sbyte) || type == typeof(byte) ||
  type == typeof(short) || type == typeof(ushort) ||
  type == typeof(int) || type == typeof(uint) ||
  type == typeof(long) || type == typeof(ulong) ||
  type == typeof(float) ||
  type == typeof(double) ||
  type == typeof(decimal);

.NET 6有一个预览功能:

https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/#generic-math

下面是文章中的一个例子:

static T Add<T>(T left, T right)
    where T : INumber<T>
{
    return left + right;
}

INumber是一个实现其他接口的接口,比如IAdditionOperators,它允许通用的+用法。现在这是可能的,因为另一个预览特性是接口中的静态抽象,因为+操作符重载是一个静态方法:

/// <summary>Defines a mechanism for computing the sum of two values.</summary>
/// <typeparam name="TSelf">The type that implements this interface.</typeparam>
/// <typeparam name="TOther">The type that will be added to <typeparamref name="TSelf" />.</typeparam>
/// <typeparam name="TResult">The type that contains the sum of <typeparamref name="TSelf" /> and <typeparamref name="TOther" />.</typeparam>
[RequiresPreviewFeatures(Number.PreviewFeatureMessage, Url = Number.PreviewFeatureUrl)]
public interface IAdditionOperators<TSelf, TOther, TResult>
    where TSelf : IAdditionOperators<TSelf, TOther, TResult>
{
    /// <summary>Adds two values together to compute their sum.</summary>
    /// <param name="left">The value to which <paramref name="right" /> is added.</param>
    /// <param name="right">The value which is added to <paramref name="left" />.</param>
    /// <returns>The sum of <paramref name="left" /> and <paramref name="right" />.</returns>
    static abstract TResult operator +(TSelf left, TOther right);
}