1. 程式人生 > 程式設計 >詳解c# 泛型類的功能

詳解c# 泛型類的功能

  在泛型類中,由於不知道泛型引數T是什麼型別,可能是引用型別,也可能是值型別,因此不能將null等賦予泛型型別。如何對泛型物件賦初值、如何保證泛型的正確性等,以使用泛型文件管理器為例:

  文件管理器用於從佇列中讀寫文件。首先建立一個泛型管理器AddDocument()方法新增一個文件到佇列中,IsDocumentAvailabe只讀屬性指示佇列中是否還有文件。

public class DocumentManager<T>
{
  private readonly Queue<T> documentQueue = new Queue<T>();

  public void AddDocument(T doc)
  {
    lock (this)
    {
      documentQueue.Enqueue(doc);
    }
  }

  public bool IsDocumentAvailable
  {
    get { return documentQueue.Count > 0; }
  }
}

1、預設值

  給DocumentManager<T>類新增一個GetDocument()方法,該方法以返回佇列中的一個文件。如果佇列中存在文件,則返回一個文件;如果佇列中已沒有文件,則返回預設值。但是,對於泛型T,不能將null賦予T的物件,因為無法確定它是引用型別還是值型別。在C#中,為我們提供了一個default關鍵字,泛型T的物件賦予預設值,如:引用型別為null、值型別int等為0……

public T GetDocument()
{
  T doc = default(T);
  lock (this)
  {
    if (documentQueue.Count > 0)
    {
      doc = documentQueue.Dequeue();
    }
  }
  return doc;
}

2、約束

  如果泛型類需要呼叫泛型型別中的方法,那麼必須對泛型新增約束。否則,不能確保宣告的泛型型別實現了對應的型別,具有相關方法。建立文件類Document,其實現了介面IDocument:

public interface IDocument
{
  string Title { get; set; }
  string Content { get; set; }
}

public class Document : IDocument
{
  public Document()
  {
  }

  public Document(string title,string content)
  {
    this.Title = title;
    this.Content = content;
  }

  public string Title { get; set; }
  public string Content { get; set; }
}

  給泛型文件管理器DocumentManager<T>新增方法DisplayAllDocuments(),使得佇列中所有文件的標題能展示出。在展示文件標題前,將型別T強制轉換為IDocumnet介面,以顯示標題:

public void DisplayAllDocuments()
{
  foreach (T doc in documentQueue)
  {
    Console.WriteLine((doc as IDocument).Title);//強制轉換
  }
}

  但是,如果型別T沒有實現介面IDocument,在對型別進行強制轉換時就會出現一個異常。如果對方法新增rty……catch處理,將非常損耗效能。同樣的,即使型別實現了介面IDocument,在進行轉換時也會出現效能的損耗。

  那麼,如果能對泛型TDocument進行約束,使得泛型型別必須實現介面IDocument,則不會出現對型別進行強制轉換時的異常。甚至不需要強制轉換,效能也將得到優化。因此,前面的泛型文件管理器改寫為(前面的T,改寫為TDocument,以此暗示是文件型別):

public class DocumentManager<TDocument>
  where TDocument : IDocument
{
   //……   
}

  對於實現了約束的泛型文件管理器,可以處理任何實現了IDocument介面的類。其DisplayAllDocuments()方法改寫為:

public void DisplayAllDocuments()
{
  foreach (TDocument doc in documentQueue)
  {
    Console.WriteLine(doc.Title);
  }
}

  在其他地方呼叫時,可以用Document型別例項化泛型型別DocumentManager<TDocument>。因為Document實現了介面IDocument:

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();
    Console.WriteLine(d.Content);
  }
}

  泛型型別支援的幾種約束:struct(結構約束,型別T必須是值型別)、class(類約束,型別T必須是引用型別)、IFoo(型別T必須實現介面IFoo)、new()(建構函式約束,型別T必須有一個無參建構函式)、TOther(型別T派生自TOther,也稱“裸型別約束”)。

  泛型約束中:

  • 只能為無參建構函式定義構造約束,不能為有任何引數的建構函式定義建構函式約束。
  • 泛型可以有多個約束。如:public class DocumentManager<TDocument> where TDocument : IDocument,new()。
  • where不能定義必須由泛型型別實現的運算子

3、繼承

  泛型類也可以實現繼承,如Queue<T>裡,繼承實現了介面IEnumerable<T>介面。泛型型別可以實現泛型介面,也可以派生自一個類。泛型型別可以派生自泛型基類:

class Base<T>
{
  //...............
}

class Derived<T>:Base<T>
{
  //...............
}

  派生類可以是泛型類,也可以是非泛型型別:

abstract class Calc<T>
{
  public abstract T Add(T x,T y);
  public abstract T Sub(T x,T y);
}

class IncCalc: Calc<int>
{
  public override int Add(int x,int y)
  {
    return x + y;
  }
  public override int Sub(int x,int y)
  {
    return x - y;
  }
}
class DoubleCalc : Calc<double>
{
  public override double Add(double x,double y)
  {
    return x + y;
  }
  public override double Sub(double x,double y)
  {
    return x - y;
  }
}

4、靜態成員

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

class StaticDemo<T>
{
  public static string Type;
}

static void Main()
{
  StaticDemo<int>.Type = "int型別";
  StaticDemo<object>.Type = "Object型別";
  Console.WriteLine(StaticDemo<int>.Type);//輸出:int型別
}

  實際上,每當用一個型別去代替泛型中的T時,都是在創造一個例項型別。因此,泛型型別中的靜態欄位,會在不同的型別替代泛型T的例項中重新生成。這樣設計也有好處,可以為程式提供一個“泛型快取”的概念,使用泛型的靜態成員,使它存放在快取中,方便呼叫。

以上就是詳解c# 泛型類的功能的詳細內容,更多關於c# 泛型類的資料請關注我們其它相關文章!