改善C#程序的方法 c# is和as的區別 c# is和as的區別

寫在開頭: html

http://www.cnblogs.com/luminji    157個建議_勘誤表c++

一:屬性算法

屬性和方法同樣。也能夠是virtual和abstract.數據庫

條款2:運行時常量(readonly)優於編譯時常量(const)

運行時常量優於編譯時常量【能正確運行纔是關鍵】。編譯時常量比運行時常量稍微塊一點,可是缺少靈活性。性能很是關鍵,其值永遠不變的狀況下,咱們才應該使用編譯時常量。編程

c# readonly 運行時常量【構造器一旦執行則不能對值進行修改】    const編譯時常量c#

編譯時常量編譯後會把該常量替換成常量的值,相似於c++的宏【編譯時常量只能夠用於基元類型(整數浮點數枚舉字符串)】。運行時編譯後任然是該變量的引用windows

條款3:操做符is或as優於強制轉型

操做符as和is都只檢查被轉換對象的運行時類型,並不執行其餘的操做。若是被轉換對象的運行時類型既不是所轉換的目標類型,也不是其派生類型,那麼轉型將告失敗跨域

as操做符不能用於值類型,int值類型,不能爲null.數組

as是直接轉安全

只有當咱們不能使用as操做符來進行類型轉換時,才應該使用is操做符。

若是咱們打算使用as來作轉型,那麼再使用is檢查就沒有必要了。直接將as操做符的運算結果和null進行比對就能夠了,這樣比較簡單

 

c# is和as的區別
is就是處於對類型的判斷。返回true和false。若是一個對象是某個類型或是其父類型的話就返回爲true,不然的話就會返回爲false。另外is操做符永遠不會拋出異常。代碼以下:

​System.Boolean b1 = (o is System.Object);//b1 爲true
​System.Boolean b2 = (o is Employee);//b2爲false

若是對象引用爲null,那麼is操做符老是返回爲false,由於沒有對象能夠檢查其類型,就像下面代碼同樣

if(o is Employee) 

{
   Employee e = (Employee) o;
   //在if語句中使用e
}

as :as必須和能夠爲NUll類型使用。轉int則不行 

Employee e = o as Employee;
if(e != null)
{
   //在if語句中使用e
}

這種as操做即使等同於上面代碼,同時只進行了1次的類型檢查,因此提升了性能。若是類型相同就返回一個非空的引用,不然就返回一個空引用。

 

 

 

條款4:使用Conditional特性代替#if條件編譯

條款5:老是提供ToString()方法

條款6:明辨值類型和引用類型的使用場合

    值類型用於存儲數據,引用類型用於定義行爲

條款7:將值類型儘量實現爲具備常量性和原子性的類型

條款8:確保0爲值類型的有效狀態

枚舉將None=0聲明出來

條款9:理解幾個相等判斷之間的關係

ReferenceEquals(): 無論比較的是引用類型仍是值類型,該方法都判斷的是「引用相等」,而非「值相等」,意味着若是咱們使用此來比較兩個值類型,其結果永遠返回false。即便咱們將一個值類型和自身進行比較,ReferenceEquals()的返回值還是false。致使這種結果的緣由在於裝箱

Object.Equals() 默認是引用判斷,可是值類型例外,判斷值類型時須要重寫Equals()方法。若是兩個值類型變量的類型相同,而且內容一致,這兩個變量才被認爲相等。

判斷是否引用的同一個對象時的注意點:    string a="aa";        string b = "aa"; 兩個比較都是相等的。這是由於系統並無給字符串b分配內存,只是將"aa"指向了b。因此a和b指向的是同一個字符串(字符串在這種賦值的狀況下作了內存的優化)
      

//靜態Object.Equals()方法的實現
public static bool Equals( object left, object right )
{
  // 檢查是否引用相等。
  if (left == right ) //System.object類型的==實現的是引用相等
    return true;
  // 二者同時爲null引用的狀況在上面已經處理。
  if ((left == null) || (right == null))
    return false;
  return left.Equals (right);
}

相等的數學屬性:自反(任何對象都與其自身相等)、對稱(相等判斷的順序是可有可無的)和可傳遞(a=b b=c 則a=c)

條款10:理解GetHashCode()方法的缺陷

注意; a="aa" b="aa"  unity中能夠經過散列碼GetHashCode()間接的查看兩個變量的地址是否相等,可是數組的地址是連續存儲的,可是輸出的散列碼確實同樣的???。

條款11:優先採用foreach循環語句

int [] foo = new int[100]; 
// 循環1:
foreach ( int i in foo)
  Console.WriteLine( i.ToString( ));
// 循環2:
for ( int index = 0;  index < foo.Length;  index++ )
  Console.WriteLine( foo[index].ToString( ));
// 循環3:
int len = foo.Length;
for ( int index = 0;  index < len;  index++ )
  Console.WriteLine( foo[index].ToString( ));

c#1.0以上則第一個最好(c#1.0的化 第二個最好【由於第一個由裝箱】)。

在1.0版本的編譯器產生的代碼中,在數組上使用foreach語句其實是經過IEnumerator接口來遍歷數組,而這會致使裝箱與拆箱操做:遍歷類型=(遍歷類型)Current(接口類型);  1.0之後用的是for來遍歷的

  foreach語法簡潔   自帶finally{ dispose()  }  釋放內存 。

擴展: Unity5.5版本以後修復了foreach的GC  http://www.mamicode.com/info-detail-2103245.html

第三個最差:

緣由解析:安全託管環境中每一個內存都會檢查,而經過將Length變量放到循環以外,實際上阻礙了JIT編譯器移除循環中的範圍檢查。

反編譯後: 

int len = foo.Length;
for ( int index = 0;  index < len;  index++ )
{
  if ( index < foo.Length )
    Console.WriteLine( foo[index].ToString( ));
  else
    throw new IndexOutOfRangeException( );
}

CLR 會在訪問每個特定數組元素以前,產生一個數組界限(並不是上面的len變量)測試。C#編譯器和JIT編譯器能夠確保循環中的數組界限是安全的。只要循環變量不是數組的Length屬性,每一次迭代時都會執行數組界限檢查。破壞了JIT自己的優化

 

 

《編寫高質量代碼改善C#程序的157個建議》

string:

1.  值類型轉string時,須要重寫ToString(),使其調用值類型中的ToString()方法。由於值類型中的ToString()時非託管代碼,能夠直接操做內存來完成操做,效率高不少。

Debug.LogError("wyj "+9);                     //須要裝箱
        Debug.LogError("wyj " + 9.ToString());  //效率高

2.  字符串拼接時,使用StringBuilder。若是沒有先定義長度的話,則默認分配長度未16。當字符串小於16時,不會重寫分配。32>=str>=16時,則重寫分配,使之成爲16的倍數。注意指定的長度搖合適,過小須要頻繁分配內存。

3.  Format格式化,內部是使用的stringbuilder.

枚舉:最好不要賦值  , 若是賦值的話最好從0開始

4.重載運算符:

用戶本身定義的運算方式,通常用於對幾個對象之間內部進行的一些操做。

5.  重寫Equals時也要重寫GetHashCode

  若是自定義對象被用做基於散列集合的鍵,則建議重寫Equals方法。查詢時是基於key值的HashCode來查找鍵值的。【若是須要全部new的對象當成一個key,即須要重寫HashCode(),包裝一個int值的HashCode來看成該對象建立的全部對象的HashCode();

  字符串不一樣到那時產生的HashCode()是同樣的狀況和緣由。獲得的哈希值是int型,而若是是字符串,字符串的長度和這個值的大小是正比,過長的字符串會致使這個值超過int.max,因此會哈希值同樣的狀況,解決方案是在這個哈希值的前邊把方法名加上。

string str1 = "ABCDEa123abc";
        string str2 = "ABCDFB123abc";
        Debug.Log(str1.GetHashCode()+"          "+ str2.GetHashCode());
    }
    public int hashCode()
    {
        int h = hash;  // hash默認值爲0
        int len = count;// count是字符串的長度
        if (h == 0 && len > 0)
        {
            int off = offset;  // offset 是做爲String操做時做爲下標使用的
            char val[] = value;// value 是字符串分割成一個字符數組
            for (int i = 0; i < len; i++)
            {
                // 這裏是Hash算法體現處, 能夠看到H是一個哈希值,每次是將上次一算出的hash值乘以31 而後再加上當前字符編碼值,
                //因爲這裏使用的是int確定會有一個上限,當字符長時產生的數值過大int放不下時會進行截取,一旦截取HashCode的正確性就沒法保證了,
                //因此這點能夠推斷出HashCode存在不相同字符擁有相同HashCode。
                h = 31 * h + val[off++];  
            }
            hash = h;
        }
        return h;
    }

14. 正確實現淺拷貝和深拷貝

 淺拷貝: 值類型拷貝的是值  引用類型拷貝的是引用

 深拷貝:  值類型拷貝的是值  引用類型拷貝的是引用指向的值

第2章 集合和LINQ

16. 元素數量可變的狀況下不該該使用數組

不要讓數組成爲大對象【>85000字節數】,大對象的回收效率低

17.多數狀況下使用foreach遍歷

  理由:語法簡潔   自帶finally{ dispose()  }  釋放內存 。

   for[索引器實現的]   foreach(迭代器實現)  

 foreach不應修改內部元素的緣由:   foreach對集合版本進行判斷,任何對集合的增刪改查都會使版本號+1 . MoveNext() 會進行版本號的檢查,有變更時會拋出異常【System.InvalidOperationException】。

foreach (int value in list)
            {
                Console.WriteLine("值: " + value);
                list.Remove(value);
            }

 

通常使用匿名函數或者lambda 來對數據進行查詢

第三章 泛型、委託和事件

32 老是優先使用泛型

若T指向的數據類型是一致的,那麼泛型對象間能夠共享靜態成員。可是爲了規避混淆,泛型中要避免申明靜態成員。

private void Start()
    {
        A_Books<int> aint = new global::A_Books<int>();
        A_Books<string> astr = new global::A_Books<string>();

        Debug.Log(A_Books<int>.num+"          " + A_Books<string>.num);  //6 6

        A_Books<int> aint_1 = new global::A_Books<int>();
        Debug.Log(A_Books<int>.num); //7
    }
}

public class A_Books<T>
{
    public static int num=5;
    public A_Books()
    {
        num++;
    }
}

泛型方法: 非泛型類型中的泛型方法,並不會在運行時的本地代碼中生成不一樣的類型。

泛型參數增長該泛型參數的行爲。編碼時多考慮對泛型進行約束

 

 

使用default爲泛型類型變量指定初始值:

當返回值是一個泛型類型時,則

37 . 使用lambda表達式代替匿名方法:

38. 當心閉包中的陷阱。

閉包: 指可以讀取其餘函數內部變量的函數。

 所謂閉包對象:若是匿名方法(Lambda)引用了某個局部變量(在for中),編輯器就會自動將該對象引用提高到閉包對象中。

 這樣即便代碼執行後離開了局部變量i的做用域【如for循環】,包含該閉包對象的做用於也還存在。

上述代碼避免閉包:

閉包的實現過程: 經過捕獲變量來實現的閉包。

List<MethodInvoke> list = new List<MethodInvoke> ();
	void TestFun()
	{
		for(int row=0;row<5;row++)
		{
			int count=row*10;
			list.Add(delegate
				{
					print(count);
					count=count+1;
				});
		}

		//每一個元素都是一個委託,每一個委託都是建立了一個新的count
                //若是把count換成是row,捕獲變量從5開始,猜想這個委託類的建立時在調用時才生成的
		foreach(MethodInvoke t in list)
		{
			t();
		}

		print("調用同一個委託");
		//此時count是上次foreach中 list[0]委託中已經建立過委託實例了,即該實例類中也有了count變量,
		//因此再次調用是上次調用後獲得的值=1。

		list[0]();//1
		list[0]();//2
		list[0]();//3

		print("調用另外一個委託");
		list[1]();//11
	}

40. 泛型參數兼容泛型接口的不可變型        泛型的可變性// 基礎不夠之後再研究 ???

協變: 讓返回值類型返回比聲明的類型派生程度【子類比父類派生程度大】更大的類型,就是協變。

逆變:方法的參數能夠是委託或者泛型接口的參數類型的基類。

out在c#4.0 新增功能,能夠在泛型接口和委託中使用,用來讓類型支持協變。

除非考慮到該委託聲明確定不會用於可變性,不然爲委託中的泛型參數指定out關鍵字將會擴展該委託的應用。

public delegate TResult Func<out TResult>();

 

第四章  資源管理

託管資源: 由CLR管理和分配

非託管:不受CLR  管理的對象,套接字,文件,數據庫連接,windows內核,com對象

 

53.必要時應將再也不使用的對象的引用賦值爲null

 引用賦值爲null 沒 必要的狀況: 

      局部變量和方法的參數變量,不管咱們是否在方法內部將局部變量賦值爲null,a=null該語句會被忽略。這也說明JIT編譯器是一個優化過的編譯器。若是是Release模式,則a=null都不會編譯進運行時。

 引用賦值爲null必要的狀況

     靜態字段,好比建立一個對象,該對象中有靜態字段,當該局部變量對象被釋放後,該對象中的靜態字段不會被釋放。由於靜態字段建立後,該「根」就一直存在。因此手動置爲null.  這也是最好少用靜態字段的緣由。

 

54. 爲無用字段標註不可序列化

55.利用定製特性減小可序列化的字段

 

第6章 異步 多線程 任務 並行

71.  區分異步和多線程應用場景

DMA(Direct Memory Access): 直接內存訪問,是一種不通過CPU而直接進行內存數據存儲的數據交換模式。幾乎不損耗CPU. CLR異步編程模型就是充分利用DMA功能釋放CPU壓力。 

多線程本質: 建立一個線程,一直等待獲取數據,一直佔着CPU資源。線程不是一個計算機硬件的功能,而是操做系統提供的一種邏輯功能,線程本質上是進程中一段併發運行的代碼,因此線程須要操做系統投入CPU資源來運行和調度。

異步本質:開始異步操做時,CLR把工做交給線程池中的某個線程進行完成。當開始IO操做時,異步會把工做線程還給線程池。至關於獲取工做不會再佔用CPU資源,直到異步完成,獲取數據結束後,異步纔會通知回調的方式通知線程池。先幹別的事,當它須要的數據準備完畢後,再會來幹這件事。

計算 密集型工做: 多線程,(例如耗時較長的圖形處理和算法執行)

IO 密集型工做: 採用異步機制。(文件,網絡數據修改,數據庫操做、Web Service、HttpRequest以及.Net Remoting等跨進程的調用)

 

 

多線程建立線程,一直等待,獲取數據,獲取完畢。異步線程池中的線程,等待。開始IO操做時,還給線程池,獲取完畢後回調。

異步,讓線程池中的一個線程獲取網頁,獲取後開始IO操做(讀取網頁),此時把線程還給線程池,直到異步完成,即獲取網頁完畢後,異步纔會經過回調的方式通知線程池。

72. 在線程同步中使用的信號量

EventWaitHandle 維護一個內核產生的布爾類型對象(「阻滯狀態」),若是值=false,那麼在上邊等待的線程就阻塞【應用程序域內的線程同步】

Semaphore: 維護一個內核產生的整形變量。值=0,則在上邊等待的線程就阻塞。>0解除阻塞,每解除一個其值減1.【應用程序域內的線程同步】

Mutex : 能夠跨域阻塞和解除阻塞。

lock鎖注意點:

1. 主要是鎖對象,不能鎖值類型【值拷貝方式】  ,

2. 不能鎖字符串,沒有必要並且很危險【若是兩個變量分配了相同內容的字符串,那麼兩個引用指的同一個內存,用了鎖後,實際鎖的時同一個對象,會致使程序崩潰】

3. 不能寫成lock(this)   會new幾個對象,達不到鎖定的目的。

同步鎖時很耗費時的。線程池中的線程默認是後臺線程。  建立的線程默認是前臺線程【默認isbackground=false 前臺線程不退出,應用程序的進程則一直存在,要殺死】

75.  線程並非實時當即啓動

76.警戒線程的優先級

77. 正確中止線程

問題: 和啓動線程同樣,不是想停就馬上停的。得幹完手頭要緊的活,好比如今在執行非託管代碼,引起異常得回到託管代碼中。

線程中止主要取決於工做線程是否能主動訪問調用者的中止請求。

標準的取消模式:協議式取消。

CancellationTokenSource cts=new CancellationTokenSource();

cts.Token.Register(fun()); //線程中止時的回調 

cts.Cancel();  //發送Cancel信號   線程中止

socket  1000臺客戶端異步技術 只需幾個線程就能夠了(取決於心跳頻率)

 

79. 使用TreadPool或BackgroundWorker代替Thread

80. Task代替ThreadPool

ThreadPool: 不支持線程的取消,完成,失敗通知。不支持線程執行的前後順序。

81. Parallel簡化同步狀態

82. 並行

第二部分 架構篇

第7章  成員設計

90.

<1 . 不要爲抽象類提供公開的構造方法,抽象類設計只是爲了繼承,而不是用於生成實例對象

 <2. 可見字段應該重構爲屬性,屬性和字段的區別:一個是方法,一個是字段

 <3. 謹慎把數組或者集合做爲屬性

 <4. 構造方法應初始化主要屬性和字段。【一個貓生下來就已經具有尾巴了】

 <5. 區別對待override和new.[new 重寫覆蓋了父類方法,至關於該類中的一個新方法,和父類中的方法沒有一點關係]

 <6. 避免在構造方法中調用虛方法

 <7. 成員優先考慮公開基類型或者接口

 <8 . 用params減小重複參數

 <9. 重寫時不該該使用子類參數

建議100: 靜態方法和實例方法沒有區別

101. 使用擴展方法,向現有類型「添加」方法

 

第八章 類型設計

 

103. 區分組合和繼承的應用場合

組合; 在新類A中聲明 類B,C,D的實例。【有一個的概念】

107. 區分靜態類和單例

靜態類不是一個真正的對象,可是單例類時一個對象。

109. 謹慎使用嵌套類

當某一個類須要訪問另外一個類型的私有成員時,才實現爲嵌套類

111. 避免雙重耦合【常見的解耦就是提煉接口】

112. 把現實世界中的對象抽象爲類【貓,狗】,將可複用對象圈起來就是命名空間【植物,動物】

 

第9章 安全性設計

考慮可能出現的最大值:定義加工資,最大值。checked{} 關鍵字i行覈實,會主動拋出異常

114. MD5 再也不安全 【窮舉法破解】

115. HASH 檢驗文件是否被纂改

116. 避免非對稱算法加密

117. ......

 

編碼規範和習慣:

 <1.  Company.Component 命名空間命名

 <2. 考慮命名空間使用複數,System.Books   不要System.AllBook

 <3. 用名詞和名詞組給類型命名    推薦ScoreManager    不要SoreManage

 <4. 考慮讓派生類的名字以基類名字做爲後綴

 <5. 泛型類型參數要以T做爲前綴

 <6. 以複數命名枚舉類型,以單數命名枚舉元素【Week  不要Day】

 <7. 用camelCasing命名私有字段和局部變量

 <8. 常量如下劃線的方式   TASK_STATE_CANCELED    s_ 靜態變量   

 <9. 考慮使用確定性的短語命名bool屬性  IsEnabled 

 <10.優先使用後綴做爲一個類型的信版本,不到不得已並不推薦  Book1   Book2

 <11. 委託和事件加上上級後綴 HttpDelegate()

 <12. 事件處理器函數採用組合式命名: Button_SizeChanged()

 

代碼整潔: 

代碼整潔的要求之一,就是儘可能減小代碼。如省略默認的訪問修飾符

<1. 使用表驅動法避免過長的if和switch分支

 

<2. 使用匿名方法,Lambda表達式代替方法   若是方法體小於3行

<3. 使用事件訪問器替換公開的事件成員變量

    

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

寫在開頭: 

http://www.cnblogs.com/luminji    157個建議_勘誤表

一:屬性

屬性和方法同樣。也能夠是virtual和abstract.

條款2:運行時常量(readonly)優於編譯時常量(const)

運行時常量優於編譯時常量【能正確運行纔是關鍵】。編譯時常量比運行時常量稍微塊一點,可是缺少靈活性。性能很是關鍵,其值永遠不變的狀況下,咱們才應該使用編譯時常量。

c# readonly 運行時常量【構造器一旦執行則不能對值進行修改】    const編譯時常量

編譯時常量編譯後會把該常量替換成常量的值,相似於c++的宏【編譯時常量只能夠用於基元類型(整數浮點數枚舉字符串)】。運行時編譯後任然是該變量的引用

條款3:操做符is或as優於強制轉型

操做符as和is都只檢查被轉換對象的運行時類型,並不執行其餘的操做。若是被轉換對象的運行時類型既不是所轉換的目標類型,也不是其派生類型,那麼轉型將告失敗

as操做符不能用於值類型,int值類型,不能爲null.

as是直接轉

只有當咱們不能使用as操做符來進行類型轉換時,才應該使用is操做符。

若是咱們打算使用as來作轉型,那麼再使用is檢查就沒有必要了。直接將as操做符的運算結果和null進行比對就能夠了,這樣比較簡單

 

c# is和as的區別
is就是處於對類型的判斷。返回true和false。若是一個對象是某個類型或是其父類型的話就返回爲true,不然的話就會返回爲false。另外is操做符永遠不會拋出異常。代碼以下:

​System.Boolean b1 = (o is System.Object);//b1 爲true
​System.Boolean b2 = (o is Employee);//b2爲false

若是對象引用爲null,那麼is操做符老是返回爲false,由於沒有對象能夠檢查其類型,就像下面代碼同樣

if(o is Employee) 

{
   Employee e = (Employee) o;
   //在if語句中使用e
}

as :as必須和能夠爲NUll類型使用。轉int則不行 

Employee e = o as Employee;
if(e != null)
{
   //在if語句中使用e
}

這種as操做即使等同於上面代碼,同時只進行了1次的類型檢查,因此提升了性能。若是類型相同就返回一個非空的引用,不然就返回一個空引用。

 

 

 

條款4:使用Conditional特性代替#if條件編譯

條款5:老是提供ToString()方法

條款6:明辨值類型和引用類型的使用場合

    值類型用於存儲數據,引用類型用於定義行爲

條款7:將值類型儘量實現爲具備常量性和原子性的類型

條款8:確保0爲值類型的有效狀態

枚舉將None=0聲明出來

條款9:理解幾個相等判斷之間的關係

ReferenceEquals(): 無論比較的是引用類型仍是值類型,該方法都判斷的是「引用相等」,而非「值相等」,意味着若是咱們使用此來比較兩個值類型,其結果永遠返回false。即便咱們將一個值類型和自身進行比較,ReferenceEquals()的返回值還是false。致使這種結果的緣由在於裝箱

Object.Equals() 默認是引用判斷,可是值類型例外,判斷值類型時須要重寫Equals()方法。若是兩個值類型變量的類型相同,而且內容一致,這兩個變量才被認爲相等。

判斷是否引用的同一個對象時的注意點:    string a="aa";        string b = "aa"; 兩個比較都是相等的。這是由於系統並無給字符串b分配內存,只是將"aa"指向了b。因此a和b指向的是同一個字符串(字符串在這種賦值的狀況下作了內存的優化)
      

//靜態Object.Equals()方法的實現
public static bool Equals( object left, object right )
{
  // 檢查是否引用相等。
  if (left == right ) //System.object類型的==實現的是引用相等
    return true;
  // 二者同時爲null引用的狀況在上面已經處理。
  if ((left == null) || (right == null))
    return false;
  return left.Equals (right);
}

相等的數學屬性:自反(任何對象都與其自身相等)、對稱(相等判斷的順序是可有可無的)和可傳遞(a=b b=c 則a=c)

條款10:理解GetHashCode()方法的缺陷

注意; a="aa" b="aa"  unity中能夠經過散列碼GetHashCode()間接的查看兩個變量的地址是否相等,可是數組的地址是連續存儲的,可是輸出的散列碼確實同樣的???。

條款11:優先採用foreach循環語句

int [] foo = new int[100]; 
// 循環1:
foreach ( int i in foo)
  Console.WriteLine( i.ToString( ));
// 循環2:
for ( int index = 0;  index < foo.Length;  index++ )
  Console.WriteLine( foo[index].ToString( ));
// 循環3:
int len = foo.Length;
for ( int index = 0;  index < len;  index++ )
  Console.WriteLine( foo[index].ToString( ));

c#1.0以上則第一個最好(c#1.0的化 第二個最好【由於第一個由裝箱】)。

在1.0版本的編譯器產生的代碼中,在數組上使用foreach語句其實是經過IEnumerator接口來遍歷數組,而這會致使裝箱與拆箱操做:遍歷類型=(遍歷類型)Current(接口類型);  1.0之後用的是for來遍歷的

  foreach語法簡潔   自帶finally{ dispose()  }  釋放內存 。

擴展: Unity5.5版本以後修復了foreach的GC  http://www.mamicode.com/info-detail-2103245.html

第三個最差:

緣由解析:安全託管環境中每一個內存都會檢查,而經過將Length變量放到循環以外,實際上阻礙了JIT編譯器移除循環中的範圍檢查。

反編譯後: 

int len = foo.Length;
for ( int index = 0;  index < len;  index++ )
{
  if ( index < foo.Length )
    Console.WriteLine( foo[index].ToString( ));
  else
    throw new IndexOutOfRangeException( );
}

CLR 會在訪問每個特定數組元素以前,產生一個數組界限(並不是上面的len變量)測試。C#編譯器和JIT編譯器能夠確保循環中的數組界限是安全的。只要循環變量不是數組的Length屬性,每一次迭代時都會執行數組界限檢查。破壞了JIT自己的優化

 

 

《編寫高質量代碼改善C#程序的157個建議》

string:

1.  值類型轉string時,須要重寫ToString(),使其調用值類型中的ToString()方法。由於值類型中的ToString()時非託管代碼,能夠直接操做內存來完成操做,效率高不少。

Debug.LogError("wyj "+9);                     //須要裝箱
        Debug.LogError("wyj " + 9.ToString());  //效率高

2.  字符串拼接時,使用StringBuilder。若是沒有先定義長度的話,則默認分配長度未16。當字符串小於16時,不會重寫分配。32>=str>=16時,則重寫分配,使之成爲16的倍數。注意指定的長度搖合適,過小須要頻繁分配內存。

3.  Format格式化,內部是使用的stringbuilder.

枚舉:最好不要賦值  , 若是賦值的話最好從0開始

4.重載運算符:

用戶本身定義的運算方式,通常用於對幾個對象之間內部進行的一些操做。

5.  重寫Equals時也要重寫GetHashCode

  若是自定義對象被用做基於散列集合的鍵,則建議重寫Equals方法。查詢時是基於key值的HashCode來查找鍵值的。【若是須要全部new的對象當成一個key,即須要重寫HashCode(),包裝一個int值的HashCode來看成該對象建立的全部對象的HashCode();

  字符串不一樣到那時產生的HashCode()是同樣的狀況和緣由。獲得的哈希值是int型,而若是是字符串,字符串的長度和這個值的大小是正比,過長的字符串會致使這個值超過int.max,因此會哈希值同樣的狀況,解決方案是在這個哈希值的前邊把方法名加上。

string str1 = "ABCDEa123abc";
        string str2 = "ABCDFB123abc";
        Debug.Log(str1.GetHashCode()+"          "+ str2.GetHashCode());
    }
    public int hashCode()
    {
        int h = hash;  // hash默認值爲0
        int len = count;// count是字符串的長度
        if (h == 0 && len > 0)
        {
            int off = offset;  // offset 是做爲String操做時做爲下標使用的
            char val[] = value;// value 是字符串分割成一個字符數組
            for (int i = 0; i < len; i++)
            {
                // 這裏是Hash算法體現處, 能夠看到H是一個哈希值,每次是將上次一算出的hash值乘以31 而後再加上當前字符編碼值,
                //因爲這裏使用的是int確定會有一個上限,當字符長時產生的數值過大int放不下時會進行截取,一旦截取HashCode的正確性就沒法保證了,
                //因此這點能夠推斷出HashCode存在不相同字符擁有相同HashCode。
                h = 31 * h + val[off++];  
            }
            hash = h;
        }
        return h;
    }

14. 正確實現淺拷貝和深拷貝

 淺拷貝: 值類型拷貝的是值  引用類型拷貝的是引用

 深拷貝:  值類型拷貝的是值  引用類型拷貝的是引用指向的值

第2章 集合和LINQ

16. 元素數量可變的狀況下不該該使用數組

不要讓數組成爲大對象【>85000字節數】,大對象的回收效率低

17.多數狀況下使用foreach遍歷

  理由:語法簡潔   自帶finally{ dispose()  }  釋放內存 。

   for[索引器實現的]   foreach(迭代器實現)  

 foreach不應修改內部元素的緣由:   foreach對集合版本進行判斷,任何對集合的增刪改查都會使版本號+1 . MoveNext() 會進行版本號的檢查,有變更時會拋出異常【System.InvalidOperationException】。

foreach (int value in list)
            {
                Console.WriteLine("值: " + value);
                list.Remove(value);
            }

 

通常使用匿名函數或者lambda 來對數據進行查詢

第三章 泛型、委託和事件

32 老是優先使用泛型

若T指向的數據類型是一致的,那麼泛型對象間能夠共享靜態成員。可是爲了規避混淆,泛型中要避免申明靜態成員。

private void Start()
    {
        A_Books<int> aint = new global::A_Books<int>();
        A_Books<string> astr = new global::A_Books<string>();

        Debug.Log(A_Books<int>.num+"          " + A_Books<string>.num);  //6 6

        A_Books<int> aint_1 = new global::A_Books<int>();
        Debug.Log(A_Books<int>.num); //7
    }
}

public class A_Books<T>
{
    public static int num=5;
    public A_Books()
    {
        num++;
    }
}

泛型方法: 非泛型類型中的泛型方法,並不會在運行時的本地代碼中生成不一樣的類型。

泛型參數增長該泛型參數的行爲。編碼時多考慮對泛型進行約束

 

 

使用default爲泛型類型變量指定初始值:

當返回值是一個泛型類型時,則

37 . 使用lambda表達式代替匿名方法:

38. 當心閉包中的陷阱。

閉包: 指可以讀取其餘函數內部變量的函數。

 所謂閉包對象:若是匿名方法(Lambda)引用了某個局部變量(在for中),編輯器就會自動將該對象引用提高到閉包對象中。

 這樣即便代碼執行後離開了局部變量i的做用域【如for循環】,包含該閉包對象的做用於也還存在。

上述代碼避免閉包:

閉包的實現過程: 經過捕獲變量來實現的閉包。

List<MethodInvoke> list = new List<MethodInvoke> ();
	void TestFun()
	{
		for(int row=0;row<5;row++)
		{
			int count=row*10;
			list.Add(delegate
				{
					print(count);
					count=count+1;
				});
		}

		//每一個元素都是一個委託,每一個委託都是建立了一個新的count
                //若是把count換成是row,捕獲變量從5開始,猜想這個委託類的建立時在調用時才生成的
		foreach(MethodInvoke t in list)
		{
			t();
		}

		print("調用同一個委託");
		//此時count是上次foreach中 list[0]委託中已經建立過委託實例了,即該實例類中也有了count變量,
		//因此再次調用是上次調用後獲得的值=1。

		list[0]();//1
		list[0]();//2
		list[0]();//3

		print("調用另外一個委託");
		list[1]();//11
	}

40. 泛型參數兼容泛型接口的不可變型        泛型的可變性// 基礎不夠之後再研究 ???

協變: 讓返回值類型返回比聲明的類型派生程度【子類比父類派生程度大】更大的類型,就是協變。

逆變:方法的參數能夠是委託或者泛型接口的參數類型的基類。

out在c#4.0 新增功能,能夠在泛型接口和委託中使用,用來讓類型支持協變。

除非考慮到該委託聲明確定不會用於可變性,不然爲委託中的泛型參數指定out關鍵字將會擴展該委託的應用。

public delegate TResult Func<out TResult>();

 

第四章  資源管理

託管資源: 由CLR管理和分配

非託管:不受CLR  管理的對象,套接字,文件,數據庫連接,windows內核,com對象

 

53.必要時應將再也不使用的對象的引用賦值爲null

 引用賦值爲null 沒 必要的狀況: 

      局部變量和方法的參數變量,不管咱們是否在方法內部將局部變量賦值爲null,a=null該語句會被忽略。這也說明JIT編譯器是一個優化過的編譯器。若是是Release模式,則a=null都不會編譯進運行時。

 引用賦值爲null必要的狀況

     靜態字段,好比建立一個對象,該對象中有靜態字段,當該局部變量對象被釋放後,該對象中的靜態字段不會被釋放。由於靜態字段建立後,該「根」就一直存在。因此手動置爲null.  這也是最好少用靜態字段的緣由。

 

54. 爲無用字段標註不可序列化

55.利用定製特性減小可序列化的字段

 

第6章 異步 多線程 任務 並行

71.  區分異步和多線程應用場景

DMA(Direct Memory Access): 直接內存訪問,是一種不通過CPU而直接進行內存數據存儲的數據交換模式。幾乎不損耗CPU. CLR異步編程模型就是充分利用DMA功能釋放CPU壓力。 

多線程本質: 建立一個線程,一直等待獲取數據,一直佔着CPU資源。線程不是一個計算機硬件的功能,而是操做系統提供的一種邏輯功能,線程本質上是進程中一段併發運行的代碼,因此線程須要操做系統投入CPU資源來運行和調度。

異步本質:開始異步操做時,CLR把工做交給線程池中的某個線程進行完成。當開始IO操做時,異步會把工做線程還給線程池。至關於獲取工做不會再佔用CPU資源,直到異步完成,獲取數據結束後,異步纔會通知回調的方式通知線程池。先幹別的事,當它須要的數據準備完畢後,再會來幹這件事。

計算 密集型工做: 多線程,(例如耗時較長的圖形處理和算法執行)

IO 密集型工做: 採用異步機制。(文件,網絡數據修改,數據庫操做、Web Service、HttpRequest以及.Net Remoting等跨進程的調用)

 

 

多線程建立線程,一直等待,獲取數據,獲取完畢。異步線程池中的線程,等待。開始IO操做時,還給線程池,獲取完畢後回調。

異步,讓線程池中的一個線程獲取網頁,獲取後開始IO操做(讀取網頁),此時把線程還給線程池,直到異步完成,即獲取網頁完畢後,異步纔會經過回調的方式通知線程池。

72. 在線程同步中使用的信號量

EventWaitHandle 維護一個內核產生的布爾類型對象(「阻滯狀態」),若是值=false,那麼在上邊等待的線程就阻塞【應用程序域內的線程同步】

Semaphore: 維護一個內核產生的整形變量。值=0,則在上邊等待的線程就阻塞。>0解除阻塞,每解除一個其值減1.【應用程序域內的線程同步】

Mutex : 能夠跨域阻塞和解除阻塞。

lock鎖注意點:

1. 主要是鎖對象,不能鎖值類型【值拷貝方式】  ,

2. 不能鎖字符串,沒有必要並且很危險【若是兩個變量分配了相同內容的字符串,那麼兩個引用指的同一個內存,用了鎖後,實際鎖的時同一個對象,會致使程序崩潰】

3. 不能寫成lock(this)   會new幾個對象,達不到鎖定的目的。

同步鎖時很耗費時的。線程池中的線程默認是後臺線程。  建立的線程默認是前臺線程【默認isbackground=false 前臺線程不退出,應用程序的進程則一直存在,要殺死】

75.  線程並非實時當即啓動

76.警戒線程的優先級

77. 正確中止線程

問題: 和啓動線程同樣,不是想停就馬上停的。得幹完手頭要緊的活,好比如今在執行非託管代碼,引起異常得回到託管代碼中。

線程中止主要取決於工做線程是否能主動訪問調用者的中止請求。

標準的取消模式:協議式取消。

CancellationTokenSource cts=new CancellationTokenSource();

cts.Token.Register(fun()); //線程中止時的回調 

cts.Cancel();  //發送Cancel信號   線程中止

socket  1000臺客戶端異步技術 只需幾個線程就能夠了(取決於心跳頻率)

 

79. 使用TreadPool或BackgroundWorker代替Thread

80. Task代替ThreadPool

ThreadPool: 不支持線程的取消,完成,失敗通知。不支持線程執行的前後順序。

81. Parallel簡化同步狀態

82. 並行

第二部分 架構篇

第7章  成員設計

90.

<1 . 不要爲抽象類提供公開的構造方法,抽象類設計只是爲了繼承,而不是用於生成實例對象

 <2. 可見字段應該重構爲屬性,屬性和字段的區別:一個是方法,一個是字段

 <3. 謹慎把數組或者集合做爲屬性

 <4. 構造方法應初始化主要屬性和字段。【一個貓生下來就已經具有尾巴了】

 <5. 區別對待override和new.[new 重寫覆蓋了父類方法,至關於該類中的一個新方法,和父類中的方法沒有一點關係]

 <6. 避免在構造方法中調用虛方法

 <7. 成員優先考慮公開基類型或者接口

 <8 . 用params減小重複參數

 <9. 重寫時不該該使用子類參數

建議100: 靜態方法和實例方法沒有區別

101. 使用擴展方法,向現有類型「添加」方法

 

第八章 類型設計

 

103. 區分組合和繼承的應用場合

組合; 在新類A中聲明 類B,C,D的實例。【有一個的概念】

107. 區分靜態類和單例

靜態類不是一個真正的對象,可是單例類時一個對象。

109. 謹慎使用嵌套類

當某一個類須要訪問另外一個類型的私有成員時,才實現爲嵌套類

111. 避免雙重耦合【常見的解耦就是提煉接口】

112. 把現實世界中的對象抽象爲類【貓,狗】,將可複用對象圈起來就是命名空間【植物,動物】

 

第9章 安全性設計

考慮可能出現的最大值:定義加工資,最大值。checked{} 關鍵字i行覈實,會主動拋出異常

114. MD5 再也不安全 【窮舉法破解】

115. HASH 檢驗文件是否被纂改

116. 避免非對稱算法加密

117. ......

 

編碼規範和習慣:

 <1.  Company.Component 命名空間命名

 <2. 考慮命名空間使用複數,System.Books   不要System.AllBook

 <3. 用名詞和名詞組給類型命名    推薦ScoreManager    不要SoreManage

 <4. 考慮讓派生類的名字以基類名字做爲後綴

 <5. 泛型類型參數要以T做爲前綴

 <6. 以複數命名枚舉類型,以單數命名枚舉元素【Week  不要Day】

 <7. 用camelCasing命名私有字段和局部變量

 <8. 常量如下劃線的方式   TASK_STATE_CANCELED    s_ 靜態變量   

 <9. 考慮使用確定性的短語命名bool屬性  IsEnabled 

 <10.優先使用後綴做爲一個類型的信版本,不到不得已並不推薦  Book1   Book2

 <11. 委託和事件加上上級後綴 HttpDelegate()

 <12. 事件處理器函數採用組合式命名: Button_SizeChanged()

 

代碼整潔: 

代碼整潔的要求之一,就是儘可能減小代碼。如省略默認的訪問修飾符

<1. 使用表驅動法避免過長的if和switch分支

 

<2. 使用匿名方法,Lambda表達式代替方法   若是方法體小於3行

<3. 使用事件訪問器替換公開的事件成員變量

    

相關文章
相關標籤/搜索