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

Is there a constraint that restricts my generic method to numeric types?

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

  • Int16
  • Int32
  • Int64
  • UInt16
  • UInt32
  • <5233>

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

就像是:

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

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

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 >实现。只要它不必是可扩展的,即如果你只想支持固定数量的类型,例如intdouble,你就可以使用相对简单的接口:

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

(GitHub Gist中的最小实现。)

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

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

...并实现DfpCalculator : ICalculator的所有成员。

不幸的是,另一种不同的方法是使用政策类,正如谢尔盖莎达尔的回答中所讨论的那样。


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

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

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

为此:

  • 创建一个名为GenericNumberMethodTemplate.tt的新文本模板文件。
  • 删除自动生成的代码(您将保留大部分代码,但不需要某些代码)。
  • 添加以下代码段:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<#@ 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;
        }
    <#
    } #>
}

而已。你现在完成了。

保存此文件将自动将其编译为此源文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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方法中,您可以验证您是否具有编译时确定性:

1
2
3
4
5
6
7
8
9
10
11
12
13
namespace TTTTTest
{
    class Program
    {
        static void Main(string[] args)
        {
            long val1 = 5L;
            long val2 = 10L;
            Console.WriteLine(MaxMath.Max(val1, val2));
            Console.Read();
        }
    }
}

enter image description here

我会提前一句话:不,这不违反DRY原则。 DRY原则是为了防止人们在多个地方复制代码,导致应用程序难以维护。

这里的情况并非如此:如果您想要进行更改,那么您只需更改模板(所有代的单一来源!)即可完成。

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

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

老实说:这很酷。

免责声明:此示例受到了来自Kevin Hazzard和Jason Bock,Manning Publications的.NET中元编程的严重影响。


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

我会更进一步说我们需要

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

甚至

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

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

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

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


使用策略的解决方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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();
}

算法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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;
    }

}

用法:

1
2
3
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结构。


这个问题有点像常见问题解答之一,所以我把它作为wiki发布(因为我以前发过类似的,但这是一个较旧的);无论如何...

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

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

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

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

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

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

不幸的是,您只能在此实例的where子句中指定struct。看起来很奇怪你不能具体指定Int16,Int32等,但我确信在where子句中不允许值类型的决定存在一些深层实现原因。

我想唯一的解决方案是进行运行时检查,不幸的是,这会阻止在编译时拾取问题。那就像: -

1
2
3
4
5
6
7
8
9
10
11
12
13
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
static bool IntegerFunction< T >(T value) where T: struct

不确定您是否可以执行以下操作

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

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
    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语句。编译器还会删除虚假强制转换。所以有些东西会在编译器中解决

1
2
3
4
        internal static int Sum(int first, int second)
        {
            return first + second;
        }

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

代替:

1
2
3
4
5
6
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.

你可以写:

1
2
3
4
5
6
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


如果您使用的是.NET 4.0及更高版本,那么您可以使用dynamic as method参数并在运行时检查传递的动态参数类型是数值/整数类型。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
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包装动态,它的重载公共构造函数只接受所需类型的参数,并给struct / class相应的名称。

有意义的是,包装的动态始终是类/结构的私有成员,并且它是结构/类的唯一成员,并且结构/类的唯一成员的名称是"value"。

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

这也是有意义的,struct / class有一个特殊的/唯一的构造函数,它接受动态的参数,初始化它唯一的私有动态成员,名为"value",但这个构造函数的修饰符当然是私有的。

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
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且在该版本中未定义动态,那么您将不得不使用对象并进行转换为整数类型,这很麻烦,因此我建议您使用at至少.NET 4.0或更新版本,如果可以,您可以使用动态而不是对象。


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

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


这项运动有什么意义?

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

1
static bool IntegerFunction(Int64 value) { }

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

1
2
3
static bool IntegerFunction(Int64 value) { }
...
static bool IntegerFunction(Int16 value) { }

当我试图为泛型类型重载运算符时,这个限制影响了我;由于没有"INumeric"约束,并且由于其他原因,stackoverflow上的优秀人员乐于提供,因此无法在泛型类型上定义操作。

我想要类似的东西

1
2
3
4
5
6
7
8
9
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动态运行时输入解决了这个问题。

1
2
3
4
5
6
7
8
9
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的两件事是

  • 性能。所有值类型都被装箱。
  • 运行时错误。你"击败"编译器,但失去了类型安全性。如果泛型类型没有定义运算符,则在执行期间将抛出异常。

  • 目前还没有"好"的解决方案。但是,您可以显着缩小类型参数,以排除许多针对您的hypotetical"INumeric"约束的错误,正如Haacked所示。

    static bool IntegerFunction < T >(T value)其中T:IComparable,IFormattable,IConvertible,IComparable < T >,IEquatable < T >,struct
    {...


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

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


    我会使用一个你可以处理的通用的...

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    /// <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;
    }

    我有类似的情况,我需要处理数字类型和字符串;看起来有点混乱,但你去了。

    同样,像许多人一样,我查看了约束,并提出了一些必须支持的接口。然而,a)它不是100%不透水的,而且b),任何看到这长长的限制列表的新人都会立即感到困惑。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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
    }

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

    所以,而不是非常通用

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

    你可以有

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

    如果需要,这可能允许您轻松地从double转到int或其他,但是您无法在同一程序中将ComputeSomethingdoubleint一起使用。

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


    它们都没有继承的单个接口或基类(也不是其他类继承的),所以简单的答案是否定的。

    我不知道为什么这是一个问题。你想在IntegerFunction类中做什么只能对整数做?


    我认为你误解了泛型。如果您尝试执行的操作仅适用于特定数据类型,那么您不会执行"通用"操作。

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

    如果你根据传入函数的int的实际大小执行不同的操作,那么我认为你应该认真考虑重新考虑甚至尝试做你正在做的事情。如果你不得不愚弄语言,你应该多考虑一下你想要完成什么,而不是如何做你想要的。

    如果失败了,可以使用Object类型的参数,然后您必须检查参数的类型并采取适当的操作或抛出异常。


    推荐阅读