谁能告诉我泛型是否可以将泛型类型参数T限制为:
Int16
Int32
Int64
UInt16
UInt32
UInt64
我知道where关键字,但是找不到仅适用于这些类型的接口,
就像是:
static bool IntegerFunction(T value) where T : INumeric
#1楼
这个问题有点像是一个常见问题,所以我将其发布为Wiki(因为我之前发布过类似的文章,但这是一个较旧的问题); 无论如何...
您正在使用什么版本的.NET? 如果您使用的是.NET 3.5,那么我在MiscUtil (免费等)中有一个通用的运算符实现 。
它具有T Add(T x, T y) ,以及用于不同类型(如DateTime + TimeSpan )的其他算术变体。
此外,这适用于所有内置,提升和定制的运算符,并缓存代表以提高性能。
为什么这是棘手的一些其他背景在这里 。
您可能还想知道dynamic (4.0)排序也间接解决了此问题-即
dynamic x = ..., y = ...
dynamic result = x + y; // does what you expect
#2楼
.NET数字基元类型不共享任何允许它们用于计算的通用接口。 可以定义自己的接口(例如ISignedWholeNumber ),该接口将执行此类操作,定义包含单个Int16 , Int32等的结构并实现这些接口,然后具有接受约束到ISignedWholeNumber泛型类型的方法,但具有将数值转换为您的结构类型可能会很麻烦。
一种替代方法是使用静态属性bool Available {get;};定义静态类Int64Converter bool Available {get;}; 以及Int64 GetInt64(T value) , T FromInt64(Int64 value) , bool TryStoreInt64(Int64 value, ref T dest)静态委托。 该类的构造函数可以使用硬编码来加载已知类型的委托,并可以使用Reflection来测试T类型是否使用正确的名称和签名来实现方法(如果它像包含Int64并表示数字的结构那样,但具有自定义的ToString()方法)。 这种方法将失去与编译时类型检查相关的优势,但是仍然可以避免装箱操作,并且每种类型仅需“检查”一次。 之后,与该类型关联的操作将被委托分派替换。
#3楼
我创建了一些库功能来解决这些问题:
代替:
public T DifficultCalculation(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(Number a, Number b)
{
Number result = a * b + a;
return (T)result;
}
Console.WriteLine(DifficultCalculation(2, 3)); // Results in 8.
#4楼
我会使用一种通用的,您可以处理外部性...
///
/// Generic object copy of the same type
///
/// The type of object to copy
/// The source object to copy
public T CopyObject(T ObjectSource)
{
T NewObject = System.Activator.CreateInstance();
foreach (PropertyInfo p in ObjectSource.GetType().GetProperties())
NewObject.GetType().GetProperty(p.Name).SetValue(NewObject, p.GetValue(ObjectSource, null), null);
return NewObject;
}
#5楼
考虑到这个问题的普遍性以及这种功能背后的兴趣,我很惊讶地看到还没有涉及T4的答案。
在这个示例代码中,我将演示一个非常简单的示例,说明如何使用功能强大的模板引擎来完成编译器在泛型背后的工作。
您无需花钱,也不必牺牲编译时的确定性,您只需为所需的每种类型生成所需的函数,然后相应地使用它即可(在编译时!)。
为此:
创建一个名为GenericNumberMethodTemplate.tt的新文本模板文件。
删除自动生成的代码(您将保留其中的大部分,但不需要其中的一部分)。
添加以下代码段:
typeof(Int16), typeof(Int32), typeof(Int64),
typeof(UInt16), typeof(UInt32), typeof(UInt64)
};
#>
using System;
public static class MaxMath {
#>
public static Max ( val1, 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 。 然后,将这些行添加到模板文件中,以便将其包括在最终的编译中:
老实说:这很酷。