MSDN中泛型的相关介绍 本文关键词:相关,介绍,MSDN,中泛型
MSDN中泛型的相关介绍 本文简介:泛型(C#编程指南)泛型是2.0版C#语言和公共语言运行库(CLR)中的一个新功能。泛型将类型参数的概念引入.NETFramework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如,通过使用泛型类型参数T,您可以编写其他客
MSDN中泛型的相关介绍 本文内容:
泛型(C#
编程指南)
泛型是
2.0
版
C#
语言和公共语言运行库
(CLR)
中的一个新功能。泛型将类型参数的概念引入
.NET
Framework,类型参数使得设计如下类和方法成为可能:这些类和方法将一个或多个类型的指定推迟到客户端代码声明并实例化该类或方法的时候。例如,通过使用泛型类型参数
T,您可以编写其他客户端代码能够使用的单个类,而不致引入运行时强制转换或装箱操作的成本或风险,如下所示:
C#
复制代码
//
Declare
the
generic
class
public
class
GenericList
{
void
Add(T
input)
{
}
}
class
TestGenericList
{
private
class
ExampleClass
{
}
static
void
Main()
{
//
Declare
a
list
of
type
int
GenericList
list1
=
new
GenericList();
//
Declare
a
list
of
type
string
GenericList
list2
=
new
GenericList();
//
Declare
a
list
of
type
ExampleClass
GenericList
list3
=
new
GenericList();
}
}
泛型概述
·
使用泛型类型可以最大限度地重用代码、保护类型的安全以及提高性能。
·
泛型最常见的用途是创建集合类。
·
.NET
Framework
类库在
System.Collections.Generic
命名空间中包含几个新的泛型集合类。应尽可能地使用这些类来代替普通的类,如
System.Collections
命名空间中的
ArrayList。
·
您可以创建自己的泛型接口、泛型类、泛型方法、泛型事件和泛型委托。
·
可以对泛型类进行约束以访问特定数据类型的方法。
·
关于泛型数据类型中使用的类型的信息可在运行时通过反射获取。
泛型介绍(C#
编程指南)
泛型类和泛型方法同时具备可重用性、类型安全和效率,这是非泛型类和非泛型方法无法具备的。泛型通常用在集合和在集合上运行的方法中。.NET
Framework
2.0
版类库提供一个新的命名空间
System.Collections.Generic,其中包含几个新的基于泛型的集合类。建议面向
2.0
版的所有应用程序都使用新的泛型集合类,而不要使用旧的非泛型集合类,如
ArrayList。有关更多信息,请参见
.NET
Framework
类库中的泛型(C#
编程指南)。
当然,也可以创建自定义泛型类型和方法,以提供自己的通用解决方案,设计类型安全的高效模式。下面的代码示例演示一个用于演示用途的简单泛型链接列表类。(大多数情况下,建议使用
.NET
Framework
类库提供的
List
类,而不要自行创建类。)在通常使用具体类型来指示列表中所存储项的类型时,可使用类型参数
T。其使用方法如下:
·
在
AddHead
方法中作为方法参数的类型。
·
在
Node
嵌套类中作为公共方法
GetNext
和
Data
属性的返回类型。
·
在嵌套类中作为私有成员数据的类型。
注意,T
可用于
Node
嵌套类。如果使用具体类型实例化
GenericList(例如,作为
GenericList),则所有的
T
都将被替换为
int。
C#
复制代码
//
type
parameter
T
in
angle
brackets
public
class
GenericList
{
//
The
nested
class
is
also
generic
on
T
private
class
Node
{
//
T
used
in
non-generic
constructor
public
Node(T
t)
{
next
=
null;
data
=
t;
}
private
Node
next;
public
Node
Next
{
get
{
return
next;
}
set
{
next
=
value;
}
}
//
T
as
private
member
data
type
private
T
data;
//
T
as
return
type
of
property
public
T
Data
{
get
{
return
data;
}
set
{
data
=
value;
}
}
}
private
Node
head;
//
constructor
public
GenericList()
{
head
=
null;
}
//
T
as
method
parameter
type:
public
void
AddHead(T
t)
{
Node
n
=
new
Node(t);
n.Next
=
head;
head
=
n;
}
public
IEnumerator
GetEnumerator()
{
Node
current
=
head;
while
(current
!=
null)
{
yield
return
current.Data;
current
=
current.Next;
}
}
}
下面的代码示例演示客户端代码如何使用泛型
GenericList
类来创建整数列表。只需更改类型参数,即可方便地修改下面的代码示例,创建字符串或任何其他自定义类型的列表:
C#
复制代码
class
TestGenericList
{
static
void
Main()
{
//
int
is
the
type
argument
GenericList
list
=
new
GenericList();
for
(int
x
=
0;
x
集合中,向该集合添加项的操作类似于以下形式:
C#
复制代码
//
The
.NET
Framework
2.0
way
to
create
a
list
List
list1
=
new
List();
//
No
boxing,no
casting:
list1.Add(3);
//
Compile-time
error:
//
list1.Add(“It
is
raining
in
Redmond.“);
对于客户端代码,与
ArrayList
相比,使用
List
时添加的唯一语法是声明和实例化中的类型参数。虽然这稍微增加了些编码的复杂性,但好处是您可以创建一个比
ArrayList
更安全并且速度更快的列表,特别适用于列表项是值类型的情况。
泛型类型参数(C#
编程指南)
在泛型类型或方法定义中,类型参数是客户端在实例化泛型类型的变量时指定的特定类型的占位符。泛型类(如泛型介绍(C#
编程指南)中列出的
GenericList)不可以像这样使用,因为它实际上并不是一个类型,而更像是一个类型的蓝图。若要使用
GenericList,客户端代码必须通过指定尖括号中的类型参数来声明和实例化构造类型。此特定类的类型参数可以是编译器识别的任何类型。可以创建任意数目的构造类型实例,每个实例使用不同的类型参数,如下所示:
C#
复制代码
GenericList
list1
=
new
GenericList();
GenericList
list2
=
new
GenericList();
GenericList
list3
=
new
GenericList();
在每个
GenericList
实例中,类中出现的每个
T
都会在运行时替换为相应的类型参数。通过这种替换方式,我们使用一个类定义创建了三个独立的类型安全的有效对象。有关
CLR
如何执行此替换的更多信息,请参见运行库中的泛型(C#
编程指南)。
类型参数命名准则
·
务必使用描述性名称命名泛型类型参数,除非单个字母名称完全可以让人了解它表示的含义,而描述性名称不会有更多的意义。
C#
复制代码
public
interface
ISessionChannel
{
/*.*/
}
public
delegate
TOutput
Converter(TInput
from);
public
class
List
{
/*.*/
}
·
考虑使用
T
作为具有单个字母类型参数的类型的类型参数名。
C#
复制代码
public
int
IComparer()
{
return
0;
}
public
delegate
bool
Predicate(T
item);
public
struct
Nullable
where
T
:
struct
{
/*.*/
}
·
务必将“T”作为描述性类型参数名的前缀。
C#
复制代码
public
interface
ISessionChannel
{
TSession
Session
{
get;
}
}
·
考虑在参数名中指示对此类型参数的约束。例如,可以将带有
ISession
约束的参数命名为
TSession。
类型参数的约束(C#
编程指南)
在定义泛型类时,可以对客户端代码能够在实例化类时用于类型参数的类型种类施加限制。如果客户端代码尝试使用某个约束所不允许的类型来实例化类,则会产生编译时错误。这些限制称为约束。约束是使用
where
上下文关键字指定的。下表列出了六种类型的约束:
约束
说明
T:结构
类型参数必须是值类型。可以指定除
Nullable
以外的任何值类型。有关更多信息,请参见使用可空类型(C#
编程指南)。
T:类
类型参数必须是引用类型,包括任何类、接口、委托或数组类型。
T:new()
类型参数必须具有无参数的公共构造函数。当与其他约束一起使用时,new()
约束必须最后指定。
T:
类型参数必须是指定的基类或派生自指定的基类。
T:
类型参数必须是指定的接口或实现指定的接口。可以指定多个接口约束。约束接口也可以是泛型的。
T:U
为
T
提供的类型参数必须是为
U
提供的参数或派生自为
U
提供的参数。这称为裸类型约束。
使用约束的原因
如果要检查泛型列表中的某个项以确定它是否有效,或者将它与其他某个项进行比较,则编译器必须在一定程度上保证它需要调用的运算符或方法将受到客户端代码可能指定的任何类型参数的支持。这种保证是通过对泛型类定义应用一个或多个约束获得的。例如,基类约束告诉编译器:仅此类型的对象或从此类型派生的对象才可用作类型参数。一旦编译器有了这个保证,它就能够允许在泛型类中调用该类型的方法。约束是使用上下文关键字
where
应用的。下面的代码示例演示可通过应用基类约束添加到
GenericList
类(在泛型介绍(C#
编程指南)中)的功能。
C#
复制代码
public
class
Employee
{
private
string
name;
private
int
id;
public
Employee(string
s,int
i)
{
name
=
s;
id
=
i;
}
public
string
Name
{
get
{
return
name;
}
set
{
name
=
value;
}
}
public
int
ID
{
get
{
return
id;
}
set
{
id
=
value;
}
}
}
public
class
GenericList
where
T
:
Employee
{
private
class
Node
{
private
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
{
get
{
return
data;
}
set
{
data
=
value;
}
}
}
private
Node
head;
public
GenericList()
//constructor
{
head
=
null;
}
public
void
AddHead(T
t)
{
Node
n
=
new
Node(t);
n.Next
=
head;
head
=
n;
}
public
IEnumerator
GetEnumerator()
{
Node
current
=
head;
while
(current
!=
null)
{
yield
return
current.Data;
current
=
current.Next;
}
}
public
T
FindFirstOccurrence(string
s)
{
Node
current
=
head;
T
t
=
null;
while
(current
!=
null)
{
//The
constraint
enables
access
to
the
Name
property.
if
(current.Data.Name
==
s)
{
t
=
current.Data;
break;
}
else
{
current
=
current.Next;
}
}
return
t;
}
}
约束使得泛型类能够使用
Employee.Name
属性,因为类型为
T
的所有项都保证是
Employee
对象或从
Employee
继承的对象。
可以对同一类型参数应用多个约束,并且约束自身可以是泛型类型,如下所示:
C#
复制代码
class
EmployeeList
where
T
:
Employee,IEmployee,System.IComparable,new()
{
//
.
}
通过约束类型参数,可以增加约束类型及其继承层次结构中的所有类型所支持的允许操作和方法调用的数量。因此,在设计泛型类或方法时,如果要对泛型成员执行除简单赋值之外的任何操作或调用
System.Object
不支持的任何方法,您将需要对该类型参数应用约束。
在应用
where
T
:
class
约束时,建议不要对类型参数使用
==
和
!=
运算符,因为这些运算符仅测试引用同一性而不测试值相等性。即使在用作参数的类型中重载这些运算符也是如此。下面的代码说明了这一点;即使
String
类重载
==
运算符,输出也为
false。
C#
复制代码
public
static
void
OpTest(T
s,T
t)
where
T
:
class
{
System.Console.WriteLine(s
==
t);
}
static
void
Main()
{
string
s1
=
“foo“;
System.Text.StringBuilder
sb
=
new
System.Text.StringBuilder(“foo“);
string
s2
=
sb.ToString();
OpTest(s1,s2);
}
这种情况的原因在于,编译器在编译时仅知道
T
是引用类型,因此必须使用对所有引用类型都有效的默认运算符。如果需要测试值相等性,建议的方法是同时应用
where
T
:
IComparable
约束,并在将用于构造泛型类的任何类中实现该接口。
未绑定的类型参数
没有约束的类型参数(如公共类
SampleClass{}
中的
T)称为未绑定的类型参数。未绑定的类型参数具有以下规则:
·
不能使用
!=
和
==
运算符,因为无法保证具体类型参数能支持这些运算符。
·
可以在它们与
System.Object
之间来回转换,或将它们显式转换为任何接口类型。
·
可以将它们与
null
进行比较。将未绑定的参数与
null
进行比较时,如果类型参数为值类型,则该比较将始终返回
false。
裸类型约束
用作约束的泛型类型参数称为裸类型约束。当具有自己的类型参数的成员函数需要将该参数约束为包含类型的类型参数时,裸类型约束很有用,如下面的示例所示:
C#
复制代码
class
List
{
void
Add(List
items)
where
U
:
T
{/*.*/}
}
在上面的示例中,T
在
Add
方法的上下文中是一个裸类型约束,而在
List
类的上下文中是一个未绑定的类型参数。
裸类型约束还可以在泛型类定义中使用。注意,还必须已经和其他任何类型参数一起在尖括号中声明了裸类型约束:
C#
复制代码
//naked
type
constraint
public
class
SampleClass
where
T
:
V
{
}
泛型类的裸类型约束的作用非常有限,因为编译器除了假设某个裸类型约束派生自
System.Object
以外,不会做其他任何假设。在希望强制两个类型参数之间的继承关系的情况下,可对泛型类使用裸类型约束。
泛型类(C#
编程指南)
泛型类封装不是特定于具体数据类型的操作。泛型类最常用于集合,如链接列表、哈希表、堆栈、队列、树等,其中,像从集合中添加和移除项这样的操作都以大体上相同的方式执行,与所存储数据的类型无关。
对于大多数需要集合类的方案,推荐的方法是使用
.NET
Framework
2.0
类库中所提供的类。有关使用这些类的更多信息,请参见
.NET
Framework
类库中的泛型(C#
编程指南)。
一般情况下,创建泛型类的过程为:从一个现有的具体类开始,逐一将每个类型更改为类型参数,直至达到通用化和可用性的最佳平衡。创建您自己的泛型类时,需要特别注意以下事项:
·
将哪些类型通用化为类型参数。
一般规则是,能够参数化的类型越多,代码就会变得越灵活,重用性就越好。但是,太多的通用化会使其他开发人员难以阅读或理解代码。
·
如果存在约束,应对类型参数应用什么约束(请参见类型参数的约束(C#
编程指南))。
一个有用的规则是,应用尽可能最多的约束,但仍使您能够处理需要处理的类型。例如,如果您知道您的泛型类仅用于引用类型,则应用类约束。这可以防止您的类被意外地用于值类型,并允许您对
T
使用
as
运算符以及检查空值。
·
是否将泛型行为分解为基类和子类。
由于泛型类可以作为基类使用,此处适用的设计注意事项与非泛型类相同。有关从泛型基类继承的规则,请参见下面的内容。
·
是否实现一个或多个泛型接口。
例如,如果您设计一个类,该类将用于创建基于泛型的集合中的项,则可能需要实现一个接口,如
IComparable,其中
T
是您的类的类型。
有关简单泛型类的示例,请参见泛型介绍(C#
编程指南)
类型参数和约束的规则对于泛型类行为有几方面的含义,特别是关于继承和成员可访问性。请务必先理解一些术语,然后再继续进行。对于泛型类
Node,,客户端代码可以通过指定类型参数引用该类,以创建封闭式构造类型
(Node),或者可以让类型参数处于未指定状态(例如在指定泛型基类时)以创建开放式构造类型
(Node)。泛型类可以从具体的、封闭式构造或开放式构造基类继承:
C#
复制代码
class
BaseNode
{
}
class
BaseNodeGeneric
{
}
//
concrete
type
class
NodeConcrete
:
BaseNode
{
}
//closed
constructed
type
class
NodeClosed
:
BaseNodeGeneric
{
}
//open
constructed
type
class
NodeOpen
:
BaseNodeGeneric
{
}
非泛型(具体)类可以从封闭式构造基类继承,但无法从开放式构造类或裸类型参数继承,因为在运行时客户端代码无法提供实例化基类所需的类型变量。
C#
复制代码
//No
error
class
Node1
:
BaseNodeGeneric
{
}
//Generates
an
error
//class
Node2
:
BaseNodeGeneric
{}
//Generates
an
error
//class
Node3
:
T
{}
从开放式构造类型继承的泛型类必须为任何未被继承类共享的基类类型参数提供类型变量,如以下代码所示:
C#
复制代码
class
BaseNodeMultiple
{
}
//No
error
class
Node4
:
BaseNodeMultiple
{
}
//No
error
class
Node5
:
BaseNodeMultiple
{
}
//Generates
an
error
//class
Node6
:
BaseNodeMultiple
{}
从开放式构造类型继承的泛型类必须指定约束,这些约束是基类型约束的超集或暗示基类型约束:
C#
复制代码
class
NodeItem
where
T
:
System.IComparable,new()
{
}
class
SpecialNodeItem
:
NodeItem
where
T
:
System.IComparable,new()
{
}
泛型类型可以使用多个类型参数和约束,如下所示:
C#
复制代码
class
SuperKeyType
where
U
:
System.IComparable
where
V
:
new()
{
}
开放式构造类型和封闭式构造类型可以用作方法参数:
C#
复制代码
void
Swap(List
list1,List
list2)
{
//code
to
swap
items
}
void
Swap(List
list1,List
list2)
{
//code
to
swap
items
}
泛型类是不变的。也就是说,如果输入参数指定
List,则当您试图提供
List
时,将会发生编译时错误。
泛型方法(C#
编程指南)
泛型方法是使用类型参数声明的方法,如下所示:
C#
复制代码
static
void
Swap(ref
T
lhs,ref
T
rhs)
{
T
temp;
temp
=
lhs;
lhs
=
rhs;
rhs
=
temp;
}
下面的代码示例演示一种使用
int
作为类型参数的方法调用方式:
C#
复制代码
public
static
void
TestSwap()
{
int
a
=
1;
int
b
=
2;
Swap(ref
a,ref
b);
System.Console.WriteLine(a
+
““+
b);
}
也可以省略类型参数,编译器将推断出该参数。下面对
Swap
的调用等效于前面的调用:
C#
复制代码
Swap(ref
a,ref
b);
相同的类型推断规则也适用于静态方法以及实例方法。编译器能够根据传入的方法参数推断类型参数;它无法仅从约束或返回值推断类型参数。因此,类型推断不适用于没有参数的方法。类型推断在编译时、编译器尝试解析任何重载方法签名之前进行。编译器向共享相同名称的所有泛型方法应用类型推断逻辑。在重载解析步骤中,编译器仅包括类型推断取得成功的那些泛型方法。
在泛型类中,非泛型方法可以访问类级别类型参数,如下所示:
C#
复制代码
class
SampleClass
{
void
Swap(ref
T
lhs,ref
T
rhs)
{
}
}
如果定义的泛型方法接受与包含类相同的类型参数,编译器将生成警告
CS0693,因为在方法范围内,为内部
T
提供的参数将隐藏为外部
T
提供的参数。除了类初始化时提供的类型参数之外,如果需要灵活调用具有类型参数的泛型类方法,请考虑为方法的类型参数提供其他标识符,如下面示例中的
GenericList2
所示。
C#
复制代码
class
GenericList
{
//
CS0693
void
SampleMethod()
{
}
}
class
GenericList2
{
//No
warning
void
SampleMethod()
{
}
}
使用约束对方法中的类型参数启用更专门的操作。此版本的
Swap
现在称为
SwapIfGreater,它只能与实现
IComparable
的类型参数一起使用。
C#
复制代码
void
SwapIfGreater(ref
T
lhs,ref
T
rhs)
where
T
:
System.IComparable
{
T
temp;
if
(lhs.CompareTo(rhs)
>
0)
{
temp
=
lhs;
lhs
=
rhs;
rhs
=
temp;
}
}
泛型方法可以使用许多类型参数进行重载。例如,下列方法可以全部存在于同一个类中:
C#
复制代码
void
DoWork()
{
}
void
DoWork()
{
}
void
DoWork()
{
}
运行库中的泛型(C#
编程指南)
将泛型类型或方法编译为
Microsoft
中间语言
(MSIL)
时,它包含将其标识为具有类型参数的元数据。泛型类型的
MSIL
的使用因所提供的类型参数是值类型还是引用类型而不同。
第一次用值类型作为参数来构造泛型类型时,运行库会创建专用泛型类型,将提供的参数代入到
MSIL
中的适当位置。对于每个用作参数的唯一值类型,都会创建一次专用泛型类型。
例如,假设您的程序代码声明了一个由整数构造的堆栈,如下所示:
C#
复制代码
Stack
stack;
在此位置,运行库生成
Stack
类的专用版本,并相应地用整数替换其参数。现在,只要程序代码使用整数堆栈,运行库就会重用生成的专用
Stack
类。在下面的示例中,创建了整数堆栈的两个实例,它们共享
Stack
代码的单个实例:
C#
复制代码
Stack
stackOne
=
new
Stack();
Stack
stackTwo
=
new
Stack();
但是,如果在程序代码中的其他位置创建了另一个
Stack
类,这次使用不同的值类型(如
long
或用户定义的结构)作为其参数,则运行库会生成泛型类型的另一版本(这次将在
MSIL
中的适当位置代入
long)。由于每个专用泛型类本身就包含值类型,因此不再需要转换。
对于引用类型,泛型的工作方式略有不同。第一次使用任何引用类型构造泛型类型时,运行库会创建专用泛型类型,用对象引用替换
MSIL
中的参数。然后,每次使用引用类型作为参数来实例化构造类型时,无论引用类型的具体类型是什么,运行库都会重用以前创建的泛型类型的专用版本。之所以可以这样,是因为所有引用的大小相同。
例如,假设您有两个引用类型:一个
Customer
类和一个
Order
类,并且进一步假设您创建了一个
Customer
类型的堆栈:
C#
复制代码
class
Customer
{
}
class
Order
{
}
C#
复制代码
Stack
customers;
在此情况下,运行库生成
Stack
类的一个专用版本,该版本不是存储数据,而是存储稍后将填写的对象引用。假设下一行代码创建另一个引用类型的堆栈,称为
Order:
C#
复制代码
Stack
orders
=
new
Stack();
不同于值类型,对于
Order
类型不创建
Stack
类的另一个专用版本。而是创建
Stack
类的一个专用版本实例,并将
orders
变量设置为引用它。假设接下来您遇到一行创建
Customer
类型堆栈的代码:
C#
复制代码
customers
=
new
Stack();
与前面使用
Order
类型创建的
Stack
类一样,创建了专用
Stack
类的另一个实例,并且其中所包含的指针被设置为引用
Customer
类型大小的内存区域。因为引用类型的数量会随程序的不同而大幅变化,C#
泛型实现将编译器为引用类型的泛型类创建的专用类的数量减小到一个,从而大幅减小代码量的增加。
此外,使用类型参数实例化泛型
C#
类时,无论它是值类型还是引用类型,可以在运行时使用反射查询它,并且可以确定它的实际类型和类型参数。
.NET
Framework
类库中的泛型(C#
编程指南)
.NET
Framework
2.0
版类库提供一个新的命名空间
System.Collections.Generic,其中包括几个随时可用的泛型集合类和关联接口。其他命名空间(如
System)也提供新的泛型接口,如
IComparable。与早期版本的
.NET
Framework
所提供的非泛型集合类相比,这些类和接口更为高效和类型安全。在设计和实现自己的自定义集合类之前,请考虑是否能够使用基类库所提供的类,或是否能从基类库所提供的类派生。