1. 程式人生 > >c# 誤區系列(二)

c# 誤區系列(二)

### 前言 繼續整理誤區系列,可能會對剛入門的新手有些幫助,然後希望有錯誤的地方可以指出。 ### 正文 #### 關於泛型方法的確定 ``` class Person { public void add(T a) { } } ``` 那麼請問這個add 是否是泛型方法。 初學者可能認為有泛型引數的就是泛型,其實不是的。 這個是為什麼呢?其實是這樣子的,當泛型型別確認的時候,那麼add 定義的時候就已經確定了型別。 比如說Person,那麼這個T就是string,Person 是泛型,但是Person不是。 當T確定是string的時候,在方法申明的時候就已經是string了,而不存在泛型這個概念。 所有泛型的開銷沒有我們想象的這麼大,在應用中,甚至使用泛型效率更高,不是說泛型是優化,而是泛型幫我們避免了太多裝箱和拆箱操作。 比如說以前的ArrayList,當我們把int 存進去,是裝箱,使用int又是拆箱。 泛型方法是這樣的: ``` class Person { public void add(T a,Q b) { } } ``` 在每次呼叫add的時候都必須確認Q的型別。 #### 可空型別是引用型別? 因為可空型別是可以為空的,那麼初學者可能就認為可空型別是引用型別了。 其實可空型別是值型別,這個是為什麼?看下其中的原理。 說是可空值型別,裡面包含著一個判斷位。 這個是什麼意思呢,比如說一個位元組表示0-255,那麼會多分配一位去判斷這個位元組是否為空。 ![](https://img2020.cnblogs.com/blog/1289794/202010/1289794-20201021065548018-1060045756.png) 就是多一個位去判斷是否為空。 那麼這樣我們是不是就可以隨便使用呢? 從記憶體和cpu的角度來說,一個可空會增加一位,會增加記憶體消耗。同樣每次使用的時候都要判斷是否為null,會增加cpu負擔。 既然是值型別,那麼就存在裝箱和拆箱過程,那麼這個過程有什麼不同嗎? 裝箱時檢查是否為null,如果為null則直接返回null,如果不是null則獲取值進行裝箱。 拆箱時如果不是null,則返回值,否則返回null。 所以在c# 不能把null 看做是某個具體的地址,0x00之類的,更多的是一個概念。 那麼問題來了,為什麼int 不能為空?或者值型別不能為空? 很多回答是這樣子的,值變數的本身是具體的值。那麼難道引用型別不是指的具體的地址嗎? 個人覺得是這樣子的,int 型別的定義就規定了多少位為(應用程式如果判斷是int的),具有某種穩定的結構,如果破壞這種結構,那麼就不是int了。所以int型別不能為null,這是int型別的定義。 #### 事件是一種特殊的委託? 個人認為這句話存在很大的問題,是一個概念性問題。 比如說,我們說正方形是一種特殊的長方形。 為什麼可以這麼說呢?來看一下長方形的定義。 長方形是有一個角是直角的平行四邊形。 長方形的性質為:兩條對角線相等;兩條對角線互相平分;兩組對邊分別平行; 兩組對邊分別相等;四個角都是直角;有2條對稱軸(正方形有4條); 具有不穩定性(易變形);長方形對角線長的平方為兩邊長平方的和;順次連線矩形各邊中點得到的四邊形是菱形。 從這個定義中,我們得知長方形包含了正方形,因為其中長方形並沒有定義長和寬不相等啊。 同樣正方形本身就是長方形,只是說正方形在長方形的條件下,增加了其他條件。 綜上所述,是可以這麼說的。 但是事件是一種特殊的委託,是真的不能這麼講,因為是兩種完全不同的概念。 什麼是事件? 1.事件的擁有者 2.事件成員(事件的本身) 3.事件響應者 4.事件處理器:本質上是一種回撥方法 5.事件的訂閱:誰響應誰訂閱 什麼是委託? 委託是一個類,它定義了方法的型別,使得可以將方法當作另一個方法的引數來進行傳遞,這種將方法動態地賦給引數的做法,可以避免在程式中大量使用If-Else(Switch)語句,同時使得程式具有更好的可擴充套件性。 你會發現這是兩種是不同的概念。那麼是如何產生這種誤解的呢? 看到網上大量流傳著:public delegate void EventHandler(object sender, EventArgs e); 這只是說明委託是事件的一種驅動方式,如果把事件認為是一種委託就比較狹隘了,因為有些業務用到事件,如果想到事件就想到委託,就會陷入到僵局中,這樣沒有去從新定義更復雜的事件。 可能這樣不好理解,舉一個例子,比如說觀察者模式,c# 中委託作為觀察者例子,但是觀察者和委託沒有任何直接關係,難道沒有委託,觀察者就不存在? 具體可見觀察者:https://www.cnblogs.com/aoximin/p/13726813.html #### datetime 是引用型別? 初學者看到datetime 有方法就認為是引用型別,因為值型別都更加簡單,沒有那麼多可操作的方法,然而datetime的確是值型別。 這裡涉及到一個問題,那麼就是值型別的定義上,值型別的判斷不是說存在的位置,也不是說值型別沒有方法,而是指這種型別的值是否具有一個穩定的結構(大小等)。 那麼是否值型別比引用型別效能更好呢? 這個肯定不是的。值型別(棧上)的優點在於,不用垃圾回收,不會因為型別標識而產生開銷,也不用解引用。儲存在堆上的值型別,直接和物件一起回收。 這裡解釋一下,為什麼不用垃圾回收,因為如果int 型別不可引用,表示在執行系統中沒有其地址了。再舉個例子,就是我們磁碟清空了,格式化了,資料還在,只是在它的執行系統中不認為其存在有效資料。 所以說為什麼值型別在建立的時候要清空分配的地址,是在使用的時候抹除的。 引用型別的有點在於傳遞,因為引用型別不用複製整個地址塊,只需要複製堆上物件的指定位置,32位是4個位元組,64是8個位元組。 #### 物件在c# 中預設傳遞是引用傳遞的 這個問題涉及於,這樣一個場景。 ``` void doSomething(Student student) { } doSomething(a); ``` 那麼問題是student是如何賦值的過程?是將這個a物件賦值給他嗎? 這個問題就是student這個變數存在堆上還是棧上了?student的值本身是地址,而地址是固定的型別(32位4個位元組,64位8個位元組),其實是值型別。 傳遞過程是將a的值傳遞給student,之所以叫做引用型別,是他們的值指向的位置。 ![](https://img2020.cnblogs.com/blog/1289794/202010/1289794-20201021075529675-1684975334.png) 那麼問題來了: ``` void doSomething(Student student) { } doSomething(null); ``` 那麼student是否有值?也是有值的,指向就是null,在引用型別中它本身就是一個物件。 #### 結 未完