《深入理解C#》泛型高级
1.泛型约束#
约束要放到泛型方法或泛型类型声明的末尾,用where
来引入。
1.1.引用类型约束#
表示成 T:Class
,确保为引用类型,例如:
struct RefSample<T> where T : class
允许使用==和!=来比较引用(包括null)。
1.2.值类型约束#
表示成 T:struct
,却表类型参数为值类型,包括枚举(enums)。
class ValSample<T> where T : struct
如果一个类型参数存在多个约束,值约束必须为第1个指定。不允许使用==和!=来比较。
1.3.构造函数类型约束#
构造函数类型约束表示成T : new()
,必须是所有类型参数的最后一个约束,它检查类型实参是否有一个可用于创建类型实例的无参构造函数。这适用于所有值类型;所有没有显式声明构造函数的非静态、非抽象类;所有显式声明了一个公共无参数构造函数的非抽象类。
public T CreateInstance<T>() where T : new()
{
return new T();
}
1.4.转换类型约束#
最后(也是最复杂的)一种约束允许你指定另一个类型,类型实参必须可以通过一致性、引用或装箱转换隐式地转换为该类型。你还可以规定一个类型实参必须可以转换为另一个类型实参——这称为类型参数约束(type param eter constraint);
例子:
声 明 | 已构造类型的例子 |
---|---|
class Sample<T> where T : Stream |
有效: Sample<Stream> (一致性转换)无效: Sample<string> |
struct Sample<T> where T : IDisposable |
有效: Sample<SqlConnection> (引用转换)无效: Sample<StringBuilder> |
class Sample<T> where T : IComparable<T> |
有效: Sample<int> (装箱转换)无效: Sample<FileInfo> |
class Sample<T,U> where T : U |
有效: Sample<Stream,IDisposable> (引用转换)无效: Sample<string,IDisposable> |
可以指定多个接口,但只能指定一个类。例如,以下声明毫无问题(尽管很难满足):
//success, 可以指定多个接口,但只能指定一个类
class Sample<T> where T : Stream,
IEnumerable<string>,
IComparable<int>
但以下声明就有问题了:
//faild, 可以指定多个接口,但只能指定一个类
class Sample<T> where T : Stream,
ArrayList,
IComparable<int>
1.5.组合约束#
- 不能既是引用类型又是值类型
- 每一个值类型都有一个构造函数,就不允许再指定一个构造函数约束
- 多个转换类型时,并且其中一个为类,那它应该出现在接口前面,而我们不能多次指定 同一个接口
- 不同类型参数可以有不同约束,它们分别由一个
where
引入
来看一些有效和无效的例子。
有效:
class Sample<T> where T : class, IDisposable, new()
class Sample<T> where T : struct, IDisposable
class Sample<T,U> where T : class where U : struct, T
class Sample<T,U> where T : Stream where U : IDisposable
无效:
class Sample<T> where T : class, struct //既是引用又是值类型
class Sample<T> where T : Stream, class //不能那个同时多个类
class Sample<T> where T : new(), Stream //构造约束在后面
class Sample<T> where T : IDisposable, Stream //引用约束在前
class Sample<T> where T : XmlReader, IComparable, IComparable //多个同个接口
class Sample<T,U> where T : struct where U : class, T //U可能是object,但不好
class Sample<T,U> where T : Stream, U : IDisposable //缺少where
总结:#
- 值类型在最前,引用类型第二,然后是其他约束,new()构造函数约束必须在最后
- 同一个接口只能约束一次
2.实现泛型#
2.1.默认值表达式 default(T)#
如果已经明确了要处理的类型,也就知道了它的“默认”值,例如未初始化字段的默认值。不知道要引用的类型,就不能直接指定默认值。你不能使用null,因为它可能不是一个引用类型。也不能使用0,因为它可能不是数值类型。
为了满足这方面的需求,C# 2提供了默认值表达式(default value expression)。虽然C#语言规范没有说它是一个操作符,但可以把它看做是与typeof相似的操作符,只是返回值不同。代码清单3-4在一个泛型方法中对此进行了演示,并给出了“类型推断”和“转换类型约束”的实例。
例子:以泛型方式将一个给定的值和默认值进行比较
static int CompareToDefault<T>(T value)
where T : IComparable<T>
{
return value.CompareTo(default(T));
}
...
Console.WriteLine(CompareToDefault("x"));
Console.WriteLine(CompareToDefault(10));
Console.WriteLine(CompareToDefault(0));
Console.WriteLine(CompareToDefault(-10));
Console.WriteLine(CompareToDefault(DateTime.MinValue));
2.2.直接比较#
泛型比较接口
共有4个主要的泛型接口可用于比较。
IComparer<T>
和IComparable<T>
用于排序(判断某个值是小于、等于还是大于另一个值),而IEqualityComparer<T>
和IEquatable<T>
通过某种标准来比较两个项的相等性,或查找某个项的散列(通过与相等性概念匹配的方式)。
如果换一种方式来划分这4个接口,IComparaer<T>
和IEqualityComparer<T>
的实例能够比较两个不同的值,而IComparable<T>
和IEquatable<T>
的实例则可以比较它们本身和其他值。
2.3.完整的比较例子:表示一对值#
public sealed class Pair<T1, T2> : IEquatable<Pair<T1, T2>>
{
private static readonly IEqualityComparer<T1> FirstComparer = EqualityComparer<T1>.Default;
private static readonly IEqualityComparer<T2> SecondComparer = EqualityComparer<T2>.Default;
private readonly T1 first;
private readonly T2 second;
public Pair(T1 first, T2 second)
{
this.first = first;
this.second = second;
}
public T1 First { get { return first; } }
public T2 Second { get { return second; } }
public bool Equals(Pair<T1, T2> other)
{
return other != null &&
FirstComparer.Equals(this.First, other.First) &&
SecondComparer.Equals(this.Second, other.Second);
}
public override bool Equals(object o)
{
return Equals(o as Pair<T1, T2>);
}
public override int GetHashCode()
{
return FirstComparer.GetHashCode(first) * 37 +
SecondComparer.GetHashCode(second);
}
}
想要创建实例,
Pair<int, string> pair = new Pair<int, string>(10, "value");
这不是很理想,我们想要的使用类型推断。
类型推断只能用于泛型方法,而且
Pair
类不包含任何泛型方法。如果我们在泛型类型中放入一个泛型方法,那么在调用该方法时仍然需要指定该类型的类型参数。解决方法是使用包含泛型方法的非泛型辅助类
解决方法是使用包含泛型方法的非泛型辅助类
public static class Pair
{
public static Pair<T1, T2> New<T1, T2>(T1 first, T2 second)
{
return new Pair<T1, T2>(first, second);
}
}
这样创建更加优雅
Pair<int, string> pair2 =Pair.New(10,"value");
3.泛型在C#与其他语言中的限制#
1.泛型没有可变性(协变、逆变)
左侧的数组版本虽然可以通过编译,但执行时一样会失败。泛型的设计者认为,这比编译时就失败还要糟糕——静态类型的全部意义就在于在代码运行之前找出错误。
作者:【唐】三三
出处:https://www.cnblogs.com/tangge/p/14496225.html
版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
2020-03-07 Beyond Compare C# 格式与颜色