1.
为什么提出泛型,先来看使用非泛型集合的两个问题:性能问题和类型安全问题:
(1)性能问题:
装箱:显式将值类型分配给System.Object变量的过程。当我们对一个值进行装箱时,CLR就会在堆上分配新的对象并且将值类型的值复制到那个实例上,因此,返回给我们的就是新分配在堆上的对象的引用。
拆箱:把保存在对象引用中的值转换回栈上的相应值类型。CLR会验证收到的值类型是不是等价于装箱的类型。例:
int myInt = 25;
object boxedInt = myInt;
//将int装箱为object用
int unboxedInt = (int)boxedInt;//将引用拆箱为相应的int
try
{
string unboxedInt2 = (string)boxedInt; //此行发生错误,不等价于装箱的类型
}
catch (InvalidCastException ex)//拆箱为错误的数据类型将触发运行时异常
{
Console.WriteLine(ex.Message);
}
以ArrayList来说,它操作的原型均为System.Object数据,传入数据时会进行自动装箱,使用类型索引器从ArrayList中获取项时又会自动拆箱,这样会引起性能问题。例:
//在传递给需要object的方法时,值类型会自动装箱
ArrayList myInts = new ArrayList();
myInts.Add(10);
myInts.Add(20);
myInts.Add(30);
//当将object转换回栈数据时,会发生拆箱
int i = (int)myInts[0];
//由于WriteLine()要求object类型,因此在此发生装箱操作
Console.WriteLine("Value of your int:{0}",i);
(2)类型安全问题:
在泛型之前,要解决类型安全问题的唯一方法是手工创建自定义(强类型的)集合类。
首先,创建一个类Person:
public int
Age { get; set; }
public
string FirstName { get; set;
}
public
string LastName{get;set;}
public
Person(){}
public
Person(string firstName,
string lastName, int age)
{
Age
= age;
FirstName
= firstName;
LastName
= lastName;
}
public
override string ToString()
{
return
string.Format("Name:{0}{1},Age:{2}",FirstName,LastName,Age);
}
其次,自定义集合类PersonCollection:
private
ArrayList arPeople =
new ArrayList();
//为提供者进行转换
public
Person GetPerson(int pos)
{
return
(Person)arPeople[pos];
}
//只插入Person对象
public
void AddPerson(Person p)
{
arPeople.Add(p);
}
public
void ClearPeople()
{
arPeople.Clear();
}
public
int Count
{
get
{ return arPeople.Count;
}
}
//支持foreach枚举
IEnumerator
IEnumerable.GetEnumerator()
{
return
arPeople.GetEnumerator();
}
最后,定义这些类型后,就不用担心类型安全了,因为C#编译器会检查任何尝试插入不兼容数据类型的请求:
PersonCollection myPeolple = new PersonCollection();
myPeolple.AddPerson(new
Person("Homer","Simpson",40));
myPeolple.AddPerson(new
Person("Marge", "Simpson", 38));
myPeolple.AddPerson(new
Person("Lisa", "Simpson", 9));
myPeolple.AddPerson(new
Person("Bart", "Simpson", 7));
myPeolple.AddPerson(new
Person("Maggie", "Simpson", 2));
//这会产生编译时错误
//myPeople.AddPerson(new
Car());
foreach (Person p in myPeolple)
{
Console.WriteLine(p);
}
结论:虽然自定义的集合可以确保类型安全,但是如果要是用这个方法,就必须为每一个希望包含的类型创建一个基本一模一样的自定义集合,比如CarCollection、IntCollection等,而且这些自定义集合并没有消除装箱/拆箱的损失,不管选择哪个类型来保存,都不能避免使用非泛型容器带来的装箱问题,所以提出了泛型。
2.
我们可以用泛型List<>来解决上面3的例子:
//该List<>只能容纳Person对象
List<Person>
morePeople = new List<Person>();
morePeople.Add(new
Person("Frank","Black",50));
Console.WriteLine(morePeople[0]);
//该List<>只能容纳整数
List<int>
moreInts = new List<int>();
moreInts.Add(10);
moreInts.Add(2);
int sum = moreInts[0] +
moreInts[1];
//编译错误!不能将Person对象添加到整型列表中
//moreInts.Add(new
Person());
3.
任何使用.NET 2.0或更高版本创建的项目都应该放弃使用System.Collections中的类,而使用System.Collections.Generic中的类。
4.
<T>的正式名称为类型参数,也可通俗的称为占位符,符号读作of T
5.
在基础类库中引入一个以集合为中心的新命名空间:System.Collections.Generic命名空间,该命名空间位于mscorlib.dll和System.dll程序集内。
6.
只有类、结构、接口和委托可以使用泛型,枚举类型不可以。
7.
对比非泛型接口的实现与泛型接口的实现:
非泛型接口IComparable的实现:
public class Car
: IComparable
{
int carID;
public int CarID
{
get { return carID; }
set { carID = value; }
}
//IComparable的实现
int IComparable.CompareTo(Object obj)
{
Car temp = obj as Car;
if (temp != null)
{
if (this.CarID >
temp.CarID)
return 1;
if (this.CarID <
temp.CarID)
return -1;
else
return 0;
}
else
{
throw new ArgumentException("Parameter is not a Car!");
}
}
}
泛型接口IComparable<>的实现:
public class Car
: IComparable<Car>
{
int carID;
public int CarID
{
get { return carID; }
set { carID = value; }
}
//IComparable<T>的实现
int IComparable<Car>.CompareTo(Car obj)
{
if (this.CarID >
obj.CarID)
return 1;
if (this.CarID <
obj.CarID)
return -1;
else
return 0;
}
}
结论:对比后发现泛型接口的实现不用判断传入的参数是否为Car,因为它只能为Car,如果传入的数据类型不兼容,将得到编译时错误。
8.
使用List<T>类:
private static void
UseGenericList()
{
//
使用集合/对象初始化语法,构建一个Person对象的列表
List<Person>
people = new List<Person>()
{
new Person {FirstName= "Homer", LastName="Simpson", Age=47},
new Person {FirstName= "Marge", LastName="Simpson", Age=45},
new Person {FirstName= "Lisa", LastName="Simpson", Age=9},
new Person {FirstName= "Bart", LastName="Simpson", Age=8}
};
//打印列表中项的个数
Console.WriteLine("Items in list: {0}",
people.Count);
//
枚举列表
foreach (Person p in people)
Console.WriteLine(p);
//
插入一个新Person
Console.WriteLine("\n->Inserting new
person.");
people.Insert(2, new Person { FirstName = "Maggie", LastName = "Simpson", Age = 2 });
Console.WriteLine("Items in list: {0}",
people.Count);
//
将数据复制到新的数组中
Person[] arrayOfPeople =
people.ToArray();
for (int i = 0; i <
arrayOfPeople.Length; i++)
{
Console.WriteLine("First Names: {0}",
arrayOfPeople[i].FirstName);
}
}
注意:Insert()方法和ToArray()方法的使用
9.
使用Stack<T>
static void
UseGenericStack()
{
Stack<Person> stackOfPeople =
new Stack<Person>();
stackOfPeople.Push(new
Person { FirstName =
"Homer", LastName =
"Simpson", Age = 47
});
stackOfPeople.Push(new
Person { FirstName =
"Marge", LastName =
"Simpson", Age = 45
});
stackOfPeople.Push(new
Person { FirstName =
"Lisa", LastName = "Simpson", Age = 9 });
//
观察栈顶的项,取出,再次观察
Console.WriteLine("First person is: {0}",
stackOfPeople.Peek());
Console.WriteLine("Popped off {0}",
stackOfPeople.Pop());
Console.WriteLine("\nFirst person is: {0}",
stackOfPeople.Peek());
Console.WriteLine("Popped off {0}",
stackOfPeople.Pop());
Console.WriteLine("\nFirst person item is: {0}",
stackOfPeople.Peek());
Console.WriteLine("Popped off {0}",
stackOfPeople.Pop());
try
{
Console.WriteLine("\nnFirst person is: {0}",
stackOfPeople.Peek());
Console.WriteLine("Popped off {0}",
stackOfPeople.Pop());
}
catch (InvalidOperationException ex)
{
Console.WriteLine("\nError! {0}", ex.Message);
}
}
注意:在观察栈时,得到的永远是栈顶对象。如果栈为空,这时再调用Peek()和Pop()将触发系统异常。
加载中,请稍候......