C#中的泛型和泛型集合
⼀、什么是泛型?
泛型是C#语⾔和公共语⾔运⾏库(CLR)中的⼀个新功能,它将类型参数的概念引⼊.NET Framework。类型参数使得设计某些类和⽅法成为可能,例如,通过使⽤泛型类型参数T,可以⼤⼤简化类型之间的强制转换或装箱操作的过程(下⼀篇将说明如何解决装箱、拆箱问题)。说⽩了,泛型就是通过参数化类型来实现在同⼀份代码上操作多种数据类型,利⽤“参数化类型”将类型抽象化,从⽽实现灵活的复⽤。
  使⽤泛型给代码带来的5点好处:1、可以做⼤限度的重⽤代码、保护类型的安全以及提⾼性能。
                  2、可以创建集合类。
                  3、可以创建⾃⼰的泛型接⼝、泛型⽅法、泛型类、泛型事件和泛型委托。
                  4、可以对泛型类进⾏约束,以访问特定数据类型的⽅法。
                  5、关于泛型数据类型中使⽤的类型的信息,可在运⾏时通过反射获取。
  例⼦:
using System;
namespace ConsoleApp
{
class Program
{
class Test<T>
{
public T obj;
public Test(T obj)
{
this.obj = obj;
}
}
static void Main(string[] args)
{
int obj1 = 2;
var test = new Test<int>(obj1);
Console.WriteLine("int:" + test.obj);
string obj2 = "hello world";
var test1 = new Test<string>(obj2);
Console.WriteLine("String:" + test1.obj);
Console.ReadKey();
}
}
}
 输出结果是:
  int:2
  String:hello world
  分析: 
  1、  Test是⼀个泛型类。T是要实例化的范型类型。如果T被实例化为int型,那么成员变量obj就是int型的,如果T被实例化为string型,那么obj就是string类型的。
  2、根据不同的类型,上⾯的程序显⽰出不同的值。
⼆、泛型的主约束和次约束是什么?
   六种类型的约束:
T:结构类型参数必须是值类型。可以指定除 Nullable 以外的任何值类型。有关更多信息,请参见使⽤可空类型(C# 编程指南)。T:类类型参数必须是引⽤类型,包括任何类、接⼝、委托或数组类型。
T:new()类型参数必须具有⽆参数的公共构造函数。当与其他约束⼀起使⽤时,new() 约束必须最后指定。
T:<;基类名>类型参数必须是指定的基类或派⽣⾃指定的基类。
T:<;接⼝名称>类型参数必须是指定的接⼝或实现指定的接⼝。可以指定多个接⼝约束。约束接⼝也可以是泛型的。
T:U为 T 提供的类型参数必须是为 U 提供的参数或派⽣⾃为 U 提供的参数。这称为裸类型约束。
  例⼦:
  1.接⼝约束。
  例如,可以声明⼀个泛型类 MyGenericClass,这样,类型参数 T 就可以实现 IComparable<T> 接⼝:
public class MyGenericClass<T> where T:IComparable { } 
  2.基类约束。
  指出某个类型必须将指定的类作为基类(或者就是该类本⾝),才能⽤作该泛型类型的类型参数。这样的约束⼀经使⽤,就必须出现在该类型参数的所有其他约束之前。
class MyClassy<T, U>
where T : class
where U : struct
{
}
  3.构造函数约束。
  以使⽤ new 运算符创建类型参数的实例;但类型参数为此必须受构造函数约束 new() 的约束。new() 约束可以让编译器知道:提供的任何类型参数都必须具有可访问的⽆参数(或默认)构造函数。new()
约束出现在 where ⼦句的最后。
public class MyGenericClass <T> where T: IComparable, new()
{
T item = new T();
}
  4.对于多个类型参数,每个类型参数都使⽤⼀个 where ⼦句。
interface MyI { }
class Dictionary<TKey,TVal>
where TKey: IComparable, IEnumerable
where TVal: MyI
{
public void Add(TKey key, TVal val)
{
}
}
  5.还可以将约束附加到泛型⽅法的类型参数。
public bool MyMethod<T>(T t) where T : IMyInterface { } 
  6. 裸类型约束
  ⽤作约束的泛型类型参数称为裸类型约束。当具有⾃⼰的类型参数的成员函数需要将该参数约束为包含类型的类型参数时,裸类型约束很有⽤。
class List<T>
{
void Add<U>(List<U> items) where U : T {}
}
  为什么要有约束呢?
  当⼀个泛型参数没有任何约束时,它可以进⾏的操作和运算是⾮常有限的,因为不能对实参做任何类型上的保证,这时候就需要⽤到泛型的约束。泛型的主要约束和次要约束都是指泛型的实参必须满⾜⼀定的规范。C#编译器在编译的过程中可以根据约束来检查所有泛型类型的实参并确保其满⾜约束条件。
  ⼀个泛型参数可以⾄多拥有⼀个主要约束,主要约束可以是⼀个引⽤类型、class或者struct。如果指定⼀个引⽤类型,则实参必须是该类型或者该类型派⽣类型。class规定实参必须是⼀个引⽤类型。struct规定了参数必须是⼀个之类新。以下代码是泛型参数主要约束的⽰例。
using System;
namespace Test
{
class GenericPrimaryConstraint
{
static void Main()
{
Console.Read();
}
}
//主要约束限定T继承⾃Exception类型
public class ClassT1<T> where T : Exception
private T myException;
public ClassT1(T t)
{
myException = t;
}
public override string ToString()
{
//主要约束保证了myException拥有Source成员
return myException.Source;
}
}
//主要约束限定T是引⽤类型
public class ClassT2<T> where T : class
{
private T myT;
public void Clear()
{
//T是引⽤类型,可以置null
myT = null;
}
}
//主要约束限定T是值类型
public class ClassT3<T> where T : struct
{
private T myT;
public override string ToString()
{
//T是值类型,不会发⽣NullReferenceException异常
return myT.ToString();
}
}
}
  以上代码,泛型参数具备了主要约束后,就能够在类型中对其进⾏⼀定的操作,否则任何算法就只能基于⼀个System.Object类型的成员。
  可以说,主要约束是实参类型的限定,⽽相对的次要约束,则是指实参实现的接⼝的限定。对于⼀个泛型类型,可以有0⾄⽆限的次要约束,次要约束规定了参数必须实现所有次要约束中规定的接⼝。次要约束的语法和主要约束基本⼀致,区别仅在于提供的不是⼀个引⽤类型⽽是⼀个或多个接⼝。
  ps:同时拥有主要约束和次要约束的泛型参数,表⽰实参必须同时满⾜主要约束和次要约束。
  三、什么是泛型集合?
  字符串可以说是⼀个字符的集合,和字符串⼀样,数据对象也可以是集合的⽅式存在,所以泛型类对象也可以是集合的⽅式存在(泛型集合)
  同传统的集合相⽐,泛型集合是⼀种强类型的集合,它解决了类型安全问题,同时避免了集合中每次的装箱与拆箱的操作,提升了性能。
  泛型集合类型:
  1. List,这是我们应⽤最多的泛型种类,它对应ArrayList集合。
  2. Dictionary,这也是我们平时运⽤⽐较多的泛型种类,对应Hashtable集合。
  3. Collection对应于CollectionBase
  4. ReadOnlyCollection 对应于ReadOnlyCollectionBase,这是⼀个只读的集合。
  5. Queue,Stack和SortedList,它们分别对应于与它们同名的⾮泛型类。
  性能问题
  下⾯以ArrayList与List<T>为例说明泛型集合的优点及⾮泛型集合的缺点。例如,有这么⼀段代码:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Web
{
public partial class b : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
ArrayList numbers = new ArrayList();
numbers.Add(1);//装箱
numbers.Add(2);//装箱
int number = (int)numbers[1];//拆箱
Label1.Text = number.ToString();
}
}
}
这段代码的背后会发⽣什么呢?⾸先,ArrayList中将所有元素都看成Object类型的,是引⽤类型。调⽤Add⽅法增加两个整数,在这个过程中,整数1,2被CLR装箱(boxing)成object类型的,⽽后⼆个元素时⼜被拆箱(unboxing),装箱与拆箱⼤体上会发⽣以下过程
1.      在托管堆中⾮配⼀个新的object
2.      基于栈(stack-based)的数据必须移动到刚⾮配的内存区中
3.      当拆箱时,位于堆中的数据⼜得移动到栈中
4.      堆中⽆⽤的数据进⾏垃圾回收
当涉及⼤量装箱与拆箱操作时,必然会影响应⽤程序的性能。⽽是⽤泛型的集合类时就会减少装箱与拆箱的⼯作,当存在⼤量数据时,⾃然可以提⾼很多性能。,⽐如⽤
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace Web
{
public partial class b : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
List<int> numbers = new List<int>();//不同
numbers.Add(1);
numbers.Add(2);
int number = numbers[1];//不同
Label1.Text = number.ToString();
}
}
}
  类型安全问题
  对于ArrayList,下⾯的代码编译时时不会报错的。
ArrayList numbers = new ArrayList();
numbers.Add(22);
numbers.Add(35.5);
numbers.Add(true);
foreach (object item in numbers)
{
Console.WriteLine((int)item);
}
因为可以将int类型,float等数值类型装箱成object,因此即使ArrayList增加的数据类型不⼀致。编译器也不会提⽰错误,但是运⾏时,会报错。
但是如果是⽤泛型类型⽐如List<T>,那么在编译时就会进⾏类型检查。防⽌运⾏时错误。writeline方法属于类

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。