這篇文章主要講解了“Effective C#之如何使用泛型”,文中的講解內(nèi)容簡(jiǎn)單清晰,易于學(xué)習(xí)與理解,下面請(qǐng)大家跟著小編的思路慢慢深入,一起來(lái)研究和學(xué)習(xí)“Effective C#之如何使用泛型”吧!
創(chuàng)新互聯(lián)自2013年起,是專(zhuān)業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目成都網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、外貿(mào)網(wǎng)站建設(shè)網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元溫泉做網(wǎng)站,已為上家服務(wù),為溫泉各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:18980820575
泛型約束可以規(guī)定一個(gè)泛型類(lèi)必須采用什么樣的類(lèi)型參數(shù)才能夠正常地運(yùn)作。設(shè)定約束條件的時(shí)候,太寬或太嚴(yán)都不合適。 如果根本就不加約束,那么程序必須在運(yùn)行的時(shí)候做很多檢查,并執(zhí)行更多的強(qiáng)制類(lèi)型轉(zhuǎn)換操作。而且在編譯器為這個(gè)泛型類(lèi)型的定義生成IL碼的時(shí)候,通過(guò)約束還可以為提供更多的提示,如果你不給出任何提示,那么編譯器就只好假設(shè)這些類(lèi)型參數(shù)所表示的都是最為基本的System.Object,也就是假設(shè)將來(lái)的實(shí)際類(lèi)型只支持由System.Object所公布的那些方法,這使得凡是沒(méi)有定義在System.Object里面的用法全都會(huì)令編譯器報(bào)錯(cuò),甚至連最為基本的new T()等操作也不支持。
但添加約束的時(shí)候也不要過(guò)分嚴(yán)格,以至于限制了泛型類(lèi)的使用范圍,只添加確實(shí)有必要的約束即可。
如果在泛型類(lèi)里面根據(jù)類(lèi)型參數(shù)創(chuàng)建了實(shí)例,那么就應(yīng)該判斷該實(shí)例所屬的類(lèi)型是否實(shí)現(xiàn)了IDisposable接口。如果實(shí)現(xiàn)了,就必須編寫(xiě)相關(guān)的代碼,以防程序在離開(kāi)泛型類(lèi)之后發(fā)生資源泄漏。這還要分不同的情況: 泛型類(lèi)的方法根據(jù)類(lèi)型參數(shù)所表示的類(lèi)型來(lái)創(chuàng)建實(shí)例并使用該實(shí)例類(lèi)似下面的寫(xiě)法,如果T是非托管資源,那么就會(huì)造成內(nèi)存泄露:
public interface IEngine { void DoWork(); } public class EngineDriverwhere T : IEngine, new() { public void GetThingsDone() { var driver =new T(); driver.DoWork(); } }
正確的寫(xiě)法應(yīng)該是:
var driver =new T(); using (driver as IDisposable) { driver.DoWork(); }
編譯器會(huì)把driver視為IDisposable,并創(chuàng)建隱藏的局部變量,用以保存指向這個(gè)IDisposable的引用。在T沒(méi)有實(shí)現(xiàn)IDisposable的情況下,這個(gè)局部變量的值是null,此時(shí)編譯器不調(diào)用Dispose(),因?yàn)樗谡{(diào)用之前會(huì)先做檢查。反之,如果T實(shí)現(xiàn)了IDisposable,那么編譯器會(huì)生成相應(yīng)的代碼,以便在程序退出using塊的時(shí)候調(diào)用Dispose()方法。 這段代碼等同于:
var a = driver as IDisposable; driver.DoWork(); a?.Dispose();
使用using后,需要注意的是所有調(diào)用driver實(shí)例的操作都不可以放在using區(qū)域之后,因?yàn)槟菚r(shí)driver已經(jīng)被釋放了。
泛型類(lèi)將根據(jù)類(lèi)型參數(shù)所創(chuàng)建的那個(gè)實(shí)例當(dāng)作成員變量在這種情況下,那么代碼會(huì)復(fù)雜一些。該類(lèi)擁有的這個(gè)引用所指向的對(duì)象類(lèi)型可能實(shí)現(xiàn)了IDisposable接口,也可能沒(méi)有實(shí)現(xiàn),但為了應(yīng)對(duì)可能實(shí)現(xiàn)了IDisposable接口的情況,泛型類(lèi)本身就必須實(shí)現(xiàn)IDisposable,并且要判斷相關(guān)的資源是否實(shí)現(xiàn)了這個(gè)接口,如果實(shí)現(xiàn)了,就要調(diào)用該資源的Dispose()方法。
public class EngineDriver2: IDisposable where T : IEngine, new() { // it's expensive to create, so create to null private Lazy driver = new Lazy (() => new T()); public void GetThingsDone() => driver.Value.DoWork(); public void Dispose() { if (driver.IsValueCreated) { var resource = driver.Value as IDisposable; resource?.Dispose(); } } }
或者可以將driver的所有權(quán)轉(zhuǎn)移到該類(lèi)之外,于是也就不用關(guān)心資源的釋放了。|
public sealed class EngineDriver3where T : IEngine { private T driver; public EngineDriver3(T driver) { this.driver = driver; } }
如果有多個(gè)相互重載的方法,那么編譯器就需要判斷哪一個(gè)方法應(yīng)該得到調(diào)用。而在引入泛型方法之后,這套判斷規(guī)則會(huì)變得更加復(fù)雜,因?yàn)橹灰軌蛱鎿Q其中的類(lèi)型參數(shù),就可以與這個(gè)泛型方法相匹配。 比如有下面三個(gè)類(lèi)型,它們之間的關(guān)系如代碼所示:
public class MyBase { } public interface IMsgWriter { void WriteMsg(); } public class MyDerived : MyBase, IMsgWriter { void IMsgWriter.WriteMsg() => Console.WriteLine("Inside MyDerived.WriteMsg"); }
接下來(lái)定義三個(gè)重載方法,其中包括了泛型方法:
static void WriteMsg(MyBase b) { Console.WriteLine("Inside WriteMsg(MyBase b)"); } static void WriteMsg(T obj) { Console.WriteLine("Inside WriteMsg (T obj)"); } static void WriteMsg(IMsgWriter obj) { Console.Write("Inside WriteMsg(IMsgWriter obj)"); }
那么如下三種調(diào)用寫(xiě)法,結(jié)果是怎樣的呢?
MyDerived derived = new MyDerived(); WriteMsg(derived); var msgWriter = derived as IMsgWriter; WriteMsg(msgWriter); var mbase = derived as MyBase; WriteMsg(mbase);
下面為運(yùn)行結(jié)果,與你預(yù)想是否一致呢?
Inside WriteMsg(T obj) Inside WriteMsg(IMsgWriter obj) Inside WriteMsg(MyBase b)
第一條結(jié)果表明了一個(gè)極為重要的現(xiàn)象:如果對(duì)象所屬的類(lèi)繼承自基類(lèi)MyBase,那么以該對(duì)象為參數(shù)來(lái)調(diào)用WriteMsg時(shí),WriteMsg
一般來(lái)說(shuō),我們通常的習(xí)慣是定義泛型類(lèi),但有時(shí)更推薦用泛型方法。因?yàn)槭褂梅盒头椒〞r(shí)所提供的泛型參數(shù)只需與該方法的要求相符即可,而使用泛型類(lèi)時(shí)所提供的泛型參數(shù)則必須滿足該類(lèi)所定義的每一條約束。如果將來(lái)還要給類(lèi)里面添加代碼,那么可能會(huì)對(duì)類(lèi)級(jí)別的泛型參數(shù)施加更多的約束,從而令該類(lèi)的適用場(chǎng)景變得越來(lái)越窄。
此外,泛型方法相比泛型類(lèi)會(huì)更加靈活,比如下面的泛型工具類(lèi)獲取提供了獲取較大值的方法:
public class Utils{ public static T Max(T left, T right) { return Comparer .Default.Compare(left, right) > 0 ? left : right; } }
因?yàn)槭欠盒?,那么每次調(diào)用都要提供類(lèi)型:
Utils.Max("c", "d"); Utils .Max(4, 3);
這樣雖然類(lèi)本身的實(shí)現(xiàn)比較方便,但調(diào)用端使用起來(lái)卻比較麻煩,更重要的是,值類(lèi)型可以直接使用Math.Max,而不需要每次都讓程序在運(yùn)行的時(shí)候先去判斷相關(guān)類(lèi)型是否實(shí)現(xiàn)了IComparer
public class Utils1 { public static T Max(T left, T right) { return Comparer .Default.Compare(left, right) > 0 ? left : right; } public static int Max(int left, int right) { return Math.Max(left, right) > 0 ? left : right; } public static double Max(double left, double right) { return Math.Max(left, right) > 0 ? left : right; } }
經(jīng)過(guò)這樣的修改,將泛型類(lèi)改成了部分使用泛型方法,對(duì)于int、double,編譯器會(huì)直接調(diào)用非泛型的版本,其它的類(lèi)型會(huì)匹配到泛型版本。
Utils1.Max("c", "d"); Utils1.Max(4, 3);
這樣寫(xiě)還有個(gè)好處是,將來(lái)如果又添加了一些針對(duì)其他類(lèi)型的具體版本,那么編譯器在處理那些類(lèi)型的參數(shù)時(shí)就不會(huì)去調(diào)用泛型版本,而是會(huì)直接調(diào)用與之相應(yīng)的具體版本。
但也要注意的是,并非每一種泛型算法都能夠繞開(kāi)泛型類(lèi)而單純以泛型方法的形式得以實(shí)現(xiàn)。有兩種情況,必須把類(lèi)寫(xiě)成泛型類(lèi):
該類(lèi)需要將某個(gè)值用作其內(nèi)部狀態(tài),而該值的類(lèi)型必須以泛型來(lái)表達(dá)(例如集合類(lèi))
該類(lèi)需要實(shí)現(xiàn)泛型版的接口。
除此之外的其他情況通常都可以考慮用包含泛型方法的非泛型來(lái)實(shí)現(xiàn)。
如果程序中有很多個(gè)類(lèi)都必須實(shí)現(xiàn)所要設(shè)計(jì)的某個(gè)接口,那么定義接口的時(shí)候就應(yīng)該定義盡量少的方法,后續(xù)可以采用擴(kuò)展方法的形式編寫(xiě)一些針對(duì)該接口的便捷方法。這樣做不僅可以使實(shí)現(xiàn)接口的人少寫(xiě)一些代碼,而且可以令使用接口的人能夠充分利用那些擴(kuò)展方法。
但使用擴(kuò)展方法時(shí)需要注意一點(diǎn):如果已經(jīng)針對(duì)某個(gè)接口定義了擴(kuò)展方法,而其他一些類(lèi)又想要以它們自己的方式來(lái)實(shí)現(xiàn)這個(gè)同名方法,那么擴(kuò)展方法就會(huì)被覆蓋,類(lèi)似下面這樣,針對(duì)IFoo定義了擴(kuò)展方法NextMarker,同時(shí)也在MyType中實(shí)現(xiàn)了NextMarker。
public interface IFoo { int Marker { get; set; } } public static class FooExtension { public static void NextMarker(this IFoo foo) { foo.Marker++; } } public class MyType: IFoo { public int Marker { get; set; } public void NextMarker() { this.Marker += 5; } }
那么下面代碼的結(jié)果就是5,而不是1
var myType =new MyType(); myType.NextMarker(); Console.WriteLine(myType.Marker); // 5
而如果需要調(diào)用擴(kuò)展方法,需要顯示地將myType轉(zhuǎn)換為IFoo。
var myType =new MyType(); var a = myType as IFoo; a.NextMarker();
感謝各位的閱讀,以上就是“Effective C#之如何使用泛型”的內(nèi)容了,經(jīng)過(guò)本文的學(xué)習(xí)后,相信大家對(duì)Effective C#之如何使用泛型這一問(wèn)題有了更深刻的體會(huì),具體使用情況還需要大家實(shí)踐驗(yàn)證。這里是創(chuàng)新互聯(lián),小編將為大家推送更多相關(guān)知識(shí)點(diǎn)的文章,歡迎關(guān)注!