C#知识 泛型
泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个新功能。泛型将类型参数的概念引入 .NET Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如,通过使用泛型类型参数 T,您可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本或风险,避免进行强制类型转换的需求提高类型安全性。
泛型概述及优点
泛型类和泛型方法同时具备可重用性、类型安全和效率高等特点。它通常用在集合和在集合上运行的方法中。.NET 2.0版类库提供了一个新的名为System.Collections.Generic的命名空间,其中包含几个新的基于泛型的集合类。
也可以创建自定义泛型类型和方法。
泛型类和方法接收”类型参数“,指定了要操作的对象的类型:
public class Test<T> {}
在实例化时才指定类型。Test<int> tree = new Test<int>();
使用泛型类型有以下优点:- 使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
- 泛型最常见的用途是创建集合类。
- .NET Framework 类库在 System.Collections.Generic 命名空间中包含几个新的泛型集合类。应尽可能地使用这些类来代替普通的类,如 System.Collections 命名空间中的 ArrayList。
- 您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
- 可以对泛型类进行约束以访问特定数据类型的方法。
- 关于泛型数据类型中使用的类型的信息可在运行时通过反射获取。
泛型中的类型参数T
类型参数T用来定义泛型类时的占位符,并不是一种类型,仅代表某种可能的类型。在定义时,T出现的位置可以在使用是用任何类型来代替。
决定在何时何地使用泛型
您需要考虑以下几件事件:
- 您所使用的类型是否包含或操作未指定的数据类型(如集合类型)?如果是这样,创建泛型类型将能提供更多的好处。如果您的类型只操作单一的指定类型,那么就没有必要去创建一个泛型类。
- 如果您的类型将操作值类型,那么就会产生装箱和拆箱操作,就应该考虑使用泛型来防止装箱和拆箱操作。
- 泛型的强类型检查有助于快速查找错误(也就是编译期而非运行期),从而缩短bug修复周期。
- 在编写多个类操作多个数据类型时是否遭遇到“代码膨胀”问题(如一个ArrayList只存储StreamReaders而另一个存储StreamWriters)?其实编写一次代码并让它工作于多个数据类型非常简单。
- 泛型使得代码更为清晰。通过消除代码膨胀并进行强制检查,您的代码将变得更易于阅读和理解。
针对类的泛型
针对不同类型的数组,写一个针对数组的”冒泡排序“。
思路
- 针对类的泛型,泛型打在类旁。
- 由于在”冒泡排序“中需要对元素进行比较,所以泛型要约束成实现IComparable接口。
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
SortHelper<int> isorter = new SortHelper<int>();
int[] iarray = { 8, 7, 1, 2, 12 };
isorter.BubbleSort(iarray);
foreach (int item in iarray)
{
Console.Write(item + ",");
}
Console.ReadKey();
}
}
public class SortHelper<T> where T : IComparable
{
public void BubbleSort(T[] array)
{
int length = array.Length;
for (int i = 0; i <= length - 2; i++)
{
for (int j = length - 1; j >= 1; j--)
{
if (array[j].CompareTo(array[j - 1]) < 0)
{
T temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
}
}
}
输出:关于泛型约束
- where T : IComparable 把T约束为实现IComparable接口
- where T : class
- where T : struct
- where T : IComparable, new() 约束泛型必须有构造函数
关于冒泡算法
- 之所以for (int i = 0; i <= length -2; i++),这是边界思维,比如有一个长度为5的数组,如果0号位元素最终调换到4号位,每次调一个位,需要经过4次才能到4号位,即for(int i = 0; i <= 5-2, i++),i依次为0, 1, 2, 4,期间经历了4次。
- 至于for (int j = length - 1; j >= 1; j--)循环,即遍历从最后一个元素开始到索引为1的元素,每次与前一个位置上的元素比较。
关于比较
int 类型之所以能比较,是因为int类型实现了IComparable接口。byte类型也一样。
自定义一个类,使之也能实现冒泡算法
冒泡算法涉及到元素比较,所以自定义类必须实现IComparable接口。
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Book[] bookArray = new Book[2];
Book book1 = new Book(100, "书一");
Book book2 = new Book(80, "书二");
bookArray[0] = book1;
bookArray[1] = book2;
Console.WriteLine("冒泡之前:");
foreach (Book b in bookArray)
{
Console.WriteLine("书名:{0},价格:{1}", b.Title, b.Price);
}
SortHelper<Book> sorter = new SortHelper<Book>();
sorter.BubbleSort(bookArray);
Console.WriteLine("冒泡之后:");
foreach (Book b in bookArray)
{
Console.WriteLine("书名:{0},价格:{1}", b.Title, b.Price);
}
Console.ReadKey();
}
}
public class SortHelper<T> where T : IComparable
{
public void BubbleSort(T[] array)
{
int length = array.Length;
for (int i = 0; i <= length - 2; i++)
{
for (int j = length - 1; j >= 1; j--)
{
if (array[j].CompareTo(array[j - 1]) < 0)
{
T temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
}
}
//自定义实现IComparable接口
public class Book : IComparable
{
private int price;
private string title;
public Book(){ }
public Book(int price, string title)
{
this.price = price;
this.title = title;
}
public int Price
{
get
{
return this.price;
}
}
public string Title
{
get
{
return this.title;
}
}
public int CompareTo(object obj)
{
Book book = (Book)obj;
return this.Price.CompareTo(book.Price);
}
}
}
输出:针对方法的泛型
继续上面的例子,自定义一个类,并定义泛型方法。
namespace ConsoleApplication1
{
// 方法泛型
public class MethodSortHelper
{
public void BubbleSort<T>(T[] array) where T : IComparable
{
int length = array.Length;
for (int i = 0; i <= length - 2; i++)
{
for (int j = length - 1; j >= 1; j--)
{
if (array[j].CompareTo(array[j - 1]) < 0)
{
T temp = array[j];
array[j] = array[j - 1];
array[j - 1] = temp;
}
}
}
}
}
}
主程序如下:namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
Book[] bookArray = new Book[2];
Book book1 = new Book(100, "书一");
Book book2 = new Book(80, "书二");
bookArray[0] = book1;
bookArray[1] = book2;
Console.WriteLine("冒泡之前:");
foreach (Book b in bookArray)
{
Console.WriteLine("书名:{0},价格:{1}", b.Title, b.Price);
}
MethodSortHelper sorter = new MethodSortHelper();
sorter.BubbleSort<Book>(bookArray);
Console.WriteLine("冒泡之后:");
foreach (Book b in bookArray)
{
Console.WriteLine("书名:{0},价格:{1}", b.Title, b.Price);
}
Console.ReadKey();
}
}
//自定义实现IComparable接口
public class Book : IComparable
{
private int price;
private string title;
public Book() { }
public Book(int price, string title)
{
this.price = price;
this.title = title;
}
public int Price
{
get
{
return this.price;
}
}
public string Title
{
get
{
return this.title;
}
}
public int CompareTo(object obj)
{
Book book = (Book)obj;
return this.Price.CompareTo(book.Price);
}
}
}
输出:使用泛型方法的时候,除了这样:
MethodSortHelper sorter = new MethodSortHelper();
sorter.BubbleSort<Book>(bookArray);
还可以这样写:MethodSortHelper sorter = new MethodSortHelper();
sorter.BubbleSort(bookArray);
可见,泛型方法可以根据数据实例隐式推断泛型是否满足条件。泛型的其他优点
避免隐式装箱和拆箱
以下包含隐式装箱和拆箱:
ArrayList list = new ArrayList();
for(int i = 0; i < 3; i++)
{
list.Add(i); //Add接收的参数类型是引用类型object,这里包含了隐式装箱
}
for(int i = 0; i < 3; i++)
{
int value = (int)list[i]; //引用类型强转成值类型,拆箱
Console.WriteLine(value);
}
使用泛型避免隐式装箱和拆箱:List<int> list = new List<int>();
for(int i = 0; i < 3; i++)
{
list.Add(i);
}
for(int i = 0; i < 3; i++)
{
int value = list[i];
Console.WriteLine(value);
}
能在编译期间及时发现错误
不使用泛型,在编译期间不会报错的一个例子:
List<int> list = new List<int>();
for(int i = 0; i < 3; i++)
{
list.Add(i);
}
for(int i = 0; i < 3; i++)
{
int value = list[i];
Console.WriteLine(value);
}
使用泛型,在编译期间及时发现错误:List<int> list = new List<int>();
int i = 100;
list.Add(i);
string value = (string)list[0];
使用泛型的技巧
在当前文件中给泛型起别名
using IntList = List<int>;
IntList list = new IntList();
list.Add(1);
在不同文件中使用泛型别名,定义一个类派生于泛型
public class IntList : List<int>{}