鼎鼎知识库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

21.泛型.md 13KB

泛型概述

  • 泛型不仅是C#编程语言的一部分,而且与程序集中的IL代码紧密地集成。有了泛型,就可以创建独立于被包含类型的类和方法。 泛型的优点
  • 性能
  • 类型安全性
  • 二进制代码重用
  • 代码的扩展
  • 命名约定 性能
  • 对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作。
  • 值类型存储在栈上,引用类型存储在堆上。C#类时引用类型,结构是值类型。.NET很容易把值类型转换为引用类型,所以可以在需要对象(对象是引用类型)的任意地方使用值类型。
  • 从值类型转换为引用类型称为装箱。

命名约定

  • 泛型类型的命名规则
    • 泛型类型的名称用字母T作为前缀
    • 如果没有特殊要求,泛型类型允许用任意类替代,且只使用一个泛型类型,就可以用字符T作为泛型类型的名称。 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);

} ```

  • 每个处理对象类型的类都可以有泛型实现方式,另外,如果类使用了层次结构,泛型就非常有助于消除类型强制转换操作

泛型类的功能

  • 默认值
  • 约束
  • 继承
  • 静态成员

默认值

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

}

  • 其中应累加余额的所有账户操作都添加到List类型的账户列表中 var accounts=new List() { new Account(“Christian”,1500), new Account(“Stephanie”,2200), new Account(“Angela”,1800), new Account(“Matthias”,2400) }; ```
  • 累加所有Account对象的传统方式是用foreach语句遍历所有的Account对象。
  • 如下所示。foreach语句使用IEnumerable接口迭代集合的元素,所以AccumulateSimple()方法的参数是IEnumerable类型。foreach语句处理实现IEnumerable接口的每个对象。这样,AccumulateSimple()方法就可以用于所有实现IEnumerable接口的集合类。在这个方法的实现代码中,直接访问Account对象的Balance属性 ``` public static class Algorithms { public static decimal AccumulateSimple(IEnumrablesource) { decimal sum=0; foreach(Account a in source) { sum+=a.Balance; } return sum; } }

decimal amount =Algorithms.AccumulateSimple(accounts); ```