C#基础知识之泛型
泛型在c#中有很重要的位置,对于写出高可读性,高性能的代码有着关键的作用。
其实官方文档说明的很详细,我这边算是做个记录吧
一、什么是泛型?
泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个非常重要的新功能。
泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候。换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法。您可以通过数据类型的替代参数编写类或方法的规范。当编译器遇到类的构造函数或方法的函数调用时,它会生成代码来处理指定的数据类型。
简单的说,我们在编写程序时,经常遇到两个模块的功能非常相似,只是一个是处理int数据,另一个是处理string数据,或者其他自定义的数据类型,但我们没有办法,只能分别写多个方法处理每个数据类型,因为方法的参数类型不同。有没有一种办法,在方法中传入通用的数据类型,这样不就可以合并代码了吗?泛型的出现就是专门解决这个问题的。
二、为什么使用泛型?
我们先来看一下下面的代码
static void Main(string[] args) { #region 方式一 普通方法 ShowInt(); ShowStr("string1"); #endregion Console.ReadKey(); } public static void ShowInt(int num) { Console.WriteLine("输出INT型数据:{0}", num); } public static void ShowStr(string str) { Console.WriteLine("输出String型数据:{0}",str); }
这两个方法,除了传入的参数不同外,其里面实现的功能都是一样的。在1.1版的时候,还没有泛型这个概念,那么怎么办呢。就有人想到了OOP三大特性之一的继承,C#语言中,所有类型都源自同一个类型,那就是object。其实就是一个装箱拆箱的过程,会损耗一些性能。
static void Main(string[] args) { #region 方式二 继承,C#语言中,所有类型都继承自object。 ShowObj(); ShowObj("string2"); #endregion Console.ReadKey(); } public static void ShowObj(object obj) { Console.WriteLine("输出的类型{0},值{1}",obj.GetType(),obj); }
微软在2.0的时候发布了泛型。接下来我们用泛型方法来实现该功能。下面是一个简单的泛型示例:
static void Main(string[] args) { #region 方式三 泛型 ShowInfo(); ShowInfo("STRING3"); ShowInfo<); ShowInfo<string>("STRING4"); #endregion Console.ReadKey(); } public static void ShowInfo<T>(T para) { Console.WriteLine("输出的类型{0},值{1}",para.GetType(),para); }
三、泛型的特性
使用泛型是一种增强程序功能的技术,具体表现在以下几个方面:
- 它有助于您最大限度地重用代码、保护类型的安全以及提高性能。
- 您可以创建泛型集合类。.NET 框架类库在 System.Collections.Generic 命名空间中包含了一些新的泛型集合类。您可以使用这些泛型集合类来替代 System.Collections 中的集合类。
- 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
- 您可以对泛型类进行约束以访问特定数据类型的方法。
- 关于泛型数据类型中使用的类型的信息可在运行时通过使用反射获取。
四、泛型类型参数
在泛型类型或方法定义中,类型参数是在其实例化泛型类型的一个变量时,客户端指定特定类型的占位符。泛型类无法按原样使用,因为他不是真正的类型,他更像是类型的蓝图,若要使用GenericList<T>,客户端代码必须通过指定尖括号内的类型参数来声明并实例化构造类型。此特定类的类型参数可以是编译器可识别的任何类型,可创建任意数量的构造类型实例,其中每个使用不同的类型参数。在 GenericList<T>
的每个实例中,类中出现的每个 T
在运行时均会被替换为类型参数。 通过这种替换,我们已通过使用单个类定义创建了三个单独的类型安全的有效对象。
GenericList<float> list1 = new GenericList<float>(); GenericList<ExampleClass> list2 = new GenericList<ExampleClass>(); GenericList<ExampleStruct> list3 = new GenericList<ExampleStruct>();
五、泛型类型参数约束
1、什么是泛型类型参数约束
约束告知编译器类型参数必须具备的功能。在没有约束的情况下,类型参数可以是任何类型,编译器只能假定Object的成员,object是任何.NET类型的最终基类。如果客户端代码尝试使用约束所不允许的类型来实例化类,则会产生编译时错误。通过使用where上下文关键字指定约束。下表列出了其中类型的约束:
某些约束是互斥的。 所有值类型必须具有可访问的无参数构造函数。 struct
约束包含 new()
约束,且 new()
约束不能与 struct
约束结合使用。 unmanaged
约束包含 struct
约束。 unmanaged
约束不能与 struct
或 new()
约束结合使用。
2、使用泛型参数约束的原因
通过约束类型参数,可以增加约束类型及其继承层次结构中的所有类型所支持的允许操作和方法调用的数量。 设计泛型类或方法时,如果要对泛型成员执行除简单赋值之外的任何操作或调用 System.Object 不支持的任何方法,则必须对该类型参数应用约束。 例如,基类约束告诉编译器,仅此类型的对象或派生自此类型的对象可用作类型参数。 编译器有了此保证后,就能够允许在泛型类中调用该类型的方法。 基类约束的例子如下:
3、各个泛型参数约束简介
(1)where T:类(类型参数必须是引用类型;这一点也适用于任何类、接口、委托或数组类型。)
class Program { static void Main(string[] args) { #region 基类约束实例 ; ; SchoolGeneric<BeijingSchool> beijingSchool = new SchoolGeneric<BeijingSchool>(); beijingSchool.Add(new BeijingSchool(stuNum,teacherNum, "beijing",true)); beijingSchool.FindSchoolByNum(stuNum); #endregion Console.ReadKey(); } } class School { private int _studentNum; private int _teacherNum; private string _schoolName; public int StudentNum { get { return this._studentNum; } set { this._studentNum = value; } } public int TeacherNum { get { return this._teacherNum; } set { this._teacherNum = value; } } public string SchoolName { get{ return this._schoolName; } set { this._schoolName = value; } } public School(int studentNum,int teacherNum,string schoolName) { this.StudentNum = studentNum; this.TeacherNum = teacherNum; this.SchoolName = schoolName; } } class BeijingSchool:School { private bool isPrivateSchool; public bool IsPrivateSchool { get { return isPrivateSchool; } set { isPrivateSchool = value; } } public BeijingSchool(int stuNum, int teacherNum,string schoolName,bool isPrivateSchool) : base(stuNum, teacherNum, schoolName) { this.IsPrivateSchool = isPrivateSchool; } } class SchoolGeneric<T> where T : School { T[] TList; int end; public SchoolGeneric() { ]; end = ; } public void Add(T school) { TList[end] = school; end++; } public void FindSchoolByNum(int StuNum) { Console.WriteLine("学生人数满足:{0} 的学校有:", StuNum); ; i < end; i++) { if (TList[i].StudentNum == StuNum) { Console.WriteLine(TList[i].SchoolName); } } } }
(2)where T:结构(类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。)
class PersonGeneric<T> where T:struct { public void Print(T person) { Console.WriteLine("{0}",person); } } class Program { static void Main(string[] args) { #region where T : struct 类型参数必须是值类型,可以指定除 Nullable<T> 以外的任何值类型 PersonGeneric<int> personAge = new PersonGeneric<int>(); personAge.Print(); PersonGeneric<double> personName = new PersonGeneric<double>(); personName.Print(12.21); #endregion Console.ReadKey(); } }
(3)where T:new()(类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。)
class People<T> where T : Person, System.IComparable<T>, new() { // ... }
(4)where T:<接口名称>(类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。)
interface IMyInterface { } class Dictionary<TKey, TVal> where TKey : IComparable,IEnumerable where TVal : IMyInterface { public void Add(TKey key,TVal val) { Console.WriteLine("key:{0},val:{1}",key,val); } } class Program { static void Main(string[] args) { #region where T:new()(类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。) SplitLine("where T:new()"); #endregion Console.ReadKey(); } }
(5)where T:U(为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。也就是说T和U的参数必须一样)
class List<T> { void Add<U>(List<U> items) where U : T {/*...*/} }
六、泛型类
通常,创建泛型类是从现有具体类开始,然后每次逐个将类型更改为类型参数,直到泛化和可用性达到最佳平衡。创建自己的泛型类时,需要考虑以下重要注意事项:
要将哪些类型泛化为类型参数。
通常,可参数化的类型越多,代码就越灵活、其可重用性就越高。 但过度泛化会造成其他开发人员难以阅读或理解代码。
要将何种约束(如有)应用到类型参数(请参阅类型参数的约束)。
其中一个有用的规则是,应用最大程度的约束,同时仍可处理必须处理的类型。 例如,如果知道泛型类仅用于引用类型,则请应用类约束。 这可防止将类意外用于值类型,并使你可在
T
上使用as
运算符和检查 null 值。是否将泛型行为分解为基类和子类。
因为泛型类可用作基类,所以非泛型类的相同设计注意事项在此也适用。 请参阅本主题后文有关从泛型基类继承的规则。
实现一个泛型接口还是多个泛型接口。
class BaseNode { } class BaseNodeGeneric<T> { } // concrete type class NodeConcrete<T> : BaseNode { } //closed constructed type class NodeClosed<T> : BaseNodeGeneric<int> { } //open constructed type class NodeOpen<T> : BaseNodeGeneric<T> { }
七、泛型方法
泛型方法是通过类型参数声明的方法
static void Swap<T>(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; } public static void TestSwap() { ; ; Swap<int>(ref a, ref b); System.Console.WriteLine(a + " " + b); }
八、泛型接口
为泛型集合类或表示集合中的项的泛型类定义接口通常很有用处。 为避免对值类型的装箱和取消装箱操作,泛型类的首选项使用泛型接口,例如 IComparable<T>而不是 IComparable。 .NET Framework 类库定义多个泛型接口,以将其用于 System.Collections.Generic 命名空间中的集合类。接口被指定为类型参数上的约束时,仅可使用实现接口的类型。 如下代码示例演示一个派生自 GenericList<T>
类的 SortedList<T>
类。 SortedList<T>
添加约束 where T : IComparable<T>
。这可使 SortedList<T>
中的 BubbleSort
方法在列表元素上使用泛型 CompareTo 方法。 在此示例中,列表元素是一个实现 IComparable<Person>
的简单类 Person
。
public class GenericList<T> : System.Collections.Generic.IEnumerable<T> { protected Node head; protected Node current = null; protected class Node { public Node next; private T data; public Node(T t) { next = null; data = t; } public Node Next { get { return next; } set { next = value; } } public T Data //T as return type of property { get { return data; } set { data = value; } } } public GenericList() { head = null; } public void AddHead(T t) { Node n = new Node(t); n.Next = head; head = n; } public System.Collections.Generic.IEnumerator<T> GetEnumerator() { Node current = head; while (current != null) { yield return current.Data; current = current.Next; } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } public class SortedList<T> : GenericList<T> where T : System.IComparable<T> { public void BubbleSort() { if (null == head || null == head.Next) { return; } bool swapped; do { Node previous = null; Node current = head; swapped = false; while (current.next != null) { ) { Node tmp = current.next; current.next = current.next.next; tmp.next = current; if (previous == null) { head = tmp; } else { previous.next = tmp; } previous = tmp; swapped = true; } else { previous = current; current = current.next; } } } while (swapped); } } public class Person : System.IComparable<Person> { string name; int age; public Person(string s, int i) { name = s; age = i; } // This will cause list elements to be sorted on age values. public int CompareTo(Person p) { return age - p.age; } public override string ToString() { return name + ":" + age; } // Must implement Equals. public bool Equals(Person p) { return (this.age == p.age); } } class Program { static void Main() { //Declare and instantiate a new generic SortedList class. //Person is the type argument. SortedList<Person> list = new SortedList<Person>(); //Create name and age values to initialize Person objects. string[] names = new string[] { "Franscoise", "Bill", "Li", "Sandra", "Gunnar", "Alok", "Hiroyuki", "Maria", "Alessandro", "Raul" }; , , , , , , , , , }; //Populate the list. ; x < ; x++) { list.AddHead(new Person(names[x], ages[x])); } //Print out unsorted list. foreach (Person p in list) { System.Console.WriteLine(p.ToString()); } System.Console.WriteLine("Done with unsorted list"); //Sort the list. list.BubbleSort(); //Print out sorted list. foreach (Person p in list) { System.Console.WriteLine(p.ToString()); } System.Console.WriteLine("Done with sorted list"); } }
九、泛型和数组
在 C# 2.0 和更高版本中,下限为零的单维数组自动实现 IList<T>。 这可使你创建可使用相同代码循环访问数组和其他集合类型的泛型方法。 此技术的主要用处在于读取集合中的数据。 IList<T> 接口无法用于添加元素或从数组删除元素。 如果在此上下文中尝试对数组调用 IList<T> 方法(例如 RemoveAt),则会引发异常。如下代码示例演示具有 IList<T> 输入参数的单个泛型方法如何可循环访问列表和数组(此例中为整数数组)。
class Program { static void Main() { , , , , }; List<int> list = new List<int>(); ; x < ; x++) { list.Add(x); } ProcessItems<int>(arr); ProcessItems<int>(list); } static void ProcessItems<T>(IList<T> coll) { System.Console.WriteLine ("IsReadOnly returns {0} for this collection.", coll.IsReadOnly); foreach (T item in coll) { System.Console.Write(item.ToString() + " "); } System.Console.WriteLine(); } }
十、泛型委托
委托可以定义它自己的类型参数。 引用泛型委托的代码可以指定类型参数以创建封闭式构造类型,就像实例化泛型类或调用泛型方法一样,如以下示例中所示:
public delegate void Del<T>(T item); public static void Notify(int i) { } Del<int> m1 = new Del<int>(Notify);
C#基础知识之泛型的更多相关文章
- C# 篇基础知识11——泛型和集合
.NET提供了一级功能强大的集合类,实现了多种不同类型的集合,可以根据实际用途选择恰当的集合类型. 除了数组 Array 类定义在System 命名空间中外,其他的集合类都定义在System.Coll ...
- (整理)C#基础知识_泛型的实现
本文是截取自MSDN的文章部分,方便自己查看,原文地址:https://msdn.microsoft.com/zh-cn/library/ms379564(VS.80).aspx 泛型实现 表面上,C ...
- C#基础知识之泛型集合转换为DataTable
在做项目中,遇到了将集合转换为DataTable的使用,在网上看了资料,在这里记录下来,分享. using System; using System.Collections.Generic; usin ...
- C#基础知识之类和结构体
虽然项目中一直在使用类.结构体等类型,仔细琢磨,还真无法系统的说出个所以然.记录一下类.结构体.类和结构体区别 一.类 对于类,大家都特别熟悉.简单的介绍一下类的结构,然后记录一下Class需要注意的 ...
- C#基础知识之类和结构
虽然项目中一直在使用类.结构体等类型,仔细琢磨,还真无法系统的说出个所以然.记录一下类.结构体.类和结构体区别 一.类 对于类,大家都特别熟悉.简单的介绍一下类的结构,然后记录一下Class需要注意的 ...
- [C# 基础知识梳理系列]专题六:泛型基础篇——为什么引入泛型
引言: 前面专题主要介绍了C#1中的2个核心特性——委托和事件,然而在C# 2.0中又引入一个很重要的特性,它就是泛型,大家在平常的操作中肯定会经常碰到并使用它,如果你对于它的一些相关特性还不是很了解 ...
- [C# 基础知识系列]专题九: 深入理解泛型可变性
引言: 在C# 2.0中泛型并不支持可变性的(可变性指的就是协变性和逆变性),我们知道在面向对象的继承中就具有可变性,当方法声明返回类型为Stream,我们可以在实现中返回一个FileStream的类 ...
- C# 基础知识系列- 10 反射和泛型(二)
0. 前言 这篇文章延续<C# 基础知识系列- 5 反射和泛型>,继续介绍C#在反射所开发的功能和做的努力.上一篇文章大概介绍了一下泛型和反射的一些基本内容,主要是通过获取对象的类型,然后 ...
- .NET面试题系列[1] - .NET框架基础知识(1)
很明显,CLS是CTS的一个子集,而且是最小的子集. - 张子阳 .NET框架基础知识(1) 参考资料: http://www.tracefact.net/CLR-and-Framework/DotN ...
随机推荐
- 『2019/3/8 USACO测试 反思与总结』
2019/3/8 USACO测试 这一次是到高中的第一次考试,考得不太好,原因有很多. 先看一下试题安排: 题号 试题分组 考察算法 思维难度 代码难度 1 金组\(T1\) 建图+最短路 ★★★ ★ ...
- AJAX应用【股票案例、验证码校验】
一.股票案例 我们要做的是股票的案例,它能够无刷新地更新股票的数据.当鼠标移动到具体的股票中,它会显示具体的信息. 我们首先来看一下要做出来的效果: 1.1服务器端分析 首先,从效果图我们可以看见很多 ...
- Java开发知识之Java中的集合上List接口以及子类讲解.
Java开发知识之Java中的集合类 一丶什么是集合类 如果你学习说数据结构,那么学习集合就很简单. 因为集合就是存储数据的结构. 例如 有链表结构 (list ) 还有 map结构.等等. 集合类就 ...
- Linux平台运行jmeter
这篇博客介绍 jmeter 在 Linux 环境进行压测,大致流程是 在 window上编辑好测试脚本,然后拷贝到 Linux上运行,再把 Linux上的运行结果拿到 windows 的 jmeter ...
- Django学习笔记(6)——Form表单
知识储备:HTML表单form学习 表单,在前端页面中属于最常见的一个东西了.基本上网站信息的提交都用到了表单,所以下面来学习Django中优雅的表单系统:Form 表单的主要作用是在网页上提供一个图 ...
- 关于利用maven搭建ssm的博客,我们一起来探讨下问的最多的问题
前言 开心一刻 有个同学去非洲援建,刚到工地接待他的施工员是个黑人,他就用英语跟人家交流,黑人没做声. 然后他又用法语,黑人还是没说话. 然后他用手去比划.黑人终于开口了:瞎比划嘎哈,整个工地都中国人 ...
- MySQL/MariaDB系列文章目录
以下是本系列文章的大纲,此页博文完全原创,花费了作者本人的极大心血,如转载,请务必标明原文链接. 如果觉得文章不错,还请帮忙点下"推荐",各位的支持,能激发和鼓励我更大的写作热情. ...
- React Fiber源码分析 (介绍)
写了分析源码的文章后, 总觉得缺少了什么, 在这里补一个整体的总结,输出个人的理解~ 文章的系列标题为Fiber源码分析, 那么什么是Fiber,官方给出的解释是: React Fiber是对核心算法 ...
- nginx错误界面优化和日志管理
nginx错误界面优化 在进行web访问的时候,经常会遇到网站打不开报错的情况,nginx默认的界面并不美观,我们可以通过重定向到自定义的错误页面,提升用户体验,比如淘宝的错误页面还有商品信息和广告. ...
- Java开发笔记(九)赋值运算符及其演化
前面的加减乘除四则运算,计算结果通过等号输出给指定变量,注意此时代码把变量放到等号左边.而在算术课本里,加法运算的完整写法类似于“1+1=2”这样,运算结果应该跟在等号右边.不过代数课本里的方程式存在 ...