C#泛型(三)——泛型类的功能

在创建泛型类时,还需要一些其他C#关键字。例如,不能把null赋予泛型类型。此时,如下一节所述,可以使用default关键字。如果泛型类型不需要Object类的功能,但需要调用泛型类上的某些特定方法,就可以定义约束。

本节讨论如下主题:

●默认值

●约束

●继承

●静态成员

首先介绍一个使用泛型文档管理器的示例。文档管理器用于从队列中读写文档。先创建一个新的控制台项目DocumentManager,并添加DocumentManager<T>类。AddDocument()方法将一个文档添加到队列中。如果队列不为空,IsDocumentAvailable只读属性就返回true

注意:在.NETCore中,这个示例需要引用NuGetSystem.Collections

using System;

using System.Collections.Generic;

namespace Rbsoft.ProCSharp.Generics

{

public class DocumentManager<T>

{

private readonly Queue<T> documentQueue = new Queue<T>();

public void AddDocument(T doc)

{

lock (this)

{

documentQueue.Enqueue(doc);

}

}

public bool IsDocumentAvailable => documentQueue. Count > 0;

}

}

一、默认值

现在给DocumentManager<T>类添加一个GetDocument()方法。在这个方法中,应把类型T指定为null。但是,不能把null赋予泛型类型。原因是泛型类型也可以实例化为值类型,而null只能用于引用类型。为了解决这个问题,可以使用default关键字。通过default关键字,将null赋予引用类型,将0赋予值类型。

public T GetDocument()

{

T doc = default(T);

lock (this)

{

doc = documentQueue. Dequeue();

}

return doc;

}

注意:default关键字根据上下文可以有多种含义。switch语句使用default定义默认情况。在泛型中,取决于泛型类型是引用类型还是值类型,泛型default将泛型类型初始化为null0

二、约束

如果泛型类需要调用泛型类型中的方法,就必须添加约束。对于DocumentManager<T>,文档的所有标题应在DisplayAllDocuments()方法中显示。Document类实现带有TitleContent属性的IDocument接口:

public interface IDocument

{

string Title { get; set; }

string Content { get; set; }

}

public class Document: IDocument

{

public Document()

{

}

public Document(string title, string content)

{

Title = title;

Content = content;

}

public string Title { get; set; }

public string Content { get; set; }

}

要使用DocumentManager<T>类显示文档,可以将类型T强制转换为IDocument接口,以显示标题:

public void DisplayAllDocuments()

{

foreach (T doc in documentQueue)

{

WriteLine((( IDocument) doc). Title);

}

}

问题是,如果类型T没有实现IDocument接口,这个类型强制转换就会导致一个运行时异常。最好给DocumentManager<TDocument>类定义一个约束:TDocument类型必须实现IDocument接口。为了在泛型类型的名称中指定该要求,将T改为TDocumentwhere子句指定了实现IDocument接口的要求。

public class DocumentManager<TDocument> where TDocument: IDocument { }

注意:给泛型类型添加约束时,最好包含泛型参数名称的一些信息。现在,示例代码给泛型参数使用TDocument,来代替T。对于编译器而言,参数名不重要,但更具可读性。

这样就可以编写foreach语句,从而使类型TDocument包含属性TitleVisual Studio IntelliSense和编译器都会提供这个支持。

public void DisplayAllDocuments()

{

foreach (TDocument doc in documentQueue)

{

WriteLine( doc. Title);

}

}

Main()方法中,用Document类型实例化DocumentManager<TDocument>类,而Document类型实现了需要的IDocument接口。接着添加和显示新文档,检索其中一个文档:

public static void Main()

{

var dm = new DocumentManager<Document>();

dm.AddDocument(new Document(" Title A", "Sample A"));

dm. AddDocument( new Document(" Title B", "Sample B"));

dm. DisplayAllDocuments();

if (dm. IsDocumentAvailable)

{

Document d = dm. GetDocument();

WriteLine( d. Content);

}

}

DocumentManager现在可以处理任何实现了IDocument接口的类。在示例应用程序中介绍了接口约束。泛型支持几种约束类型,如下表所示。

约束

说明

where T : struct

对于结构约束,类型T必须是值类型

where T : class

类约束指定类型T必须是引用类型

Where T:IFoo

指定类型T必须实现接口IFoo

Where T:Foo

指定类型T必须派生自基类Foo

Where T:new()

这是一个构造函数约束,指定类型T必须有一个默认构造函数

 where T1 : T2

这个约束也可以指定,类型T1派生自泛型类型T2

注意:只能为默认构造函数定义构造函数约束,不能为其他构造函数定义构造函数约束。

使用泛型类型还可以合并多个约束。where T:IFoo,new()约束和MyClass<T>声明指定,类型T必须实现IFoo接口,且必须有一个默认构造函数。

public class MyClass<T> where T: IFoo, new()

{ //...

 注意:在C#中,where子句的一个重要限制是,不能定义必须由泛型类型实现的运算符。运算符不能在接口中定义。在where子句中,只能定义基类、接口和默认构造函数。

三、继承

前面创建的LinkedList<T>类实现了IEnumerable<T>接口:

public class LinkedList< T>: IEnumerable< T>

{ //...

泛型类型可以实现泛型接口,也可以派生自一个类。泛型类可以派生自泛型基类:

public class Base<T>

{

}

public class Derived< T>: Base< T>

{

}

其要求是必须重复接口的泛型类型,或者必须指定基类的类型,如下例所示:

public class Base< T>

{

}

public class Derived< T>: Base< string>

{

}

于是,派生类可以是泛型类或非泛型类。例如,可以定义一个抽象的泛型基类,它在派生类中用一个具体的类实现。这允许对特定类型执行特殊的操作:

public abstract class Calc< T>

{

public abstract T Add( T x, T y);

public abstract T Sub( T x, T y);

}

public class IntCalc: Calc< int>

{

public override int Add( int x, int y)=> x + y;

public override int Sub( int x, int y) => x - y;

}

还可以创建一个部分的特殊操作,如从Query中派生StringQuery类,只定义一个泛型参数,如字符串TResult。要实例化StringQuery,只需要提供TRequest的类型:

public class Query<TRequest, TResult>

{

}

public StringQuery<TRequest> : Query< TRequest, string>

{

}

四、静态成员

泛型类的静态成员需要特别关注。泛型类的静态成员只能在类的一个实例中共享。下面看一个例子,其中StaticDemo<T>类包含静态字段x

public class StaticDemo<T>

{

public static int x;

}

由于同时对一个string类型和一个int类型使用了StaticDemo<T>类,因此存在两组静态字段:

StaticDemo< string>. x = 4;

StaticDemo< int>. x = 5;

WriteLine( StaticDemo< string>. x); // writes4

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

掌控自身命运

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值