有人能告诉我是否有一种方法可以用泛型将泛型类型参数T限制为:
Int16 Int32 Int64 UInt16 UInt32 UInt64
我知道哪里关键字,但不能找到只有这些类型的接口,
喜欢的东西:
static bool IntegerFunction<T>(T value) where T : INumeric
有人能告诉我是否有一种方法可以用泛型将泛型类型参数T限制为:
Int16 Int32 Int64 UInt16 UInt32 UInt64
我知道哪里关键字,但不能找到只有这些类型的接口,
喜欢的东西:
static bool IntegerFunction<T>(T value) where T : INumeric
当前回答
不幸的是,. net并没有提供一种本地的方法。
为了解决这个问题,我创建了OSS库generatics,它为以下内置数字类型及其可空等价提供了大多数标准数字操作,并能够添加对其他数字类型的支持。
sbyte、byte、short、ushort、int、uint、long、ulong、float、double、decimal、BigInteger
其性能相当于特定于数值类型的解决方案,允许您创建高效的通用数值算法。
下面是一个代码使用示例。
public static T Sum(T[] items)
{
T sum = Number.Zero<T>();
foreach (T item in items)
{
sum = Number.Add(sum, item);
}
return sum;
}
public static T SumAlt(T[] items)
{
// implicit conversion to Number<T>
Number<T> sum = Number.Zero<T>();
foreach (T item in items)
{
// operator support
sum += item;
}
// implicit conversion to T
return sum;
}
其他回答
这个问题有点像常见问题,所以我在维基上发布了这个(因为我之前发布过类似的问题,但这是一个较老的问题);无论如何……
你用的是什么版本的。net ?如果你使用的是。net 3.5,那么我在MiscUtil中有一个通用操作符实现(免费等)。
它有T Add<T>(T x, T y)等方法,以及不同类型上的其他算术变体(如DateTime + TimeSpan)。
此外,这适用于所有内置、提升和定制操作符,并缓存委托以获得性能。
这里有一些关于为什么这很棘手的额外背景。
你可能还想知道动态(4.0)也间接地解决了这个问题。
dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
考虑到这个问题的受欢迎程度和这样一个函数背后的兴趣,我很惊讶地看到,还没有涉及T4的答案。
在这个示例代码中,我将演示一个非常简单的示例,说明如何使用强大的模板引擎来完成编译器在幕后使用泛型所做的工作。
你可以简单地为你喜欢的每种类型生成你想要的函数,并相应地使用它(在编译时!),而不是通过循环和牺牲编译时的确定性。
为了做到这一点:
创建一个新的名为GenericNumberMethodTemplate.tt的文本模板文件。 删除自动生成的代码(您将保留大部分代码,但有些代码不需要)。 添加以下片段:
<#@ template language="C#" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Core" #>
<# Type[] types = new[] {
typeof(Int16), typeof(Int32), typeof(Int64),
typeof(UInt16), typeof(UInt32), typeof(UInt64)
};
#>
using System;
public static class MaxMath {
<# foreach (var type in types) {
#>
public static <#= type.Name #> Max (<#= type.Name #> val1, <#= type.Name #> val2) {
return val1 > val2 ? val1 : val2;
}
<#
} #>
}
就是这样。你现在完成了。
保存这个文件会自动编译成这个源文件:
using System;
public static class MaxMath {
public static Int16 Max (Int16 val1, Int16 val2) {
return val1 > val2 ? val1 : val2;
}
public static Int32 Max (Int32 val1, Int32 val2) {
return val1 > val2 ? val1 : val2;
}
public static Int64 Max (Int64 val1, Int64 val2) {
return val1 > val2 ? val1 : val2;
}
public static UInt16 Max (UInt16 val1, UInt16 val2) {
return val1 > val2 ? val1 : val2;
}
public static UInt32 Max (UInt32 val1, UInt32 val2) {
return val1 > val2 ? val1 : val2;
}
public static UInt64 Max (UInt64 val1, UInt64 val2) {
return val1 > val2 ? val1 : val2;
}
}
在main方法中,你可以验证你是否具有编译时确定性:
namespace TTTTTest
{
class Program
{
static void Main(string[] args)
{
long val1 = 5L;
long val2 = 10L;
Console.WriteLine(MaxMath.Max(val1, val2));
Console.Read();
}
}
}
我先说一句:不,这并没有违反DRY原则。DRY原则的存在是为了防止人们在多个地方复制代码,从而导致应用程序变得难以维护。
这里的情况完全不同:如果您想要更改,那么您只需更改模板(对于您的所有生成都是一个单一的源代码!),然后就完成了。
为了将它与您自己的自定义定义一起使用,请向生成的代码添加一个名称空间声明(确保它与您将定义自己的实现的名称空间声明相同),并将该类标记为partial。然后,将这些行添加到你的模板文件中,这样它就会被包含在最终的编译中:
<#@ import namespace="TheNameSpaceYouWillUse" #>
<#@ assembly name="$(TargetPath)" #>
说实话:这太酷了。
免责声明:这个示例受到了Kevin Hazzard和Jason Bock, Manning Publications在。net中的元编程的严重影响。
话题老了,但对未来的读者来说:
这个特性与歧视联盟紧密相关,而歧视联盟目前还没有在c#中实现。我发现它的问题在这里:
https://github.com/dotnet/csharplang/issues/113
这个问题仍然没有解决,并且已经计划在c# 10中推出新特性
所以我们仍然需要等待一段时间,但在释放之后,你可以这样做:
static bool IntegerFunction<T>(T value) where T : Int16 | Int32 | Int64 | ...
从c# 7.3开始,您可以使用更接近的非托管约束来指定类型形参是非指针、非空的非托管类型。
class SomeGeneric<T> where T : unmanaged
{
//...
}
非托管约束意味着结构约束,并且不能与结构或new()约束组合。
如果是以下类型之一,则该类型为非托管类型:
Sbyte、byte、short、ushort、int、uint、long、ulong、char、float、double、decimal或bool 任何enum类型 任何指针类型 任何只包含非托管类型字段的用户定义结构类型,在c# 7.3及更早版本中,都不是构造类型(至少包含一个类型参数的类型)。
为了进一步限制和消除没有实现IComparable的指针和用户定义类型add IComparable(但enum仍然派生自IComparable,因此通过添加IEquatable < T >来限制enum,您可以根据您的情况进一步添加额外的接口。Unmanaged可以让这个列表更短):
class SomeGeneric<T> where T : unmanaged, IComparable, IEquatable<T>
{
//...
}
但是这并没有阻止DateTime实例化。
我想知道和samjudson一样,为什么只对整数有效?如果是这样的话,你可能想要创建一个helper类或者类似的东西来保存你想要的所有类型。
如果你想要的只是整数,不要使用泛型,那不是泛型;或者更好的是,通过检查其类型来拒绝任何其他类型。