麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 開發設計 > 正文

深入淺出.NET泛型編程

2019-11-17 04:39:44
字體:
來源:轉載
供稿:網友
  前言

  .NET 2.0中泛型的出現是一個令人激動的特征。但是,什么是泛型?你需要它們嗎?你會在自己的應用軟件中使用它們?在本文中,我們將回答這些問題并細致地分析泛型的使用,能力及其局限性。

  類型安全

  .NET中的許多語言如C#,C++和VB.NET(選項strict為on)都是強類型語言。作為一個程序員,當你使用這些語言時,總會期望編譯器進行類型安全的檢查。例如,假如你把對一個Book類型的引用轉換成一個Vehicle型的引用,編譯器將告訴你這樣的cast是無效的。

  然而,當談到.NET 1.0和1.1中的集合時,它們是無助于類型安全的。請考慮一個ArrayList的例子,它擁有一個對象集合--這答應你把任何類型的對象放于該ArrayList中。讓我們看一下例1中的代碼。

  例1.缺乏類型安全的ArrayList

using System;
using System.Collections;
namespace TestApp
{
 class Test
 {
  [STAThread]
  static void Main(string[] args)
  {
   ArrayList list = new ArrayList();
   list.Add(3);
   list.Add(4);
   //list.Add(5.0);
   int total = 0;
   foreach(int val in list)
   {
    total = total + val;
   }
   Console.WriteLine("Total is {0}", total);
  }
 }
}
  本例中,我們建立了一個ArrayList的實例,并把3和4添加給它。然后我循環遍歷該ArrayList,從中取出整型值然后把它們相加。這個程序將產生結果"Total is 7"?,F在,假如我注釋掉下面這句:

list.Add(5.0);
  程序將產生如下的運行時刻異常:

Unhandled Exception: System.InvalidCastException: Specified cast is not valid.
AtTestApp.Test.Main(String[]args)in :/workarea/testapp/class1.cs:line 17
  哪里出錯了呢?記住ArrayList擁有一個集合的對象。當你把3加到ArrayList上時,你已把值3裝箱了。當你循環該列表時,你是把元素拆箱成int型。然而,當你添加值5.0時,你在裝箱一個double型值。在第17行,那個double值被拆箱成一個int型。這就是失敗的原因。

  注重:上面的實例,假如是用VB.NET書寫的話,是不會失敗的。原因在于,VB.NET不使用裝箱機制,它激活一個把該double轉換成整型的方法。但是,假如ArrayList中的值是不能轉換成整型的,VB.NET代碼還會失敗。

  作為一個習慣于使用語言提供的類型安全的程序員,你希望這樣的問題在編譯期間浮出水面,而不是在運行時刻。這正是泛型產生的原因。

  3. 什么是泛型?

  泛型答應你在編譯時間實現類型安全。它們答應你創建一個數據結構而不限于一特定的數據類型。然而,當使用該數據結構時,編譯器保證它使用的類型與類型安全是相一致的。泛型提供了類型安全,但是沒有造成任何性能損失和代碼臃腫。在這方面,它們很類似于C++中的模板,不過它們在實現上是很不同的。

  4. 使用泛型集合

  .NET 2.0的System.Collections.Generics 命名空間包含了泛型集合定義。各種不同的集合/容器類都被"參數化"了。為使用它們,只需簡單地指定參數化的類型即可。請看例2:

  例2.類型安全的泛型列表

List<int> aList = new List<int>();
aList.Add(3);
aList.Add(4);
// aList.Add(5.0);
int total = 0;
foreach(int val in aList)
{
 total = total + val;
}
Console.WriteLine("Total is {0}", total);
  在例2中,我編寫了一個泛型的列表的例子,在尖括號內指定參數類型為int。該代碼的執行將產生結果"Total is 7"?,F在,假如我去掉語句doubleList.Add(5.0)的注釋,我將得到一個編譯錯誤。編譯器指出它不能發送值5.0到方法Add(),因為該方法僅接受int型。不同于例1,這里的代碼實現了類型安全。

  5. CLR對于泛型的支持

  泛型不僅是一個語言級上的特征。.NET CLR能識別出泛型。在這種意義上說,泛型的使用是.NET中最為優秀的特征之一。對每個用于泛型化的類型的參數,類也同樣沒有脫離開微軟中間語言(MSIL)。換句話說,你的配件集僅包含你的參數化的數據結構或類的一個定義,而不管使用多少種不同的類型來表達該參數化的類型。例如,假如你定義一個泛型類型MyList<T>,僅僅該類型的一個定義出現在MSIL中。當程序執行時,不同的類被動態地創建,每個類對應該參數化類型的一種類型。假如你使用MyList<int>和MyList<double>,有兩種類即被創建。當你的程序執行時,讓我們進一步在例3中分析這一點。

  例3.創建一個泛型類

//MyList.cs
#region Using directives
using System;
using System.Collections.Generic;

using System.Text;
#endregion
namespace CLRSupportExample
{
 public class MyList<T>
 {
  PRivate static int objCount = 0;
  public MyList()
  {objCount++; }
  public int Count
  {
   get
    {return objCount; }
  }
 }
}
//Program.cs
#region Using directives
using System;
using System.Collections.Generic;
using System.Text;
#endregion
namespace CLRSupportExample
{
 class SampleClass {}
 class Program
 {
  static void Main(string[] args)
  {
   MyList<int> myIntList = new MyList<int>();
   MyList<int> myIntList2 = new MyList<int>();
   MyList<double> myDoubleList = new MyList<double>();
   MyList<SampleClass> mySampleList = new MyList<SampleClass>();
   Console.WriteLine(myIntList.Count);
   Console.WriteLine(myIntList2.Count);
   Console.WriteLine(myDoubleList.Count);
   Console.WriteLine(mySampleList.Count);
   Console.WriteLine(new MyList<sampleclass>().Count);
   Console.ReadLine();
  }
 }
}
  該例中,我創建了一個稱為MyList泛型類。為把它參數化,我簡單地插入了一個尖括號。在<>內的T代表了實際的當使用該類時要指定的類型。在MyList類中,定義了一個靜態字段objCount。我在構造器中增加它的值。因此我能發現使用我的類的用戶共創建了多少個那種類型的對象。屬性Count返回與被調用的實例同類型的實例的數目。

  在Main()方法,我創建了MyList<int>的兩個實例,一個MyList<double>的實例,還有兩個MyList<SampleClass>的實例--其中SampleClass是我已定義了的類。問題是:Count(上面的程序的輸出)的值該是多少?在你繼閱讀之前,試一試回答這個問題。

  解決了上面的問題?你得到下列的答案了嗎?

2
2
1
1
2
  前面兩個2對應MyList<int>,第一個1對應MyList<double>,第二個1對應MyList<SampleClass>--在此,僅創建一個這種類型的實例。最后一個2對應MyList<SampleClass>,因為代碼中又創建了這種類型的另外一個實例。上面的例子說明MyList<int>是一個與MyList<double>不同的類,而MyList<double>又是一個與MyList<SampleClass>不同的類。因此,在這個例中,我們有四個類:MyList: MyList<T>,MyList<int>,MyList<double>和MyList<X>。注重,雖然有4個MyList類,但僅有一個被存儲在MSIL。怎么能證實這一點?請看圖1顯示出的使用工具ildasm.exe生成的MSIL代碼。


圖 1.例3的MSIL

  6. 泛型方法

  除了有泛型類,你也可以有泛型方法。泛型方法可以是任何類的一部分。讓我們看一下例4:

  例4.一個泛型方法

public class Program
{
 public static void Copy<T>(List<T> source, List<T> destination)
 {
  foreach (T obj in source)
  {
   destination.Add(obj);
  }
 }
 static void Main(string[] args)
 {
  List<int> lst1 = new List<int>();
  lst1.Add(2);
  lst1.Add(4);
  List<int> lst2 = new List<int>();
  Copy(lst1, lst2);
  Console.WriteLine(lst2.Count);
 }
}
  Copy()方法就是一個泛型方法,它與參數化的類型T一起工作。當在Main()中激活Copy()時,編譯器根據提供給Copy()方法的參數確定出要使用的具體類型。

進入討論組討論。
  7. 無限制的類型參數

  假如你創建一個泛型數據結構或類,就象例3中的MyList,注重其中并沒有約束你該使用什么類型來建立參數化類型。然而,這帶來一些限制。如,你不能在參數化類型的實例中使用象==,!=或<等運算符,如:

if (obj1 == obj2) …
  象==和!=這樣的運算符的實現對于值類型和引用類型都是不同的。假如隨意地答應之,代碼的行為可能很出乎你的意料。另外一種限制是缺省構造器的使用。例如,假如你編碼象new T(),會出現一個編譯錯,因為并非所有的類都有一個無參數的構造器。假如你真正編碼象new T()來創建一個對象,或者使用象==和!=這樣的運算符,情況會是怎樣呢?你可以這樣做,但首先要限制可被用于參數化類型的類型。讀者可以自己先考慮如何實現之。


  8. 約束機制及其優點

  一個泛型類答應你寫自己的類而不必拘泥于任何類型,但答應你的類的使用者以后可以指定要使用的具體類型。通過對可能會用于參數化的類型的類型施加約束,這給你的編程帶來很大的靈活性--你可以控制建立你自己的類。讓我們分析一個例子:

  例5.需要約束:代碼不會編譯成功

public static T Max<T>(T op1, T op2)
{
 if (op1.CompareTo(op2) < 0)
  return op1;
 return op2;
}
  例5中的代碼將產生一個編譯錯誤:

Error 1 ’T’ does not contain a definition for ’CompareTo’
  假定我需要這種類型以支持CompareTo()方法的實現。我能夠通過加以約束--為參數化類型指定的類型必須要實現IComparable接口--來指定這一點。例6中的代碼就是這樣:

  例6.指定一個約束

public static T Max<T>(T op1, T op2) where T : IComparable
{
 if (op1.CompareTo(op2) < 0)
  return op1;
 return op2;
}
  在例6中,我指定的約束是,用于參數化類型的類型必須繼續自(實現)Icomparable。下面的約束是可以使用的:

  where T : strUCt 類型必須是一種值類型(struct)

  where T : class 類型必須是一種引用類型(class)

  where T : new() 類型必須有一個無參數的構造器

  where T : class_name 類型可以是class_name或者是它的一個子類

  where T : interface_name 類型必須實現指定的接口

  你可以指定約束的組合,就象: where T : IComparable, new()。這就是說,用于參數化類型的類型必須實現Icomparable接口并且必須有一個無參構造器。

  9. 繼續與泛型

  一個使用參數化類型的泛型類,象MyClass1<T>,稱作開放結構的泛型。一個不使用參數化類型的泛型類,象MyClass1<int>,稱作封閉結構的泛型。

  你可以從一個封閉結構的泛型進行派生;也就是說,你可以從另外一個稱為MyClass1的類派生一個稱為MyClass2的類,就象:

public class MyClass2<T> : MyClass1<int>
  你也可以從一個開放結構的泛型進行派生,假如類型被參數化的話,如:

public class MyClass2<T> : MyClass2<T>
  是有效的,但是

public class MyClass2<T> : MyClass2<Y>
  是無效的,這里Y是一個被參數化的類型。非泛型類可以從一個封閉結構的泛型類進行派生,但是不能從一個開放結構的泛型類派生。即:

public class MyClass : MyClass1<int>
  是有效的, 但是

public class MyClass : MyClass1<T>
  是無效的。

  10. 泛型和可代替性

  當我們使用泛型時,要小心可代替性的情況。假如B繼續自A,那么在使用對象A的地方,可能都會用到對象B。假定我們有一籃子水果(a Basket of Fruits (Basket<Fruit>)),而且有繼續自Fruit的Apple和Banana(皆為Fruit的種類)。一籃子蘋果--Basket of Apples (Basket<apple>)可以繼續自Basket of Fruits (Basket<Fruit>)?答案是否定的,假如我們考慮一下可代替性的話。為什么?請考慮一個a Basket of Fruits可以工作的方法:

public void Package(Basket<Fruit> aBasket)
{
 aBasket.Add(new Apple());
 aBasket.Add(new Banana());
}
  假如發送一個Basket<Fruit>的實例給這個方法,這個方法將添加一個Apple對象和一個Banana對象。然而,發送一個Basket<Apple>的實例給這個方法時,會是什么情形呢?你看,這里布滿技巧。這解釋了為什么下列代碼:

Basket<Apple> anAppleBasket = new Basket<Apple>();
Package(anAppleBasket);
  會產生錯誤:

Error 2 Argument ’1’:
cannot convert from ’TestApp.Basket<testapp.apple>’
to ’TestApp.Basket<testapp.fruit>’
  編譯器通過確保我們不會隨意地傳遞一個集合的派生類(此時需要一個集合的基類),保護了我們的代碼。這不是很好嗎?

  這在上面的例中在成功的,但也存在非凡情形:有時我們確實想傳遞一個集合的派生類,此時需要一個集合的基類。例如,考慮一下Animal(如Monkey),它有一個把Basket<Fruit>作參數的方法Eat,如下所示:

public void Eat(Basket<Fruit> fruits)
{
 foreach (Fruit aFruit in fruits)

 {
  //將吃水果的代碼
 }
}
  現在,你可以調用:

Basket<Fruit> fruitsBasket = new Basket<Fruit>();
… //添加到Basket對象中的對象Fruit
anAnimal.Eat(fruitsBasket);
  假如你有一籃子(a Basket of)Banana-一Basket<Banana>,情況會是如何呢?把一籃子(a Basket of)Banana-一Basket<Banana>發送給Eat方法有意義嗎?在這種情形下,會成功嗎?真是這樣的話,編譯器會給出錯誤信息:

Basket<Banana> bananaBasket = new Basket<Banana>();
//…
anAnimal.Eat(bananaBasket);
  編譯器在此保護了我們的代碼。我們怎樣才能要求編譯器答應這種非凡情形呢?約束機制再一次幫助了我們:

public void Eat<t>(Basket<t> fruits) where T : Fruit
{
 foreach (Fruit aFruit in fruits)
 {
  //將吃水果的代碼
 }
}
  在建立方法Eat()的過程中,我要求編譯器答應一籃子(a Basket of)任何類型T,這里T是Fruit類型或任何繼續自Fruit的類。

進入討論組討論。
  11. 泛型和代理

  代理也可以是泛型化的。這樣就帶來了巨大的靈活性。

  假定我們對寫一個框架程序很感愛好。我們需要提供一種機制給事件源以使之可以與對該事件感愛好的對象進行通訊。我們的框架可能無法控制事件是什么。你可能在處理某種股票價格變化(double price),而我可能在處理水壺中的溫度變化(temperature value),這里Temperature可以是一種具有值、單位、門檻值等信息的對象。那么,怎樣為這些事件定義一接口呢?

  讓我們通過pre-generic代理技術細致地分析一下如何實現這些:

public delegate void NotifyDelegate(Object info);
public interface ISource
{
 event NotifyDelegate NotifyActivity;
}
  我們讓NotifyDelegate接受一個對象。這是我們過去采取的最好措施,因為Object可以用來代表不同類型,如double,Temperature,等等--盡管Object含有因值類型而產生的裝箱的開銷。ISource是一個各種不同的源都會支持的接口。這里的框架展露了NotifyDelegate代理和ISource接口。

  讓我們看兩個不同的源碼:

public class StockPriceSource : ISource
{
 public event NotifyDelegate NotifyActivity;
 //…
}
public class BoilerSource : ISource
{
 public event NotifyDelegate NotifyActivity;
 //…
}
  假如我們各有一個上面每個類的對象,我們將為事件注冊一個處理器,如下所示:

StockPriceSource stockSource = new StockPriceSource();
stockSource.NotifyActivity
+= new NotifyDelegate(stockSource_NotifyActivity);
//這里不必要出現在同一個程序中
BoilerSource boilerSource = new BoilerSource();
boilerSource.NotifyActivity
+= new NotifyDelegate(boilerSource_NotifyActivity);
在代理處理器方法中,我們要做下面一些事情:
對于股票事件處理器,我們有:
void stockSource_NotifyActivity(object info)
{
 double price = (double)info;
 //在使用前downcast需要的類型
}
  溫度事件的處理器看上去會是:

void boilerSource_NotifyActivity(object info)
{
Temperature value = info as Temperature;
//在使用前downcast需要的類型
}
  上面的代碼并不直觀,且因使用downcast而有些凌亂。借助于泛型,代碼將變得更易讀且更輕易使用。讓我們看一下泛型的工作原理:

  下面是代理和接口:

public delegate void NotifyDelegate<t>(T info);
public interface ISource<t>
{
 event NotifyDelegate<t> NotifyActivity;
}
  我們已經參數化了代理和接口?,F在的接口的實現中應該能確定這是一種什么類型。

  Stock的源代碼看上去象這樣:

public class StockPriceSource : ISource<double>
{
 public event NotifyDelegate<double> NotifyActivity;
 //…
}
  而Boiler的源代碼看上去象這樣:

public class BoilerSource : ISource<temperature>
{
 public event NotifyDelegate<temperature> NotifyActivity;
 //…

}
  假如我們各有一個上面每種類的對象,我們將象下面這樣來為事件注冊一處理器:

StockPriceSource stockSource = new StockPriceSource();
stockSource.NotifyActivity += new NotifyDelegate<double>(stockSource_NotifyActivity);
//這里不必要出現在同一個程序中
BoilerSource boilerSource = new BoilerSource();
boilerSource.NotifyActivity += new NotifyDelegate<temperature>(boilerSource_NotifyActivity);
  現在,股票價格的事件處理器會是:

void stockSource_NotifyActivity(double info)
{ //… }
  溫度的事件處理器是:

void boilerSource_NotifyActivity(Temperature info)
{ //… }
  這里的代碼沒有作downcast并且使用的類型是很清楚的。

  12. 泛型與反射

  既然泛型是在CLR級上得到支持的,你可以使用反射API來取得關于泛型的信息。假如你是編程的新手,可能有一件事讓你迷惑:你必須記住既有你寫的泛型類也有在運行時從該泛型類創建的類型。因此,當使用反射API時,你需要另外記住你在使用哪一種類型。我將在例7說明這一點:

  例7.在泛型上的反射

public class MyClass<t> { }
class Program
{
 static void Main(string[] args)
 {
  MyClass<int> obj1 = new MyClass<int>();
  MyClass<double> obj2 = new MyClass<double>();
  Type type1 = obj1.GetType();
  Type type2 = obj2.GetType();
  Console.WriteLine("obj1’s Type");
  Console.WriteLine(type1.FullName);
  Console.WriteLine(type1.GetGenericTypeDefinition().FullName);
  Console.WriteLine("obj2’s Type");
  Console.WriteLine(type2.FullName);
  Console.WriteLine(type2.GetGenericTypeDefinition().FullName);
 }
}
  在本例中,有一個MyClass<int>的實例,程序中要查詢該實例的類名。然后我查詢這種類型的GenericTypeDefinition()。GenericTypeDefinition()會返回MyClass<T>的類型元數據。你可以調用IsGenericTypeDefinition來查詢是否這是一個泛型類型(象MyClass<T>)或者是否已指定它的類型參數(象MyClass<int>)。同樣地,我查詢MyClass<double>的實例的元數據。上面的程序輸出如下:

obj1’s Type
TestApp.MyClass`1
[[System.Int32, mscorlib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089]]
TestApp.MyClass`1
obj2’s Type
TestApp.MyClass`1
[[System.Double, mscorlib, Version=2.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089]]
TestApp.MyClass`1
  可以看到,MyClass<int>和MyClass<double>是屬于mscorlib配件集的類(動態創建的),而類MyClass<t>屬于我自建的配件集。

  13. 泛型的局限性

  至此,我們已了解了泛型的強大威力。是否其也有不足呢?我發現了一處。我希望微軟能夠明確指出泛型存在的這一局制性。在表達約束的時候,我們能指定參數類型必須繼續自一個類。然而,指定參數必須是某種類的基類型該如何呢?為什么要那樣做呢?

  在例4中,我展示了一個Copy()方法,它能夠把一個源List的內容復制到一個目標list中去。我可以象如下方式使用它:

List<Apple> appleList1 = new List<Apple>();
List<Apple> appleList2 = new List<Apple>();

Copy(appleList1, appleList2);
  然而,假如我想要把apple對象從一個列表復制到另一個Fruit列表(Apple繼續自Fruit),情況會如何呢?當然,一個Fruit列表可以容納Apple對象。所以我要這樣編寫代碼:

List<Apple> appleList1 = new List<Apple>();
List<Fruit> fruitsList2 = new List<Fruit>();

Copy(appleList1, fruitsList2);
  這不會成功編譯。你將得到一個錯誤:

Error 1 The type arguments for method
’TestApp.Program.Copy<t>(System.Collections.Generic.List<t>,
System.Collections.Generic.List<t>)’ cannot be inferred from the usage.
  編譯器基于調用參數并不能決定T應該是什么。其實我想說,Copy方法應該接受一個某種數據類型的List作為第一個參數,一個相同類型的List或者它的基類型的List作為第二個參數。

  盡管無法說明一種類型必須是另外一種類型的基類型,但是你可以通過仍然使用約束機制來克服這一限制。下面是這種方法的實現:


public static void Copy<T, E>(List<t> source,
List<e> destination) where T : E
  在此,我已指定類型T必須和E屬同一種類型或者是E的子類型。我們很幸運。為什么?T和E在這里都定義了!我們能夠指定這種約束(然而,C#中并不鼓勵當E也被定義的時候使用E來定義對T的約束)。

  然而,請考慮下列的代碼:

public class MyList<t>
{
 public void CopyTo(MyList<t> destination)
 {
  //…
 }
}
  我應該能夠調用CopyTo:

MyList<apple> appleList = new MyList<apple>();
MyList<apple> appleList2 = new MyList<apple>();
//…
appleList.CopyTo(appleList2);
  我也必須這樣做:

MyList<apple> appleList = new MyList<apple>();
MyList<fruit> fruitList2 = new MyList<fruit>();
//…
appleList.CopyTo(fruitList2);
  這當然不會成功。如何修改呢?我們說,CopyTo()的參數可以是某種類型的MyList或者是這種類型的基類型的MyList。然而,約束機制不答應我們指定一個基類型。下面情況又該如何呢?

public void CopyTo<e>(MyList<e> destination) where T : E
  抱歉,這并不工作。它將給出一個編譯錯誤:

Error 1 ’TestApp.MyList<t>.CopyTo<e>()’ does not define type
parameter ’T’
  當然,你可以把代碼寫成接收任意類型的MyList,然后在代碼中,校驗該類型是可以接收的類型。然而,這把檢查工作推到了運行時刻,丟掉了編譯時類型安全的優點。

  14. 結論

  .NET 2.0中的泛型是強有力的,你寫的代碼不必限定于一特定類型,然而你的代碼卻能具有類型安全性。泛型的實現目標是既提高程序的性能又不造成代碼的臃腫。然而,在它的約束機制存在不足(無法指定一類型必須是另外一種類型的基類型)的同時,該約束機制也給你書寫代碼帶來很大的靈活性,因為你不必拘泥于各種類型的"最小公分母"能力。進入討論組討論。


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 亚洲网站一区 | 成年人免费高清视频 | 欧美综合在线观看 | 国产羞羞网站 | 国产精品久久久久国产精品三级 | 国产99久久久久久免费看 | 久久精品小短片 | 国产喷白浆10p | 亚洲国产精品久久久久制服红楼梦 | 日韩黄a | 国产精品视频成人 | 亚洲精品久久久久久久久久 | 亚洲性一区| 国产免费观看电影网站 | 激情亚洲一区二区三区 | 性日本xxx| 91av资源在线 | 91精品国产日韩91久久久久久360 | 久国久产久精永久网页 | 成人三级电影网 | 中文字幕免费在线看 | 欧美一级网 | 日本在线免费观看 | 久久国产精品免费视频 | 国产在线精品一区二区夜色 | 黄色av电影在线 | 亚洲国产精品99 | 精品国产91久久久久久浪潮蜜月 | 538任你躁在线精品视频网站 | 久草视频免费 | 久久久久久久久久91 | 亚洲午夜天堂吃瓜在线 | 色欧美视频| 美女久久久久 | 麻豆视频在线观看免费网站 | 日本在线一区二区 | 九九久久视频 | 成人综合一区二区 | 娇妻被各种姿势c到高潮小说 | 福利在线免费视频 | 在线观看精品视频 |