泛型概述
- 泛型不仅是C#编程语言的一部分,而且与程序集中的IL代码紧密地集成。有了泛型,就可以创建独立于被包含类型的类和方法。 泛型的优点
- 性能
- 类型安全性
- 二进制代码重用
- 代码的扩展
- 命名约定 性能
- 对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作。
- 值类型存储在栈上,引用类型存储在堆上。C#类时引用类型,结构是值类型。.NET很容易把值类型转换为引用类型,所以可以在需要对象(对象是引用类型)的任意地方使用值类型。
- 从值类型转换为引用类型称为装箱。
命名约定
public class List<T>{}
public class LinkedList<T>{}
public delegate void EventHandle<TEventArgs>(object sender,TEventArgs e);
public delegate TOutput Converter<TInput,TOutput>(TInput from);
public class SortedList<TKey,TValue>{}
创建泛型类
- 在链表中,一个元素引用下一个元素。所以必须创建一个类,它将对象封装在链表中,并引用下一个对象。
- 类LinkedListNode包含一个属性value,该属性用构造函数初始化。另外,LinkedListNode类包含对链表中下一个元素和上一个元素的引用,这些元素都可以从属性中访问。
public class LinkedListNode { public LinkedListNode(object value) { Value=value; } public object Value{get;private set;} public LinkedListNode Next{get;internal set;} public LinkedListNode Prev{get;internal set;} }
LinkedList类包含LinkedListNode类型的First和Last属性,它们分别标记了链表的头尾。AddLast()方法在链表尾添加一个新元素。首页创建一个LinkedListNode类型的对象。如果链表是空的,First和Last属性就设置为该新元素;否则,就把新元素添加为链表中的最后一个元素。通过实现GetEnumerator()方法,可以用foreach语句遍历链表。GetEnumerator()方法使用yield语句创建一个枚举器类型。 ``` public class LinkedList:IEnumerable { public LinkedListNode First{get;private set;} public LinkedListNode Last{get;private set;}
public LinkedListNode AddLast(object node) {
var newNode=new LinkedListNode(node); if(First==null) { First=newNode; Last=First; } else { LinkedListNode previous=Last; Last.Next=newNode; Last=newNode; Last.Prev=previous; } return newNode;
} public IEnumerator GrtEnumerator() {
LinkedListNode current =First; while(current!=null) { yield return current.Value; current=current.Next; }
}
}
创建链表的泛型版本 - LinkedListNode 类用一个泛型类型T声明。属性Value的类型是T,而不是object。构造函数也变为可以接受T类型的对象。也可以返回和设置泛型类型,所以属性Next和Prev的类型是LinkedLisTNode<T>
public class LinkedListNode {
public LinkedListNode(T value) { Value=value; } public T Value{get;private set;} public LinkedListNode<T> Next{get;internal set;} public LinkedListNode<T> Prev{get;internal set;}
}
public class LinkedList:IEnumerable {
public LinkedListNode<T>Frist{get;private set;} public LinkedListNode<T>Last{get;private set;} public LinkedListNOde<T>AddLast(T node) { var newNode=new LinkedListNode<T>(node); if(First==null) { First=newNode; Last=First; } else { LinkedListNode<T>previous=Last; LAst.Next=newNode; Lsat=newNode; Last.Prev=previous; } return newNode; } public IEnumerator<T> GetEnumerator() { LinkedListNode<T>current=First; while(current!=null) { yield return current.Value; current=current.Next; } } IEnumerator IEnumerable.GetEnumerator()=>GetEnumerator();
}
- 使用泛型LinkedList<T>,可以用int类型实例化它,且无需装箱操作。如果不使用AddLast()去传递int,就会出现一个编译器错误。使用泛型IEnumerable<T>,foreach语句也是类型安全的,如果foreach语句中的变量不是int,就会出现一个编译器错误
var list2=new LinkedList(); list2.AddLast(1); list2.AddLAst(3); list2.AddLAst(5);
foreach(int i in list2) {
WriteLine(i);
} 对于字符串类型使用泛型LinkList,将字符串传递给AddLast()方法。 var list3=new LinkedList(); list3.AddLast(“2”); list3.AddLast(“four”); list3.AddLast(“foo”);
foreach(string s in list3) {
WriteLine(s);
} ```
- 每个处理对象类型的类都可以有泛型实现方式,另外,如果类使用了层次结构,泛型就非常有助于消除类型强制转换操作
泛型类的功能
- 默认值
- 约束
- 继承
- 静态成员
默认值
public T GetDocument()
{
T doc =default(T);
lock(this)
{
doc=documentQueue.Dequeue();
}
return doc;
}
约束
对于DocumentManager,文档的所有标题应在DisplayAllDocuments()方法中显示。Document类实现带有Title和Content属性的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;ste;} public string Content{get;set;} }
要使用DocumentManager类显示文档,可以将类型T强制转换为IDocument接口,以显示标题
public void DisplayAllDocuments() {
foreach(T doc in documentQueue)
{
WriteLine(((IDocument)doc).Title);
}
}
- 如果类型T没有实现IDocument接口,这个类型强制转换就会导致一个运行时异常。最好给DocumentManager<TDocument>类定义一个约束:TDocument类型必须实现IDocument接口。
- 为了在泛型类型的名称中制定该要求,将T改为TDocument。where子句指定了实现IDocument接口的需求
public class DocumentManager where TDocument:IDocument {
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
**继承**
前面创建的LinkedList<T>类实现了IEnumerable<T>接口
public class LinkedList:IEnumerable {
//泛型类型可以实现泛型接口,也可以派生自一个类。泛型类可以派生自泛型基类
} public class Base {
}
public class Derived:Base {
} 其要求是必须重复接口的泛型类型,或者必须指定基类的类型
public class Base {
} public class Derived:Base {}
- 派生类可以是泛型类或非泛型类。例如,可以定义一个抽象的泛型基类,它在派生类中用一个具体的类实现。这允许对特定类型执行特殊的操作
public abstract class Calc {
public abstract T Add(T x,T y);
public abstract T Sub(T x,T y);
} public class IntCalc:Calc {
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 {} public StringQuery:Query {}
**静态成员**
- 泛型类的静态成员需要特别关注。泛型类的静态成员只能在类的一个实例中共享。
public class StaticDemo {
public static int x;
} 由于同时对一个string类型和一个int类型使用StaticDemole类,因此存在两组静态字段: StaticDemo.x=4; StaticDemo.x=5; WriteLine(StaticDemo.x);
> 泛型接口的协变
- 如果泛型类型用out关键字标注,泛型接口就是协变得。这也意味着返回类型只能是T。接口IIndex与类型T是协变得,并从一个只读索引器中返回这个类型
public interface IIndex {
T this[int index]{get;}
int Count{get;}
} IIndex接口用RectangleCollection类来实现。RectangleCollection类为泛型类型T定义了Rectangle
>泛型接口的抗变
- 如果泛型类型用in关键字标注,泛型接口就是抗变。这样,接口只能把泛型类型T用作其方法的输入
public interface IDisplay {
void Show(T item);
}
- ShapeDisplay 类实现IDisplay<Shape>,并使用Shape对象作为输入参数
public class ShapeDisplay:IDisplay {
public void Show(Shape s)=> WriteLine($"{s.GetType().Name}Width:{s.Width},Height:{s.Height}");
}
> 泛型方法
- 在泛型方法中,泛型类型用方法声明来定义。泛型方法可以在非泛型类中定义.
- Swap<T>()方法把T定义为泛型类型,该泛型类型用于两个参数和一个变量temp:
void Swap(ref T x,ref T y) {
T temp;
temp=x;
x=y;
y=temp;
} 把泛型类型赋予方法调用,就可以调用泛型方法: int i=4; int j=5; Swap(ref i,ref j);
但是,因为C#编译器会通过调用Swap()方法来获取参数的类型,所以不需要把泛型类型赋予方法调用。泛型方法可以像非泛型方法那样调用:
int i=4; int j=5; Swap(ref i,ref j)
> 泛型方法示例
- 使用包含Name和Balance属性的Account类
public class Account {
public string Name{get;}
public decimal Balance{get;private set;}
public Account(string name,Decimal balance)
{
Name=name;
Balance=balance;
}
}
decimal amount =Algorithms.AccumulateSimple(accounts); ```