|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406 |
- > 泛型概述
- - 泛型不仅是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<T>
- {
- 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<T>:IEnumerable<T>
- {
- 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<int>();
- list2.AddLast(1);
- list2.AddLAst(3);
- list2.AddLAst(5);
-
- foreach(int i in list2)
- {
- WriteLine(i);
- }
- 对于字符串类型使用泛型LinkList<T>,将字符串传递给AddLast()方法。
- var list3=new LinkedList<string>();
- list3.AddLast("2");
- list3.AddLast("four");
- list3.AddLast("foo");
-
- foreach(string s in list3)
- {
- WriteLine(s);
- }
- ```
- - 每个处理对象类型的类都可以有泛型实现方式,另外,如果类使用了层次结构,泛型就非常有助于消除类型强制转换操作
-
-
- > 泛型类的功能
- - 默认值
- - 约束
- - 继承
- - 静态成员
-
- **默认值**
- - 给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;
- }
- ```
-
- **约束**
- - 如果泛型类需要调用泛型类型中的方法,就必须添加约束
- - 对于DocumentManager<T>,文档的所有标题应在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>类显示文档,可以将类型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<TDocument> 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<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>
- {}
- ```
-
- **静态成员**
- - 泛型类的静态成员需要特别关注。泛型类的静态成员只能在类的一个实例中共享。
- ```
- public class StaticDemo<T>
- {
- public static int x;
- }
- 由于同时对一个string类型和一个int类型使用StaticDemo<T>le类,因此存在两组静态字段:
- StaticDemo<string>.x=4;
- StaticDemo<int>.x=5;
- WriteLine(StaticDemo<string>.x);
- ```
-
-
- > 泛型接口的协变
- - 如果泛型类型用out关键字标注,泛型接口就是协变得。这也意味着返回类型只能是T。接口IIndex与类型T是协变得,并从一个只读索引器中返回这个类型
- ```
- public interface IIndex<out T>
- {
- T this[int index]{get;}
- int Count{get;}
- }
- IIndex<T>接口用RectangleCollection类来实现。RectangleCollection类为泛型类型T定义了Rectangle
- ```
-
- >泛型接口的抗变
- - 如果泛型类型用in关键字标注,泛型接口就是抗变。这样,接口只能把泛型类型T用作其方法的输入
- ```
- public interface IDisplay<in T>
- {
- void Show(T item);
- }
- ```
- - ShapeDisplay 类实现IDisplay<Shape>,并使用Shape对象作为输入参数
- ```
- public class ShapeDisplay:IDisplay<Shape>
- {
- public void Show(Shape s)=> WriteLine($"{s.GetType().Name}Width:{s.Width},Height:{s.Height}");
- }
- ```
-
- > 泛型方法
- - 在泛型方法中,泛型类型用方法声明来定义。泛型方法可以在非泛型类中定义.
- - Swap<T>()方法把T定义为泛型类型,该泛型类型用于两个参数和一个变量temp:
- ```
- void Swap<T>(ref T x,ref T y)
- {
- T temp;
- temp=x;
- x=y;
- y=temp;
- }
- 把泛型类型赋予方法调用,就可以调用泛型方法:
- int i=4;
- int j=5;
- Swap<int>(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<Account>类型的账户列表中
- var accounts=new List<ACcount>()
- {
- 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>接口的集合类。在这个方法的实现代码中,直接访问Account对象的Balance属性
- ```
- public static class Algorithms
- {
- public static decimal AccumulateSimple(IEnumrable<Account>source)
- {
- decimal sum=0;
- foreach(Account a in source)
- {
- sum+=a.Balance;
- }
- return sum;
- }
- }
-
- decimal amount =Algorithms.AccumulateSimple(accounts);
- ```
-
-
-
-
-
-
-
|