泛型的優勢是多方面的,不管是泛型類仍是泛型方法都同時具有可重用性、類型安全和高效率等特性,這都是非泛型類和非泛型方法沒法具有的html
static void Main(string[]args) { Console.WriteLine(MyList.Func<int>()); Console.WriteLine(MyList.Func<int>()); Console.WriteLine(MyList.Func<string>()); } class MyList { static int count; public static int Func<T>() { return count++; }} 輸出 0 ;1;2
在編碼過程當中,應該始終考慮爲泛型參數設定約束。約束使泛型參數成爲一個實實在在的「對象」,讓它具備了咱們想要的行爲和屬性,而不只僅是一個object。算法
指定約束示例:安全
指定參數具備無參數的公共構造方法。 where T:new()閉包
注意,CLR目前只支持無參構造方法約束。異步
能夠對同一類型的參數應用多個約束,而且約束自身能夠是泛型類型。編碼
有些算法,好比泛型集合List<T>的Find算法,所查找的對象有可能會是值類型,也有多是引用類型。在這種算法內部,咱們經常會爲這些值類型變量或引用類型變量指定默認值。因而,問題來了:值類型變量的默認初始值是0值,而引用類型變量的默認初始值是null值,顯然,這會致使下面的代碼編譯出錯:線程
public T Func<T>() { T t=null; T t=0; return t; }
代碼"T t=null;"在Visual Studio編譯器中會警示:錯誤1不能將Null轉換爲類型形參「T」,由於它多是不能夠爲null值的類型。請考慮改用「default(T)」.
代碼"T t=0;"會警示:錯誤1沒法將類型「int」隱式轉換爲「T」。
改進指針
public T Func<T>() { T t=default(T); return t; }
//如用於表示註冊事件方法的委託聲明: public delegate void EventHandler(object sender,EventArgs e); public delegate void EventHandler<TEventArgs>(object sender,TEventArgs e); //表示線程方法的委託聲明: public delegate void ThreadStart(); public delegate void ParameterizedThreadStart(object obj); //表示異步回調的委託聲明: public delegate void AsyncCallback(IAsyncResult ar);
在FCL中每一類委託聲明都表明一類特殊的用途,雖然可使用本身的委託聲明來代替,可是這樣作不只沒有必要,並且會讓代碼失去簡潔性和標準性。在咱們實現本身的委託聲明前,應該首先查看MSDN,確信有必要以後才這樣作。code
在實際的編碼工做中熟練運用它,避免寫出煩瑣且不美觀的代碼。htm
若是匿名方法(Lambda表達式)引用了某個局部變量,編譯器就會自動將該引用提高到該閉包對象中,即將for循環中的變量i 修改爲了引用閉包對象(編譯器自動建立)的公共變量i。
示例以下:
static void Main(string[]args) { List<Action>lists=new List<Action>(); for(int i=0;i<5;i++) { Action t=()=> { Console.WriteLine(i.ToString()); }; lists.Add(t); } foreach(Action t in lists) { t(); } }
以上結果所有輸出5;
另一種實現方式;
static void Main(string[]args) { List<Action>lists=new List<Action>(); TempClass tempClass=new TempClass(); for(tempClass.i=0;tempClass.i<5;tempClass.i++) { Action t=tempClass.TempFuc; lists.Add(t); } foreach(Action t in lists) { t(); } } class TempClass { public int i; public void TempFuc() { Console.WriteLine(i.ToString()); } }
這段代碼所演示的就是閉包對象。所謂閉包對象,指的是上面這種情形中的TempClass對象(在第一段代碼中,也就是編譯器爲咱們生成的「<>c__DisplayClass2」對象)。若是匿名方法(Lambda表達式)引用了某個局部變量,編譯器就會自動將該引用提高到該閉包對象中,即將for循環中的變量i修改爲了引用閉包對象的公共變量i。這樣一來,即便代碼執行後離開了原局部變量i的做用域(如for循環),包含該閉包對象的做用域也還存在。理解了這一點,就能理解代碼的輸出了。
理解C#中的委託須要把握兩個要點:
首先沒有event加持的委託,咱們能夠對它隨時進行修改賦值,以致於一個方法改動了另外一個方法的委託鏈引用,好比賦值爲null,另一個方法中調用的時候將拋出異常。
若是有event加持的時候,咱們修改的時候,好比:
fl.FileUploaded=null; fl.FileUploaded=Progress; fl.FileUploaded(10);
以上代碼編譯會出現錯誤警告:
事件 「ConsoleApplication1.FileUploader.FileUploaded 」
只能出如今+=或-=的左邊(從類型「ConsoleApplication1.FileUploader」中使用時除外)
有了上面的event加持,可是還不可以規範。
EventHandler的原型聲明:
public delegate void EventHandler(object sender,EventArgs e);
微軟爲事件模型設定的幾個規範:
class Program{ static void Main(string[]args) { ISalary<Programmer>s=new BaseSalaryCounter<Programmer>(); PrintSalary(s); } static void PrintSalary(ISalary<Employee>s) { s.Pay(); } } interface ISalary<T> { void Pay(); } class BaseSalaryCounter<T>:ISalary<T> { public void Pay() { Console.WriteLine("Pay base salary"); } } class Employee { public string Name{get;set;} } class Programmer:Employee{} class Manager:Employee{}
報錯: 沒法從「ConsoleApplication4.ISalary<ConsoleApplication4.Programmer>」轉換爲「ConsoleApplication4.ISalary<ConsoleApplication4.Employee>」
要讓PrintSalary完成需求,咱們可使用泛型類型參數:
static void PrintSalary<T>(ISalary<T>s) { s.Pay(); }
實際上,只要泛型類型參數在一個接口聲明中不被用來做爲方法的輸入參數,咱們均可姑且把它當作是「返回值」類型的。因此,泛型類型參數這種模式是知足「協變」的定義的。可是,只要將T做爲輸入參數,便不知足「協變」的定義了。如:
interface ISalary<out T> { void Pay(T t); }
編譯會提示:差別無效:類型參數「T」必須是在「ISalary
除了11中提到的使用泛型參數兼容泛型接口的不可變性外,還有一種辦法就是爲接口中的泛型聲明加上out關鍵字來支持協變。
out關鍵字是FCL 4.0中新增的功能,它能夠在泛型接口和委託中使用,用來讓類型參數支持協變性。經過協變,可使用比聲明的參數派生類型更大的參數。經過下面例子咱們應該能理解這種應用。
好比:
static void Main(string[]args) { ISalary<Programmer>s=new BaseSalaryCounter<Programmer>(); ISalary<Manager>t=new BaseSalaryCounter<Manager>(); PrintSalary(s); PrintSalary(t); } static void PrintSalary(ISalary<Employee>s)//用法正確 { s.Pay(); } } interface ISalary<out T> //使用了out關鍵字 { void Pay(); }
FCL 4.0對多個接口進行了修改以支持協變,如IEnumerable<out T>、IEnumerator<out T>、IQuerable<out T>等。因爲IEnumerable<out T>如今支持協變,因此上段代碼在FCL 4.0中能運行得很好。
在咱們本身的代碼中,若是要編寫泛型接口,除非肯定該接口中的泛型參數不涉及變體,不然都建議加上out關鍵字。協變增大了接口的使用範圍,並且幾乎不會帶來什麼反作用。
委託中的泛型變量自然是部分支持協變的。
好比:
public delegate T GetEmployeeHanlder<T>(string name); static void Main(){ GetEmployeeHanlder<Employee>getAEmployee=GetAManager; Employee e=getAEmployee("Mike"); }
由於存在下面這樣一種狀況,因此編譯通不過:
GetEmployeeHanlder<Manager>getAManager=GetAManager;GetEmployeeHanlder<Employee>getAEmployee=getAManager; static Manager GetAManager(string name) { Console.WriteLine("我是經理:"+name); return new Manager(){Name=name}; } static Employee GetAEmployee(string name) { Console.WriteLine("我是僱員:"+name); return new Employee(){Name=name}; }
要讓上面的代碼編譯經過,一樣須要爲委託中的泛型參數指定out關鍵字:
public delegate T GetEmployeeHanlder<out T>(string name);
FCL 4.0中的一些委託聲明已經用out關鍵字來讓委託支持協變了,如咱們經常會使用到的:
public delegate TResult Func<out TResult>()和 public delegate TOutput Converter<in TInput,out TOutput>(TInput input)
逆變是指方法的參數能夠是委託或泛型接口的參數類型的基類。FCL 4.0中支持逆變的經常使用委託有:
Func<in T,out TResult> Predicate<in T> //經常使用泛型接口有: IComparer<in T>
舉例:
class Program { static void Main() { Programmer p=new Programmer{Name="Mike"}; Manager m=new Manager{Name="Steve"}; Test(p,m); } static void Test<T>(IMyComparable<T>t1,T t2) { //省略 }} public interface IMyComparable<in T> { int Compare(T other); } public class Employee:IMyComparable<Employee> { public string Name{get;set;} public int Compare(Employee other) { return Name.CompareTo(other.Name); } } public class Programmer:Employee,IMyComparable<Programmer> { public int Compare(Programmer other) { return Name.CompareTo(other.Name); } } public class Manager:Employee{ }
在上面的這個例子中,若是不爲接口IMy-Comparable的泛型參數T指定in關鍵字,將會致使Test(p, m)編譯錯誤。因爲引入了接口的逆變性,這讓方法Test支持了更多的應用場景。在FCL4.0以後版本的實際編碼中應該始終注意這一點。
若有須要, 上一篇的《C#規範整理·集合和Linq》也能夠看看!
深刻理解協變和逆變傳送門《逆變與協變詳解》