改善C#程序的方法

寫在開頭: 

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

https://www.iteye.com/blog/dsqiu-2029701 //文章

https://www.cnblogs.com/ricolee/category/1199290.html //字符串深入理解

一:屬性

屬性和方法一樣。也可以是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);
}

總結:

<1. 引用類型中Equals是和ReferenceEquals()是一樣的,比較的是引用。如果是string的比較( string a="aa";        string b = "aa";),因爲有內存優化,所以Equals比較出來的引用一樣的。

<2. 值類型中Equals和==是一樣的。比較的是值。

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

2019/10/28複習:

"==" 和 “Equals” :兩者都是如果是比較的是值類型,即比較的是值,如果比較的引用類型(自定義),則比較的是引用。

所以爲了明確有種方法是比較的引用相等性,則Object.ReferenceEquals

FCL:框架類庫(Framework Class Library)

對於string類型,微軟覺着它的作用更加接近於值類型,因此在FCL中,string的比較被重載爲針對 "類型的值"  的比較,而不是針對 “引用本身” 的比較。

[ 在需要與默認實現不同的比對行爲時就需要重寫 ]。

https://www.cnblogs.com/michaelxu/archive/2008/05/06/1184385.html  //字符串賦值問題和比較問題解釋:

https://gameinstitute.qq.com/community/detail/114056  //字符串拘留在系統字符串的解釋

<1.字符串是引用類型。

<2.字符串的string a="abc"   string b=a; 兩個的引用指向的是同一個對象,但是和其他的引用類型不同,改變a不會改變b,因爲在改變a的時候,又創建了一個新的字符串(這也是效率低的問題),所以變a,不會改變b.

<3.比較。string a="abc"  string b="abc",比較時

  當我們用System.Object.Equals(a,b)比較時,返回值是true;按理說str1和str2應該指向不同的空間,應該返回false纔對啊。原來Equals有三個版本:   

public   override   bool   Equals(object);   
public   bool   Equals(string);   
public   static   bool   Equals(string,   string); 

  前兩個實例方法內部會調用CompareOrdinal靜態方法,它會比較字符串中的各個字符,如果相等就返回true。第三個首先會檢查兩個引用指向的是否是同一個對象,如果是,就返回true,不再去比較各個字符了。在檢查是否是同一個對象時,因爲CLR使用了一種叫字符串駐留的技術,對於
string   str1="abc";
string   str2="abc";
當CLR初始化時,會創建一個內部的散列表,其中的鍵爲字符串,值爲指向託管堆中字符串的引用。剛開始,散列表爲空,JIT編譯器編譯方法時,會在散列表中查找每一個文本常量字符串,首先會查找"abc"字符串,並且因爲沒有找到,編譯器會在託管堆中構造一個新的指向"abc"的String對象引用,然後將"abc"字符串和指向該對象的引用添加到散列表中。
  接着,在散列表中查找第二個"abc",這一次由於找到了該字符串,所以編譯器不會執行任何操作,代碼中再沒有其它的文本常量字符串,編譯器的任務完成,代碼開始執行。執行時,CLR發現第一個語句需要一個"abc"字符串引用,於是,CLR會在內部的散列表中查找"abc",並且會找到,這樣指向先前創建的String對象的引用就被保存在變量s1中,執行第二條語句時,CLR會再一次在散列表中查找"abc",並且仍然會找到,指向同一個String對象的引用會被保存在變量s2中,到此s1和s2指向了同一個引用,所以System.Object.Equals(s1,s2)就會返回true了。

 

 

條款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());  //效率高,因爲調用的是值類型中的ToString()方法

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

StringBuilder

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

 StringBuilder和string的性能測試對比:

固定長度的StringBuilder和每次創建新的字符串的相比:StringBuilder性能強於string.即對一個字符串頻繁的操作用StringBuilder.

   StringBuilder sb = new StringBuilder(1000);
    void Update () {
        if (Input.GetMouseButtonDown(0))
        {
            string name = "";
            for (int row = 0; row < 1000; row++)
            {
                sb.Append("2");
            }
            Debug.Log(sb.ToString());
        }

        if (Input.GetMouseButtonDown(1))
        {

            string name = "";
            for (int row = 0; row < 1000; row++)
            {
                name += "2";
            }
            Debug.Log(name);
        }
    }

 

並不是把所有的字符串都換個StringBuilder,如果過程中創建了很多的StringBuilderd對象,那樣也不好。

 

 

 

枚舉:最好不要賦值  , 如果賦值的話最好從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 A_Books<int>();
        A_Books<string> astr = new A_Books<string>();

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

        A_Books<int> aint_1 = new 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中),編輯器就會自動將該對象引用提升到閉包對象中。

捕獲變量來實現閉包: 即把局部變量定義在Lambda 函數外部

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

下述代碼避免閉包:在委託函數外部定義局部變量,相當於把5個局部變量的對象引用提升到閉包對象中

閉包的實現過程: 通過捕獲變量來實現的閉包。通過閉包才能訪問到變量的預期值。

猜測這個委託類的創建是在調用時才生成的,所以在執行委託函數時,成員變量(下邊的row=5了,在輸出時,執行5次函數都是輸出5)

	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章 異步 多線程 任務 並行

https://blog.csdn.net/qq_36936155/article/details/78991050

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. 謹慎把數組或者集合作爲屬性

    因爲數據和集合都是引用類型,通過list2來初始化list1,   所以程序員1對 list進行修改,程序員2對list2修改會直接導致list的變化。

 <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. 使用事件訪問器替換公開的事件成員變量

    

 
儘量減少函數調用棧,用x = (x > 0 ? x : ­x);代替x = Mathf.Abs(x)

 

27 類中字段優化:假設基類中定義了一個public float a = 0;
但是派生類中只有部分派生類用到了這個變量,可以進行這樣的優化
1public float a {get;set;} 會增加4B
2public float bObstacle
{
get{ return 0;}
set { }
} 不產生CG
由此可見,在基類中通過第二種方式定義屬性,例如
public virtual float bObstacle
{
get{ return 0;}
set { }
}
然後在需要用到的派生類中重寫這個字段

 

get 屬性:

在c#get屬性中,不要進行賦值操作,或者其他操作,只用作返回數據使用。

因爲:如果int b=Student.age;   如果在年齡get裏進行了加減操作 ,那麼當b分配了內存但是沒有用到,有的程序員可能會刪除

int b=Student.age; 那麼在get裏的操作也會失效。

 

bug繼承關係注意點:

【不能從父類調用子類的方法,因爲子類的細節比父類多,父類並不知道子類的細節。【遊戲開發中需要注意這種問題】

【重寫虛函數時的調用流程: 先構造父類,因爲父類保存的基本的信息,再構造子類構造器。都構造完畢後,根據構造順序先執行父類的方法,再執行子類的方法】

隱藏父類方法:關鍵字new

對修飾符的理解:

在繼承中,子類會繼承父類所有的成員,和父類成員的修飾符private等無關,修飾符只是決定該成員是否可以在本類/類外部/培生類中訪問。

unity中父類有OnDestroy() 方法,在此方法中調用了父類的虛方法Destroy(),而子類重寫了父類的Destroy方法。如下圖:

解析:

此時刪除了go物件,則執行UIBase.Destroy() 【因爲子類也是繼承了父類的Destroy的,執行子類的重寫方法】,此時如果在子類中寫了go.OnDestroy(),則需要使用new關鍵字來隱藏父類基因,使用新的基因。就不會走老的基因了。

 

 

協程的一些理解:

整理得到:通過設置MonoBehaviour腳本的enabled對協程是沒有影響的,但如果 gameObject.SetActive(false) 則已經啓動的協程則完全停止了,即使在Inspector把gameObject 激活還是沒有繼續執行。也就說協程雖然是在MonoBehvaviour啓動的(StartCoroutine)但是協程函數的地位完全是跟MonoBehaviour是一個層次的,不受MonoBehaviour的狀態影響,但跟MonoBehaviour腳本一樣受gameObject 控制,也應該是和MonoBehaviour腳本一樣每幀“輪詢” yield 的條件是否滿足。

<1. 協程不受自身腳本的enable/disEable的影響;

<2. 雖然協程是在MonoBehaviour中開啓的,但是協程和MonoBehaviour協程和腳本是同級的,都受gameObject的影響;

<3. 由2知,所以在inspecter面板裏,把gameObject.active=false,即協程也會停止,但是再gameObject.active=true,協程不會開啓;

 

 

 

image

 

https://www.cnblogs.com/dotnet261010/p/9034594.html

 

C#中List集合使用Exists方法判斷是否存在符合條件的元素對象

https://blog.csdn.net/CAO11021/article/details/100213362

判斷testList中是否存在t.index==7的對象,存在true,否則false.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章