首先我們來看一下泛型的基本概念:
最顯著的一點就是它參數化了類型,把類型作爲參數抽象出來,從而使我們在實際的運用當中能夠更好的實現代碼的重複利用,同時它提供了更強的類型安全,更高的效率,不過在約束方面,它只支持顯示的約束,這樣在靈活性方面就顯得不是那麼好了.我覺得它之所以能夠提供更高的效率是因爲泛型在實例化的時候採用了"on-demand"的模式,即按需實例化,發生在JIT(Just In Time)編譯時.
下面來看如何定義一個泛型類,很簡單,你只需要意識到一點,在這裏,類型已經被參數化了:
using System;
using System.Collections.Generic;
using System.Text;
namespace GenericTest
{
class Program
{
static void Main(string[] args)
{
//使用string,int來實例化Test<T,S>類
Test<string, int> t = new Test<string, int>("SHY520",22);
//調用泛型類中的方法
t.SetValue();
}
}
/**//// <summary>
/// 定義一個泛型類,該類有兩個類型參數,分別是T,S
/// http://pw.cnblogs.com
/// </summary>
/// <typeparam name="T">類型參數</typeparam>
/// <typeparam name="S">類型參數</typeparam>
public class Test<T,S>
{
//泛型類的類型參數可用於類成員
private T name;
private S age;
public Test(T Name,S Age)
{
this.name = Name;
this.age = Age;
}
public void SetValue()
{
Console.WriteLine(name.ToString());
Console.WriteLine(age.ToString());
}
}
}
上面的例子不是很恰當,目的是讓初學泛型的你瞭解一下泛型的定義及實例化方法,如上,我們定義了一個泛型類,那麼如何實現泛型類的繼承呢?這裏需要滿足下面兩點中的任何一點即可:
1、泛型類繼承中,父類的類型參數已被實例化,這種情況下子類不一定必須是泛型類;
2、父類的類型參數沒有被實例化,但來源於子類,也就是說父類和子類都是泛型類,並且二者有相同的類型參數;
//如果這樣寫的話,顯然會報找不到類型T,S的錯誤
public class TestChild : Test<T, S> { }
//正確的寫法應該是
public class TestChild : Test<string, int>{ }
public class TestChild<T, S> : Test<T, S> { }
public class TestChild<T, S> : Test<String, int> { }
接着我們來看看泛型接口,其創建以及繼承規則和上面說的泛型類是一樣的,看下面的代碼:
public interface IList<T>
{
T[] GetElements();
}
public interface IDictionary<K,V>
{
void Add(K key, V value);
}
// 泛型接口的類型參數要麼已實例化
// 要麼來源於實現類聲明的類型參數
class List<T> : IList<T>, IDictionary<int, T>
{
public T[] GetElements() { return null; }
public void Add(int index, T value)
{
}
}
再來看一下泛型委託,首先我們定義一個類型參數爲T的委託,然後在類中利用委託調用方法:
using System;
using System.Collections.Generic;
using System.Text;
namespace GenericTest
{
//定義一個委託,類型參數爲T,返回值類型T
//泛型委託支持在返回值和參數上應用類型參數
delegate string GenericDelete<T>(T value);
class test
{
static string F(int i) { return "SHY520"; }
static string G(string s) { return "SHY520"; }
static void Main(string[] args)
{
GenericDelete<string> G1 = G;
GenericDelete<int> G2 = new GenericDelete<int>(F);
}
}
}
我們再來看泛型方法,C#的泛型機制只支持在方法申明上包含類型參數,也即是泛型方法。特別注意的是,泛型不支持在除了方法以外的其他類/接口成員上使用類型參數,但這些成員可以被包含在泛型類型中,並且可以使用泛型類型的類型參數。還有一點需要說的就是,泛型方法可以在泛型類型中,也可以存在於非泛型類型中。下面我們分別看一下泛型類型的申明,調用,重載和覆蓋。
using System;
using System.Collections.Generic;
using System.Text;
namespace GenericTest
{
class GenericClass
{
//申明一個泛型方法
public T getvalue<T>(T t)
{
return t;
}
//調用泛型方法
//注意:在調用泛型方法時,對泛型方法的類型參數實例化
public int useMethod()
{
return this.getvalue<int>(10);
}
//重載getvalue方法
public int getvalue(int i)
{
return i;
}
}
//下面演示覆蓋
//要注意的是,泛型方法被覆蓋時,約束被默認繼承,不需要重新指定約束關係
abstract class Parent
{
public abstract K TEST<K, V>(K k, V v) where K : V;
}
class Child : Parent
{
public override T TEST<T, S>(T t, S s)
{
return t;
}
}
}
最後我們來看一下泛型中的約束:
C#中的泛型只支持顯示的約束,因爲這樣才能保證C#所要求的類型安全,但顯示的約束並非時必須的,如果不加約束,泛型類型參數將只能訪問 System.Object類型中的公有方法。“顯式約束”由where子句表達,可以指定“基類約束”,“接口約束”,“構造器約束”,“值類型/引用類型約束”共四種約束。下面的例子來源於李建忠老師的講座PPT。
1、基類約束:
class A { public void F1() {} }
class B { public void F2() {} }
class C<S,T>
where S: A // S繼承自A
where T: B // T繼承自B
{
// 可以在類型爲S的變量上調用F1,
// 可以在類型爲T的變量上調用F2
}
2、接口約束
interface IPrintable { void Print();
}
interface IComparable<T> { int CompareTo(T v);}
interface IKeyProvider<T> { T GetKey(); }
class Dictionary<K,V>
where K: IComparable<K>
where V: IPrintable, IKeyProvider<K>
{
// 可以在類型爲K的變量上調用CompareTo,
// 可以在類型爲V的變量上調用Print和GetKey
}
3、構造器約束
class A { public A() { } }
class B { public B(int i) { } }
class C<T>
where T : new()
{
//可以在其中使用T t=new T();
}
C<A> c=new C<A>(); //可以,A有無參構造器
C<B> c=new C<B>(); //錯誤,B沒有無參構造器
4、值/引用類型約束
public struct A { }
public class B { }
class C<T>
where T : struct
{
// T在這裏面是一個值類型
}
C<A> c=new C<A>(); //可以,A是一個值類型
C<B> c=new C<B>(); //錯誤,B是一個引用類型
default 關鍵字
在泛型類和泛型方法中產生的一個問題是,在預先未知以下情況時,如何將默認值分配給參數化類型 T:
T 是引用類型還是值類型。
如果 T 爲值類型,則它是數值還是結構。
給定參數化類型 T 的一個變量 t,只有當 T 爲引用類型時,語句 t = null 纔有效;只有當 T 爲數值類型而不是結構時,語句 t = 0 才能正常使用。解決方案是使用 default 關鍵字,此關鍵字對於引用類型會返回空,對於數值類型會返回零。對於結構,此關鍵字將返回初始化爲零或空的每個結構成員,具體取決於這些結構是值類型還是引用類型。以下來自 GenericList<T> 類的示例顯示瞭如何使用 default 關鍵字。有關更多信息,請參見泛型概述。