寫在開頭:
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 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. 使用事件訪問器替換公開的事件成員變量
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,協程不會開啓;
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.