c# 誤區系列(二)

夢裡小探花發表於2020-10-21

前言

繼續整理誤區系列,可能會對剛入門的新手有些幫助,然後希望有錯誤的地方可以指出。

正文

關於泛型方法的確定

class Person<T>
{
   public void add(T a)
   {
   }
}

那麼請問這個add 是否是泛型方法。

初學者可能認為有泛型引數的就是泛型,其實不是的。

這個是為什麼呢?其實是這樣子的,當泛型型別確認的時候,那麼add 定義的時候就已經確定了型別。

比如說Person<string>,那麼這個T就是string,Person<T> 是泛型,但是Person<string>不是。

當T確定是string的時候,在方法申明的時候就已經是string了,而不存在泛型這個概念。

所有泛型的開銷沒有我們想象的這麼大,在應用中,甚至使用泛型效率更高,不是說泛型是優化,而是泛型幫我們避免了太多裝箱和拆箱操作。

比如說以前的ArrayList,當我們把int 存進去,是裝箱,使用int又是拆箱。

泛型方法是這樣的:

class Person<T>
{
   public void add<Q>(T a,Q b)
   {
   }
}

在每次呼叫add的時候都必須確認Q的型別。

可空型別是引用型別?

因為可空型別是可以為空的,那麼初學者可能就認為可空型別是引用型別了。

其實可空型別是值型別,這個是為什麼?看下其中的原理。

說是可空值型別,裡面包含著一個判斷位。

這個是什麼意思呢,比如說一個位元組表示0-255,那麼會多分配一位去判斷這個位元組是否為空。

就是多一個位去判斷是否為空。

那麼這樣我們是不是就可以隨便使用呢?

從記憶體和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,之所以叫做引用型別,是他們的值指向的位置。

那麼問題來了:

void doSomething(Student student)
{
}
doSomething(null);

那麼student是否有值?也是有值的,指向就是null,在引用型別中它本身就是一個物件。

未完,續。

相關文章