任何人都可以告诉我是否有一种方法可以使用泛型来限制泛型类型参数T
:
Int16
Int32
Int64
UInt16
UInt32
UInt64
我知道的where
关键字,但无法找到一个接口只有这些类型,
就像是:
static bool IntegerFunction(T value) where T : INumeric
Konrad Rudol.. 132
Hejlsberg 在接受 Bruce Eckel 采访时描述了不实现该功能的原因.
不过,我不得不承认,我不知道他认为他提议的解决方法是如何工作的.他的建议是将算术运算推迟到其他一些通用类(阅读采访!).这有什么用?恕我直言,并不多.
Hejlsberg 在接受 Bruce Eckel 采访时描述了不实现该功能的原因.
不过,我不得不承认,我不知道他认为他提议的解决方法是如何工作的.他的建议是将算术运算推迟到其他一些通用类(阅读采访!).这有什么用?恕我直言,并不多.
考虑到这个问题的普及以及这种功能背后的兴趣,我很惊讶地发现还没有涉及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中元编程的严重影响.
对此没有任何限制.对于想要使用泛型进行数值计算的人来说,这是一个真正的问题.
我会更进一步说我们需要
static bool GenericFunction(T value) where T : operators( +, -, /, * )
甚至
static bool GenericFunction(T value) where T : Add, Subtract
不幸的是,你只有接口,基类和关键字struct
(必须是值类型),class
(必须是引用类型)和new()
(必须有默认构造函数)
您可以将数字包装在codeproject上的其他内容(类似于INullable
)中.
您可以在运行时应用限制(通过反映运算符或检查类型),但这确实失去了首先使用泛型的优势.
使用策略的解决方法:
interface INumericPolicy{ T Zero(); T Add(T a, T b); // add more functions here, such as multiplication etc. } struct NumericPolicies: INumericPolicy , INumericPolicy // add more INumericPolicy<> for different numeric types. { int INumericPolicy .Zero() { return 0; } long INumericPolicy .Zero() { return 0; } int INumericPolicy .Add(int a, int b) { return a + b; } long INumericPolicy .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(this P p, params T[] a) where P: INumericPolicy
{ 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结构.
这个问题有点像常见问题解答之一,所以我把它作为wiki发布(因为我以前发过类似的,但这是一个较旧的); 无论如何...
您使用的是什么版本的.NET?如果您使用的是.NET 3.5,那么我在MiscUtil中有一个通用运算符实现(免费等).
这有类似的方法T Add
,以及不同类型的算术的其他变体(如DateTime + TimeSpan
).
此外,这适用于所有内置,提升和定制的操作员,并缓存代表的性能.
关于为什么这很棘手的一些额外背景在这里.
您可能还想知道dynamic
(4.0)排序也间接地解决了这个问题 - 即
dynamic x = ..., y = ... dynamic result = x + y; // does what you expect
不幸的是,您只能在此实例的where子句中指定struct.看起来很奇怪你不能具体指定Int16,Int32等,但我确信在where子句中不允许值类型的决定存在一些深层实现原因.
我想唯一的解决方案是进行运行时检查,不幸的是,这会阻止在编译时拾取问题.那就像: -
static bool IntegerFunction(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... }
我知道这有点难看,但至少提供了所需的约束.
我还会研究这种实现可能带来的性能影响,也许还有更快的方法.
可能你最接近的是
static bool IntegerFunction(T value) where T: struct
不确定您是否可以执行以下操作
static bool IntegerFunction(T value) where T: struct, IComparable , IFormattable, IConvertible, IComparable , IEquatable
对于某些特定的东西,为什么不只是为每种类型都有重载,列表是如此之短,它可能会有更少的内存占用.