c#基礎:
1、請介紹關鍵字internal、sealed、readonly、static、is、as、params、this。請結合實際場景說明你在項目中的使用情況。
(1) internal 訪問修飾符 只有在同一程序集的文件中,內部類型或成員才是可訪問的,內部訪問通常用於基於組件的開發,因為它使一組組件能夠以私有方式進行合作,而不必向應用程序代碼的其余部分公開。
(2) sealed 修飾符可以應用於類、實例方法和屬性。密封類不能被繼承。密封方法會重寫基類中的方法,但其本身不能在任何派生類中進一步重寫。當應用於方法或屬性時,sealed 修飾符必須始終與 override一起使用
(3) readonly 字段可以在聲明或構造函數中初始化。因此,根據所使用的構造函數,readonly 字段可能具有不同的值。const 字段是編譯時常數,常數表達式是在編譯時被完全計算出來,它只能在該字段的聲明中初始化. const 默認就是靜態的,而 readonly 如果設置成靜態的就必須顯示聲明。
(4) static 修飾符聲明屬於類型本身而不是屬於特定對象的靜態成員,盡管類的實例包含該類所有實例字段的單獨副本,但每個靜態字段只有一個副本。不可以使用
this 來引用靜態方法或屬性訪問器。如果對類應用 static 關鍵字,則該類的所有成員都必須是靜態的。
(5) abstract 修飾符指示所修飾的內容缺少實現或未完全實現。 abstract 修飾符可用於類、方法、屬性、索引器和事件。 標記該類為抽象類,抽象方法只能由派生類實現,是一個隱式的虛方法。
(6) is 檢查對象是否與給定類型兼容。 例如,if (obj is MyObject) 的代碼可以確定對象是否為 MyObject 類型的一個實例,或者對象是否為從 MyObject 派生的一個類型。請注意,is 運算符只考慮引用轉換、裝箱轉換和取消裝箱轉換。 不考慮其他轉換,如用戶定義的轉換。在 is 運算符的左側不允許使用匿名方法。 lambda 表達式屬於例外
(7) as用於在兼容的引用類型之間執行轉換,as 運算符類似於強制轉換操作;但是,如果轉換不可行,as 會返回 null 而不是引發異常。更嚴格地說,這種形式的表達式 就相當於 obj as type===>obj is type ? (type)obj : (type)null
as 運算符只執行引用轉換和裝箱轉換。as 運算符無法執行其他轉換,如用戶定義的轉換,這類轉換應使用 cast 表達式來執行。
(8) this 關鍵字引用類的當前實例(對象),還可用作擴展方法的第一個參數的修飾符。由於靜態成員函數存在於類一級,並且不是對象的一部分,因此沒有 this 指針。 在靜態方法中引用 this 是錯誤的。
this可以限定被相似的名稱隱藏的成員,例如當前實例有變量name,有一個方法的參數也是name,那么此時this.name就是指當前實例的成員,而不是只參數,起到一個區分的作用。This也可以將對象作為參數傳遞到其他方法
(9) params 關鍵字可以指定在參數數目可變處采用參數的方法參數。在方法聲明中的 params 關鍵字之后不允許任何其他參數,並且在方法聲明中只允許一個 params 關鍵字。public static void UseParams2(params object[] list){}
2 ref 和out
關鍵字使參數按引用傳遞(參數默認是按照值傳遞的)。其效果是,當控制權傳遞回調用方法時,在方法中對參數所做的任何更改都將反映在該變量中。若要使用 ref或out參數,則方法定義和調用方法都必須顯式使用 ref 和out關鍵字。傳遞到 ref 參數的參數必須最先初始化。這與 out 不同,out 的參數在傳遞之前不需要顯式初始化
3 delegate委托 ,事件
是一種可用於封裝命名或匿名方法的引用類型,通過將委托與命名方法或匿名方法關聯,可以實例化委托。 必須使用具有兼容返回類型和輸入參數的方法或 lambda 表達式實例化委托
delegate void testDelegateMes 聲明了一個委托 可以理解為一個特殊的類
testDelegateMes tdm1=new testDelegateMes(showMes);
testDelegateMes tdm1 //可以理解為聲明了該類的一個變量
new testDelegateMes(showMes);//將委托與命名方法或匿名方法關聯,此時tdm1就等價於showmes方法,一個委托可以搭載多個方法
委托是一個類,它定義了方法的類型,使得可以將方法當作另一個方法的參數來進行傳遞,這種將方法動態地賦給參數的做法,可以避免在程序中大量使用If-Else(Switch)語句,同時使得程序具有更好的可擴展性。所有委托都有一個構造器默認獲取兩個參數,一個是對象引用@object,一個是引用了回調方法的整數method;在構造器內部這兩個參數被保存在_target和_methodPtr私有字段中,編譯器在定義委托類的時候定義了一個Invoke方法,在Invoke被調用時(默認使用委托時,其實是調用了該委托的Invoke方法),它使用私有字段_target和_methodPtr在指定對象上調用包裝好的回調方法(委托代替方法執行的原理)。(invoke方法的簽名和委托的簽名匹配)
委托的屬性表現形式就是事件,利用委托的多播特性可以將多個行為與委托綁定。
4 請說一下靜態構造函數和默認構造函數的區別
靜態構造函數同其他靜態成員一樣是屬於類的,它用來初始化一些靜態成員。靜態構造函數只會被執行一次。也就是在創建第一個實例或引用任何靜態成員之前,由.NET自動調用。也就是說我們無法直接調用靜態構造函數。如果沒有寫靜態構造函數,而類中包含帶有初始值設定的靜態成員,那么編譯器會自動生成默認的靜態構造函數。一個類只能有一個靜態構造函數。
5 請說一下屬性和字段的區別。
屬性有比字段更大的靈活性和可擴展性,字段”是直接在類或結構中聲明的任何類型的變量。屬性提供靈活的機制來讀取、編寫或計算某個私有字段的值。 可以像使用公共數據成員一樣使用屬性,但實際上它們是稱作“訪問器”的特殊方法。有助於提高方法的安全性和靈活性。對於屬性,你可以自定義取值賦值的操作。可以只讀也可以只寫,而字段始終是可讀可寫的(字段一般屬於類私有的,在外部訪問他破壞了類的封裝型)
6 什么是堆,棧, 值類型, 引用類型 ,裝修, 拆箱
托管堆和線程棧是內存上的兩塊存儲區域,應用程序的每個線程都會被分配1m的存儲空間作為線程棧,用於存儲自身的數據(局部變量,實參等)這塊內存區域的分配和回收不受應用程序干擾,當一個變量離開其作用域的時候內存空間立即被釋放。棧在內存中是由高內存地址往低內存地址填充,釋放是由低往高釋放(先進后出保證了不與變量的聲明周期起沖突)。棧中存儲值類型
另一塊內存區域稱為“堆(heap)”,在.NET 這種托管環境下,堆由CLR 進行管理,所以又稱為“托管堆(managed heap)”。 托管堆存儲引用類型,類是引用類型,用new關鍵詞創建的對象分配的內存空間位於托管堆上。當對象使用完被垃圾回收機制回收時,空間會被回收 與棧不同,堆是從下往上分配,所以自由的空間都在已用空間的上面
通用類型系統(CTS)區分兩種基本類型:值類型和引用類型。它們之間的根本區別在於它們在內存中的存儲方式。值類型存儲於棧中,引用類型存儲在堆中。值類型比引用類型輕。原因是它不作為對象在托管堆中分配,不被垃圾回收,也不通過指針進引用。沒有堆上每個對象成員都有的額外成員:(類型對象指針和同步塊索引),當這個值類型離開作用域時,所占空間即被釋放
string是特殊的引用類型,字符串代表一個固定不變的Unicode字符序列(類似於不變集合一樣,每當改變都不是針對原來的對象的改變,而是產生的一個新的對象)。這種不變性意味着,一旦在堆中分配了一個字符串,它的值將永遠不會改變。如果值改變了,.NET就創建一個全新的String對象,並把它賦值給該變量。
裝箱:
值類型轉化為引用類型要使用裝箱機制,裝箱分三步:
- 在托管堆中分配內存,分配的內存量是值類型各字段所需的內存量,還要加上托管堆上所有對象都有的兩個成員(類型對象指針和同步塊索引)
- 值類型的字段復制到新分配的堆內存
- 返回對象地址,現在該地址是對象引用。值類型成了引用類型。
拆箱:
將一個引用類型轉化為值類型的時候,要拆箱。值類型轉為引用類型分2步:
- 獲取已裝箱的對象(此時為引用類型)的各個成員的地址;叫拆箱
- 將字段包含的值從堆中復制到基於棧的值類型實例中
拆箱不是將裝箱過程倒過來,拆箱的代價要比裝箱低很多,拆箱就是獲取指針的過程,該指針指向一個對象中的原始值類型(數據字段).緊接着拆箱往往會發生一次字段復制
發生裝箱和拆箱的幾種情況:
1 發生值類型轉為引用類型的情況 會裝箱
2 類型在調用tostring方法時,內部並未重寫該虛方法,而是使用的基類(object)得tostring方法,會裝箱,在重寫了基類tostring方法的時候,是用base.tostring一樣會發生裝箱
3 是用gettype 同上,在值類型的派生類未重寫基類(object)得方法時一樣會發生裝箱
4 將值類型的某個實例轉換為類型的某個接口時,要對實例進行裝箱。這是因為接口變量必須包含對堆對象的引用
7 CLR垃圾回收機制
clr要求所有對象都從托管堆分配,當使用new操作符創建一個對象的時候會在堆上分配一塊空間,並返回指向這塊存儲區域的指針(引用地址),當托管堆沒有足夠的空間的時候會執行垃圾回收。在執行垃圾回收時,
1 clr會暫停所有線程,
2 並標記所有對象是否是可達的(是否還有根(變量)引用它),不可達對象將被執行回收
3 之后進入壓縮階段,將所有可達的對象執行壓縮,使他們占用連續的堆內存空間(好處是:1所有對象內存中緊挨在一起,恢復了引用的局部化,減小了應用程序的工作集,提升了性能。2可用空間是連續的,允許其他對象入住。3壓縮意味着解決了原生堆空間碎片化問題)。同時,clr還要從每個根減去所引用的對象在內存中偏移的字節數,這樣就能保證每個根還是引用和之前一樣的對象。
4 clr恢復應用程序所有線程
簡單來說整個過程分五步:暫停進程的所有線程——>將所有對象的同步塊索引位設置為0——>檢查所有根引用的對象,並標記其同步塊索引位1,沒有被根引用的對象保持原來的0不被重新標記——>刪除標記為0的對象,執行壓縮,並根據對象在內存中偏移的字節數調整根的引用地址——>clr恢復暫停的線程
clr的GC是基於代的垃圾回收器
clr的代一共分為3代:0,1,2;為每一代都分配了預算容量(KB),這個預算容量是根據回收的情況進行動態調整的,如果一次對第0代垃圾回收的對象較少,說明可用對象較多,則會增大對第一代預算,相反則相應減少容量預算。
對象被創建時未第0代對象,當0代存儲空間滿后,執行一次gc后幸存的對象升級為第1代對象,之后在0代空間執行第2次gc后幸存的對象繼續升級為第一代對象,直到第1代存儲空間也滿了,在執行gc時,會同時回收第0代和第一代的對象(此時1代空間的對象很可能已經有不可達對象等待被回收),這時原來存儲在第1代空間的幸存對象會被提升未第2代。第0代幸存的提升為第1代。當所有存儲空間都滿了的時候,就內存溢出了。
垃圾回收的觸發條件:
- clr在檢測第0代超過預算時
- 代碼顯示調用system.GC的靜態方法Collect,強制垃圾回收
- window報告低內存時
- clr正在卸載AppDomain時,clr認為一切都不是根,執行涵蓋所有代碼的垃圾回收
- 進程結束時,clr正常關閉,windows將回收進程的所有內存
8 深拷貝和淺拷貝
深拷貝和淺拷貝之間的區別在於是否復制了子對象。淺拷貝(影子克隆/shallow copy):只復制對象的值類型字段,對象的引用類型,仍屬於原來的引用. 深拷貝(深度克隆):不僅復制對象的值類型字段,同時也復制原對象中的對象.就是說完全是新對象產生的。例如一個學校類里不僅包含了schoolname屬性,還包含了students對象的引用,此時淺拷貝就是會創建schoolname的副本和對students對象指針的副本(並不會創建一個全新的students對象),而深拷貝則會創建一個全新的students對象副本出來
9 請說明Equals和==的區別。
C#中的相等有兩種類型:引用相等(ReferenceEquals)和值相等(Equals)。值相等就是說兩個對象包含相同的值。而引用相等則比較的是兩個對象的引用是否是同一個對象。也就是說,如果ReferenceEquals為True,則Equals必然為True,反過來就不一定了
==運算符(引用相等):通過過判斷兩個引用是否指示同一對象來測試是否相等。
Equals是要實現值相等比較的,為object類的方法,一個類默認情況下,在不重寫基類Equals方法的時候對比的是對象的引用是否指向同一塊內存地址。重寫Equals函數的目的是為了比較兩個對象的value值是否相同,例如利用equals比較八大包裝對象(如int,float等)和String類(因為該類已重寫了equals和hashcode方法)對象時,默認比較的是值,在比較其它自定義對象時都是比較的引用地址(例如自定義的類,因為沒有重寫equals方法)。
如何重寫equals方法:
主要用於通過對比自定義類中的每個成員來對比兩個類是否值相等;重寫equals也要重寫hashcode方法
在創建1個對象的時候會自動為這個對象創建hashcode,這樣如果我們對2個對象重寫了euqals,意思是只要對象的成員變量值都相等那么euqals就等於true,此時在未重寫hashcode方法的時候,這兩個對象卻擁有不同的hashcode,由此將產生了理解的不一致,在存儲散列集合時(如Set類),將會存儲了兩個值一樣的對象,導致混淆,因此,就也需要重寫hashcode()
hashcode是用於散列數據的快速存取,如利用HashSet/HashMap/Hashtable類來存儲數據時,都是根據存儲對象的hashcode值來進行判斷是否相同的,在這里對比兩個對象相同時,其中最先對比的是對象的hashcode,如果兩個對象的hashcode不同,則這兩個對象必然不同,從而大大提高了效率
10 面向對象三大特征:
封裝:封裝就是將數據或函數等集合在一個個的類中,被封裝的對象通常被稱為抽象數據類型。封裝的意義在於保護或者防止代碼(數據)被我們無意中破壞。例如我們會將屬於類本身的行為,字段或方法定義為private,外部不能能訪問它。使用屬性來控制對數據字段的修改或只讀只寫。訪問修飾符:Private,Protected(類和派生類可以存取),Internal(只有同一個項目中的類可以存取),Public
繼承:繼承主要實現代碼間的縱向聯系,繼承了父類的子類也會有父類的行為。
1、C#中的繼承符合下列規則:
- 繼承是可傳遞的。如果C從B中派生,B又從A中派生,那么C不僅繼承了B中聲明的成員,同樣也繼承了A中的成員。Object類作為所有類的基類。
- 派生類應當是對基類的擴展。派生類可以添加新的成員,但不能除去已經繼承的成員的定義。
- 構造函數和析構函數不能被繼承。除此之外的其它成員,不論對它們定義了怎樣的訪問方式,都能被繼承。基類中成員的訪問方式只能決定派生類能否訪問它們。
- 派生類如果定義了與繼承而來的成員同名的新成員,就可以覆蓋已繼承的成員。但這並不因為這派生類刪除了這些成員,只是不能再訪問這些成員。
- 類可以定義虛文法、虛屬性以及虛索引指示器,它的派生類能夠重載這些成員,從而實現類可以展示出多態性。
2、new關鍵字
如果父類中聲明了一個沒有friend修飾的protected或public方法,子類中也聲明了同名的方法。則用new可以隱藏父類中的方法。(不建議使用)
3、base關鍵字 base 關鍵字用於從派生類中訪問基類的成員:
- 調用基類上已被其他方法重寫的方法。
- 指定創建派生類實例時應調用的基類構造函數。
多態:(重載就是多態)
編譯時的多態性:
編譯時的多態性是通過重載來實現的。對於非虛的成員來說,系統在編譯時,根據傳遞的參數、返回的類型等信息決定實現何種操作。
運行時的多態性:運行時的多態性就是指直到系統運行時,才根據實際情況決定實現何種操作。C#中,運行時的多態性通過虛成員實現。編譯時的多態性為我們提供了運行速度快的特點,而運行時的多態性則帶來了高度靈活和抽象的特點。
2、實現多態:接口多態性。繼承多態性。通過抽象類實現的多態性。
3、override關鍵字:重寫父類中的virtual修飾的方法,實現多態。
11 請談一下你對Exception的理解
在 C# 中,程序中的運行時錯誤通過使用一種稱為“異常”的機制在程序中傳播。異常由遇到錯誤的代碼引發,由能夠更正錯誤的代碼(try catch)捕捉。 一旦引發了一個異常,clr自上而下搜索匹配的catch塊,一個try塊對應多個catch塊,越具體的catch應該在上面,接着是她們的基類型,最后是exception類,未指定也是exception類型。如果上下順序反了,那么具體的catch塊會執行不到,編譯器會報錯。try catch 以后,如果只是處理了捕獲的異常(例如記日志),沒有拋出異常(throw),后續代碼會繼續執行
throw是拋出一個異常,中斷執行后續代碼,如果一個方法可能會有異常,但你不想處理這個異常,就是用throw,誰調用了這個方法誰就要處理這個異常,或者繼續拋出。
throw和throw ex
1 throw ex 拋出與catch捕捉到的相同的異常對象,導致clr重置該異常的起點。認為你catch到的異常已經被處理了,只不過處理過程中又拋出新的異常,從而找不到真正的錯誤源 try{ }catch (Exception ex){ throw ex; }
2 throw 重新拋出異常對象,clr不會重置起點(推薦使用這個)try{} catch{ throw;}
3 或者對異常就行重新包裝,保留原始異常點信息,然后拋出
finally:
無論代碼有沒有拋出異常,finally塊始終會執行。應該先用finally塊清理那些已經成功的操作,還可以用finally顯示釋放對象避免資源泄露。Finally 塊使程序員能夠清除中止的 try 塊可能遺留下的任何模糊狀態,或者釋放任何外部資源(例如圖形句柄、數據庫連接或文件流),而無需等待運行時中的垃圾回收器終結這些對象.
使用了 lock,using,foreach的語句,c#編譯器會自動加上try finally塊,並在finally中自動釋放資源
可以在一個線程中補獲異常,在另外一個線程中重新拋出異常;
clr在調用棧中向上查找與拋出的異常對象類型匹配的catch塊,沒有任何catch塊匹配拋出的異常類型,就會發生一個未處理的異常
12、你平時常用的集合類有哪些? 分別有什么區別?請結合實際場景說明你在項目中的使用情況。
System.Collections命名空間包含各種集合對象和接口的定義(如列表、隊列、位數組、哈希表和字典)System.Collections.Generic 命名空間包含定義泛型集合的接口和類,泛型集合允許用戶創建強類型集合,它能提供比非泛型強類型集合更好的類型安全性和性能。
數組、arraylist、list、hashtable、directonary、stack堆棧、quene隊列
數組:數組是固定大小的,不能伸縮,數組要聲明元素的類型
a ArrayList動態數組可以存儲任何類型,不像List泛型那樣有嚴格要求,ArrayList就相當於是list<object>,List類在大多數情況下執行得更好並且是類型安全的。注意list非線程安全的,多線程中操作一個list最好使用ConcurrentList,和ConcurrentDictionary一樣內部自帶加速機制
b HashTable應用場合:做對象緩存,樹遞歸算法的替代,和各種需提升效率的場合。HashTable中的key/value均為object類型,由包含集合元素的存儲桶組成。HashTable的優點就在於其索引的方式,速度非常快。如果以任意類型鍵值訪問其中元素會快於其他集合,特別是當數據量特別大的時候,效率差別尤其大。
c Hashtable和Dictionary<K, V>類型
1:單線程中推薦使用Dictionary,有泛型優勢,且讀取速度較快,容量利用更充分.
2:多線程中推薦使用Hashtable,默認Hashtable允許單線程寫入, 多線程讀取對Hashtable進一步調用Synchronized()方法可以獲得完全線程安全的類型,而Dictionary非線程安全,必須人為使用lock語句進行保護, 效率大減。 .net提供了另外一個字典類ConcurrentDictionary,是線程安全的支持並發操作
3:Dictionary有按插入順序排列數據的特性(注:但當調用Remove()刪除過節點后順序被打亂), 因此在需要體現順序的情境中使用Dictionary能獲得一定方便。
d HashTable、HashMap、HashSet之間的區別
HashTable是基於Dictionary的key value集合,是線程安全的
HashMap是基於Map接口的一個實現,HashMap可以將空值作為一個表的條目的key或者value,HashMap中由於鍵不能重復,因此只有一條記錄的Key可以是空值,而value可以有多個為空,但HashTable不允許null值(鍵與值均不行)
HashSet 是set的一個實現類,存儲的是一個對象列表,底層采用的是HashMap進行實現的,但是沒有key-value,只有HashMap的key set的視圖,HashSet不容許重復的對象
13、什么是接口,什么是抽象類?二者有什么區別?請結合實際場景說明你在項目中的使用情況。
抽象類和接口都是對業務的一種抽象方式。
1、接口定義了一種行為,一種規則,它告訴了你要做什么,但不會去具體實現。當一個類繼承了這個接口的時候,它將負責實現這個接口
2、當類選擇實現某個接口的時候,類必須將這個接口全部實現,不能只實現其中一部分。
3、接口和抽象類的區別:一個類只能繼承一個父類,但一個類可以同時實現多個接口,格式為: 類名:接口1,接口2
4、接口里的成員方法(行為),沒有修飾符,但接口本身是可以有修飾符的
5、抽象類的作用:負責定義“實現和部分的動作”(抽象類中可以實現某些方法),接口只負責定義動作不負責實現
interface的應用場合:
- 類與類之前需要特定的接口進行協調,而不在乎其如何實現。
- 作為能夠實現特定功能的標識存在,也可以是什么接口方法都沒有的純粹標識。
- 需要將一組類視為單一的類,而調用者只通過接口來與這組類發生聯系。
- 需要實現特定的多項功能,而這些功能之間可能完全沒有任何聯系。
abstract class的應用場合:一句話,在既需要統一的接口,又需要實例變量或缺省的方法的情況下,就可以使用它。最常見的有:
- 定義了一組接口,但又不想強迫每個實現類都必須實現所有的接口。可以用abstract class定義一組方法體,甚至可以是空方法體,然后由子類選擇自己所感興趣的方法來覆蓋。
- 某些場合下,只靠純粹的接口不能滿足類與類之間的協調,還必需類中表示狀態的變量來區別不同的關系。abstract的中介作用可以很好地滿足這一點。
- 規范了一組相互協調的方法,其中一些方法是共同的,與狀態無關的,可以共享的,無需子類分別實現;而另一些方法卻需要各個子類根據自己特定的狀態來實現特定的功能
典型的是包含主訂單和子訂單的系統中應該使用抽象類,因為他們有共享的業務邏輯不需要子訂單類實現,同時也有各子訂單必須實現訂單的操作約定
14、什么是進程,什么是線程,什么是AppDomain?三者有什么區別?請結合實際場景說明你在項目中的使用情況。
答:進程是系統進行資源分配和調度的單位;線程是CPU調度和分派的單位,一個進程可以有多個線程,這些線程共享這個進程的資源。
AppDomain表示應用程序域,它是一個應用程序在其中執行的獨立環境。是CLR的運行單元,它可以加載Assembly、創建對象以及執行程序。AppDomain是CLR實現代碼隔離的基本機制。AppDomain被創建在進程中,一個進程內可以有多個AppDomain。一個AppDomain只能屬於一個進程。AppDomain是個靜態概念,只是限定了對象的邊界;線程是個動態概念,它可以運行在不同的AppDomain。一個AppDomain內可以創建多個線程,但是不能限定這些線程只能在本AppDomain內執行代碼。每個appdomain都有一個托管堆,每個托管堆內有兩塊區域,一塊存儲對象,一塊存儲對象元數據(靜態成員)。
15、請談一下你對泛型的理解。請結合實際場景說明你在項目中的使用情況。
1、所謂泛型,即通過參數化類型來實現在同一份代碼上操作多種數據類型,泛型編程是一種編程范式,它利用“參數化類型”將類型抽象化,從而實現更靈活的復用。 C#泛型賦予了代碼更強的類型安全,更好的服用,更高的效率,更清晰的約束。
泛型的意義何在?類型安全和減少裝箱、拆箱並不是泛型的意義,而是泛型帶來的兩個好處而已(或許在.net泛型中,這就是明顯的好處) 泛型的意義在於—把類型作為參數,它實現了代碼見的很好的橫向聯系,我們知道繼承為了代碼提供了一種從上往下的縱向聯系, 但泛型提供了方便的橫向聯系(從某種程度上說,它和AOP在思想上有相同之處)
2、泛型類型參數:在【泛型類型】或【方法定義】中,類型參數是客戶端在實例化泛型類型的變量時指定的特定類型的占位符。就是那個list<T>中的T
類型參數的約束:在定義泛型類時,可以對客戶端代碼能夠在實例化類時用於類型參數的類型種類施加限制。 如果客戶端代碼嘗試使用某個約束所不允許的類型來實例化類 ,則會產生編譯時錯誤。 這些限制稱為約束。 約束是使用 where 上下文關鍵字指定的。 采用“基類、接口、構造器、值類型、引用類型”的約束方式來實現對類型能數 的“顯式約束“, C#泛型要求對“所有泛型類型或泛型方法的類型參數”的任何假定,都要基於“顯示的約束”,以維護C# 所要求的類型安全
16、請談一下你對同步和異步,多線程的理解。請結合實際場景說明你在項目中的使用情況。
並發:同時處理多件事情,在處理第一個請求時同時響應第二個請求;
同步:同步就是順序執行,執行完一個再執行下一個,需要等待、協調運行,同步 調用在繼續之前等待響應或返回值。如果不允許調用繼續,就說調用被阻塞 了
異步:並發的一種形式,(1)它采用回調機制,避免產生不必要的線程。(2)多線程也可以成為實現程序異步的一種方式,在這里 異步和多線程並不是一個同等關系,異步是最終目的,多線程只是我們實現異步的一種手段(這違反了異步操作的本質)
多線程:並發的另一種形式,它采用多個線程來執行程序。對多個線程的管理使用了線程池。
並行處理:把正在執行的大量任務,分割成小塊分配個多個運行的線程,線程池是存放任務的隊列,這個隊列能根據需要自行調整。由此產生了並行處理這個概念,多線程的一種,而多線程是並發的一種
異步優缺點:
因為異步操作無須額外的線程負擔,並且使用回調的方式進行處理,在設計良好的情況下,處理函數可以不必使用共享變量(即使無法完全不用,最起碼可以減少 共享變量的數量),減少了死鎖的可能。當然異步操作也並非完美無暇。編寫異步操作的復雜程度較高,程序主要使用回調方式進行處理,與普通人的思維方式有些出入,而且難以調試。
多線程優缺點:
多線程的優點很明顯,線程中的處理程序依然是順序執行,符合普通人的思維習慣,所以編程簡單。但是多線程的缺點也同樣明顯,線程的使用(濫用)會給系統帶來上下文切換的額外負擔。並且線程間的共享變量可能造成死鎖的出現
兩者適用范圍:
在了解了線程與異步操作各自的優缺點之后,我們可以來探討一下線程和異步的合理用途。我認為:當需要執行I/O操作時,使用異步操作比使用線程+同步 I/O操作更合適。I/O操作不僅包括了直接的文件、網絡的讀寫,還包括數據庫操作、Web Service、HttpRequest以及.net Remoting等跨進程的調用。
而線程的適用范圍則是那種需要長時間CPU運算的場合,例如耗時較長的圖形處理和算法執行。但是往往由於使用線程編程的簡單和符合習慣,所以很多朋友往往會使用線程來執行耗時較長的I/O操作。這樣在只有少數幾個並發操作的時候還無傷大雅,如果需要處理大量的並發操作時就不合適了。
17 線程池和TPL
線程池能管理很多個線程。和手動創建一個線程來執行特定操作不同,我們將工作任務扔到線程池中,它會選擇合適的線程,然后去執行我們給定的方法。線程池通過限制創建線程的總數,根據給定的工作量來決定創建合適的線程數量,降低了在極端情況下,如處理量比較少的情況下創建和銷毀線程的開銷,幫助我們解決了對系統資源的獨占和過度使用。
TPL=>TASK Parallel Library 並行庫,它是基於線程池的, 一種框架要使得並行執行不會產生太多的線程,能夠保證工作量能夠平均的分配到所有線程上,並且能夠報告錯誤和產生可靠地結果,這就是並行庫Task Parallel Library的工作,任務並行化是指通過一系列API將大的任務分解成一系列小的任務,然后再多線程上並行執行 (TPL)並行庫有一系列API能夠基於線程池同時管理成千上萬個任務。TPL的核心是System.Threading.Tasks類,他代表一個小的任務,“Task是一種對線程和線程池里面的工作項的一種結構話抽象
18、請說明using和new的區別。
new有兩種用法:1.實例化一個對象 2.聲明隱藏方法
1、using導入其它命名空間中定義的類型,這樣,您就不必在該命名空間中限定某個類型的使用
2、using為命名空間或類型創建別名 using Project = PC.MyCompany.Project; (2)為類去別名,右邊不能有開放式泛型類型list<T>,必須會LIST<INT>這種
3、using提供一種能確保正確使用Idisposable對象的比較方便的語法;(idisposable接口有一個方法DIspose()我們一般用它釋放對象占用的資源),它定義了一個范圍,在此范圍內的末尾將執行dispose方法(即使范圍內發生異常也會執行dispose()方法)
19、 dynamic 動態類型:標記了dynamic標記的表達式在運行時才會被解析。編譯時,不會被解析。可以用dynamic表達式調用 類型成員。它的出現方便了開發人員使用反射或者與其他非c#組建通信(例如com,例如html dom)。dynamic被編譯后,實際是一個 object類型,只不過編譯器會對dynamic類型進行特殊處理,讓它在編譯期間不進行任何的類型檢查,而是將類型檢查放到了運行期。
20、反射和序列化:
允許在運行時發現並使用編譯時還不了解的類型及成員
1 由於反射嚴重依賴字符串,造成反射在編譯時無法保證類型安全,例如Type.GetType("int")
2 反射速度慢,使用反射時類型及其成員的名稱在編譯時未知,你要用字符串名稱標識每個類型及其成員,然后在運行時發現他們。反射機制會不停的在程序集的元數據中執行字符串搜索,字符串搜索執行的是不區分大小寫的比較。從而影響速度。使用反射調用方法也會影響性能。盡量避免使用反射來訪問字段或調用方法
反射:程序集包含模塊,而模塊包含類型,類型又包含成員。反射則提供了封裝程序集、模塊和類型的對象。您可以使用反射動態地創建類型的實例,將類型綁定到現有對象,或從現有對象中獲取類型。然后,可以調用類型的方法或訪問其字段和屬性
序列化:序列化是將對象轉換為容易傳輸的格式的過程。例如,可以序列化一個對象,然后使用 HTTP 通過 Internet 在客戶端和服務器之間傳輸該對象。在另一端,反序列化將從該流重新構造對象。
21、特性:
特性是為程序添加元數據的一種機制(存儲在程序文件里的元數據),特性本身就是用來描述數據的元數據(metadata),通過它可以給編譯器提供指示或者提供對數據的說明,特性是一個對象,它可以加載到程序集及程序集的對象中,這些對象包括 程序集本身、模塊、類、接口、結構、構造函數、方法、方法參數等,加載了特性的對象稱作特性的目標。
.net內置的常見特性有:ObsoleteAttribute,使用方式:[Obsolete("該方法已經過期")] 此時在你調用打了標記的方法時,會發出警告消息,來提醒你該方法已經過期
自定義特性:
1、自定義特性類與定義一個類沒有什么區別,只需要繼承自Attribute基類即可
2、特性類(metadata)本身需要三個特性去描述它Serializable、AttributeUsage 和 ComVisible,由於特性本身就是用來描述數據的元數據,所以描述元數據的特性被又稱為元元數據meta-metadata,大多數情況下我們只需要掌握AttributeUsage 特性就好了,該特性有三個參數:
ValidOn:這是特性應用位置參數,定義了一個自定義特性能夠應用到那種編程元素上,是方法還是屬性
AllowMultiple:命名參數,它指定一個特性能否在一個程序集中被多次使用
Inherited:命名參數,用於控制一個自定義特性是否能被該類型的子類繼承
其中,位置參數ValidOn使用AttributeTargets枚舉
例如:[AttributeUsage(AttributeTargets.Method)] //控制自定義特性的使用,該特性就只能用於方法上面{可以不設置}
一般通過反射來遍歷檢查應用的特性信息,我們可以借助該信息來做權限,安全,登錄狀態驗證
21 擴展方法:
如果要給已編譯好的類添加新功能,開發人員一般只能從這個類中派生一個基類,或者更改這個類的源代碼方式;
擴展方法的出現解決了這個問題,我們可以在一個單獨的類中對已經存在的類進行擴展,為其添加擴展方法並不需要對源代碼進行改動或繼承基類
定義擴展方法:
* 1、擴展方法必須要定義到一個靜態類中,擴展方法本身也必須是靜態的
* 2、擴展方法首個參數必須是this 后面緊跟擴展的類 如:static void fun (this int i)
* 3、擴展方法可以被正確的對象實例調用 也可以使用靜態類名進行靜態調用
* 4、擴展方法的名稱不能與要擴展的類名相同 編譯時,擴展方法的優先級要低於擴展類本身方法的優先級
* 5、不能再擴展方法中直接訪問擴展類的成員變量
22、 表達式樹
表達式樹提供一個將可執行代碼轉換成數據的方法,如果你要在執行代碼之前修改或轉換此代碼他將變得很有價值。
表達式樹被創建是為了制造一個像將查詢表達式轉換成字符串以傳遞給其他程序並在那里執行這樣的轉換任務。把代碼,轉換成數據,然后分析數據發現其組成部分,最后轉換成可以傳遞到其他程序的字符串,Linq to SQL,實際上它就是C#作為源語言,SQL作為目標語言的一個編譯過程, 一個LINQ to SQL查詢不是在你的C#程序里執行的。它會被轉換成SQL,通過網絡發送,最后在數據庫服務器上執行
表單式樹有四個屬性:
* Body:得到表達式的主體
* Parameters:得到lambda表達式的參數
* NodeType:獲取樹的節點的ExpressionType共45種不同值,包含所有表達式節點各種可能的類型,例如返回常量,例如返回參數,例如取兩個值的小值(<),例如取兩個值的大值(>),例如將值相加(+),等等。
23、在日常開發中你常用哪些設計模式?分別解決什么問題?請結合實際場景說明你在項目中的使用情況。
簡單工廠、工廠方法、策略模式、抽象工廠模式、單利模式(將對象的構造函數設置為私有的,阻止外界直接實例化它,必須通過訪問靜態實例化屬性/方法來創建)
24 CLR名詞解釋
CLR(common language runtime)公共語言運行時:clr 公共語言運行時,核心功能是內存管理,程序集加載,安全性,異常處理和線程同步。面向clr的所有語言都可以使用核心功能。在運行時clr不關心開發人員使用的是什么語言開發的源代碼,因為針對每種語言都有一個面向clr的代碼編譯器,例如c#有針對c#的編譯器,c++也有它自己的編譯器。編譯器將其最終生成托管模塊。
托管模塊: 托管模塊是32位或者64位的windows可移植執行體pe32文件。需要clr才能運行。它包含四部分內容
程序集和托管模塊:clr實際不和托管模塊工作,它和程序集工作。程序集是一個抽象概念,他是一個或多個托管模塊/資源文件的邏輯分組,程序集是重用和安全性以及版本控制的最小單元
托管代碼和非托管代碼:
在CLR監視下運行的程序屬於“托管代碼”,而不再CLR監視下直接在裸機上運行的程序屬於“非托管代碼”,非托管代碼可以對系統進行低級控制,可以按照自己的想法管理內存,更方便的創建線程。c++編譯器默認生成包含非托管代碼的模塊,並在運行時操作非托管內存數據。這些模塊不需要clr就可運行,c++的編譯器是獨一無二的,它允許開發人員同時寫托管代碼和非托管代碼,並生成到同一模塊中。
FCL(framework class library) framework類庫:
是一組DLL程序集的統稱,是微軟發布的數千個類型的定義,包含了很多輔助功能。開發人員可以利用這些程序集創建應用程序,例如:windows控制台,windows服務,web應用等
CTS (common type system) 通用類型系統:
CLR一切都圍繞類型展開,類型向應用程序和其他類型公開功能,類型是clr的根本,所以微軟制訂了正式的規范來描述類型的定義和行為;例如cts規范規定了一個類型可以有0個或多個成員,成員包括字段,方法,屬性,事件。cts他還規定了類型和成員的訪問規則,例如標記為public還是product等。
CLS (common language specification)公共語言運行規范:
CLR集成了所有的語言,用一個語言創建的對象在另外一個語言中具有和它自己語言創建的對象同等的地位,但是各種語言在語法上存在很大差別,要創建其他語言都能訪問的類型只能從自身語言床挑選其他語言可識別的功能;微軟提供了一種語法規則,凡是符合cls公共語言允許規范的功能就能在其他語言中同樣符合其規范的功能訪問。它是cts/clr的一個子集。
實踐題: