C#程序设计 c# programming 泛型 C#程序设计课程组
教学内容 什么是泛型 泛型的类型 泛型的约束 泛型集合类型
什么是泛型?
public class Stack { private int[] m_item; public int Pop(){...} public void Push(int item){...} public Stack(int i) this.m_item = new int[i]; }
当我们需要一个栈来保存string类型时,该怎么办呢? public class Stack { private string[] m_item; public string Pop(){...} public void Push(string item){...} public Stack(int i) this.m_item = new string[i]; }
优秀的程序员会想到用一个通用的数据类型object来实现这个栈 若以后再需要long、byte类型的栈该怎样做呢?还要再复制吗? 优秀的程序员会想到用一个通用的数据类型object来实现这个栈
这个栈完美吗? public class Stack { private object[] m_item; public object Pop(){...} public void Push(object item){...} public Stack(int i) { this.m_item = new[i]; } } 非常灵活,可以接收任何数据类型
当Stack处理值类型时,会出现装箱、折箱操作,这将在托管堆上分配和回收大量的变量,若数据量大,则性能损失非常严重. 装箱:是指将一个值类型隐式或显式地转换成一个object类型 拆箱:是指将一个对象类型显式地转换成一个值类型 在数据类型的强制转换上还有更严重的问题(假设stack是Stack的一个实例): Node1 x = new Node1(); stack.Push(x); Node2 y = (Node2)stack.Pop(); 上面的代码在编译时是完全没问题的,但由于Push了一个Node1类型的数据,但在Pop时却要求转换为Node2类型,这将出现程序运行时的类型转换异常,但却逃离了编译器的检查
引入泛型,可以优雅地解决这些问题。 泛型用一个通过的数据类型T来代替object,在类实例化时指定T的类型,运行时(Runtime)自动编译为本地代码,运行效率和代码质量都有很大提高,并且保证数据类型安全。
引入了通用数据类型T就可以适用于任何数据类型 public class Stack<T> { private T[] m_item; public T Pop(){...} public void Push(T item){...} public Stack(int i) this.m_item = new T[i]; } 引入了通用数据类型T就可以适用于任何数据类型
//实例化只能保存int类型的类 Stack<int> a = new Stack<int>(100); a.Push(10); a.Push("8888"); //这一行编译不通过,因为类a只接收int类型的数据 int x = a.Pop(); //实例化只能保存string类型的类 Stack<string> b = new Stack<string>(100); b.Push(10); //这一行编译不通过,因为类b只接收string类型的数据 b.Push("8888"); string y = b.Pop();
泛型是C# 2.0的一个新增加的特性,它把指定类型的工作推迟到客户端代码声明并实例化类或方法的时候进行。 泛型是具有占位符(类型参数)的类、结构、接口和方法,这些占位符是类、结构、接口和方法所存储或使用的一个或多个类型的占位符
泛型的类型
泛型:类 public class GenericList<T> { void Add(T input) { } } class TestGenericList private class ExampleClass { } static void Main() GenericList<int> list1 = new GenericList<int>(); GenericList<string> list2 = new GenericList<string>(); GenericList<ExampleClass> list3 = new GenericList<ExampleClass>();
泛型接口 interface IPerson<T> { void PrintYourName( T t); } interface Iinterface<T> { } interface IPerson<T> { void PrintYourName( T t); } class Person { public string Name = "aladdin"; } class PersonManager : IPerson<Person> { public void PrintYourName( Person t ) Console.WriteLine( "My Name Is {0}!" , t.Name ); }
泛型方法 static void Swap<T>(ref T lhs, ref T rhs) { T temp; temp = lhs; lhs = rhs; rhs = temp; } public static void TestSwap() { int a = 1; int b = 2; Swap<int>(ref a, ref b); System.Console.WriteLine(a + " " + b); }
泛型的约束
public class Node<T, V> where T : Stack, IComparable where V: Stack {...} 以上的泛型类的约束表明,T必须是从Stack和IComparable继承,V必须是Stack或从Stack继承,否则将无法通过编译器的类型检查,编译失败。
在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。 如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用 where 上下文关键字指定的。
六种类型的约束 约束 说明 T:结构 类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使用可空类型(C# 编程指南)。 T:类 类型参数必须是引用类型,包括任何类、接口、委托或数组类型。 T:new() 类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new() 约束必须最后指定。 T:<基类名> 类型参数必须是指定的基类或派生自指定的基类。 T:<接口名称> 类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。 T:U 为 T 提供的类型参数必须是为 U 提供的参数或派生自为 U 提供的参数。这称为裸类型约束。
接口约束 例如,可以声明一个泛型类 MyGenericClass,这样,类型参数 T 就可以实现 IComparable<T> 接口: Interface MyGenericClass<T> where T:IComparable { }
基类约束 指出某个类型必须将指定的类作为基类(或者就是该类本身),才能用作该泛型类型的类型参数。 这样的约束一经使用,就必须出现在该类型参数的所有其他约束之前。 class MyClassy<T, U> where T : class where U : struct { }
构造函数约束 可以使用 new 运算符创建类型参数的实例;但类型参数为此必须受构造函数约束 new() 的约束。 new() 约束可以让编译器知道:提供的任何类型参数都必须具有可访问的无参数(或默认)构造函数。例如: public class MyGenericClass <T> where T: IComparable, new() { // The following line is not possible without new() constraint: T item = new T(); }
泛型集合类型
使用命名空间:System.Collections.Generic 泛型最常见的用途是创建集合类 使用命名空间:System.Collections.Generic 非泛型集合类 泛型集合类 ArrayList List HashTable Dictionary Queue Stack SortedList
何时使用泛型集合 通常情况下,建议使用泛型集合,因为这样可以获得类型安全的直接优点而不需要从基集合类型派生并实现类型特定的成员。 此外,如果集合元素为值类型,泛型集合类型的性能通常优于对应的非泛型集合类型,因为使用泛型时不必对元素进行装箱。
List<T> 引入命名空间:System.Collections.Generic List<Student> students = new List<Student>(); students.Add(scofield); … students.Add(jacky); 将Student对象加入班级 将Teacher对象加入班级 编译出错 遍历List<Student>集合 只能保存Student对象 foreach (Student stu in students) { Console.WriteLine(stu.Name); } 不需类型转换
Teacher对象 Student对象 List<Student> Student对象 泛型集合可以约束集合内的元素类型 不允许添加 允许添加 List<Student> 无需转换类型 Student对象 泛型集合可以约束集合内的元素类型
List<T>的属性与方法 属性 说明 Capacity 获取或设置集合能够保存的元素总数。 Count 获取集合中实际包含的元素数。 方法 Add 将对象添加到集合的结尾处。 AddRange 将指定集合的元素添加到List集合的末尾。 Insert 将元素插入到集合的指定索引处。 Remove 从集合中移除特定对象的第一个匹配项。 RemoveAt 移除集合中指定索引处的元素。
Person p1 = new Person("张三", 30); Person p2 = new Person("李四", 20); class Person { private string _name; //姓名 private int _age; //年龄 //创建Person对象 public Person(string Name, int Age) this._name= Name; this._age = Age; } //姓名 public string Name get { return _name; } //年龄 public int Age get { return _age; } //创建Person对象 Person p1 = new Person("张三", 30); Person p2 = new Person("李四", 20); Person p3 = new Person("王五", 50); //创建类型为Person的对象集合 List<Person> persons = new List<Person>(); //将Person对象放入集合 persons.Add(p1); persons.Add(p2); persons.Add(p3); //输出第2个人的姓名 Console.Write(persons[1].Name);
练习:创建一个关于教师基本情况的记录薄,用来管理教师的基本信息,即能添加、删除和预览教师记录。 解决思路: 1.创建一个保存教师记录的泛型集合; 2.向泛型集合中添加记录; 3.从泛型集合中删除指定姓名的元素; 4.遍历泛型集合,输出所有的教师记录
using System; using System.Collections.Generic; namespace Chapter10_5 { class TeacherList List<Teacher> list = new List<Teacher>();//创建一个保存教师记录的泛型 public void AddTeacher()//向泛型集合list中添加元素 string name, sex, subject; int age; Console.WriteLine("添加教师记录,按-1退出"); Console.Write("姓名:"); name = Console.ReadLine();
while(name!="-1") { Console.Write("性别:"); sex = Console.ReadLine(); Console.Write("年龄:"); age =int.Parse(Console.ReadLine()); Console.Write("讲授学科:"); subject = Console.ReadLine(); Teacher teacher = new Teacher(name,sex,age,subject); list.Add(teacher); Console.Write("姓名:"); name = Console.ReadLine(); }
public void DelTeacher(string name)//删除泛型集合list中指定姓名的元素 { foreach(Teacher teacher in list) { if(teacher.Name==name) { list.Remove(teacher); break; } public void ViewTeacher()//预览泛型集合list中所有的教师记录 { Console.WriteLine (teacher.Name,teacher.Sex,teacher.Age,teacher.Subject);
static void Main(string[] args) { TeacherList tl = new TeacherList(); tl.AddTeacher();//添加教师记录 Console.Write("输入要删除的教师姓名:"); string name = Console.ReadLine(); tl.DelTeacher(name);//删除指定姓名name的教师记录 Console.WriteLine("操作完记录薄后教师的记录如下:"); tl.ViewTeacher();//浏览所以的教师记录 }
比如有两个数1、2,要对他们排序,首先就要比较这两个数,根据比较结果来排序。 List<T>的排序 排序基于比较,要排序,首先要比较。 比如有两个数1、2,要对他们排序,首先就要比较这两个数,根据比较结果来排序。 如果要比较的是对象,情况就要复杂一点,比如对Person对象进行比较,则既可以按姓名进行比较,也可以按年龄进行比较,这就需要确定比较规则。 一个对象可以有多个比较规则,但只能有一个默认规则,默认规则放在定义该对象的类中。 默认比较规则在CompareTo方法中定义,该方法属于IComparable<T>泛型接口
foreach (Person p in persons) { Console.WriteLine(p.Name); } class Person :IComparable<Person> { //按年龄比较 public int CompareTo(Person p) return this.Age - p.Age; } //按照默认规则对集合进行排序 persons.Sort(); //输出所有人姓名 foreach (Person p in persons) { Console.WriteLine(p.Name); }
class PersonPredicate { //找出中年人(40岁以上) List<T>的搜索 搜索就是从集合中找出满足特定条件的项,可以定义多个搜索条件,并根据需要进行调用。 class PersonPredicate { //找出中年人(40岁以上) public static bool MidAge(Person p) if (p.Age >= 40) return true; else return false; }
List<T>搜索 System.Predicate<Person> MidAgePredicate = new System.Predicate<Person>(PersonPredicate.MidAge); List<Person> MidAgePersons = persons.FindAll(MidAgePredicate); //输出所有的中年人姓名 foreach (Person p in MidAgePersons) { Console.WriteLine(p.Name); }
List<T>的扩展 如果要得到集合中所有人的姓名,中间以逗号隔开,那该怎么处理?
//定义Persons集合类 class Persons : List<Person> { //取得集合中所有人姓名 public string GetAllNames() if (this.Count == 0) return ""; string val = ""; foreach (Person p in this) val += p.Name + ","; } return val.Substring(0, val.Length - 1); //创建并填充Persons集合 Persons PersonCol = new Persons(); PersonCol.Add(p1); PersonCol.Add(p2); PersonCol.Add(p3); //输出所有人姓名 Console.Write(PersonCol.GetAllNames()); //输出“张三,李四,王五”
List<T> 与 ArrayList 通过索引删除元素 添加对象方法相同 通过索引访问集合的元素 相同点 需要装箱拆箱 无需装箱拆箱 可以增加任何类型 增加元素时类型严格检查 不同点 ArrayList List<T> 异同点
Dictionary<K,V> Dictionary<K,V>具有List<T>相同的特性 <K,V>约束集合中元素类型 编译时检查类型约束 无需装箱拆箱操作 与哈希表类似存储Key和Value的集合 利用Dictionary<K,V>存储学员集合 Dictionary<string,Student> students = new Dictionary<string,Student>(); value存储Student类型 Key存储String类型
Dictionary<K,V>属性和方法 说明 Count 获取包含在Dictionary中的键/值对的数目。 Keys 获取包含Dictionary中的键的集合。 Values 获取包含Dictionary中的值的集合。 方法 Add 将指定的键和值添加到字典中。 Remove 从Dictionary中移除所指定的键的值。
Dictionary<K,V> students.Add(scofield.Name, scofield); … student stu2 = students["周杰杰"]; students.Remove("周杰杰"); 添加一对Key/Value 通过Key获取元素 通过Key删除元素 //Dictionary<string, Student> 方式 foreach (Student student in students.Values) { Console.WriteLine(student.Name); } 遍历Values
Dictionary<K,V>与哈希表 遍历方法相同 添加对象方法相同 通过Key获取Value 相同点 需要装箱拆箱 无需装箱拆箱 可以增加任何类型 增加元素时类型严格检查 不同点 哈希表 Dictionary<K,V> 异同点
[练习]:设计一个电话号码薄,来管理联系人的电话号码,即能实现添加号码、删除和浏览号码。 解决思路: 1.创建一个用来保存电话号码/联系人姓名的字典; 2.向字典中添加号码; 3.从字典中删除指定姓名的号码; 4.浏览字典中的号码。
class TelCode { private string phonecode; private string name; public string Phonecode get { return phonecode; } set { phonecode = value; } } public string Name get { return name; } set { name = value; } } }
class TelBook { //定义一个保存电话号码/姓名对的集合 Dictionary<string, TelCode> dictionary = new Dictionary<string, TelCode>(); public void AddPhoneCode()//添加电话号码记录 { string phonecode, name; Console.WriteLine("输入电话号码和联系人姓名,按-1退出"); while(true) {Console.Write("电话号码:"); phonecode = Console.ReadLine(); if (phonecode=="-1") break; Console.Write("姓名:"); name = Console.ReadLine(); TelCode telcode=new TelCode(); telcode.Phonecode=phonecode; telcode.Name=name; dictionary.Add(phonecode, telcode); }
public bool DelPhoneCode(string phonecode)//按指定号码删除元素 { return dictionary.Remove(phonecode); } public void ViewPhoneCode()//列出集合中的姓名/电话号码 Console.WriteLine("{0,-10}{1,-10}", "姓名", "电话号码"); foreach(TelCode telcode in dictionary.Values)//遍历集合中所有的值 Console.WriteLine(telcode.Name,telcode.Phonecode);
static void Main(string[] args) { TelBook tb = new TelBook();//创建一个实例 tb.AddPhoneCode();//添加电话号码 Console.Write("输入要删除的电话号码:"); string telcode = Console.ReadLine(); tb.DelPhoneCode(telcode);//删除指定号码对应的元素 tb.ViewPhoneCode();//预览集合中的姓名/号码 }