【讀書筆記】C#高級編程 第五章 泛型

来源:http://www.cnblogs.com/dlxh/archive/2017/03/24/6613513.html
-Advertisement-
Play Games

(一)泛型概述 泛型不僅是C#編程語言的一部分,而且與程式集中的IL代碼緊密地集成。泛型不僅是C#語言的一種結構,而且是CLR定義的。有了泛型就可以創建獨立於被包含類型的類和方法了。 1、性能 泛型的一個主要優點就是性能。對值類型使用非泛型集合類,在把值類型轉化為引用類型,和把引用類型轉換為值類型時 ...


(一)泛型概述

泛型不僅是C#編程語言的一部分,而且與程式集中的IL代碼緊密地集成。泛型不僅是C#語言的一種結構,而且是CLR定義的。有了泛型就可以創建獨立於被包含類型的類和方法了。

1、性能

泛型的一個主要優點就是性能。對值類型使用非泛型集合類,在把值類型轉化為引用類型,和把引用類型轉換為值類型時,需要進行裝箱和拆箱操作。

下麵的例子顯示了System.Collections名稱空間中的ArrayList類。ArrayList存儲對象,Add()方法定義為需要把對象作為參數,所以要裝箱一個整數類型。在讀取ArrayList中的值時,要進行拆箱操作,把對象轉化為整數類型。可以使用類型裝置轉換運算符把ArrayList集合的第一個元素賦予變數i1,在訪問int類型的變數i2foreach語句中,也要使用類型強制轉換運算符:

 

var list = new ArrayList();
list.Add(44);//此處會裝箱
int i1 = (int)list[0];//此處會拆箱
foreach (int i2 in list)
{
    Console.WriteLine(i2);//此處會拆箱
}

 

System.Collections.Generic名稱空間中的List<T>類不使用對象,而是在使用時定義類型。裝箱和拆箱操作很容易,但性能損失比較大,遍歷許多項尤其如此。

下麵的例子。List<T>類的泛型類型定義為int,所以int類型在JIT編譯器動態生成的類中使用,不再進行裝箱和拆箱操作:

var list = new List<int>();
list.Add(44);
int i1 = (int)list[0];
foreach (int i2 in list)
{
    Console.WriteLine(i2);
}

 

 

2、類型安全 

泛型的另一個特性是類型安全。

在泛型類List<T>中,泛型類型T定義了允許使用的類型。有了List<int>的定義,就只能把整數類型添加到集合中。編譯器不會編譯這段代碼,因為Add()方法無效,這樣類型就安全了:

var list = new List<int>();
list.Add(44);
list.Add("str");

這個時候編譯器會報錯:

 

 

 

3、二進位代碼的重用

泛型允許更好地重用二進位代碼。泛型類可以定義一次,並且可以用許多不同的類型實例化。

例如,System.Collections.Generic名稱空間中的List<T>類用一個int、一個字元串和一個MyClass類實例化:

var intList = new List<int>();
intList.Add(1);
 
var stringList = new List<string>();
stringList.Add("str");

var myClassList = new List<MyClass>();
myClassList.Add(new MyClass());

 

4、代碼的擴展 

在不同的特定類型實例化泛型時,會創建多少代碼?因為泛型類的定義會放在程式集中,所以用特定類型實例化泛型類不會再IL代碼中賦值這些類。但是,JIT編譯器把泛型類編譯為本地代碼時,會給每個值類型創建一個新類。引用類型共用同一個本地類的所有相同的實現代碼。這是因為引用類型在實例化的泛型類中只需要4個位元組的記憶體地址(32位系統),就可以引用一個引用類型。值類型包含在實例化的泛型類的記憶體中,同時因為每個值類型對記憶體的要求都不同,所以要為每個值類型實例化一個新類

 

5、命名的約定

在程式中使用泛型,在區分泛型類型和非泛型類型時就會有一定的幫助。下麵是泛型類型的命名規則:

泛型類型的名稱用字母T作為首碼。

如果沒有特殊的要求,泛型類型允許使用任意類替代,且只使用了一個泛型類型,就可以用字元T作為泛型類型的名稱。

public class List<T> { }

public class LinkedList<T> { }

如果泛型類型有特定的要求(例如,它必須實現一個介面或派生自基類),或者使用了兩個或多個泛型類型,就應給泛型類型使用描述性的名稱: 

public class SortedList<TKey, TValue> { }

 

(二)創建泛型類

泛型提供了一種新的創建類型的機制,使用泛型創建的類型將帶有類型形參。每個處理對象類型的類都可以有泛型實現方式。另外如果類使用了層次結構就非常有助於消除類型強制轉換操作。

public class LinkedListNode<T>
{
    public LinkedListNode(T value)
    {
        this.Value = value;
    }
    public T Value { get; private set; }
    public LinkedListNode<T> Next { get; internal set; }
    public LinkedListNode<T> Prev { get; internal set; }
}

 

 

(三)泛型類的功能

1、預設值

通過default關鍵字,將null賦予引用類型,將0賦予值類型。

 

public T GetDocument()
{
    T doc = default(T);
    return DealDocument(doc);
}

 

default關鍵字根據上下文可以有多種含義。switch語句使用default定義預設情況。在泛型中,根據泛型類型是引用類型還是值類型,泛型default用於將泛型類型初始化為null0

 

2、約束

在定義泛型類時,可以對客戶端代碼能夠在實例化類時用於類型參數的類型種類施加限制。如果客戶端代碼嘗試使用某個約束所不允許的類型來實例化類,則會產生編譯時錯誤。這些限制稱為約束。約束是使用 where 上下文關鍵字指定的。下表列出了六種類型的約束:

約束

說明

where T:struct

類型參數必須是值類型。可以指定除 Nullable 以外的任何值類型。

where T:Class

類型參數必須是引用類型,包括任何類、介面、委托或數組類型。

where T:new()

類型參數必須具有無參數的公共構造函數。當與其他約束一起使用時,new() 約束必須最後指定。

where T:<基類名>

類型參數必須是指定的基類或派生自指定的基類。

where T:<介面名稱>

類型參數必須是指定的介面或實現指定的介面。可以指定多個介面約束。約束介面也可以是泛型的。

where T1:T2

T1 提供的類型參數必須是為 T2 提供的參數或派生自為 T2 提供的參數。這稱為裸類型約束。

使用約束的原因

如果要檢查泛型列表中的某個項以確定它是否有效,或者將它與其他某個項進行比較,則編譯器必須在一定程度上保證它需要調用的運算符或方法將受到客戶端代碼可能指定的任何類型參數的支持。這種保證是通過對泛型類定義應用一個或多個約束獲得的。例如,基類約束告訴編譯器:僅此類型的對象或從此類型派生的對象才可用作類型參數。一旦編譯器有了這個保證,它就能夠允許在泛型類中調用該類型的方法。約束是使用上下文關鍵字 where 應用的。

使用泛型類型還可以合併多個約束:

public class MyClass<T> where T : IClass, new() { }

 

3、繼承

泛型類型可以實現泛型介面,也可以派生自一個類(要求是必須重覆介面或基類的泛型類型):

public class LinkedList<T> : IEnumerable<T> { }

public class MyClass<T> : MyBaseClass<T> { }

派生類也可以是泛型或非泛型的,其要求是必須制定基類的類型

public class IntClass : MyBaseClass<int> { }

 

4、靜態成員

泛型類的靜態成員只能在類的一個實例中共用:

public class StaticDemo<T>
{
    public static int x;
}

static void Main(string[] args)
{
    StaticDemo<string>.x = 1;
    StaticDemo<int>.x = 2;
    Console.WriteLine(StaticDemo<string>.x);
    Console.WriteLine(StaticDemo<int>.x);
    Console.ReadKey();
}

 運行以上代碼,結果如下:

T類型不同時靜態成員不共用。

 

(四)泛型介面

使用泛型可以定義介面,在泛型介面中定義的方法可以帶泛型參數。

public interface IDeal<T>
{
    T Deal(T value);
}

 

協變和抗變指對參數和返回值類型進行轉換。在.NET中參數類型時協變的,返回類型是抗變的。

 

1、協變和抗變

例子:

參數類型的協變

static void Main(string[] args)
{
    string str = "測試參數的協變";
    Show(str);
    Console.ReadKey();
}

public static void Show(object value)
{
    Console.WriteLine(value);
}

返回類型的抗變

例子:

static void Main(string[] args)
{
    int value = 1;
    string str = ConvertToString(value);
    Console.ReadKey();
}

public static string ConvertToString(object value)
{
    return value.ToString();
}

 

2、泛型介面的協變 

如果泛型類型用out關鍵字進行標註,泛型介面就是協變的。(子到父是協變)

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         IFactory<Chinese> ChineseFactory = new Factory<Chinese>();
 6         IFactory<People> PeopleFactory = ChineseFactory; //協變
 7         People People = PeopleFactory.Create();
 8         Console.ReadKey();
 9     }
10 }
11 public class Chinese : People { }
12 public class People { }
13 public class Factory<T> : IFactory<T>
14 {
15     public T Create()
16     {
17         return (T)Activator.CreateInstance<T>();
18     }
19 }
20 public interface IFactory<out T>
21 {
22     T Create();
23 }

 

3、泛型介面的抗變

如果泛型類型用in關鍵字標註,泛型介面就是抗變的。

 1 class Program
 2 {
 3     static void Main(string[] args)
 4     {
 5         IShow<People> ps = new PeopleShow();
 6         IShow<Chinese> cs = ps;//抗變
 7         Console.ReadKey();
 8     }
 9 }
10 public interface IShow<in T>
11 {
12     void Write(T t);
13 }
14 public class Chinese : People { }
15 public class People
16 {
17     public string Name { get; set; }
18 }
19 public class PeopleShow : IShow<People>
20 {
21     public void Write(People t)
22     {
23         Console.WriteLine("我的名字:" + t.Name + ",現在我表演寫字!");
24     }
25 }

 

 

(五)泛型結構

與類相似,結構也可以是泛型的。它們非常類似於泛型類,只是沒有繼承特性。.NET Framework中的一個泛型結構是Nullable<T>,結構Nullable<T>定義了一個約束:其中泛型類型T必須是一個結構。

因為可空類型使用得非常頻繁,所以C#有一種特殊的語法,它用於定義可空類型的變數。定義這類變數時,不使用泛型結構的語法,而是用“?”運算符。

int? x;

可以使用合併運算符從可空類型轉換為非可空類型。合併運算符??

int? x = null;

int y = x ?? 0;

 y的值顯示為0,因為xnull

 

 

(六)泛型方法

除了定義泛型類之外,還可以定義泛型方法。在泛型方法中,泛型類型用方法聲明來定義。泛型方法可以在非泛型類中定義。

public T ReviseName<T>(T person) where T : People
{
    person.Name = "改名後的:" + person.Name;
    return person;
}

C#編譯器會通過泛型方法來獲取參數類型,所以不需要把泛型類型賦予方法的調用。

Chinese chinese = new Chinese();
chinese.Name = "張三";
Chinese.ReviseName(chinese);

泛型方法可以像泛型方法那樣調用。

 

1、泛型方法示例

 1 class Program
 2 
 3 {
 4 
 5     static void Main(string[] args)
 6 
 7     {
 8 
 9         var accounts = new List<Account>()
10 
11         {
12 
13             new Account("張三",2000),
14 
15             new Account("李四",1300),
16 
17             new Account("王麻子",800),
18 
19             new Account("趙六",1000)
20 
21         };
22 
23         decimal total = AccumulateSimple(accounts);
24 
25         Console.ReadKey();
26 
27     }
28 
29     public static decimal AccumulateSimple(IEnumerable<Account> source)
30 
31     {
32 
33         decimal sum = 0;
34 
35         foreach (var item in source)
36 
37         {
38 
39             sum += item.Balance;
40 
41         }
42 
43         return sum;
44 
45     }
46 
47 }
48 
49 public class Account
50 
51 {
52 
53     public string Name { get; set; }
54 
55     public decimal Balance { get; set; }
56 
57     public Account(string name, Decimal balance)
58 
59     {
60 
61         this.Name = name;
62 
63         this.Balance = balance;
64 
65     }
66 
67 }

 

2、帶約束的泛型方法

public T ReviseName<T>(T person) where T : People
{
    person.Name = "改名後的:" + person.Name;
    return person;
}

 因為方法需要使用T參數的Name屬性,所以為了確保程式不拋出異常,對T參數進行約束,使其必須繼承自People類。

 

3、帶委托的泛型方法

 1 static void Main(string[] args)
 2 {
 3     int t1 = 1;
 4     int t2 = 9;
 5     Console.WriteLine(Cal(t1, t2, (i1, i2) =>
 6     {
 7         return i1 + i2;
 8     }));
 9     Console.ReadKey();
10 }
11 
12 public static T Cal<T>(T t1, T t2, Func<T, T, T> calMethod)
13 {
14     return calMethod(t1, t2);
15 }

  定義Cal方法,參數t1t2calMethod方法的參數(Func是內置的委托)。

 

4、泛型方法規範

泛型方法可以重載,為特定的類型定義規範。在編譯期間會使用最佳匹配。

 1 static void Main(string[] args)
 2 {
 3     string str = "str";
 4     int i = 0;
 5     First(str);
 6     First(i);
 7     Console.ReadKey();
 8 }
 9 
10 public static void First<T>(T obj)
11 {
12     Console.WriteLine("obj:"+obj.GetType().Name);
13 }
14 
15 public static void First(int obj)
16 {
17     Console.WriteLine("int");
18 }

 運行以上代碼,結果如下:

需要註意的是,所調用的方法是在編譯期間定義的,而不是運行期間。

static void Main(string[] args)
{
    int i = 0;
    Second(i);
    Console.ReadKey();
}

public static void Second<T>(T obj)
{
    First(obj);
}

 運行以上代碼,結果如下:

 

 

 

(七)小結

本章介紹了CLR中一個非常重要的特性:泛型。通過泛型類可以創建獨立於類型的類,泛型方法是獨立於類型的方法。介面、結構和委托也可以用泛型的方式創建。泛型引入了一種新的編程方式。我們介紹瞭如何實現相應的演算法(尤其是操作和謂詞)以用於不同的類,而且它們都是類型安全的。泛型委托可以去除集合中的演算法。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.VisualStudio2017設置版權 a 在團隊開發或者公司開發中,我們一般都喜歡給自己所創建的類或者介面以及其它模板設置版權說明,但是每個類一個一個的去加又是非常的費勁,所以一般情況下我們都是設置模板來實現它,當您在VS中創建類或者介面等的時候自動將這些註釋添加到您所在的類或者介面中。 b ...
  • 項目中的代碼洋洋灑灑寫了很多,最近回過頭來看看,能精簡的地方太多了。WPF MVVM是個非常實用的模式。但前提是控制項需要支持。等等,還有不支持binding的控制項麽?基礎的控制項當然不在此列,然而實踐中常常會遇到需要組合的控制項,將一組控制項放在一起完成一個基本功能。還有控制項需要根據不同的情況改變顯示形式 ...
  • 緊跟上一篇文章。通過路由和動作匹配後,最終會得到跟當前請求最匹配的一個ActionDescriptor,然後通過IActionInvoker執行動作。 我們先來看一下IActionInvoker如何得到,代碼如下: 從上面的代碼可以看到,一個IActionInvoker是通過IActionInvok ...
  • 這個問題一開始覺得還是挺簡單的,網上也看到不少解決方案。 首先一個最簡單最直接的方案就是自定義一個名為FinalValue的依賴屬性。隨後重載OnThumbDragCompleted函數,在Thumb控制項完成拖動時改寫FinalValue。代碼如下 試著跑一下,似乎搞定了。等一下,左右鍵怎麼不起作用 ...
  • 原網頁 http://www.web-jia.com/view.php?a=10 今天在開發中遇到個bug 我在datatable中添加數據的時候報錯了我傳了大於int32的數字就出錯了 datatable中的用columns.add("test", SqlDbType.BigInt.GetType ...
  • using System.Data;using System.Web;using System.Web.UI;using System.Web.UI.WebControls;using System.IO;using System.Text; namespace DotNet.Utilities{ ...
  • using System; using System.Collections.Generic; using System.Text; using Microsoft.Office.Interop.Word; using System.Windows.Forms; using System.IO; u... ...
  • using System.Threading; using System; namespace ConsoleApplication4 { public class Program { static void Main(string[] args) { try { St... ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...