歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> C#泛型中的類型約束和類型推斷

C#泛型中的類型約束和類型推斷

日期:2017/3/1 9:33:14   编辑:Linux編程

前一篇文章介紹了泛型的基本概念(見 http://www.linuxidc.com/Linux/2015-02/113465.htm)。在本文中,我們看一下泛型中兩個很重要的特性:類型約束和類型推斷。

類型約束

相信你還記得前面一篇文章中的泛型方法,在這個泛型方法中,我們就使用了類型約束。

類型約束(type constraint)進一步控制了可指定的類型實參,當我們創建自己的泛型類型或者泛型方法的時候,類型約束是很有用的。

回到前一篇例子中的泛型方法,這個泛型方法就要求可指定的類型實參必須實現了IComparable接口。

為什麼會有這個約束呢?原因很簡單,因為我們在泛型方法的實現中直接調用T類型的"CompareTo"方法。所以,我們需要通過一個約束來保證T類型都有"CompareTo"方法,也就是說我們要指定的類型實參T要實現IComparable接口。

public static T GetBiggerOne<T>(T itemOne, T itemTwo) where T : IComparable
{
    if (itemOne.CompareTo(itemTwo) > 0)
    {
        return itemOne;
    }
    return itemTwo;
}

經過上面的解釋,大家肯定對約束有了簡單的認識。

在類型約束中,有四種約束可供使用,他們的語法都是基本相同的,約束要放到泛型類型或泛型方法的末尾,並由上下文關鍵字where來引入。同時,約束也可以按照一定的規則組合在一起使用。

下面我們就分別看看可供我們使用的四種類型約束。

引用類型約束

引用類型表示為T : class,用於確保指定的類型實參都是引用類型(任何類,接口,數組或委托,以及已知為引用類型的另一個類型參數)。

如果使用引用類型約束,那麼它必須是為類型參數指定的第一個約束

一個簡單的示例,例如對於下面的聲明:

struct RefSample<T> where T : class { }

有效的封閉類型:

  • RefSample<string>
  • RefSample<IDisposable>

無效的封閉類型:

  • RefSample<int>
  • RefSample<double>

值類型約束

跟引用類型約束形式類似,值類型約束表示為T : struct,用於確保指定的類型實參都是值類型。

同樣,如果使用值類型約束,那麼它必須是為類型參數指定的第一個約束

例如對於下面的聲明:

class ValSample<T> where T : struct { }

有效的封閉類型:

  • ValSample <int>

無效的封閉類型:

  • ValSample <string>

構造函數類型約束

構造函數類型約束表示為T : new(),用於確保所有的類型參數有一個無參數的構造函數,這個構造函數可用於創建類型的實例。這適用於:所有值類型;所有非靜態、非抽象、沒有顯示聲明的構造函數的類;顯示聲明了一個公共無參構造函數的所有非抽象類。

如果使用構造函數類型約束,那麼它必須是為類型參數指定的最後一個約束

下面用一個例子進行簡單的說明:

public T CreateInstance<T>() where T : new()
{
    return new T();
}

這次例子中是一個泛型方法,約束我們指定的類型實參必須擁有無參數的構造函數,在這種情況下,這個泛型方法就可以返回該類型的一個實例。

所有下面都是有效的調用:

  • CreateInstance<int>()
  • CreateInstance<object>()

注意,在C#中,所有的值類型都有一個默認的無參數構造函數,所以當我們使用一些組合約束的時候,C#編譯器就會報出一個錯誤,因為這樣的指定是多余的,所有值類型都隱式提供一個無參公共構造函數。

public T CreateInstance<T>() where T: struct, new()

轉換類型約束

轉型類型約束允許我們指定另一個類型,類型實參必須可以通過一致性、引用或裝箱轉換隱式的轉換為改類型。

根據上面的描述,可以看到轉換類型約束可以有以下一些表示:

  • T : <基類名>,類型參數必須是指定的基類或派生自指定的基類
  • T : <接口名>,類型參數必須是指定的接口或實現指定的接口;可以指定多個接口約束;約束接口也可以是泛型的
  • T : U,用於指定T的類型實參必須是用於指定U的類型實參或者派生自用於指定U的類型實參

下面看幾個例子:

class Sample<T> where T: Stream

有效:Sample<Stream>

無效:Sample<string>

class Sample<T> where T: IDisposable

有效:Sample<SqlConnection >

無效:Sample<StringBuilder>

class Sample<T,U> where T: U

有效:Sample<Stream,IDispsable>

無效:LSample<string,IDisposable>

組合約束

組合約束就是將前面提到的多種約束集合起來使用。

對於一個類型參數,我們可以使用where關鍵字進行多個約束;對於不同的類型參數,可以有不同的約束,它們分別由單獨的where關鍵字引入。

在組合約束中,有很多組合情況是無效的,下面看一下例子:

  • class Sample<T> where T: class, struct
    • 沒有任何類型即時引用類型又是值類型的,所以這樣的組合是無效的
  • class Sample<T> where T: Stream, class
    • 引用類型約束應該為第一個約束,所以這樣的組合無效的(同樣,如果使用值類型約束,也必須是第一個)
  • class Sample<T> where T: new(), Stream
    • 構造函數約束必須放在最後面,所以這樣的組合無效的
  • class Sample<T> where T: IDisposable, Stream
    • 如果存在多個轉換類型約束,並且其中一個為類,那麼它應該出現在接口的前面
  • class Sample<T> where T: XmlReader, IComparable, IComparable
    • 對於轉換類型約束,同一個接口不能出現多次
  • class Sample<T,U> where T: struct where U:class, T
    • 類型形參"T"具有"struct"約束,因此"T"不能用作"U"的約束
  • class Sample<T,U> where T:Stream, U:IDisposable
    • 不同的類型參數可以有不同的約束,它們必須分別由單獨的where關鍵字引入

類型推斷

在調用泛型方法的時候,我們都需要通過"<>"來指定類型實參,就會顯得代碼比較復雜、冗余。其實,根據方法調用時傳遞的實參類型,可以比較容易的推斷出泛型方法的類型參數應該是什麼。

所以,編譯器就添加了一些"智能",幫我們推斷泛型方法的類型參數,這樣我們在方法調用的時候就可以不用顯示的聲明類型實參。

注意,類型推斷只適用於泛型方法。

看一個簡單的類型推斷的例子:

class Program
{
    static void Main(string[] args)
    {
        //Console.WriteLine("The bigger one is {0}", GetBiggerOne<int>(3, 9));
        //Console.WriteLine("The bigger one is {0}", GetBiggerOne<string>("Hello", "World"));
        //讓編譯器進行類型推斷
        Console.WriteLine("The bigger one is {0}", GetBiggerOne(3, 9));
        Console.WriteLine("The bigger one is {0}", GetBiggerOne("Hello", "World"));

        Console.Read();
    }

    public static T GetBiggerOne<T>(T itemOne, T itemTwo) where T : IComparable
    {
        if (itemOne.CompareTo(itemTwo) > 0)
        {
            return itemOne;
        }
        return itemTwo;
    }
}

總結

本文中介紹了泛型中的類型約束和類型推斷特性。

在我們使用自定義的泛型類型和泛型方法的時候,如果我們已經發現需要進行一些約束,最好就是直接在聲明泛型類型和方法的時候把約束加上。同時應該注意組合約束中的一系列無效的約束組合。

對於類型推斷,這個特性只適合泛型方法,可以簡化我們調用泛型方法時顯示的聲明類型實參。

Copyright © Linux教程網 All Rights Reserved