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


364

谁能告诉我泛型是否可以将泛型类型参数限制T为仅:

  • Int16
  • Int32
  • Int64
  • UInt16
  • UInt32
  • UInt64

我知道这个where关键字,但是找不到仅适用于这些类型的接口,

就像是:

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

2
现在有各种各样的C#提议可以实现这一目标,但是AFAIK只是初步的探索/讨论而已。请参阅探索:形状和扩展探索:角色,扩展接口和静态接口成员冠军“类型类(又名概念,结构通用约束)”建议:通用类型应支持运算符
Chris Yungmann,

Answers:


140

C#不支持此功能。Hejlsberg 在接受Bruce Eckel的采访时描述了不实现该功能的原因:

尚不清楚增加的复杂性是否值得您获得少量收益。如果约束系统中不直接支持您要执行的操作,则可以使用工厂模式来执行。Matrix<T>例如,您可能有一个,并且Matrix您想定义一个点积方法。那当然,这意味着你最终需要了解如何乘两个T你S,但也不能说,作为一个约束,至少不是如果Tintdoublefloat。但是,您可以做的是Matrix将a作为参数Calculator<T>,并输入Calculator<T>一个名为的方法multiply。您去实现它,然后将其传递给Matrix

但是,这导致了相当复杂的代码,用户必须为要使用的Calculator<T>每个代码提供自己的实现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的答案所述


25
顺便说一句,MiscUtil提供了一个通用类来执行此操作;Operator/ Operator<T>; yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell

1
@Mark:好评论。但是,为了清楚起见,我不认为Hejlsberg像在代码中那样将代码生成作为解决问题的一种解决方案Operator<T>(因为访谈是在Expressions框架存在之前就进行的,尽管可以当然使用Reflection.Emit))-我会对他的解决方法非常感兴趣。
康拉德·鲁道夫

@Konrad Rudolph:我认为这个类似问题的答案解释了Hejlsberg的解决方法。另一个通用类被抽象化。由于它要求您为要支持的每种类型实现另一个通用类,因此将导致代码重复,但这确实意味着您只能使用支持的类型实例化原始通用类。
Ergwun 2011年

14
我不同意Heijsberg的短语“因此,从某种意义上讲,C ++模板实际上是未类型化的或松散类型的。而C#泛型是强类型的。” 那真的是营销BS来推广C#。强/弱类型与诊断质量无关。否则:有趣的发现。
塞巴斯蒂安·马赫

100

考虑到这个问题的普遍性以及这种功能背后的兴趣,我很惊讶地看到还没有涉及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)" #>

坦白说:这很酷。

免责声明:该示例受到Manning Publications的Kevin Hazzard和Jason Bock的.NET元编程的严重影响。


这很酷,但是是否可以修改此解决方案,以使方法接受T从各个IntX类继承或从各个类继承的某种泛型类型?我喜欢这种解决方案,因为它可以节省时间,但要100%地解决该问题(尽管不如C#支持内置的这种约束那样好),每个生成的方法仍应是通用的,这样他们可以返回从一个IntXX类继承的类型的对象。
Zachary Kniebel 2014年

1
@ZacharyKniebel:IntXX类型是结构,这意味着它们首先不支持继承。即使这样做,Liskov替换原理(您可能会从SOLID习惯用法中得知)也适用:如果该方法定义为X并且Y是的子代,X那么根据定义,任何方法Y都应该能够传递给该方法作为的替代它的基本类型。
Jeroen Vannevel 2014年

1
使用策略stackoverflow.com/questions/32664/…的此变通办法确实使用T4来生成类。
谢尔盖·尚达

2
此解决方案+1,因为它保留了内置整数类型的运算效率,这与基于策略的解决方案不同。如果多次使用内置CLR运算符(例如Add)(如果是虚拟的),则调用它会严重影响性能(例如在数学库中)。并且由于整数类型的数量是恒定的(并且不能从中继承),因此您只需要重新生成代码即可修复错误。
Attila Klenik

1
非常酷,我将要开始使用它,然后我想起了重构对Resharper的依赖性,您无法通过T4模板重命名重构。这并不关键,但值得考虑。
bradgonesurfing

86

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

我走得更远,说我们需要

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

甚至

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

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

您可以将数字包装在codeproject上的其他类似(类似于INullable<T>)中。


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


2
我想知道您是否已经看到MiscUtil对通用运算符的支持... yoda.arachsys.com/csharp/miscutil/usage/genericoperators.html
Marc Gravell

10
是的-乔恩·斯凯特(Jon Skeet)不久前在其他地方给我指出了他们(但在今年以来的答复之后)-他们是一个聪明的主意,但我仍然希望获得适当的约束支持。
基思

1
等一下,where T : operators( +, -, /, * )合法的C#吗?对不起,新手问题。
kdbanman 2015年

@kdbanman我不这么认为。Keith表示C#不支持OP的要求,并建议我们应该可以where T : operators( +, -, /, * ),但不能。
AMTerp

62

使用策略的解决方法:

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结构。


当函数参数少于泛型参数时,这种模式会出现问题。打开stackoverflow.com/questions/36048248/…–
xvan

有什么理由要使用struct?如果我改用singleton-class并将实例更改为public static NumericPolicies Instance = new NumericPolicies();然后添加此构造函数,该怎么办private NumericPolicies() { }
M.kazem Akhgary '16

@ M.kazemAkhgary,您可以使用单例。我更喜欢struct。从理论上讲,可以通过编译器/ CLR对其进行优化,因为该结构不包含任何信息。如果是单例,您仍将传递参考,这可能会对GC造成额外压力。另一个优点是struct不能为null :-)。
谢尔盖·尚达

我要说的是,您找到了一个非常聪明的解决方案,但是该解决方案对我来说太有限了:我打算在中使用它T Add<T> (T t1, T t2),但Sum()仅在它可以从其参数中检索自己的T类型时才有效。当它嵌入另一个通用函数中时。
Tobias Knauss '18年

16

这个问题有点像是一个常见问题,所以我将其发布为Wiki(因为我之前发布过类似的文章,但这是一个较旧的问题);无论如何...

您正在使用什么版本的.NET?如果您使用的是.NET 3.5,那么我在MiscUtil(免费等)中有一个通用的运算符实现

它具有T Add<T>(T x, T y),和类似的方法,以及用于对不同类型(例如DateTime + TimeSpan)进行算术运算的其他变体。

此外,这适用于所有内置,提升和定制的操作员,并缓存代表以提高性能。

为什么这是棘手的一些其他背景在这里

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

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

14

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

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

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


13
+1,但是,// Rest of code...如果它取决于约束定义的操作,则可能无法编译。
尼克(Nick)

1
Convert.ToIntXX(value)可能有助于使“ //其余代码”进行编译-至少直到IntegerFunction的返回类型也为T类型,然后您才大受鼓舞。:-p
yoyo

-1; 由于@Nick给出的原因,这不起作用。当您尝试使用// Rest of code...like value + value或进行任何算术运算时value * value,就会出现编译错误。
马克·阿默里

13

您可能最接近的是

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>

对于如此具体的东西,为什么不只是每种类型都有重载,列表是如此之短,而且可能会占用更少的内存。


6

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

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

非托管约束暗含结构约束,不能与struct或new()约束结合使用。

如果类型是以下任何类型,则它是非托管类型:

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

要进一步限制并消除未实现IComparable的指针和用户定义的类型,请添加IComparable(但枚举仍是从IComparable派生的,因此可以通过添加IEquatable <T>来限制枚举,您可以根据具体情况进一步扩展并添加其他接口。不受管理允许将此列表缩短):

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

不错,但还不够...例如,DateTime瀑布下的unmanaged, IComparable, IEquatable<T>约束..
亚当卡尔韦博尔

我知道,但是您可以根据自己的情况走得更远,并添加其他接口。非托管允许使此列表更短。我刚刚展示了使用不受管理的近似方法。对于大多数情况,这已经足够了
弗拉德·诺瓦科夫斯基

4

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

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

感谢您提供实证解决方案!
zsf222

与为每种类型创建相同的方法不同吗?
路易斯

3

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

代替:

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.

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


2

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

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


2

目前还没有“好的”解决方案。但是,您可以显着缩小类型参数的范围,以排除如上Haacked所示的假设性“数字”约束的许多失配。

静态布尔IntegerFunction <T>(T值)其中T:IComparable,IFormattable,IConvertible,IComparable <T>,IEquatable <T>,结构{...


2

如果使用的是.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.
    }

当然,该解决方案只能在运行时有效,而不能在编译时有效。

如果要使解决方案始终在编译时起作用,而从不在运行时起作用,则必须用公共结构/类包装动态程序,该结构/类的重载公共构造函数仅接受所需类型的参数,并为结构/类指定适当的名称。

有道理的是,动态包装始终是类/结构的私有成员,并且是结构/类的唯一成员,并且结构/类的唯一成员的名称是“值”。

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

同样有意义的是,struct / class具有特殊的/唯一的构造函数,该构造函数接受dynamic作为参数来初始化它唯一的私有动态成员,称为“值”,但是此构造函数的修饰符当然是私有的。

准备好类/结构后,将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"
    }
}

请注意,为了在代码中使用动态,必须添加Microsoft.CSharp的引用

如果.NET Framework的版本低于/低于/低于4.0,并且在该版本中未定义dynamic,那么您将不得不使用object并强制转换为整数类型,这很麻烦,因此我建议您使用.NET 4.0或更高版本(如果可以),则可以使用动态代替对象


2

不幸的是,.NET没有提供本地执行此操作的方法。

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

sbytebyteshortushortintuintlongulongfloatdoubledecimal,和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;
}

1

练习的目的是什么?

正如人们已经指出的那样,您可能有一个非泛型函数来处理最大的项,并且编译器会自动为您转换较小的int。

static bool IntegerFunction(Int64 value) { }

如果您的功能位于性能至关重要的路径上(IMO不太可能),则可以为所有需要的功能提供重载。

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

1
我经常使用数值方法。有时我想要整数,有时我想要浮点数。两者都有64位版本,最适合处理速度。在这些之间进行转换是一个可怕的想法,因为每种方式都有损失。虽然我倾向于使用双精度型,但有时我确实发现使用整数更好,因为它们在其他地方使用的方式。但是,当我编写一次算法来让类型决定取决于实例需求时,这将非常好。
VoteCoffee,2014年

1

我会使用一种通用的,您可以处理外部性...

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

1

当我尝试重载泛型类型的运算符时,这一限制影响了我。由于没有“数字”约束,并且由于许多其他原因,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 };
    }
}

关于使用的两件事dynamic

  1. 性能。所有值类型都装箱。
  2. 运行时错误。您“击败”了编译器,但失去了类型安全性。如果未定义泛型类型,则在执行期间将引发异常。

1

.NET数字基元类型不共享任何公共接口,这些接口将使它们无法用于计算。这将有可能定义自己的界面(例如ISignedWholeNumber),这将执行这样的操作,定义包含一个单一的结构Int16Int32等,以及实现这些接口,然后具有接受泛型类型约束的方法ISignedWholeNumber,但不必转换数值对您的结构类型可能很麻烦。

另一种方法是定义静态类Int64Converter<T>与静态属性bool Available {get;};和静态代表Int64 GetInt64(T value)T FromInt64(Int64 value)bool TryStoreInt64(Int64 value, ref T dest)。该类的构造函数可以使用硬编码来加载已知类型的委托,并可以使用Reflection来测试类型是否T使用正确的名称和签名来实现方法(以防某种类似的结构,其中包含一个Int64and表示一个数字,但是具有自定义ToString()方法)。这种方法将失去与编译时类型检查相关的优势,但仍将设法避免装箱操作,并且每种类型仅需“检查”一次。之后,与该类型关联的操作将被委托分派替换。


@KenKin:IConvertible提供了一种方法,可以将任何整数添加到另一个整数类型以产生例如Int64结果,但是不提供一种方法,例如可以将任意类型的整数递增以产生另一个相同类型的整数。
2013年

1

在类似的情况下,我需要处理数字类型和字符串。似乎有点奇怪的混合,但是你去了。

再次,就像许多人一样,我查看了约束,并提出了它必须支持的一堆接口。但是,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
}

0

如果只需要使用一种数字类型,则可以考虑使用来创建类似于C ++中别名的内容using

因此,与其拥有非常通用的

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

你可以有

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

这可能让你轻松地从去doubleint,或者如果别人需要的,但你将无法使用ComputeSomethingdoubleint在同一个程序。

但是,为什么不全部更换doubleint呢?因为您的方法可能要使用a double作为输入是double还是int。别名使您可以准确知道哪个变量使用了动态类型。


0

主题很旧,但对于将来的读者:

Discriminated Unions到目前为止,此功能与C#中尚未实现的功能紧密相关。我在这里找到了问题:

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

此问题仍未解决,已经计划了该功能 C# 10

因此,我们仍然需要等待更多时间,但是在释放之后,您可以通过以下方式进行操作:

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

-11

我认为您是对仿制药的误解。如果您尝试执行的操作仅对特定数据类型有用,那么您就没有做“泛型”的事情。

另外,由于您只希望允许该函数在int数据类型上工作,因此您不需要为每个特定大小使用单独的函数。只需采用最大特定类型的参数,程序便可以自动将较小的数据类型转换为该参数。(即,调用时传递Int16将自动转换为Int64)。

如果您根据传递给函数的int的实际大小执行不同的操作,那么我认为即使尝试做您正在做的事情,您也应该认真考虑一下。如果您必须愚弄语言,则应多想一些您想完成的事情,而不是如何做自己想做的事情。

如果没有其他方法,则可以使用Object类型的参数,然后必须检查参数的类型并采取适当的操作或引发异常。


10
考虑一类直方图<T>。让它采用通用参数是有意义的,因此编译器可以针对字节,整数,双精度数,十进制,BigInt等对其进行优化,但是同时您还需要防止创建Histogram <Hashset >,因为-与Tron交谈-它无法计算。(字面意思:))
海滨

15
您是误解泛型的人。元编程不仅针对可以是任何可能类型的值进行操作,而且还针对符合各种约束的类型进行操作。
吉姆·巴尔特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.