鼎鼎知识库
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

5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. > 泛型概述
  2. - 泛型不仅是C#编程语言的一部分,而且与程序集中的IL代码紧密地集成。有了泛型,就可以创建独立于被包含类型的类和方法。
  3. **泛型的优点**
  4. - 性能
  5. - 类型安全性
  6. - 二进制代码重用
  7. - 代码的扩展
  8. - 命名约定
  9. **性能**
  10. - 对值类型使用非泛型集合类,在把值类型转换为引用类型,和把引用类型转换为值类型时,需要进行装箱和拆箱操作。
  11. - 值类型存储在栈上,引用类型存储在堆上。C#类时引用类型,结构是值类型。.NET很容易把值类型转换为引用类型,所以可以在需要对象(对象是引用类型)的任意地方使用值类型。
  12. - 从值类型转换为引用类型称为装箱。
  13. **命名约定**
  14. - 泛型类型的命名规则
  15. - 泛型类型的名称用字母T作为前缀
  16. - 如果没有特殊要求,泛型类型允许用任意类替代,且只使用一个泛型类型,就可以用字符T作为泛型类型的名称。
  17. ```
  18. public class List<T>{}
  19. public class LinkedList<T>{}
  20. ```
  21. - 如果泛型类型有特定的要求,或者使用了两个或多个泛型类型,就应给泛型类型使用描述性的名称:
  22. ```
  23. public delegate void EventHandle<TEventArgs>(object sender,TEventArgs e);
  24. public delegate TOutput Converter<TInput,TOutput>(TInput from);
  25. public class SortedList<TKey,TValue>{}
  26. ```
  27. > 创建泛型类
  28. - 在链表中,一个元素引用下一个元素。所以必须创建一个类,它将对象封装在链表中,并引用下一个对象。
  29. - 类LinkedListNode包含一个属性value,该属性用构造函数初始化。另外,LinkedListNode类包含对链表中下一个元素和上一个元素的引用,这些元素都可以从属性中访问。
  30. ```
  31. public class LinkedListNode
  32. {
  33. public LinkedListNode(object value)
  34. {
  35. Value=value;
  36. }
  37. public object Value{get;private set;}
  38. public LinkedListNode Next{get;internal set;}
  39. public LinkedListNode Prev{get;internal set;}
  40. }
  41. ```
  42. - LinkedList类包含LinkedListNode类型的First和Last属性,它们分别标记了链表的头尾。AddLast()方法在链表尾添加一个新元素。首页创建一个LinkedListNode类型的对象。如果链表是空的,First和Last属性就设置为该新元素;否则,就把新元素添加为链表中的最后一个元素。通过实现GetEnumerator()方法,可以用foreach语句遍历链表。GetEnumerator()方法使用yield语句创建一个枚举器类型。
  43. ```
  44. public class LinkedList:IEnumerable
  45. {
  46. public LinkedListNode First{get;private set;}
  47. public LinkedListNode Last{get;private set;}
  48. public LinkedListNode AddLast(object node)
  49. {
  50. var newNode=new LinkedListNode(node);
  51. if(First==null)
  52. {
  53. First=newNode;
  54. Last=First;
  55. }
  56. else
  57. {
  58. LinkedListNode previous=Last;
  59. Last.Next=newNode;
  60. Last=newNode;
  61. Last.Prev=previous;
  62. }
  63. return newNode;
  64. }
  65. public IEnumerator GrtEnumerator()
  66. {
  67. LinkedListNode current =First;
  68. while(current!=null)
  69. {
  70. yield return current.Value;
  71. current=current.Next;
  72. }
  73. }
  74. }
  75. ```
  76. > 创建链表的泛型版本
  77. - LinkedListNode 类用一个泛型类型T声明。属性Value的类型是T,而不是object。构造函数也变为可以接受T类型的对象。也可以返回和设置泛型类型,所以属性Next和Prev的类型是LinkedLisTNode<T>
  78. ```
  79. public class LinkedListNode<T>
  80. {
  81. public LinkedListNode(T value)
  82. {
  83. Value=value;
  84. }
  85. public T Value{get;private set;}
  86. public LinkedListNode<T> Next{get;internal set;}
  87. public LinkedListNode<T> Prev{get;internal set;}
  88. }
  89. public class LinkedList<T>:IEnumerable<T>
  90. {
  91. public LinkedListNode<T>Frist{get;private set;}
  92. public LinkedListNode<T>Last{get;private set;}
  93. public LinkedListNOde<T>AddLast(T node)
  94. {
  95. var newNode=new LinkedListNode<T>(node);
  96. if(First==null)
  97. {
  98. First=newNode;
  99. Last=First;
  100. }
  101. else
  102. {
  103. LinkedListNode<T>previous=Last;
  104. LAst.Next=newNode;
  105. Lsat=newNode;
  106. Last.Prev=previous;
  107. }
  108. return newNode;
  109. }
  110. public IEnumerator<T> GetEnumerator()
  111. {
  112. LinkedListNode<T>current=First;
  113. while(current!=null)
  114. {
  115. yield return current.Value;
  116. current=current.Next;
  117. }
  118. }
  119. IEnumerator IEnumerable.GetEnumerator()=>GetEnumerator();
  120. }
  121. ```
  122. - 使用泛型LinkedList<T>,可以用int类型实例化它,且无需装箱操作。如果不使用AddLast()去传递int,就会出现一个编译器错误。使用泛型IEnumerable<T>,foreach语句也是类型安全的,如果foreach语句中的变量不是int,就会出现一个编译器错误
  123. ```
  124. var list2=new LinkedList<int>();
  125. list2.AddLast(1);
  126. list2.AddLAst(3);
  127. list2.AddLAst(5);
  128. foreach(int i in list2)
  129. {
  130. WriteLine(i);
  131. }
  132. 对于字符串类型使用泛型LinkList<T>,将字符串传递给AddLast()方法。
  133. var list3=new LinkedList<string>();
  134. list3.AddLast("2");
  135. list3.AddLast("four");
  136. list3.AddLast("foo");
  137. foreach(string s in list3)
  138. {
  139. WriteLine(s);
  140. }
  141. ```
  142. - 每个处理对象类型的类都可以有泛型实现方式,另外,如果类使用了层次结构,泛型就非常有助于消除类型强制转换操作
  143. > 泛型类的功能
  144. - 默认值
  145. - 约束
  146. - 继承
  147. - 静态成员
  148. **默认值**
  149. - 给DocumentManager<T>类添加一个GetDocument()方法。在这个方法中,应把类型T指定为null。但是,不能把null赋予泛型类型。原因是泛型类型也可以实例化值类型,而null只能用于引用类型。为了解决这个问题,可以使用default关键字。通过default关键字,将null赋予引用类型,将0赋予值类型。
  150. ```
  151. public T GetDocument()
  152. {
  153. T doc =default(T);
  154. lock(this)
  155. {
  156. doc=documentQueue.Dequeue();
  157. }
  158. return doc;
  159. }
  160. ```
  161. **约束**
  162. - 如果泛型类需要调用泛型类型中的方法,就必须添加约束
  163. - 对于DocumentManager<T>,文档的所有标题应在DisplayAllDocuments()方法中显示。Document类实现带有Title和Content属性的IDocument接口
  164. ```
  165. public interface IDocument
  166. {
  167. string Title{get;set;}
  168. string Content{get;set;}
  169. }
  170. public class Document:IDocument
  171. {
  172. public Document()
  173. {
  174. }
  175. public Document(string title,string content)
  176. {
  177. Title=title;
  178. Content=content;
  179. }
  180. public string Title{get;ste;}
  181. public string Content{get;set;}
  182. }
  183. - 要使用DocumentManager<T>类显示文档,可以将类型T强制转换为IDocument接口,以显示标题
  184. public void DisplayAllDocuments()
  185. {
  186. foreach(T doc in documentQueue)
  187. {
  188. WriteLine(((IDocument)doc).Title);
  189. }
  190. }
  191. ```
  192. - 如果类型T没有实现IDocument接口,这个类型强制转换就会导致一个运行时异常。最好给DocumentManager<TDocument>类定义一个约束:TDocument类型必须实现IDocument接口。
  193. - 为了在泛型类型的名称中制定该要求,将T改为TDocument。where子句指定了实现IDocument接口的需求
  194. ```
  195. public class DocumentManager<TDocument> where TDocument:IDocument
  196. {
  197. public void DisplayAllDocuments()
  198. {
  199. foreach (TDocument doc in documentQueue)
  200. {
  201. WriteLine(doc.Title);
  202. }
  203. }
  204. }
  205. ```
  206. - 在main()方法中,用Document类型实例化DocumentManager<TDocument>类,而Document类型实现了需要的IDocument接口。接着添加和显示新文档,检索其中一个文档
  207. ```
  208. public static void Main()
  209. {
  210. var dm=new DocumentManager<Document>();
  211. dm.AddDocument(new Document("Title A","Sample A"));
  212. dm.AddDocument(new Document("Title B","Sample B"));
  213. dm.DisplayAllDocuments();
  214. if(dm.IsDocumentAvailable)
  215. {
  216. Document d=dm.GetDocument();
  217. WriteLine(d.Content);
  218. }
  219. }
  220. DocumentMAnager现在可以处理任何实现了IDocument接口的类。
  221. ```
  222. - where T:struct :对于结构约束,类型T必须是值类型
  223. - where T:class :类约束指定类型T必须是引用类型
  224. - where T:IFoo :指定类型T必须实现接口IFoo
  225. - where T:Foo :指定类型T必须派生自基类Foo
  226. - where T:new() :这是一个构造函数约束,指定类型T必须有一个默认构造函数
  227. - where T1:T2 :这个约束也可以指定,类型T1派生自泛型类型T2
  228. **继承**
  229. 前面创建的LinkedList<T>类实现了IEnumerable<T>接口
  230. ```
  231. public class LinkedList<T>:IEnumerable<T>
  232. {
  233. //泛型类型可以实现泛型接口,也可以派生自一个类。泛型类可以派生自泛型基类
  234. }
  235. public class Base<T>
  236. {
  237. }
  238. public class Derived<T>:Base<T>
  239. {
  240. }
  241. 其要求是必须重复接口的泛型类型,或者必须指定基类的类型
  242. public class Base<T>
  243. {
  244. }
  245. public class Derived<T>:Base<string>
  246. {}
  247. ```
  248. - 派生类可以是泛型类或非泛型类。例如,可以定义一个抽象的泛型基类,它在派生类中用一个具体的类实现。这允许对特定类型执行特殊的操作
  249. ```
  250. public abstract class Calc<T>
  251. {
  252. public abstract T Add(T x,T y);
  253. public abstract T Sub(T x,T y);
  254. }
  255. public class IntCalc:Calc<int>
  256. {
  257. public override int Add(int x,int y)=>x+y;
  258. public override int Sub(int x,int y)=>x-y;
  259. }
  260. ```
  261. 还可以创建一个部分的特殊操作,如从Query中派生StringQuery类,只定义一个泛型参数,如字符串TResult。要实例化StringQuery,只需要提供TRequest
  262. ```
  263. public class Query<TRequest,TResult>
  264. {}
  265. public StringQuery<TRequest>:Query<TRequest,string>
  266. {}
  267. ```
  268. **静态成员**
  269. - 泛型类的静态成员需要特别关注。泛型类的静态成员只能在类的一个实例中共享。
  270. ```
  271. public class StaticDemo<T>
  272. {
  273. public static int x;
  274. }
  275. 由于同时对一个string类型和一个int类型使用StaticDemo<T>le类,因此存在两组静态字段:
  276. StaticDemo<string>.x=4;
  277. StaticDemo<int>.x=5;
  278. WriteLine(StaticDemo<string>.x);
  279. ```
  280. > 泛型接口的协变
  281. - 如果泛型类型用out关键字标注,泛型接口就是协变得。这也意味着返回类型只能是T。接口IIndex与类型T是协变得,并从一个只读索引器中返回这个类型
  282. ```
  283. public interface IIndex<out T>
  284. {
  285. T this[int index]{get;}
  286. int Count{get;}
  287. }
  288. IIndex<T>接口用RectangleCollection类来实现。RectangleCollection类为泛型类型T定义了Rectangle
  289. ```
  290. >泛型接口的抗变
  291. - 如果泛型类型用in关键字标注,泛型接口就是抗变。这样,接口只能把泛型类型T用作其方法的输入
  292. ```
  293. public interface IDisplay<in T>
  294. {
  295. void Show(T item);
  296. }
  297. ```
  298. - ShapeDisplay 类实现IDisplay<Shape>,并使用Shape对象作为输入参数
  299. ```
  300. public class ShapeDisplay:IDisplay<Shape>
  301. {
  302. public void Show(Shape s)=> WriteLine($"{s.GetType().Name}Width:{s.Width},Height:{s.Height}");
  303. }
  304. ```
  305. > 泛型方法
  306. - 在泛型方法中,泛型类型用方法声明来定义。泛型方法可以在非泛型类中定义.
  307. - Swap<T>()方法把T定义为泛型类型,该泛型类型用于两个参数和一个变量temp:
  308. ```
  309. void Swap<T>(ref T x,ref T y)
  310. {
  311. T temp;
  312. temp=x;
  313. x=y;
  314. y=temp;
  315. }
  316. 把泛型类型赋予方法调用,就可以调用泛型方法:
  317. int i=4;
  318. int j=5;
  319. Swap<int>(ref i,ref j);
  320. ```
  321. 但是,因为C#编译器会通过调用Swap()方法来获取参数的类型,所以不需要把泛型类型赋予方法调用。泛型方法可以像非泛型方法那样调用:
  322. ```
  323. int i=4;
  324. int j=5;
  325. Swap(ref i,ref j)
  326. ```
  327. > 泛型方法示例
  328. - 使用包含Name和Balance属性的Account类
  329. ```
  330. public class Account
  331. {
  332. public string Name{get;}
  333. public decimal Balance{get;private set;}
  334. public Account(string name,Decimal balance)
  335. {
  336. Name=name;
  337. Balance=balance;
  338. }
  339. }
  340. - 其中应累加余额的所有账户操作都添加到List<Account>类型的账户列表中
  341. var accounts=new List<ACcount>()
  342. {
  343. new Account("Christian",1500),
  344. new Account("Stephanie",2200),
  345. new Account("Angela",1800),
  346. new Account("Matthias",2400)
  347. };
  348. ```
  349. - 累加所有Account对象的传统方式是用foreach语句遍历所有的Account对象。
  350. - 如下所示。foreach语句使用IEnumerable接口迭代集合的元素,所以AccumulateSimple()方法的参数是IEnumerable类型。foreach语句处理实现IEnumerable接口的每个对象。这样,AccumulateSimple()方法就可以用于所有实现IEnumerable<Account>接口的集合类。在这个方法的实现代码中,直接访问Account对象的Balance属性
  351. ```
  352. public static class Algorithms
  353. {
  354. public static decimal AccumulateSimple(IEnumrable<Account>source)
  355. {
  356. decimal sum=0;
  357. foreach(Account a in source)
  358. {
  359. sum+=a.Balance;
  360. }
  361. return sum;
  362. }
  363. }
  364. decimal amount =Algorithms.AccumulateSimple(accounts);
  365. ```