是否存在将我的泛型方法限制为数字类型的约束?

Posted

技术标签:

【中文标题】是否存在将我的泛型方法限制为数字类型的约束?【英文标题】:Is there a constraint that restricts my generic method to numeric types? 【发布时间】:2010-09-07 03:43:14 【问题描述】:

谁能告诉我泛型是否有办法将泛型类型参数T 限制为仅限:

Int16 Int32 Int64 UInt16 UInt32 UInt64

我知道where 关键字,但找不到这些类型的接口,

类似:

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

【问题讨论】:

现在有各种 C# 提案可以实现这一点,但 AFAIK 没有一个比初步探索/讨论更进一步。请参阅Exploration: Shapes and Extensions、Exploration: Roles, extension interfaces and static interface members、Champion "Type Classes (aka Concepts, Structural Generic Constraints)" 和 Proposal: Generic types should support operators 截至 2021 年 9 月,此 PR 似乎具有最大的吸引力,我认为它将被 .NET 7 接受:github.com/dotnet/designs/pull/205 【参考方案1】:

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

我会进一步说我们需要

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

甚至

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

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

您可以将数字包含在其他内容中(类似于 INullable&lt;T&gt;),例如 here on codeproject。


您可以在运行时应用限制(通过反映运算符或检查类型),但这确实失去了首先拥有泛型的优势。

【讨论】:

不知道你是否看过 MiscUtil 对泛型运算符的支持...yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html 是的 - Jon Skeet 不久前向我指出了他们的其他问题(但在今年的回应之后) - 他们是一个聪明的想法,但我仍然想要适当的约束支持。跨度> 等等,where T : operators( +, -, /, * ) 是合法的 C# 吗?抱歉新手问题。 @kdbanman 我不这么认为。 Keith 说 C# 不支持 OP 的要求,并建议我们应该能够做到 where T : operators( +, -, /, * ),但不能。 现在是 .NET 6 中的一个东西:devblogs.microsoft.com/dotnet/…【参考方案2】:

也许你能做的最接近的是

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>

对于如此具体的东西,为什么不为每种类型都设置重载呢,列表很短,而且可能占用更少的内存。

【讨论】:

【参考方案3】:

我想知道和samjudson一样,为什么只对整数?如果是这种情况,您可能需要创建一个辅助类或类似的东西来保存您想要的所有类型。

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

【讨论】:

【参考方案4】:

不幸的是,在这种情况下,您只能在 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...

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

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

【讨论】:

+1,但是,// Rest of code... 如果依赖于约束定义的操作,则可能无法编译。 Convert.ToIntXX(value) 可能有助于使“// 其余代码”编译——至少在 IntegerFunction 的返回类型也是 T 类型之前,那么你就大错特错了。 :-p -1;由于@Nick 给出的原因,这不起作用。当您尝试在 // Rest of code... 中进行任何算术运算(例如 value + valuevalue * value)时,就会出现编译错误。【参考方案5】:

练习的意义何在?

正如人们已经指出的那样,您可以使用一个非泛型函数来获取最大的项目,编译器会自动为您转换较小的整数。

static bool IntegerFunction(Int64 value)  

如果您的函数处于性能关键路径(极不可能,IMO),您可以为所有需要的函数提供重载。

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

【讨论】:

我经常使用数值方法。有时我想要整数,有时我想要浮点数。两者都有最适合处理速度的 64 位版本。在这些之间进行转换是一个糟糕的主意,因为每种方式都有损失。虽然我倾向于使用双精度数,但有时我确实发现使用整数更好,因为它们在其他地方的使用方式。但是当我编写一个算法来执行一次并将类型决定留给实例需求时会非常好。【参考方案6】:

C# 不支持这一点。 Hejlsberg 描述了未实现该功能的原因in an interview with Bruce Eckel:

并不清楚增加的复杂性是否值得您获得的少量收益。如果约束系统不直接支持你想做的事情,你可以用工厂模式来做。例如,您可能有一个Matrix&lt;T&gt;,并且您想在该Matrix 中定义一个点积方法。这当然意味着你最终需要了解如何将两个Ts 相乘,但你不能说这是一个约束,至少如果Tintdoublefloat 则不是.但是您可以做的是让您的MatrixCalculator&lt;T&gt; 作为参数,并在Calculator&lt;T&gt; 中使用一个名为multiply 的方法。你去实现它并将它传递给Matrix

但是,这会导致代码相当复杂,用户必须为他们想要使用的每个 T 提供自己的 Calculator&lt;T&gt; 实现。只要它不必是可扩展的,即如果您只想支持固定数量的类型,例如intdouble,您可以使用相对简单的接口:

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

(Minimal implementation in a GitHub Gist.)

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

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

… 并实现DfpCalculator : ICalculator&lt;DFP&gt; 的所有成员。

不幸的是,也有同样的限制,另一种方法是使用策略类as discussed in Sergey Shandar’s answer。

【讨论】:

顺便说一句,MiscUtil 提供了一个泛型类,可以做到这一点; Operator/Operator&lt;T&gt;; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html @Mark:好评。但是,为了清楚起见,我不认为 Hejlsberg 将代码生成作为解决问题的方法,就像您在 Operator&lt;T&gt; 代码中所做的那样(因为在 Expressions 框架存在之前很久就进行了采访,尽管当然可以使用Reflection.Emit)——而且我真的他的解决方法很感兴趣。 @Konrad Rudolph:我认为this answer 的类似问题解释了 Hejlsberg 的解决方法。另一个泛型类是抽象的。由于它要求您为要支持的每种类型实现另一个泛型类,这将导致重复代码,但这意味着您只能使用受支持的类型实例化原始泛型类。 我不同意 Heijsberg 的说法“所以从某种意义上说,C++ 模板实际上是无类型的,或者说是松散类型的。而 C# 泛型是强类型的。”。这真的是推广 C# 的营销 BS。强/弱类型与诊断质量无关。否则:有趣的发现。 对所有内容都使用十进制并转换回您需要的类型。【参考方案7】:

这个问题有点像常见问题解答,所以我将其发布为 wiki(因为我之前发布过类似的问题,但这是一个较旧的问题);反正...

您使用的是哪个版本的 .NET?如果您使用的是 .NET 3.5,那么我在 MiscUtil 中有一个 generic operators implementation(免费等)。

这有像 T Add&lt;T&gt;(T x, T y) 这样的方法,以及针对不同类型的算术的其他变体(如 DateTime + TimeSpan)。

此外,这适用于所有内置、提升和定制的运算符,并缓存委托以提高性能。

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

您可能还想知道dynamic (4.0) 也间接解决了这个问题 - 即

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

【讨论】:

【参考方案8】:

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

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

【讨论】:

【参考方案9】:

当我尝试为泛型类型重载运算符时,这个限制影响了我;由于没有“INumeric”约束,并且由于许多其他原因,*** 上的好人很乐意提供,因此无法在泛型类型上定义操作。

我想要类似的东西

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 ;
    

使用dynamic的两件事是

    性能。所有值类型都被装箱。 运行时错误。您“击败”了编译器,但失去了类型安全性。如果泛型类型没有定义操作符,执行过程中会抛出异常。

【讨论】:

【参考方案10】:

使用策略的解决方法:

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 Framework 提供 .NET 4.0 的编译版本。该文件是 lib/NETFramework4.0/CityLizard.Policy.dll。

它在 Nuget 中也可用:https://www.nuget.org/packages/CityLizard/。请参阅 CityLizard.Policy.I 结构。

【讨论】:

当函数参数少于泛型参数时,我遇到了这种模式的问题。已打开***.com/questions/36048248/… 有什么理由使用struct?如果我改用单例类并将实例更改为public static NumericPolicies Instance = new NumericPolicies();,然后添加此构造函数private NumericPolicies() @M.kazemAkhgary 你可以使用单例。我更喜欢结构。理论上,它可以通过编译器/CLR 进行优化,因为该结构不包含任何信息。在单例的情况下,你仍然会传递一个引用,这可能会给 GC 增加额外的压力。另一个优点是 struct 不能为 null :-) . 我要说你找到了一个非常聪明的解决方案,但解决方案对我来说太有限了:我打算在T Add&lt;T&gt; (T t1, T t2) 中使用它,但Sum() 只有在它可以的时候才有效从它的参数中检索它自己的 T 类型,当它嵌入到另一个泛型函数中时这是不可能的。【参考方案11】:

目前还没有“好的”解决方案。但是,您可以显着缩小类型参数的范围,以排除许多不适合您假设的“INumeric”约束的情况,如 Haacked 上面所示。

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

【讨论】:

【参考方案12】:

.NET 数字基元类型不共享任何允许它们用于计算的通用接口。可以定义您自己的接口(例如ISignedWholeNumber)来执行此类操作,定义包含单个Int16Int32 等的结构并实现这些接口,然后拥有接受泛型类型的方法仅限于ISignedWholeNumber,但必须将数值转换为您的结构类型可能会很麻烦。

另一种方法是使用静态属性bool Available get;;Int64 GetInt64(T value)T FromInt64(Int64 value)bool TryStoreInt64(Int64 value, ref T dest) 的静态委托定义静态类Int64Converter&lt;T&gt;。类构造函数可以使用硬编码来加载已知类型的委托,并可能使用反射来测试类型 T 是否实现具有正确名称和签名的方法(如果它类似于包含 Int64 和表示一个数字,但有一个自定义的ToString() 方法)。这种方法将失去与编译时类型检查相关的优势,但仍会设法避免装箱操作,并且每种类型只需“检查”一次。之后,与该类型关联的操作将替换为委托调度。

【讨论】:

@KenKin:IConvertible 提供了一种方法,通过该方法可以将任何整数添加到另一个整数类型以产生例如Int64 结果,但不提供一种方法,例如任意类型的整数可以递增以产生另一个相同类型的整数【参考方案13】:

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

代替:

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

【讨论】:

【参考方案14】:

考虑到这个问题的受欢迎程度以及这种功能背后的兴趣,我很惊讶地发现还没有涉及 T4 的答案。

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

您可以简单地为您喜欢的每种类型生成所需的函数,并相应地使用它(在编译时!),而不用费力和牺牲编译时的确定性。

为了做到这一点:

创建一个名为 GenericNumberMethodTemplate.tt 的新 文本模板 文件。 删除自动生成的代码(您将保留大部分代码,但有些不需要)。 添加以下 sn-p:
<#@ 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)" #>

说实话:这很酷。

免责声明:此示例受到Metaprogramming in .NET by Kevin Hazzard and Jason Bock, Manning Publications 的严重影响。

【讨论】:

这很酷,但是是否可以修改此解决方案以使方法接受一些泛型类型T,它是或继承自各种IntX 类?我喜欢这个解决方案,因为它可以节省时间,但它可以 100% 解决问题(尽管不如 C# 支持内置的这种类型的约束那么好)每个生成的方法仍然应该是通用的,这样他们可以返回一个继承自 IntXX 类之一的类型的对象。 @ZacharyKniebel:IntXX 类型是结构,表示they don't support inheritance in the first place。即使是这样,Liskov substitution principle(您可能从 SOLID 习语中知道)也适用:如果方法被定义为 X 并且 YX 的子级,那么根据定义,任何 Y 都应该能够作为其基类型的替代品传递给该方法。 这种使用策略 ***.com/questions/32664/… 的解决方法确实使用 T4 来生成类。 +1 对于这个解决方案,因为它保留了内置整数类型的操作效率,这与基于策略的解决方案不同。如果多次使用(如在数学库中),通过附加(可能是虚拟的)方法调用内置 CLR 运算符(如 Add)会严重影响性能。而且由于整数类型的数量是恒定的(并且不能继承),您只需要重新生成代码即可修复错误。 非常酷,我正要开始使用它,然后我记得我是多么依赖 Resharper 进行重构,你不能通过 T4 模板进行重命名重构。这并不重要,但值得考虑。【参考方案15】:

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

    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;
        
    

请注意,typeof 是在编译时评估的,因此编译器会删除 if 语句。编译器还删除了虚假的强制转换。所以有些东西会在编译器中解析为

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

【讨论】:

感谢您提供经验解决方案! 不是每个类型都创建同一个方法吗?【参考方案16】:

如果您使用的是 .NET 4.0 及更高版本,则只需使用 dynamic 作为方法参数并检查 in runtime 是否传递了 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.
    

当然,此解决方案仅适用于运行时,但从不适用于编译时。

如果您想要一个始终在编译时工作而不是在运行时工作的解决方案,那么您必须将 dynamic 包装为一个公共结构/类,其重载 public构造函数只接受所需类型的参数,并为结构/类提供适当的名称。

包装的dynamic总是类/结构的private成员是有道理的,它是结构/类的唯一成员和唯一的名称结构/类的成员是“值”。

如果需要,您还必须定义和实现 public 方法和/或运算符,以使用类/结构的私有动态成员所需的类型。

结构/类具有 special/unique 构造函数,该构造函数接受 dynamic 作为参数来初始化它是唯一称为“值”的私有动态成员,但 special/unique这个构造函数的strong>修饰符当然是私有的

一旦类/结构准备就绪,将 IntegerFunction 参数的类型定义为已定义的类/结构。

实现该想法的示例 long 代码类似于:

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"
    

请注意,要在您的代码中使用 dynamic,您必须添加对 Microsoft.CSharp

的引用

如果 .NET 框架的版本低于/低于/小于 4.0 并且 dynamic 在该版本中未定义,那么您将不得不改用 object 并执行转换为整数类型,这很麻烦,因此我建议您至少使用 .NET 4.0 或更新版本,以便您可以使用 dynamic 而不是 object

【讨论】:

【参考方案17】:

如果您只想使用一种数字类型,您可以考虑使用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 或其他(如果需要),但您将无法在同一程序中将 ComputeSomethingdoubleint 一起使用。

但是为什么不将所有double 替换为int 呢?因为无论输入是double 还是int,您的方法都可能希望使用double。别名可以让你准确地知道哪个变量使用了 dynamic 类型。

【讨论】:

【参考方案18】:

我也遇到过类似的情况,需要处理数字类型和字符串;看起来有点奇怪,但你去吧。

再次,像许多人一样,我查看了约束并想出了一堆它必须支持的接口。但是,a) 它不是 100% 防水的,并且 b) 任何新看这个长长的约束列表的人都会立即感到非常困惑。

因此,我的方法是将所有逻辑放入一个没有约束的泛型方法中,但将该泛型方法设为私有。然后我用公共方法公开它,一个显式处理我想要处理的类型 - 在我看来,代码是干净和明确的,例如

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

【讨论】:

【参考方案19】:

不幸的是,.NET 并没有提供本地实现这一点的方法。

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

sbytebyteshortushortintuintlongulongfloatdecimaldecimal、@9876543332 @

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

这是代码使用示例。

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;

【讨论】:

【参考方案20】:

从 C# 7.3 开始,您可以使用更接近的近似 - 非托管约束 来指定类型参数是非指针、不可为空的unmanaged 类型.

class SomeGeneric<T> where T : unmanaged

//...

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

如果一个类型是以下任何类型,那么它就是非托管类型:

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

为了进一步限制和消除不实现IComparable的指针和用户定义类型添加IComparable(但枚举仍然是从IComparable派生的,所以通过添加IEquatable来限制枚举,你可以去进一步取决于您的情况并添加其他接口。非托管允许使此列表更短):

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

但这并不妨碍 DateTime 实例化。

【讨论】:

不错,但还不够……例如,DateTime 属于unmanaged, IComparable, IEquatable&lt;T&gt; 约束…… 我知道,但是您可以根据自己的情况走得更远并添加其他接口。非托管允许使此列表更短。我刚刚展示了方法,使用非托管的近似值。在大多数情况下,这就足够了 这种方法在我们实现数学运算的重要情况下会失败 T Mult(T a,T b) return a*b; 只能用于数字类型,不能用于日期时间。 在大多数情况下,这已经足够并且容易记住了。提高可读性——对于那些阅读代码的人来说意图很明确。 文档:docs.microsoft.com/en-us/dotnet/csharp/programming-guide/…【参考方案21】:

主题很旧,但适合未来的读者:

此功能与 Discriminated Unions 密切相关,目前尚未在 C# 中实现。我在这里发现了它的问题:

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

此问题仍未解决,已计划为C# 10 提供功能

所以我们还是要再等一会儿,但是发布后你可以这样:

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

【讨论】:

我看不到连接。联合(无论是否“区分”)是关于传递一个允许包含多种类型之一的值。泛型约束是关于调用已知类型支持的方法。相关提案为Updating generic math .. for .NET 7。不同之处在于,当您调用泛型方法时,就知道您拥有什么类型。不需要工会。例如,在任何课程中,写public static T MyFunc(T a, T b) ... 。在编译时 T 被解析为特定类型。【参考方案22】:

是的,或者说很快就会有!

看看这个.NET Blog post。

从 .NET 6(我认为是预览版 7)开始,您将能够利用 INumberIFloatingPoint 等接口来创建以下程序:

using System;

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;

INumber 当前来自System.Runtime.Experimental NuGet 包。我上面示例的项目文件看起来像

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <EnablePreviewFeatures>true</EnablePreviewFeatures>
        <OutputType>Exe</OutputType>
        <TargetFramework>net6.0</TargetFramework>
        <Nullable>enable</Nullable>
        <LangVersion>preview</LangVersion>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="System.Runtime.Experimental" Version="6.0.0-preview.7.21377.19" />
    </ItemGroup>

</Project>

还有IAdditionOperatorsIComparisonOperators等接口,可以通用使用特定的操作符。

【讨论】:

【参考方案23】:

实现IComparable, IComparable&lt;T&gt;, IConvertible, IEquatable&lt;T&gt;, IFormattable 的所有数字类型are structs。但是,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);

【讨论】:

【参考方案24】:

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

【讨论】:

我想知道为什么他们仍然在 .NET 5+ 中没有将 int16、int32、int64 等标记为 IInteger? uint16,uint32 等作为 IUInteger 只是为了轻松地在泛型中放置一个约束,无论其大小,都需要整数类型。他们也可以在那里添加一些类型,也就是动态,但仅适用于在某些情况下允许使泛型无效的原语。

以上是关于是否存在将我的泛型方法限制为数字类型的约束?的主要内容,如果未能解决你的问题,请参考以下文章

泛型编程的术语

C# 泛型方法约束为继承自某类时,调用方法,传子类实参,为什么报错?应该怎么写

Java的泛型约束和限制

如何将方法的泛型类型限制为打字稿中的对象?

泛型编程类型约束与软件扩展性--面向可扩展的泛型编程就是面相类型约束编程

使用带有约束的泛型时无法隐式转换类型[重复]