我明白它们是什么,我只是想知道什么时候是使用它们的最佳时机。
我的第一个想法是 - 当我们构建一个(静态)实用程序类时,它应该对不同的数据类型执行某些操作。 因此,使用泛型方法避免某种方法的大量重载是一个好习惯吗?请对此发表评论。
我有一个小示例类。这只是为了举个例子。
public static class Math<T> where T : operator +, operator -
{
public static T Add(T a1, T a2)
{
return a1+a2;
}
public static T Subtract(T a1, T a2)
{
return a1 - a2;
}
}
这是一个很好用的泛型类和方法,例如我希望用最少量的代码添加和减去整数,双精度......等等。
为什么不编译?我试过这个以及修改类签名:
public static class Math<T> where T : struct
据我所知,我必须指定Type参数是引用还是值类型。 我通过指定T必须被约束为值类型来做到这一点,为什么我仍然得到运算符+和/或 - 不能应用于T(应该特别是值类型)的错误
答案 0 :(得分:4)
不,这不是一个好用的。泛型是在不知道类型的情况下提供类型安全的数据结构。通用约束允许您指定有关类型的一些语义,例如实现接口,具有默认构造函数或作为类或结构。
请参阅这些MSDN文章:
它不会编译,因为operator +
部分不是有效约束。
作为值类型不会推断+
或-
等运算符,它只会推断值类型语义(继承object
,是值类型,不能为null,具有默认构造函数)。
<小时/> 通用约束
通用约束有助于编译器从T
为您提供更多信息。无约束泛型只能被证明是object
,因此您只能访问论证中的object
成员。
如果您声明:public void Foo<T>() where T : new()
编译器可以证明您的类型具有默认的公共无参数构造函数。这是约束的目的,它强制可以成为泛型的一方的类型符合契约。
存在各种限制,但正如您所发现的那样存在一些限制。有趣的是,C#中存在IL中不存在的限制,正如Jon Skeet在他的Unconstrained Melody库中探讨的那样,它将enum
约束暴露给C#。
答案 1 :(得分:2)
正如其他人所写,operator+
不是有效约束。如果你想要的是做一些通用数学,你可以使用类似的东西:
public static class Add<T>
{
public static readonly Func<T, T, T> Do;
static Add()
{
var par1 = Expression.Parameter(typeof(T));
var par2 = Expression.Parameter(typeof(T));
var add = Expression.Add(par1, par2);
Do = Expression.Lambda<Func<T, T, T>>(add, par1, par2).Compile();
}
}
public static class Math<T>
{
public static T Add(T a1, T a2)
{
return Add<T>.Do(a1, a2);
}
这将创建并编译执行操作的Expression
,然后将其缓存在通用静态类中。
遗憾的是,使用此方法会丢失对编译器的静态检查(您可以执行以下操作:
object res = Math<object>.Add(new object(), new object());
它会正确编译。在运行时它会爆炸。)
通常,您不能要求特定方法(静态或非静态)或特定属性存在(操作符就像静态方法)(只有一个例外:new()
请求公共无参数构造函数的约束。你可以问的是要实现的接口,还是要存在基类,或者通用参数是class
或struct
(其中两个必须表示为“引用类型“和”值类型“,而不仅仅是class
和struct
)。遗憾的是,没有接口IAddable
,ISubtractable
,......即使你构建它们,int
,double
......也不会实现它们,并且更糟糕的是,在.NET中你不能拥有泛型特化(C ++的一个技巧,你定义一个通用的Math<T>
然后你明确定义特殊的“案例”,如Math<int>
,Math<double>
等等)
答案 2 :(得分:0)
泛型类的一个明显用例是数据结构,它可以存储任何类型的数据,而不必将其全部视为object
的实例。您可能始终使用这些 - IList<T>
,IDictionary<K, V>
等。它可以让您在保留类型安全的同时将内容存储在您不知道类型的位置。诀窍在于你也不知道你所存储的类型,所以你不能用它做很多事情。
因此,通用约束可以让你说出某种东西是引用类型或值类型,或者具有无参数构造函数,或实现接口。当你编写一个必须对参数化类型的实例做某事的泛型类时,它们会很有用。可能看起来没用 - 为什么不使用接口类型作为参数类型并完全避免泛型?因为泛型约束可以强制参数符合多个接口 - 您无法在普通参数类型中指定。因此,您可以编写一个函数:
public static void Frobnicate<T>(T thing)
where T : IList<int>, IDisposable
{
// ...
}
您也可以在其中粘贴一个基类名称。这比指定具体类型更灵活。当然,您可以创建一个继承自IList<int>
和IDisposable
的接口,但是您无法改进可能在那里实现它的所有一次性整数列表。
你也可以在运行时使用反射来检查事物,但编译器,IMO可以更好地处理这种事情。