好好学习,天天向上,一流范文网欢迎您!
当前位置:首页 >> 最新范文 内容页

MSDN中泛型的相关介绍

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

所提供的非泛型集合类相比,这些类和接口更为高效和类型安全。在设计和实现自己的自定义集合类之前,请考虑是否能够使用基类库所提供的类,或是否能从基类库所提供的类派生。

TAG标签: