泛型(Generics)

本文详细介绍了C#2.0中引入的泛型特性,包括基本概念、为何使用泛型、如何创建及使用泛型类与方法等内容。通过对泛型的深入解析,帮助读者更好地理解和应用这一强大特性。

 C# 2.0引入了很多语言扩展,最重要的就是泛型(Generics)、匿名方法(Anonymous Methods)、迭代器
(Iterators)和不完全类型(Partial Types)。
【一、基本概念】
● 泛型允许类、结构、接口、委托和方法通过它们所存贮和操作的数据的类型来参数化。(也就是对数据类型进行参数化,类型也是变量,居然是什么类型要动态赋值)泛型是很有用的,因为它提供了更为强大的编译期间类型检查,需要更少的数据类型之间的显式转换,并且减少了对装箱操作的需要和运行时的类型检查。

【二、为什么要使用泛型】

  • 使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。

  • 泛型最常见的用途是创建集合类。

  • .NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。应尽可能地使用这些类来代替普通的类,如 System.Collections 命名空间中的 ArrayList

  • 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。

  • 可以对泛型类进行约束以访问特定数据类型的方法。

  • 关于泛型数据类型中使用的类型的信息可在运行时通过反射获取。


在没有泛型之前,一些通用的数据结构只能使用object类型来存贮各种类型的数据。例如,下面这个简单Stack类将它的数据存放在一个object数组中,而它的两个方法,Push和Pop,分别使用object来接受和返回数据:
public class Stack
{
object[] items;
int count;
public void Push(object item) {...}
public object Pop() {...}
}
尽管使用object类型使得Stack类非常灵活,但它也不是没有缺点。例如,可以向堆栈中压入任何类型的值,譬如一个Customer实例。然而,重新取回一个值得时候,必须将Pop方法返回的值显式地转换为合适的类型,书写这些转换变更要提防运行时类型检查错误是很乏味的:
Stack stack = new Stack();
stack.Push(new Customer());
Customer c = (Customer)stack.Pop();
如果一个值类型的值,如int,传递给了Push方法,它会自动装箱。而当待会儿取回这个int值时,必须显式的类型转换进行拆箱:
Stack stack = new Stack();
stack.Push(3);
int i = (int)stack.Pop();
这种装箱和拆箱操作增加了执行的负担,因为它带来了动态内存分配和运行时类型检查。Stack类的另外一个问题是无法强制堆栈中的数据的种类。确实,一个Customer实例可以被压入栈中,而在取回它的时候会意外地转换成一个错误的类型:
Stack stack = new Stack();
stack.Push(new Customer());
string s = (string)stack.Pop();//语法正确,但转换不合法。
尽管上面的代码是Stack类的一种不正确的用法,但这段代码从技术上来说是正确的,并且不会发生
编译期间错误。问题直到这段代码运行的时候才会出现,这时会抛出一个InvalidCastException异常。
Stack类无疑会从具有限定其元素类型的能力中获益。使用泛型,这将成为可能。(也就是说泛型可以使得类具有限制操纵数据类型的能力)
【三、如何使用泛型(Generics)】
 A创建泛型
泛型提供了一个技巧来建立带有类型参数(type parameters)的类型。下面的例子声明了一个带有类型参数T的泛型Stack类。类型参数由类名字后面的定界符“<”和“>”指定。通过某种类型建立的Stack<T>的实例 可以无需转换地接受该种类型的数据,这强过于与object相互装换。类型参数T扮演一个占位符的角色,直到使用时指定了一个实际的类型注意T相当于内部数组的数据类型、Push方法接受的参数类型和Pop方法的返回值类型:
public class Stack<T>
{
T[] items;
int count;
public void Push(T item) {...}
public T Pop() {...}
}
使用泛型类Stack<T>时,需要指定实际的类型来替代T。下面的例子中,指定int作为参数类型T:
Stack<int> stack = new Stack<int>();
stack.Push(3);
int x = stack.Pop();
Stack<int>类型称为已构造类型(constructed type)。在Stack<int>类型中出现的所有T被替换为类型参数int。当一个Stack<int>的实例被创建时,items数组的本地存贮是int[]而不是object[],这提供了一个实质的存贮,效率要高过非泛型的Stack。同样,Stack<int>中的Push和Pop方法只操作int值,如果向堆栈中压入其他类型的值将会得到编译期间的错误,而且取回一个值时不必将它显示转换为原类型。
泛型可以提供强类型,这意味着例如向一个Customer对象的堆栈上压入一个int将会产生错误。这是因为Stack<int>只能操作int值,而Stack<Customer>也只能操作Customer对象。下面例子中的最后两行会导致编译器报错:
Stack<Customer> stack = new Stack<Customer>();
stack.Push(new Customer());
Customer c = stack.Pop();
stack.Push(3); // 类型不匹配错误
int x = stack.Pop(); // 类型不匹配错误
泛型类型的声明允许任意数目的类型参数。上面的Stack<T>例子只有一个类型参数,但一个泛型的Dictionary类可能有两个类型参数,一个是键的类型另一个是值的类型:
public class Dictionary<K,V>//多类型参数的例子
{
public void Add(K key, V value) {...}
public V this[K key] {...}
}
使用Dictionary<K,V>时,需要提供两个类型参数:
Dictionary<string,Customer> dict = new Dictionary<string,Customer>();
dict.Add("Peter", new Customer());
Customer c = dict["Peter"];
B.泛型实例化
和非泛型类型类似,编译过的泛型类型也由中间语言(IL, Intermediate Language)指令和元数据表示。泛型类型的IL表示当然已由类型参数进行了编码。当程序第一次建立一个已构造的泛型类型的实例时,如Stack<int>,.NET公共语言运行时中的即时编译器(JIT, just-in-time)将泛型IL和元数据转换为本地代码,并在进程中用实际类型代替类型参数。后面的对这个以构造的泛型类型的引用使用相同的本地代码。从泛型类型建立一个特定的构造类型的过程称为泛型类型实例化(generic type instantiation)。
.NET公共语言运行时为每个由之类型实例化的泛型类型建立一个专门的拷贝,而所有的引用类型共享一个单独的拷贝(因为,在本地代码级别上,引用知识具有相同表现的指针)。
C.泛型的约束
通常,一个泛型类不会只是存贮基于某一类型参数的数据,他还会调用给定类型的对象的方法。例如,Dictionary<K,V>中的Add方法可能需要使用CompareTo方法来比较键值:
public class Dictionary<K,V>
{
public void Add(K key, V value)
{
...
if (key.CompareTo(x) < 0) {...} // 错误,没有CompareTo方法
...
}
}
由于指定的类型参数K可以是任何类型,可以假定存在的参数key具有的成员只有来自object的成员,如Equals、GetHashCode和ToString;因此上面的例子会发生编译错误。当然可以将参数key转换成为一具有CompareTo方法的类型。例如,参数key可以转换为IComparable
public class Dictionary<K,V>
{
public void Add(K key, V value)
{
...
if (((IComparable)key).CompareTo(x) < 0) {...}
...
}
}
这种方案工作时,会在运行时引起动态类型转换,会增加开销。更要命的是,它还可能将错误报告推迟到运行时。如果一个键没有实现IComparable接口,会抛出InvalidCastException异常。为了提供更强大的编译期间类型检查和减少类型转换,C#允许一个可选的为每个类型参数提供的约束(constraints)列表。一个类型参数的约束指定了一个类型必须遵守的要求,使得这个类型参数能够作为一个变量来使用。约束由关键字where来声明,后跟类型参数的名字,再后是一个类或接口类型的列表,或构造器约束new()。要想使Dictionary<K,V>类能保证键值始终实现了IComparable接口,类的声明中应该对类型参数K指定一个约束:(格式为:[ where 类型名:[接口名][类][构造器约束new()]])
public class Dictionary<K,V> where K: IComparable
{
public void Add(K key, V value)
{
...
if (key.CompareTo(x) < 0) {...}//
CompareTo 必须实现IComparable接口
...
}
}
通过这个声明,编译器能够保证所有提供给类型参数K的类型都实现了IComparable接口。进而,在调用CompareTo方法前不再需要将键值显式转换为一个IComparable接口;一个受约束的类型参数类型的值的所有成员都可以直接使用。对于给定的类型参数,可以指定任意数目的接口作为约束,但只能指定一个类(作为约束)。每一个被约束的类型参数都有一个独立的where子句。在下面的例子中,类型参数K有两个接口约束,而类型参数E有一个类约束和一个构造器约束:
public class EntityTable<K,E>
where K: IComparable<K>, IPersistable
where E: Entity, new()     //多个where后面 类接口的后面没有;
{
public void Add(K key, E entity)
{
...
if (key.CompareTo(x) < 0) {...}
...
}
}
上面例子中的构造器约束,new(),保证了作为的E类型变量的类型具有一个公共、无参的构造器,并允许泛型类使用new E()来建立该类型的一个实例。类型参数约束的使用要小心。尽管它们提供了更强大的编译期间类型检查并在一些情况下改进了性能,它还是限制了泛型类型的使用。例如,一个泛型类List<T>可能约束T实现IComparable接口以便Sort方法能够比较其中的元素。然而,这么做使List<T>不能用于那些没有实现IComparable接口的类型,尽管在这种情况下Sort方法从来没被实际调用过。(也就是说约束过的泛型 只能用于实现实现了那些实现了此约束的类型)
D.泛型方法 (Generics function)
有的时候一个类型参数并不是整个类所必需的,而只用于一个特定的方法中。通常,这种情况发生在建立一个需要一个泛型类型作为参数的方法时。例如,在使用前面描述过的Stack<T>类时,一种公共的模式就是在一行中压入多个值,如果写一个方法通过单独调用它类完成这一工作会很方便。对于一个特定的构造过的类型,如Stack<int>,这个方法看起来会是这样:
void PushMultiple(Stack<int> stack, params int[] values) {
foreach (int value in values) stack.Push(value);
}
这个方法可以用于将多个int值压入一个Stack<int>:
Stack<int> stack = new Stack<int>();
PushMultiple(stack, 1, 2, 3, 4);
然而,上面的方法只能工作于特定的构造过的类型Stack<int>要想使他工作于任何Stack<T>,这个方法必须写成泛型方法(generic method)。一个泛型方法有一个或多个类型参数,有方法名后面的“<”和“>”限定符指定。这个类型参数可以用在参数列表、返回值和方法体中。一个泛型的PushMultiple方法看起来会是这样:
void PushMultiple<T>(Stack<T> stack, params T[] values) {
foreach (T value in values) stack.Push(value);
}
使用这个方法,可以将多个元素压入任何Stack<T>中。当调用一个泛型方法时,要在函数的调用中将类型参数放入尖括号中。例如:
Stack<int> stack = new Stack<int>();
PushMultiple<int>(stack, 1, 2, 3, 4);
这个泛型的PushMultiple方法比上面的版本更具可重用性,因为它能工作于任何Stack<T>,但这看起来并不舒服,因为必须为T提供一个类型参数。然而,很多时候编译器可以通过传递给方法的其他参数来推断出正确的类型参数,这个过程称为类型推断(type inferencing)。在上面的例子中,由于第一个正式的参数的类型是Stack<int>,并且后面的参数类型都是int,编译器可以认定类型参数一定是int。因此,在调用泛型的PushMultiple方法时可以不用提供类型参数://调用时候变压器会自动根据参数判断参数类型 所以调用函数时不用再提供.
Stack<int> stack = new Stack<int>();
PushMultiple(stack, 1, 2, 3, 4);
----------------------------------------------------------------------------------------------------------------------------------------------------

【SCI复现】含可再生能源与储能的区域微电网最优运行:应对不确定性的解鲁棒性与非预见性研究(Matlab代码实现)内容概要:本文围绕含可再生能源与储能的区域微电网最优运行展开研究,重点探讨应对不确定性的解鲁棒性与非预见性策略,通过Matlab代码实现SCI论文复现。研究涵盖多阶段鲁棒调度模、机会约束规划、需求响应机制及储能系统优化配置,结合风电、光伏等可再生能源出力的不确定性建模,提出兼顾系统经济性与鲁棒性的优化运行方案。文中详细展示了模构建、算法设计(如C&CG算法、大M法)及仿真验证全过程,适用于微电网能量管理、电力系统优化调度等领域的科研与工程实践。; 适合人群:具备一定电力系统、优化理论和Matlab编程基础的研究生、科研人员及从事微电网、能源管理相关工作的工程技术人员。; 使用场景及目标:①复现SCI级微电网鲁棒优化研究成果,掌握应对风光负荷不确定性的建模与求解方法;②深入理解两阶段鲁棒优化、分布鲁棒优化、机会约束规划等先进优化方法在能源系统中的实际应用;③为撰写高水平学术论文或开展相关课题研究提供代码参考和技术支持。; 阅读建议:建议读者结合文档提供的Matlab代码逐模块学习,重点关注不确定性建模、鲁棒优化模构建与求解流程,并尝试在不同场景下调试与扩展代码,以深化对微电网优化运行机制的理解。
个人防护装备实例分割数据集 一、基础信息 数据集名称:个人防护装备实例分割数据集 图片数量: 训练集:4,524张图片 分类类别: - Gloves(手套):工作人员佩戴的手部防护装备。 - Helmet(安全帽):头部防护装备。 - No-Gloves(未戴手套):未佩戴手部防护的状态。 - No-Helmet(未戴安全帽):未佩戴头部防护的状态。 - No-Shoes(未穿安全鞋):未佩戴足部防护的状态。 - No-Vest(未穿安全背心):未佩戴身体防护的状态。 - Shoes(安全鞋):足部防护装备。 - Vest(安全背心):身体防护装备。 标注格式:YOLO格式,包含实例分割的多边形坐标和类别标签,适用于实例分割任务。 数据格式:来源于实际场景图像,适用于计算机视觉模训练。 二、适用场景 工作场所安全监控系统开发:数据集支持实例分割任务,帮助构建能够自动识别工作人员个人防护装备穿戴状态的AI模,提升工作环境安全性。 建筑与工业安全检查:集成至监控系统,实时检测PPE穿戴情况,预防安全事故,确保合规性。 学术研究与创新:支持计算机视觉在职业安全领域的应用研究,促进AI与安全工程的结合。 培训与教育:可用于安全培训课程,演示PPE识别技术,增强员工安全意识。 三、数据集优势 精准标注与多样性:每个实例均用多边形精确标注,确保分割边界准确;覆盖多种PPE物品及未穿戴状态,增加模鲁棒性。 场景丰富:数据来源于多样环境,提升模在不同场景下的化能力。 任务适配性强:标注兼容主流深度学习框架(如YOLO),可直接用于实例分割模开发,支持目标检测和分割任务。 实用价值高:专注于工作场所安全,为自动化的PPE检测提供可靠数据支撑,有助于减少工伤事故。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值