C#與C++比較概況:
C++ | C# |
---|---|
#include <iostream> using namespace std; using namespace China::GuangDong::ShenZhen; namespace China { namespace HuBei { namespace WuHan { class Foo { public: static void Func() { cout << "Hello World!" << endl; } }; } } }
|
using System; using System.IO; using China.GuangDong.ShenZhen; namespace China { namespace HuBei.WuHan { class Foo { public static void Func() { Console.WriteLine("Hello World!"); } } } }
|
C#具有如下一些特性:
1. C#是強類型語言,所有變量都必須清楚標記為某個特定數據類型
2. C#中基本類型、枚舉、結構體為值類型,數組、類(string、object及自定義類)、接口為引用類型
3. 值類型創建在棧上,引用類型創建在托管堆上(由GC來自動管理)
4. 局部變量、out參數變量要在顯示初始化賦值后才能訪問
5. 成員變量、static成員變量,在沒有被顯示初始化時,都會被編譯器在構造函數或靜態構造函數中默認賦初始值,其規則為:值類型為0或false,引用類型為null 詳見:en
6. 大小寫敏感:關鍵字、宏、變量名、函數名、命名空間名
7. 沒有全局變量、沒有全局靜態變量、沒有局部靜態變量、沒有全局函數,所有東西必須寫入結構體或類中
8. 所有類型(含值類型)直接或間接都從object類繼承
9. 類單根繼承,接口多繼承
10. 支持運算符重載(與c++相比,運算符重載函數為public static的)
11. 變量、屬性和函數默認為private類型
12. 將程序集添加到工程后,無需顯示包含其文件或導入包,帶上完整的命名空間即可使用其定義的類型
13. 有構造函數和靜態構造函數,沒有析構函數,通過引用計數機制進行垃圾回收
14. 成員函數和成員屬性默認不是virtual的
值類型與引用類型的補充說明:
值類型和引用類型都繼承自System.Object類,不同的是,值類型從System.Object的子類System.ValueType繼承。
System.ValueType本身是一個類類型,而不是值類型;System.ValueType沒有添加任何成員,但覆蓋了所繼承的一些方法,使其更適合於值類型。
如:System.ValueType重寫了Equals()方法,從而對值類型按照實例的值來比較,而不是引用地址來比較。
C#預定義基本類型
名稱 | CTS類型 | 范圍or精度 | 示例 |
sbyte | System.SByte | 8位:-128~127(-2^7~2^7-1) SByte.MinValue~SByte.MaxValue | -123、88、0x1C |
short | System.Int16 | 16位:-32768~32767(-2^15~2^15-1) Int16.MinValue~Int16.MaxValue | -123、88、0x1C |
int | System.Int32 | 32位:-2147483648~2147483647(-2^31~2^31-1) Int32.MinValue~Int32.MaxValue | -123、88、0x1C |
long | System.Int64 | 64位:-9223372036854775808~9223372036854775807(-2^63~2^63-1) Int64.MinValue~Int64.MaxValue | 123L、-123、88、0x1C |
byte | System.Byte | 8位:0~255(0~2^8-1) Byte.MinValue~Byte.MaxValue | 88、0x1C |
ushort | System.UInt16 | 16位:0~65536(0~2^16-1) UInt16.MinValue~UInt16.MaxValue | 88、0x1C |
uint | System.UInt32 | 32位:0~4294967295(0~2^32-1) UInt32.MinValue~UInt32.MaxValue | 88、0x1C、123U、225u |
ulong | System.UInt64 | 64位:0~18446744073709551615(0~2^64-1) UInt64.MinValue~UInt64.MaxValue | 88、0x1C、123UL、225ul |
float | System.Single | 32位單精度浮點數:6~9位有效數字 Single.MinValue~Single.MaxValue | -123、88、0x1C、100.0f、-235.68F |
double | System.Double | 64位雙精度浮點數:15~17位有效數字 Double.MinValue~Double.MaxValue | -123、88、0x1C、320.5、-0.0325 |
decimal | System.Decimal | 128位高精度浮點數:28~29位有效數字 Decimal.MinValue~Decimal.MaxValue | -123、88、0x1C、-3.0m、25.6M |
bool | System.Boolean | true、false | |
char | System.Char | 16位Unicode字符 Char.MinValue~Char.MaxValue | 'c'、'R'、\u0041、\n、'\x0061' |
一些用法:
int a = 2; int a = new int(); // 等價於int a = 0; int a = default (int); // 等價於int a = 0;
C#預定義引用類型
名稱 | CTS類型 | 說明 |
object | System.Object | 根類型,CTS中其他類型都是從Object派生而來(包括值類型) 基本方法(派生類可以重寫這些方法來實現自己的邏輯): bool Equals(Object) // 判斷兩個對象是否相等 int GetHashCode() // 獲取當前對象的HashCode(每個對象的HashCode是唯一的) Type GetType() // 獲取當前對象的類型 string ToString() // 將當前對象轉換成string類型 |
數組 | System.Array | |
string | System.String | Unicode字符串,"Hello World!\n" "" "你好" "c:\\1.txt" @"d:\Music\1.mp3" "\u6700\u540e\u4fee\u6539\u7684\u7248\u672c:"//最后修改的版本: Unicode在線轉換工具:http://tool.chinaz.com/tools/unicode.aspx 注:在@字符后的所有字符都看成原來的含義,甚至包含換行符(將字符串寫成多行時) 字符串雖然是引用類型,但用起來像值類型:原因是string重寫了賦值運算符 string s1 = "Hello"; string s2 = s1; // s1、s2都指向"Hello" s1 = "World"; // s1重新賦值指向"World",s2依然指向"Hello"
|
一些用法:
object o = null; object o = default(object);
基本類型與string間轉換
1. 基本類型 --> string,可以調用對應CTS類型中的ToString方法
2. string --> 基本類型,可以調用對應CTS類型中的Parse和TryParse方法 注:如果string不滿足轉換要求,Parse方法將會引發一個異常,TryParse方法不會引發異常,它會返回fasle。
各類型之間轉換
2. 使用Convert類進行基本類型、string、DateTime結構體之間的轉換
int i = 10; float f = i;//f=10 byte b = (byte)i;//b=10 string s1 = "123.5"; f = Convert.ToSingle(s1); //f=123.5 string s2 = "123abc"; i = Convert.ToInt32(s2); //拋出System.FormatException異常
3. 子類對象可以隱式轉換為父類對象,父類對象必須顯示轉換為子類對象(若類型不匹配,則拋出'System.InvalidCastException異常)
4. 值類型可以隱式轉換為object類型(裝箱),object類型必須顯示轉換為值類型(類型必須強匹配,int裝箱的object只能拆箱成int,不能是long、float等;若類型不匹配,則拋出'System.InvalidCastException異常)
checked / unchecked
1. 編譯器默認是關閉溢出檢查的,建議在測試時開啟該checked編譯選項
2. checked/unchecked開啟/關閉溢出檢查 注:若開啟了checked溢出檢查,運行時發生溢出會拋出OverflowException異常
byte b1 = 255; int i1 = int.MinValue; try { checked { try { unchecked { b1++; // 不對b1進行溢出檢查 } } catch (OverflowException) { Console.WriteLine("b1 OverflowException."); // 不會打印該log } CheckFunc(); // 不會對調用的函數進行溢出檢查 i1--; // 檢查發現i1溢出,拋出異常 } } catch (OverflowException) { Console.WriteLine("i1 OverflowException."); // 會打印該log }
注:checked或者unchecked只影響其包圍的語句,不會影響到包圍的語句內調用函數
is、as運算符
int i1 = 10; string s1 = "Hello"; object o1 = "Hi"; object o2 = 100; if (i1 is object) // true { Console.WriteLine("i1 is object"); } if (s1 is object) // true { Console.WriteLine("s1 is object"); } if (o2 is string) // false { Console.WriteLine("o2 is string"); } object i2 = i1 as object; // i1=10被裝箱 string s2 = o1 as string; // s2="Hi" string s3 = o2 as string; // s3=null
sizeof
1.只能用於值類型(基本類型、枚舉和只包含值類型的結構體)
2.只包含值類型的結構體需要在unsafe塊中使用
struct Color { public float R; public float G; public float B; public float A; } int i1 = 10; object i2 = 20; string s1 = "Hi"; Console.WriteLine(sizeof(int)); //4 //Console.WriteLine(sizeof(i1)); //編譯不過 Console.WriteLine(sizeof(double)); //8 Console.WriteLine(sizeof(TimeOfDay));//TimeOfDay枚舉類型的size為4 Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(i1)); //4 Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(i2)); //4 //Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(int));//編譯不過 unsafe // 編譯設置中需要勾選Allow unsafe code { Console.WriteLine(sizeof(Color)); // 16 } Console.WriteLine(System.Runtime.InteropServices.Marshal.SizeOf(s1)); //拋異常System.ArgumentException
typeof
namespace ConsoleApplication1 { enum TimeOfDay { Moring = -10, Afternoon = -11, Evenving } struct Color { public float R; public float G; public float B; public float A; } } Type t1 = typeof(TimeOfDay); Type t2 = typeof(Color); Console.WriteLine(t1);//ConsoleApplication1.TimeOfDay Console.WriteLine(t2);//ConsoleApplication1.Color Console.WriteLine(typeof(int));//System.Int32 Console.WriteLine(typeof(string));//System.String
可空類型? 派生於結構體Nullable<T>
1. 可空類型與其他可空類型之間的轉換,遵循非可空類型的轉換規則:如:int?可隱式轉換為long?、float?、double?和decimal?
2. 非可空類型與其他可空類型之間的轉換,遵循非可空類型的轉換規則:如:int可隱式轉換為long?、float?、double?和decimal?
3. 可空類型必須顯示地轉換為非可空類型(若可空類型為null,轉換時拋System.InvalidOperationException異常)
4. 可空類型與一元或二元運算符一起使用時,如果其中一個操作數或兩個操作數都是null,其結果就是null
int? a = null; int? b = a + 1;//b=null int? c = a * 2;//c=null int? d = 3;//d=3 //比較的2個數中只要有一個為空類型則為false if (a >= d) { Console.WriteLine("a>=d"); } else if (a < d) { Console.WriteLine("a<d"); } else { Console.WriteLine("Oh, My God!");//該行被打印 }
空合並運算符??
1. 空合並運算符:若第一個操作數不是null,表達式為第一個操作數的值;若第一個操作數為null,則表達式為第二個操作數的值
int? a = null; int? b = 3;//b=3 int c = a ?? 4;//a為空類型,則把??后面的數4賦值給c int d = b ?? 5;//b不為空類型,則把b賦值給d
空條件運算符?.和?[] 【c#6.0】
1. 實例在訪問成員或數組訪問元素前自動判空,不用顯示添加手動判空代碼,讓程序更優雅
int? len = customers?.Length; //null if customers is null 等價於int? len = (customers==null?null:customers.Length) Customer first = customers?[0]; // // null if customers or customers[0] is null 等價於Customer first = (customers==null?null:(customers.Length==0?null:customers[0])) int? count = customers?[0]?.Orders?.Count(); // 等價於int? count = (customers==null?null:(customers.Length==0?null:(customers[0]==null?null:(customers[0].Orders==null?null:customers[0].Orders.Count))))
條件語句
1. swith case可以把字符串當作測試變量
switch (country) { case "China": break; case "America": break; default: break; }
循環語句
1. foreach循環
foreach (int n in arrayOfInts) { Console.WriteLine(n); }
命名空間別名
除了C#與C++比較概況中提到的命名空間的用法外,C#還允許使用別名來簡化類型對命名空間的使用(注:通過::符號來使用)
using System; using InterSrv = System.Runtime.InteropServices; byte a = 3; int nLen = InterSrv::Marshal.SizeOf(a.GetType()); Console.WriteLine("Type is {0}, Length is {1}.", a.GetType().ToString(), nLen); // Type is System.Byte, Length is 1.
函數參數傳遞 【ref out】
int a1 = 1, b1 = 2; Func(a1, b1);//函數返回后,a1=1 b1=2 int a2 = 1, b2 = 2; FuncRef(ref a2, ref b2);//函數返回后,a2=2 b2=1 int a3 = 1;//由於out參數不會使用該初值,因此a3可以不用賦初值 FuncOut(out a3);//函數返回后,a3=5 CA oa1 = null; Func2(oa1);//函數返回后,oa1=null CA oa2 = null; FuncRef2(ref oa2);//函數返回后,oa2不為null,oa2.Id=100 CA oa3 = new CA();//由於out參數不會使用該初值,因此oa3可以不用賦初值 FuncOut2(out oa3);//函數返回后,oa3=null void Func(int a, int b) { int c = a; a = b; b = c; } void FuncRef(ref int a, ref int b) { int c = a; a = b; b = c; } void FuncOut(out int a) { //int b = a; //在函數中未給out參數變量a賦值之前,不能訪問a a = 5;//在函數返回之前必須要對a進行賦值 } void Func2(CA o) { o = new CA(); o.Id = 100; } void FuncRef2(ref CA o) { o = new CA(); o.Id = 100; } void FuncOut2(out CA o) { //bool b = (o != null);//在函數中未給out參數變量o賦值之前,不能訪問o o = null;//在函數返回之前必須要對o進行賦值 }
命名參數 【c#4.0】
string s = GetFullName(LastName: "Li", FirstName: "Lei"); string GetFullName(string FirstName, string LastName) { return FirstName + " " + LastName; }
缺省參數 【c#4.0】
string GetFullName(string FirstName, string LastName="Chen") { return FirstName + " " + LastName; }
數組型參數
params會把傳入各個參數存放在一個object數組中
1.params參數必須放在最后
2.函數只允許有一個params參數
public static void Test(int n, params object[] list) { Console.WriteLine(n); for (int i = 0; i < list.Length; i++) { Console.WriteLine(list[i]); } } Test(100, false, 3, "Test");//打印出:100 False 3 Test
類型推斷 【c#3.0】
1.推斷出了類型后,就不能改變類型了
2.類型確定后,就遵循強類型化規則
var name = "Jim Green"; var age = 20; var height = 1.825f; var sex = true; //var weight; 編譯不過,必須在聲明時初始化 //weight = 60.5; Console.WriteLine("name's type is {0}", name.GetType());//name's type is System.String Console.WriteLine("age's type is {0}", age.GetType());//age's type is System.Int32 Console.WriteLine("height's type is {0}", height.GetType());//height's type is System.Single Console.WriteLine("sex's type is {0}", sex.GetType());//sex's type is System.Boolean Object o = new Object(); var o1 = new Object(); var o2 = o; //var friend = null;編譯不過,不能為空
匿名類型
var與關鍵字new一起使用可以創建匿名類型;匿名類型只是一個從Object派生沒有名稱的類,該類的定義從初始化器中推斷
匿名類型名有編譯器按照一定規則產生,我們不應該使用任何該類型的反射
//生成一個包含string FirstName, string MiddleName, string LastName屬性的類型,並用該類型初始化出個名為captain的實例 var captain = new { FirstName = "James", MiddleName = "T", LastName = "Kirk" }; //生成一個包含string Location, int Area, double Population屬性的類型,並用該類型初始化出個名為china的實例 var china = new { Location = "Asia", Area = 960, Population = 1.3 }; // doctor與captain的變量名、類型、順序完全一致,為同一匿名類型;因此,不會創建新的類型,而是使用captain類型來初始化captain var doctor = new { FirstName = "Leonard", MiddleName = "T", LastName = "McCoy" }; Console.WriteLine("captain's type is {0}", captain.GetType());//captain's type is <>f__AnonymousType0`3[System.String,System.String,System.String] Console.WriteLine("china's type is {0}", china.GetType());//china's type is <>f__AnonymousType1`3[System.String,System.Int32,System.Double] Console.WriteLine("doctor's type is {0}", doctor.GetType());//doctor's type is <>f__AnonymousType0`3[System.String,System.String,System.String]
格式化輸出到控制台
格式符形如:{D[,D[:C[D]]]} -- D、D、D表示數字,C表示字母
D:后續參數序號,從0開始
D:占位數目(占位數少於變量自身數目時,不產生多余占位;貨幣符號算1個占位):為正數,表示右對齊;為負數,表示左對齊
D:整型數時,為整個數的位數,不夠時左邊補0;浮點數時,為小數點后的位數
C:① D -- 十進制格式
② E、e -- 科學計數法,默認小數點后的位數為6
③ F -- 浮點數格式
④ G -- 普通格式
⑤ N -- 數字格式,逗號表示千位符
⑥ P -- 百分數格式
⑦ X -- 十六進制格式
⑧ C -- 本地貨幣格式 中國區為:¥
int i = 262, j = 350; Console.WriteLine("{0}#{1}", i, j);//262#350 Console.WriteLine("{0,4}#{1,5}", i, j);// 262# 350 Console.WriteLine("{0,-4}#{1}", i, j);//262 #350 Console.WriteLine("{0,6:D5}#{1}", i, j);// 00262#350 int m = 26252, n = 350390; Console.WriteLine("{0,0:D}#{1,0:F}", m, n);//26252#350390.00 Console.WriteLine("{0,0:G}#{1,0:N}", m, n);//26252#350,390.00 Console.WriteLine("{0,0:E}#{1,0:e}", m, n);//2.625200E+004#3.503900e+005 Console.WriteLine("{0,0:X}#{1,0:P}", m, n);//668C#35,039,000.00% float f = 35.0f, g = -20.388f; Console.WriteLine("{0,7:F3}#{1,0:E2}", f, g);// 35.000#-2.04E+001 Console.WriteLine("{0,4:C2}#{1,0:e3}", f, g);//¥35.00#-2.039e+001 Console.WriteLine("{0,6:C1}#{1,0:P3}", f, g);// ¥35.0#-2,038.800%
字符串(16位unicode)
String處理不變的字符串,,不可被繼承(sealed),任何對String的改變都會引發新的String對象的生成
string s0 = "你好"; int ns0Len = s0.Length; // ns0Len = 2 char[] sz = new char[] { 'h', 'e', 'l', 'l', 'o' }; string s1 = "hello"; // 靜態創建字符串對象 string s2 = new string(sz); // 動態創建字符串對象 int ns1Len = s1.Length; // ns1Len = 5 int ns2Len = s2.Length; // ns2Len = 5 string s3 = "hello"; string s4 = new string(sz); string s5 = "Hello"; if (s1 == s2) Console.WriteLine("s1==s2");// true if (s1.Equals(s2)) Console.WriteLine("s1 Equals s2");// true if (Equals(s1, s2)) Console.WriteLine("s1 static Equals s2");// true if (ReferenceEquals(s1, s2)) Console.WriteLine("s1 ReferenceEquals s2");// false if (s1.CompareTo(s2) == 0) Console.WriteLine("s1 Compare s2");// true if (string.Compare(s1, s2) == 0) Console.WriteLine("s1 static Compare s2");// true if (s1 == s3) Console.WriteLine("s1==s3");// true if (s1.Equals(s3)) Console.WriteLine("s1 Equals s3");// true if (Equals(s1, s3)) Console.WriteLine("s1 static Equals s3");// true if (ReferenceEquals(s1, s3)) Console.WriteLine("s1 ReferenceEquals s3");// true if (s1.CompareTo(s3) == 0) Console.WriteLine("s1 Compare s3");// true if (string.Compare(s1, s3) == 0) Console.WriteLine("s1 static Compare s3");// true if (s2 == s4) Console.WriteLine("s2==s4");// true if (s2.Equals(s4)) Console.WriteLine("s2 Equals s4");// true if (Equals(s2, s4)) Console.WriteLine("s2 static Equals s4");// true if (ReferenceEquals(s2, s4)) Console.WriteLine("s2 ReferenceEquals s4");// false if (s2.CompareTo(s4) == 0) Console.WriteLine("s2 Compare s4");// true if (string.Compare(s2, s4) == 0) Console.WriteLine("s2 static Compare s4");// true if (s1 == s5) Console.WriteLine("s1==s5");// false if (s1.Equals(s5)) Console.WriteLine("s1 Equals s5");// false if (Equals(s1, s5)) Console.WriteLine("s1 static Equals s5");// false if (ReferenceEquals(s1, s5)) Console.WriteLine("s1 ReferenceEquals s5");// false if (s1.CompareTo(s5) == 0) Console.WriteLine("s1 Compare s5");// false if (string.Compare(s1, s5) == 0) Console.WriteLine("s1 static Compare s5");// false if (s1.Equals(s5, StringComparison.OrdinalIgnoreCase)) Console.WriteLine("s1 OrdinalIgnoreCase Equals s5");// true if (string.Equals(s1, s5, StringComparison.OrdinalIgnoreCase)) Console.WriteLine("s1 OrdinalIgnoreCase static Equals s5");// true if (string.Compare(s1, s5, true) == 0) Console.WriteLine("s1 ignoreCase Compare s5");// true if (string.Compare(s1, s5, StringComparison.OrdinalIgnoreCase) == 0) Console.WriteLine("s1 OrdinalIgnoreCase Compare s5");// true
StringBuilder 處理可變字符串(擁有更高的性能),不可被繼承(sealed)
string s1 = "Hello"; StringBuilder sb1 = new StringBuilder(s1); int nLengh1 = sb1.Length;//5 int nCapacity1 = sb1.Capacity;//16 int nMaxCapacity1 = sb1.MaxCapacity;//2147483647 StringBuilder sb2 = new StringBuilder(4, 8); int nLengh2 = sb2.Length;//0 int nCapacity2 = sb2.Capacity;//4 int nMaxCapacity2 = sb2.MaxCapacity;//8 sb2.Append("ab");//sb2="ab" sb2.Length=2 sb2.Append("cdef");//sb2="abcdef" sb2.Length=6 //sb2.Append("ghijk");//拋出System.ArgumentOutOfRangeException 容量小於當前大小 StringBuilder sb3 = new StringBuilder("abcdef");//false if (sb2 == sb3) Console.WriteLine("sb2==sb3");//false if (sb2.Equals(sb3)) Console.WriteLine("sb2 Equals sb3");//false if (Equals(sb2, sb3)) Console.WriteLine("sb2 static Equals sb3");//false if (ReferenceEquals(sb2, sb3)) Console.WriteLine("sb2 ReferenceEquals sb3");//false StringBuilder sb4 = sb3; if (sb3 == sb4) Console.WriteLine("sb3==sb4");//true if (sb3.Equals(sb4)) Console.WriteLine("sb3 Equals sb4");//true if (Equals(sb3, sb4)) Console.WriteLine("sb3 static Equals sb4");//true if (ReferenceEquals(sb3, sb4)) Console.WriteLine("sb3 ReferenceEquals sb4");//true StringBuilder sb5= new StringBuilder("World"); string s2 = sb5.ToString();//s2="World"
注釋
除了與C++相同的單行注釋// Line和多行注釋/* Multi Lines */之外,還有用於幫助文檔的注釋符/// Help Documents
為了更好地生成幫助文檔,C#定義了一套XML標簽來描述注釋中各個字符串的含義 如:summary、param、returns等
/// <summary> /// Add two integers /// </summary> /// <param name="a"></param> /// <param name="b"></param> /// <returns></returns> int Add(int a, int b) { return a + b; }
在項目“屬性”--“Build”--Output下勾選“XML documentation file”:bin\Debug\ConsoleApplication1.XML
在編譯時會檢查XML標簽的格式、及內容是否與代碼一致,若有錯誤編譯器會給出warning
ConsoleApplication1.XML的內容:
<?xml version="1.0"?> <doc> <assembly> <name>ConsoleApplication1</name> </assembly> <members> <member name="M:ConsoleApplication1.Program.Add(System.Int32,System.Int32)"> <summary> Add two integers </summary> <param name="a"></param> <param name="b"></param> <returns></returns> </member> </members> </doc>
預編譯指令
C#的預編譯指令是由編譯器處理的
1. #define #undef 必須放在cs文件的起始處,符號存在,就被認為是true,否則為false
#define Run_Test //如果Run_Test已定義,則忽略該句 Run_Test=true #undef Run_Test //如果Run_Test未定義,則忽略該句 Run_Test=false #define Run_Test2 //如果Run_Test2已定義,則忽略該句 Run_Test2=true
2. #if...#elif...#else
#if Run_Test Console.WriteLine("Run Test."); #elif Run_Test2 Console.WriteLine("Run Test2."); //Run Test2. #else Console.WriteLine("No Run Test."); #endif #if Run_Test==false Console.WriteLine("No Run Test."); //No Run Test. #endif #if Run_Test || Run_Test2 Console.WriteLine("No Run Test or Run Test2."); //No Run Test or Run Test2. #endif #if Run_Test && Run_Test2 Console.WriteLine("Run Test and Run Test2."); #endif #if !Run_Test && Run_Test2 Console.WriteLine("No Run Test and Run Test2."); //No Run Test and Run Test2. #endif
3. #warning #error
#if DEBUG && !Run_Test #warning "You should define Run_Test in debug version." #endif #if DEBUG && Run_Test2 #error "Can't define Run_Test2 in debug version." #endif
4. #region #endregion 把一段代碼標記為一個給定名稱的塊,該指令能被某些編輯器(如:vs、Notepad++)識別,使得代碼在屏幕上更好的布局
#region size of Rectangle int m_nHeight; int m_nWidth; #endregion
5. #line 改變編譯器警告和錯誤中顯示的文件名和行號信息
6. #pragma 屏蔽或開啟指定編譯警告
定制特性(custom attribute)
定制特性是一種可擴展的元數據,通過運行時查詢,從而動態改變代碼的執行方式
.Net Framework類庫定義了幾百個定制特性,最常見如:DllImport(對應DllImportAttribute)
[DllImport("user32.dll", EntryPoint = "MessageBox", SetLastError = true, CharSet = CharSet.Unicode)] public static extern int WinMessageBox(IntPtr hWnd, String text, String caption, uint type);
使用Conditional定制屬性來控制成員函數或Attribute類是否編譯進二進制可執行文件
#define TRACE_ON public class TraceTest { [ConditionalAttribute("TRACE_ON")] public static void Msg(string msg) { Console.WriteLine(msg); } [Conditional("TRACE_ON")] public void TestRet(string msg) { Console.WriteLine(msg); } #if TRACE_ON public static void Msg2(string msg) { Console.WriteLine(msg); } #endif } [Conditional("TRACE_ON")] public class TraceTestAttribute : System.Attribute { public string text; public TraceTestAttribute(string text) { this.text = text; } }
定義TRACE_ON宏時,TraceTest類中會有Msg靜態方法和TestRet成員方法,TraceTestAttribute類型可用
與傳統的#if TRACE_ON #endif相比,這種方式不需要手動處理所有調用的地方(即不需要手動使用#if TRACE_ON #endif進行包裹)
示例
public class DocumentationAttribute : System.Attribute { public string text; public DocumentationAttribute(string text) { this.text = text; } } class SampleClass { // This attribute will only be included if DEBUG is defined. [Documentation("This method displays an integer.")] public static void DoWork(int i) { System.Console.WriteLine(i.ToString()); } } var lstProperties = typeof(SampleClass).GetMethods(); foreach (var oProperty in lstProperties) { foreach (Attribute attr in oProperty.GetCustomAttributes(true)) { DocumentationAttribute helpattr = attr as DocumentationAttribute; if (helpattr != null) { Console.WriteLine("Description of AnyClass: {0}", helpattr.text); // Description of AnyClass: This method displays an integer. } } }
裝箱與拆箱
int a1 = 10; string s1 = a1.ToString(); // a1裝箱成object對象 object o1 = a1; // a1裝箱成object對象,然后賦值給o1 int a2 = (int)o1; // o1拆箱成int值類型 float f1 = (float)o1; // 引發System.InvalidCastException異常
相等性比較
靜態ReferenceEquals方法:引用比較 若是值類型則會先被裝箱,因此2個值類型比較會始終返回false
對象虛擬Equals方法:基本類型比較值(若被裝箱,則會先拆箱再比較值),結構體會逐個比較各成員變量,都相等則返回true;引用類型默認比較引用,可重寫。
如:string重寫了該運算符,讓其比較字符串的內容;而Array沒有重寫該運算符,會執行引用比較
靜態Equals方法:內部會檢查參數是否為null,2個都為null則返回true,只有1個為null則返回false,然后調用對象的虛擬Equals方法進行比較
==比較運算符:基本類型比較值,結構體需要重載==和!=運算符來定義比較行為;引用類型默認比較引用,可重寫。
如:string重寫了該運算符,讓其比較字符串的內容;而Array沒有重寫該運算符,會執行引用比較
struct Color { public float R; public float G; public float B; public float A; public static bool operator ==(Color a, Color b) { return (Math.Abs(a.R - b.R) < float.Epsilon) && (Math.Abs(a.G - b.G) < float.Epsilon) && (Math.Abs(a.B - b.B) < float.Epsilon) && (Math.Abs(a.A - b.A) < float.Epsilon); } public static bool operator !=(Color a, Color b) { return !(a==b); } } int i1 = 10; int i2 = 100 / i1; if (ReferenceEquals(i1, i2)) Console.WriteLine("i1 ReferenceEquals i2");// false if (i1.Equals(i2)) Console.WriteLine("i1 Equals i2");// true if (Equals(i1, i2)) Console.WriteLine("i1 static Equals i2");// true if (i1 == i2) Console.WriteLine("i1 == i2");// true object o1 = i1; object o2 = i2; object o3 = o1; if (ReferenceEquals(o1, o2)) Console.WriteLine("o1 ReferenceEquals o2");// false if (o1.Equals(o2)) Console.WriteLine("o1 Equals o2");// true if (Equals(o1, o2)) Console.WriteLine("o1 static Equals o2");// true if (o1 == o2) Console.WriteLine("o1 == o2");// false if (ReferenceEquals(o1, o3)) Console.WriteLine("o1 ReferenceEquals o3");// true if (o1.Equals(o3)) Console.WriteLine("o1 Equals o3");// true if (Equals(o1, o3)) Console.WriteLine("o1 static Equals o3");// true if (o1 == o3) Console.WriteLine("o1 == o3");// true object o4 = new int[] { 1, 2, 3 }; object o5 = new int[] { 1, 2, 3 }; object o6 = o4; if (ReferenceEquals(o4, o5)) Console.WriteLine("o4 ReferenceEquals o5");// false if (o4.Equals(o5)) Console.WriteLine("o4 Equals o5");// false if (Equals(o4, o5)) Console.WriteLine("o4 static Equals o5");// false if (o4 == o5) Console.WriteLine("o4 == o5");// false if (ReferenceEquals(o4, o6)) Console.WriteLine("o4 ReferenceEquals o6");// true if (o4.Equals(o6)) Console.WriteLine("o4 Equals o6");// true if (Equals(o4, o6)) Console.WriteLine("o4 static Equals o6");// true if (o4 == o6) Console.WriteLine("o4 == o6");// true char[] sz = new char[] { 'h', 'e', 'l', 'l', 'o' }; string s1 = new string(sz); string s2 = "hello"; string s3 = "hello"; if (ReferenceEquals(s1, s2)) Console.WriteLine("s1 ReferenceEquals s2");// false if (s1.Equals(s2)) Console.WriteLine("s1 Equals s2");// true if (Equals(s1, s2)) Console.WriteLine("s1 static Equals s2");// true if (s1 == s2) Console.WriteLine("s1 == s2"); // true if (ReferenceEquals(s2, s3)) Console.WriteLine("s2 ReferenceEquals s3");// true if (s2.Equals(s3)) Console.WriteLine("s2 Equals s3");// true if (Equals(s2, s3)) Console.WriteLine("s2 static Equals s3");// true if (s2 == s3) Console.WriteLine("s2 == s3"); // true Color sc1, sc2; sc1.R = 1.0f; sc1.G = 0.0f; sc1.B = 0.0f; sc1.A = 0.0f; sc2.R = 1.0f; sc2.G = 0.0f; sc2.B = 0.0f; sc2.A = 0.0f; if (ReferenceEquals(sc1, sc2)) Console.WriteLine("sc1 ReferenceEquals sc2");// false if (sc1.Equals(sc2)) Console.WriteLine("sc1 Equals sc2");// true if (Equals(sc1, sc2)) Console.WriteLine("sc1 static Equals sc2");// true if (sc1 == sc2) Console.WriteLine("sc1 == sc2"); // true int[] intArray1 = new int[] { 1, 2, 3 }; int[] intArray2 = new int[] { 1, 2, 3 }; int[] intArray3 = intArray1; if (ReferenceEquals(intArray1, intArray2)) Console.WriteLine("intArray1 ReferenceEquals intArray2"); // false if (intArray1.Equals(intArray2)) Console.WriteLine("intArray1 Equals intArray2"); // false if (Equals(intArray1, intArray2)) Console.WriteLine("intArray1 static Equals intArray2"); // false if (intArray1 == intArray2) Console.WriteLine("intArray1 == intArray2"); // false if (ReferenceEquals(intArray1, intArray3)) Console.WriteLine("intArray1 ReferenceEquals intArray3"); // true if (intArray1.Equals(intArray3)) Console.WriteLine("intArray1 Equals intArray3"); // true if (Equals(intArray1, intArray3)) Console.WriteLine("intArray1 static Equals intArray3"); // true if (intArray1 == intArray3) Console.WriteLine("intArray1 == intArray3"); // true
運算符重載和自定義類型強制轉換
1. 運算符重載函數和自定義類型強制轉換函數必須是public static的
2. 比較運算符重載(==和!=、>和<、>=和<=)必須成對編寫,否則編譯出錯
3. 自定義類型強制轉換函數分為顯式的(explicit)和隱式的(implicit)
4. 不同類之間也可以自定義類型強制轉換函數,前提是之間沒有繼承體系的父子關系
5. 多重類型強制轉換隱式規則:如下面代碼中,雖然MyColor沒有定義double類型的轉換,但可以通過ulong類型再隱式地轉換成double類型。但若MyColor定義了double類型的轉換,會調用double類型進行類型轉換
class MyColor { public byte R, G, B, A; public static bool operator ==(MyColor a, MyColor b) { return (a.R == b.R) && (a.G == b.G) && (a.B == b.B) && (a.A == b.A); } public static bool operator !=(MyColor a, MyColor b) { return !(a == b); // 調用==運算符 } // 顯式轉換為string類型 public static explicit operator string(MyColor a) { return string.Format("{0}.{1}.{2}.{3}", a.R, a.G, a.B, a.A); } // 隱式轉換為ulong類型 public static implicit operator ulong(MyColor a) { return a.R * 0xFFFFFFUL + a.G * 0xFFFFul + a.B * 0xFFul + a.A; } } MyColor mc = new MyColor(); mc.R = 255; mc.G = 0; mc.B = 0; mc.A = 0; MyColor mc2 = new MyColor(); mc2.R = 0; mc2.G = 255; mc2.B = 0; mc2.A = 0; if (mc != mc2) Console.WriteLine("mc != mc2"); string smc = (string)mc; // 255.0.0.0 ulong ulmc = mc; // 4278189825 double dmc = mc; // 4278189825.0 多重類型強制轉換:雖然MyColor沒有定義double類型的轉換,但可以通過ulong類型再隱式地轉換成double類型
擴展類的方法
當某個類xxx沒有源碼,且被sealed關鍵字修飾不允許被繼承,我們可以通過靜態類來擴展類xxx的成員函數 注:靜態函數的第一個參數為this xxx類型
下面的示例中為string類型擴展了一個void Test(int n)方法
public static class stringEx { public static void Test(this string s, int n) { Console.WriteLine("s:{0} n:{1}", s, n); } } string s1 = "Hello"; s1.Test(100);// 打印出 s:Hello n:100
垃圾回收(GC)
1. 垃圾回收器管理的是托管堆內存,引用類型的對象不再被引用時,GC會自動在某個時刻將其回收其內存
2. GC可由CLR內部檢查算法(如:某個固定時間間隔、程序內存占用過高等)來自動觸發,也可以由開發者調用GC.Collect()函數來手動觸發
3. GC被觸發后,並不會立刻進行垃圾回收(該過程是異步的)
4. 垃圾回收器的邏輯不能保障在一次垃圾回收過程中,將所有未引用的對象從堆中刪除
弱引用
當實例化一個類時,只要有代碼引用它,就形成了強引用,使得GC無法回收該對象。
如果該對象很大但又不是經常訪問,使得程序會保持很高的內存占用。可以使用弱引用來解決該問題。
弱引用允許GC回收其指向的對象,在使用對象前需通過IsAlive屬性判斷對象是否已被GC
class CA { public string Name; } class Program { static void Main(string[] args) { WeakReference refCA = new WeakReference(new CA()); if (refCA.IsAlive) { (refCA.Target as CA).Name = "Test1"; } else { Console.WriteLine("refCA is not available."); } GC.Collect();//調用垃圾回收器 發起GC 注:並不會立即回收new出來的CA對象 while (refCA.IsAlive) { System.Threading.Thread.Sleep(1000);//循環等待,確保GC已回收CA對象 } Console.WriteLine("CA object has been recycled by GC."); } }
枚舉
派生於System.Enum
enum Days { Monday=1, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday}; int n1 = (int)Days.Tuesday;//n1=2 Days ed = Days.Sunday; int n2 = (int)ed;//n2=7 string s1 = ed.ToString(); // Sunday string s2 = Enum.GetName(typeof(Days), ed); // Sunday
權限控制
外部類(結構體)
internal(缺省):只有在同一程序集內才可以被訪問
public:任何地方都可以被訪問
內部類(結構體)& 類的成員
private(缺省):當前類(結構體)中可以被訪問
protected:當前類(結構體)及其子類中可以被訪問
public:任何地方都可以被訪問
internal:只有在同一程序集內才可以被訪問
protected internal:protected與internal求並集的范圍內可以被訪問
訪問范圍:public > protected internal > internal > protected > private
結構體 -- 值類型
(1)不能從其他結構體或類繼承,但可以從接口繼承
(2)不允許有protected或protected internal成員變量和成員函數(由於不能從其他結構體或類繼承,protected或protected internal無意義)
(3)不允許有析構函數(終結器finalizer)
(4)不允許顯示給出無參構造函數
(5)不能在非static字段后面對其賦初值,若無自定義帶參構造函數時,這些非static字段會被編譯器在無參構造函數中默認賦缺省初值
(6)static變量可以直接在后面對其賦初值,也可以在靜態構造函數中賦初值;
(7)與static變量一樣,const變量屬於類,但其只能在后面對其賦值來初始化
(8)若有自定義帶參構造函數,必須在其中對所有非static字段進行初始化
(9)無論是否有自定義帶參構造函數,無參構造函數都默認存在,由結構體變量聲明方式來決定調用哪個構造函數
(10)通過new來調用結構體的構造函數進行初始化,如:SA s = new SA() 或 SA s = new SA("Hello");
(11)允許有靜態構造函數,但不允許帶訪問修飾符
(12)靜態構造函數只會被執行一次,也就是在創建第一個實例或引用任何靜態成員之前,由.NET自動調用。
(13)允許有非自動實現的實例屬性 注1:自動實現的屬性特點 -- get或set中沒有內容,編譯時由編譯器生成同名的private后備字段 注2:屬性可以理解為邏輯簡單的成員函數,不占用對象size
(14)允許有自動實現和非自動實現的靜態屬性
(15)支持運算符重載(注:運算符重載必須聲明為public static)
(16)結構體成員函數不能virtual,僅能使用override重寫從System.ValueType繼承的方法
(17)從接口繼承時,必須實現所有接口函數,有兩種實現方式:顯示實現和隱式實現,兩種方式中至少有一個實現接口,也可同時存在
當僅顯式實現接口時,實現的成員不能通過類實例訪問,只能通過接口實例訪問。
當僅隱式實現接口時(需加上public),實現的成員可以通過類實例訪問,也可以通過接口實例訪問。
當同時顯式和隱式實現接口時,類實例調用隱式實現的接口,接口實例調用顯式實現的接口。
interface SI { int Add(int a, int b); int Sub(int a, int b); long Mul(int a, int b); } struct SA : SI { // 調用順序 【1】 public const string Name = "SA"; public static string Name2; public readonly string Name3;//readonly字段只能在構造函數或者構造函數的初始化器中聲明時賦值 public static readonly string Name4 = "SA4"; public string Name5; public int Id; bool Sex; private short Age; //protected float Scores; public int Attr1 { get { return 0; } } private int attr2; public int Attr2 { get { return attr2; } private set { attr2 = value; } } private int attr3; public int Attr3 { get { return attr3; } set { attr3 = value; } } //public int Attr4 { get; set; } //自動實現的屬性,特點是get或set中沒有內容 internal float Length; //protected internal double Weight; static short Sm1; static string Sm2; private static short Sm3 = 100; //protected static float Sm4; internal static float Sm5; //protected internal static double Sm6; // 靜態屬性 public static int SAttr1 { get { return 0; } } private static int sattr2; public static int SAttr2 { get { return sattr2; } private set { sattr2 = value; } } private static int sattr3; public static int SAttr3 { get { return sattr3; } set { sattr3 = value; } } public static int SAttr4 { get; set; } // 支持靜態的自動實現屬性 // 調用順序 【3】 public SA(string s) { Name3 = s; Name5 = "SA5"; Id = 6; Sex = false; Age = 10; //Attr2 = 35; // 屬性必須在所有字段初始化后才能設置 attr2 = 30; attr3 = 35; Length = 8.0f; //Attr1 = 20; // 只讀型屬性不能被賦值 Attr2 = 45; Attr3 = 50; } // 調用順序 【3】 public SA(string s, int n) : this(s) { Name3 = "Name3"; Id = 7; } // 調用順序 【2】 static SA() { SAttr2 = 10; SAttr3 = 20; SAttr4 = 30; //Name = "static SA";//const常量只能在初始化器中聲明時初始化 Name2 = "static SA2"; //Name3 = "test";//非static變量 Name4 = "hello"; } void Test() { } //protected void Test2() { } public void Test3() { } internal void Test4() { } //protected internal void Test5() { } static void STest() { } //protected static void STest2() { } public static void STest3() { } internal static void STest4() { } //protected internal static void STest5() { } // 運算符重載 public static SA operator +(SA a, SA b) { SA c = new SA(); c.Id = a.Id + b.Id; return c; } // 實現所有的SI接口 方式1和方式2至少有一個實現接口,也可同時存在 // 方式1:顯示實現 int SI.Add(int a, int b) { Console.WriteLine("SI.Add"); return a + b; } //int SI.Sub(int a, int b) //{ // Console.WriteLine("SI.Sub"); // return a - b; //} long SI.Mul(int a, int b) { Console.WriteLine("SI.Mul"); return a * b; } // 方式2:隱式實現 //public int Add(int a, int b) //{ // Console.WriteLine("SA public Add"); // return a + b; //} public int Sub(int a, int b) { Console.WriteLine("SA public Sub"); return a - b; } public long Mul(int a, int b) { Console.WriteLine("SA public Mul"); return a * b; } } string ss = SA.Name2; SA sa = new SA("Hello"); sa.Test3(); //sa.Add(2, 1);//由於只實現了SI.Add,在SA的實例接口中對Add不可見 sa.Sub(2, 1);//打印:SA public Sub sa.Mul(2, 1);//打印:SA public Mul SI sai = sa as SI; if (sai != null) { sai.Add(2, 1);//打印:SI.Add sai.Sub(2, 1);//打印:SA public Sub sai.Mul(2, 1);//打印:SI.Mul } SA sa2; SA sa3 = new SA(); SA sa4 = new SA("Hi", 12) { // 調用順序 【4】 // 對象初始化器(需.Net 3.0及以上),在其中可以對public、internal類型的非static成員變量及可寫屬性進行賦值 Name5 = "Name5", Attr3 = 60, Id = 100, Length = 3.14f, };
類 -- 引用類型
訪問修飾符 修飾符 class 類名稱 : 父類名稱, 接口名稱1, 接口名稱2
(1)單實現繼承,多接口繼承
(2)允許有析構函數(終結器finalizer)
(3)允許顯示給出無參構造函數和重載多個帶參數的構造函數
(4)若沒有提供構造函數,編譯器會默認生成無參構造函數;若編寫了帶參數的構造函數,編輯器就不再默認生成無參構造函數了
(5)可直接在后面給任何字段、readonly字段、static字段進行賦值
(6)通過new在托管堆上分配對象空間,並調用類的構造函數進行初始化,如:CA o = new CA() 或 CA o = new CA("Hello");
(7)允許有靜態構造函數,但不允許帶訪問修飾符
(8)靜態構造函數只會被執行一次,也就是在創建第一個實例或引用任何靜態成員之前,由.NET自動調用。
(9)允許有自動實現的屬性和非自動實現的屬性 注1:自動實現的屬性特點 -- get或set中沒有內容,編譯時由編譯器生成同名的private后備字段 注2:屬性可以理解為邏輯簡單的成員函數,不占用對象size
(10)支持運算符重載(注:運算符重載必須聲明為public static)
(11)除了構造函數外,成員函數和屬性默認不是virtual
class CA { // 調用順序 【1】 public const string Name = "CA"; //readonly字段是一個實例字段,類的每個實例可以有不同的值 //readonly字段只能在后面直接賦值或在構造函數或者構造函數的初始化器中聲明時賦值 internal readonly string Name2 = "CA2"; internal protected readonly string Name3; public int Id = 10; bool Sex; private short Age; protected float Scores; public static string Name4 = "CA4"; static int Sm1; private static bool Sm2 = true; internal static float Sm3 = 3.0f; protected static short Sm4 = 10; protected internal static double Sm5; static readonly string Name5; // 屬性的get與set訪問器訪問權限設置規則 // 1. 在get與set訪問器中,必須有一個具備屬性的訪問級別(即:前面不設置權限) // 2. get與set訪問器設置訪問權限必須要低於屬性的訪問級別(public > protected internal > internal > protected > private) // 自動實現的屬性,編譯時由編譯器生成同名的private后備字段 // 自動實現的屬性,必須要同時寫get與set訪問器 public int Attr1 { get; set; } public int Attr2 { get; protected internal set; } protected int Attr3 { private get; set; } private int attr4; public int Attr4 { get { return attr4; } set { attr4 = value; } } public int Attr5 { get { return 100; } set { int a = value; } } private int attr6 = 2; public int Attr6//只讀屬性 { get { return attr6; } } private int attr7; public int Attr7//只寫屬性 這是不好的編程方式,最好使用方法來替代 { set{ attr7 = value; } } // 調用順序 【3】 //自定義了構造函數,編譯器就不會生成缺省無參構造函數 public CA(string s1, int a1) { this.Attr1 = a1; this.Name2 = s1; } // 調用順序 【3】 //構造函數初始化器 在之前先調用CA(string s1, int a1)構造函數 //除了調用當前類的構造函數外,也可以調用基類的構造函數(將this改成base) //構造函數初始化器只能有一個調用 public CA(string s1) : this(s1, 10) { } // 調用順序 【3】 public CA() { this.Attr1 = 10; Attr2 = 20; this.Name2 = "CC2"; Name3 = "CA3"; } // 調用順序 【2】 static CA() { //Name = "Test"; //const常量只能在初始化器中聲明時初始化 Sm5 = 20.5; Name5 = "Hi"; } //public ~CA() //{ //} ~CA() { Name4 = null; } // 該析構函數編譯后生成如下邏輯 // 當對象被執行GC時,GC會調用Finalize函數進行最后的清理工作 //protected override void Finalize() //{ // try // { // Name4 = null; // } // finally // { // base.Finalize(); // } //} void Test() { } void Test(int a) { }//方法重載 protected virtual void Test2() { } public virtual void Test3() { } internal void Test4() { } protected internal void Test5() { } static void STest() { } protected static void STest2() { } public static void STest3() { } internal static void STest4() { } protected internal static void STest5() { } [System.Runtime.InteropServices.DllImport("user32.dll", EntryPoint = "MessageBox", SetLastError = true, CharSet = System.Runtime.InteropServices.CharSet.Unicode)] public static extern int WinMessageBox(IntPtr hWnd, String text, String caption, uint type); // 運算符重載 public static CA operator +(CA a, CA b) { CA c = new CA(); c.Id = a.Id + b.Id; return c; } } CA ca = new CA(); ca.Test3(); CA ca2 = new CA("Go", 100); ca2.Test4(); CA ca3 = new CA("Hi") { // 調用順序 【4】 // 對象初始化器(需.Net 3.0及以上),在其中可以對public、internal類型的非static成員變量及可寫屬性進行賦值 Id = 12, Attr1 = 51, Attr2 = 52, Attr4 = 53, Attr7 = 58 }; ca3.Test5(); CA.WinMessageBox(IntPtr.Zero, "Hello World!", "title", 0); // 彈出MessageBox對話框
修飾符:
sealed 密封類,前類不能被繼承
注:sealed還可修飾函數(屬性),使得當前虛函數(屬性)不能被子類重寫
partial 部分類,允許將類的成員放在不同文件中,在編譯時將采用與的方式合並成一個完整的類
// Test1.cs partial class CA : CBase, IA { public int nValue1; public void Test1() { } } // Test2.cs sealed partial class CA : IB { public int nValue2; public void Test2() { } } // 等價於 sealed class CA : CBase, IA, IB { public int nValue1; public int nValue2; public void Test1() { } public void Test2() { } }
static 靜態類,不能被實例化且只能包含非protected的靜態字段、靜態函數和靜態屬性
static class CSA { public const string Name = "CSA"; private static int nValue1; //protected static int nValue2; // 靜態類中不包含protected類型的成員變量 //internal protected static int nValue3; internal static int nAttr1 { get; set; } // 靜態構造函數 static CSA() { nValue1 = 100; } static public void Test1() { } }
abstract 抽象類,不能被實例化
1、抽象類不能被sealed,也不能用於實例化對象
2、抽象類中可以有實現的函數(屬性)
3、抽象類可以包含非抽象類含有的各種變量、函數和屬性等,也有可以從非抽象類、抽象類和接口派生
4、抽象類從接口派生時,若不想實現接口中的成員,則可把其成員聲明為abstract
5、抽象函數(屬性)不能是static或private的,只能在抽象類中聲明
6、抽象函數(屬性)是隱式虛函數,無需用virtual關鍵字修飾
7、抽象函數(屬性)只有聲明沒有實現,需在非抽象的派生類中重寫所有的抽象函數(屬性)
8、抽象函數(屬性)不能在派生類用base關鍵字進行訪問
abstract class CA { public void Test1() { Console.WriteLine("CA Test1"); } } abstract class CB : CA { public abstract void Test2(); public abstract int Attr1 { get; set; } public abstract int Attr2 { get; } } class CC : CB { public override void Test2() { Console.WriteLine("CC Test2"); } private int attr1; public override int Attr1 { get { return attr1; } set { attr1 = value; } } public override int Attr2 { get { return 0; } } } CC c = new CC(); c.Test1();// 打印出 CA Test1 c.Test2();// 打印出 CC Test2 c.Attr1 = 100; // c.attr1=100 int n = c.Attr2; // n=0
繼承
1.通過base可以調用基類中的方法和屬性
成員修飾符
sealed 修飾函數(屬性),使得當前虛函數(屬性)不能被子類重寫
abstract 在抽象類用於修飾函數(屬性),只有聲明沒有實現,需在非抽象的派生類中重寫
virtual/override 修飾函數(屬性),不能是private的,可在派生類中使用override修飾后重寫
new 可在派生類中使用new修飾相同的函數(屬性)來隱藏繼承的成員(無論是否為virtual,都可以隱藏)
extern 僅靜態DllImport方法,實際的方法在外部用其他語言實現
interface CI { int Add(int a, int b); int Sub(int a, int b); long Mul(int a, int b); } class CA { string Name; protected int Id; int attr1 = 1; internal int Attr1 { get { Console.WriteLine("CA get Attr1."); return attr1; } } int attr2 = 2; public int Attr2 { get { Console.WriteLine("CA get Attr2."); return attr2; } } protected virtual int Attr3 //虛屬性 { get { Console.WriteLine("CA get Attr3."); return 0; } } protected int attr4; public virtual int Attr4 //虛屬性 { get { Console.WriteLine("CA get Attr4."); return attr4; } set { attr4 = value; } } public CA() : this("Name1", 10)//調用CA(string s1, int n) { } public CA(string s1, int n) { this.Name = s1; this.Id = n; } ~CA() { Name = null; } internal void Test1() { Console.WriteLine("CA Test1."); } public virtual void Test2() { Console.WriteLine("CA Test2."); } public virtual void Test3() { Console.WriteLine("CA Test3."); } } class CB : CA, CI { private new int Attr1 //覆蓋基類CA的屬性 { get { Console.WriteLine("CB get Attr1."); return 0; } } public new int Attr2 { get { Console.WriteLine("CB get Attr2."); return 0; } } public new int Attr3 //覆蓋基類CA的虛屬性 { get { Console.WriteLine("CB get Attr3."); return 0; } } public override int Attr4 //重寫基類CA的虛屬性 { get { Console.WriteLine("CB get Attr4."); return attr4; } set { Console.WriteLine("CB set Attr4."); attr4 = value; } } public CB() : this("Name1", 10)//調用CB(string s1, int n) { } public CB(string s1, int n) : base(s1, n)//調用CA(string s1, int n) { } private new void Test1() { Console.WriteLine("CB Test1."); } public new void Test2() { Console.WriteLine("CB Test2."); } public override void Test3() { Console.WriteLine("CB Test3."); } public void Test4() { Console.WriteLine("CB Test4.");//打印CB Test4. base.Test1();//打印CA Test1. base.Test2();//打印CA Test2. base.Test3();//打印CA Test3. } // 實現所有的CI接口 方式1和方式2至少有一個實現接口,也可同時存在 // 方式1:顯示實現 int CI.Add(int a, int b) { Console.WriteLine("CI.Add"); return a + b; } //int CI.Sub(int a, int b) //{ // Console.WriteLine("CI.Sub"); // return a - b; //} long CI.Mul(int a, int b) { Console.WriteLine("CI.Mul"); return a * b; } // 方式2:隱式實現 //public int Add(int a, int b) //{ // Console.WriteLine("CB public Add"); // return a + b; //} public int Sub(int a, int b) { Console.WriteLine("CB public Sub"); return a - b; } public long Mul(int a, int b) { Console.WriteLine("CB public Mul"); return a * b; } } CA o = new CB(); // Attr4為虛屬性,且被派生類CB使用override重寫 o.Attr4 = 100;//打印CB set Attr4. // Attr1被派生類CB使用new隱藏 int na1 = o.Attr1;//打印CA get Attr1. // Attr2被派生類CB使用new隱藏 int na2 = o.Attr2;//打印CA get Attr2. // Attr4為虛屬性,且被派生類CB使用override重寫 int na4 = o.Attr4;//打印CB get Attr4. // Test1函數被派生類CB使用new隱藏 o.Test1();//打印CA Test1. // Test2虛函數被派生類CB使用new隱藏 o.Test2();//打印CA Test2. // Test3虛函數被派生類CB使用override重寫 o.Test3();//打印CB Test3. CB ob = o as CB; // Attr1被派生類CB使用new隱藏,並設置為private int nb1 = ob.Attr1;//打印CA get Attr1. // Attr2被派生類CB使用new隱藏 int nb2 = ob.Attr2;//打印CB get Attr2. // Attr3為protected虛屬性,被派生類CB使用new隱藏,並設置為public int nb3 = ob.Attr3;//打印CB get Attr3. // Attr4為虛屬性,且被派生類CB使用override重寫 int nb4 = ob.Attr4;//打印CB get Attr4. // Test1函數被派生類CB使用new隱藏,並設置為private ob.Test1();//打印CA Test1. // Test2虛函數被派生類CB使用new隱藏 ob.Test2();//打印CB Test2. // Test3虛函數被派生類CB使用override重寫 ob.Test3();//打印CB Test3. // Test4函數為派生類CB的函數 ob.Test4(); //ob.Add(2, 1);//由於只實現了CI.Add,在CB的實例接口中對Add不可見 ob.Sub(2, 1);//打印:CB public Sub //同時實現了CI.Mul和CB類的Mul函數,在CB的實例接口將調用CB類的Mul函數 ob.Mul(2, 1);//打印:CB public Mul CI cob = ob as CI; if (cob != null) { cob.Add(2, 1);//打印:CI.Add //由於沒有實現CI.Sub,調用CB類的Sub函數 cob.Sub(2, 1);//打印:CB public Sub //同時實現了CI.Mul和CB類的Mul函數,CI接口將調用CI.Mul cob.Mul(2, 1);//打印:CI.Mul }
內部類(嵌套類)
1、使用內部類很大意義上是為了隱藏其外部類的實現
2、內部類默認訪問修飾符為private,外部類默認訪問修飾符為internal。內部類訪問修飾符為private時,僅能在包含其的外部類中使用
3、內部類和其外部類並沒有太多關聯,在外部使用內部類時,首先需要將內部類的訪問修飾符設置為internal、internal protected或public,另外還要帶上外部類名前綴(像命名空間一樣)
4、內部類拿到外部類的引用后,使用該引用可訪問外部類所有成員(含private和protected)
5、與普通類一樣,嵌套類也可以被繼承
class COuter { private int nValue1; private int Attr1 { get; set; } private void Test() { } private static void STest() { } public virtual void PubTest() { CInner cci = new CInner(this); cci.Func(); } protected class CInner { private COuter outer; public CInner(COuter o) { this.outer = o; } public virtual void Func() { outer.nValue1 = 100;// 內部類直接修改private字段 outer.Attr1 = 200;// 內部類直接修改private屬性 outer.Test();// 內部類直接訪問private方法 COuter.STest();// 內部類直接訪問private靜態方法 } } } class COuterEx : COuter { public override void PubTest() { CInnerEx cci = new CInnerEx(this); cci.Func(); } protected class CInnerEx : CInner { public CInnerEx(COuterEx o):base(o) { } public override void Func() { base.Func(); Console.WriteLine("CInnerEx Func!"); } } } COuter cox = new COuterEx(); cox.PubTest();
接口
(1)與抽象類一樣,不能直接實例化接口
(2)接口中只能包含成員函數(屬性、索引器、事件),不能包含常量、成員變量、構造函數、析構函數、運算符、類型轉換函數、及所有靜態成員。
(3)接口成員是自動公開的,無需用public再做修飾
(4)接口可從多個接口繼承,但不能從類繼承
(5)值類型和引用類型都可以從一個或多個接口派生
(6)接口中的函數(屬性、索引器、事件)只有聲明沒有實現,需在實現類中實現所有的函數(屬性、索引器、事件)
(7)接口用於規范,抽象類用於共性 注:抽象類可提供某些方法的實現
(8)從接口繼承時,必須實現所有接口函數,有兩種實現方式:顯示實現和隱式實現,兩種方式中至少有一個實現接口,也可同時存在(詳見上文結構體和類-繼承中的示例)
當僅顯式實現接口時,實現的成員不能通過類實例訪問,只能通過接口實例訪問。
當僅隱式實現接口時(需加上public),實現的成員可以通過類實例訪問,也可以通過接口實例訪問。
當同時顯式和隱式實現接口時,類實例調用隱式實現的接口,接口實例調用顯式實現的接口。
(9)接口IB從接口IA派生時:增加一個在IA中同名接口函數Test,需要在接口函數Test前加上new關鍵字,告訴編譯器符號修飾時帶上接口名前綴以作區分
實現類從IB派生時,要同時實現IA.Test和IB.Test,如同兩個不同名稱的接口函數一樣
interface IA { void Test(); void Test1(); void Test2(); } interface IB : IA { new void Test();// 基類IA中有一個同名Test接口函數,因此需要用new進行修飾 new void Test1();// 同上 void Test3(); void Test4(); void Test5(); } abstract class CC : IB { // Only顯示實現:IA中的Test函數接口 void IA.Test() { Console.WriteLine("IA.Test"); } // Only顯示實現:IB中的Test函數接口 void IB.Test() { Console.WriteLine("IB.Test"); } // only隱式虛實現:IA和IB中的Test1函數接口 public virtual void Test1() { Console.WriteLine("CC Test1"); } // 同時顯示和隱式實現:IA中的Test2函數接口 void IA.Test2() { Console.WriteLine("IA.Test2"); } public void Test2() { Console.WriteLine("CC Test2"); } // 同時顯示和隱式虛實現:IB中的Test3函數接口 void IB.Test3() { Console.WriteLine("IB.Test3"); } public virtual void Test3() { Console.WriteLine("CC Test3"); } // only隱式虛實現:IB中的Test4函數接口 public virtual void Test4() { Console.WriteLine("CC Test4"); } // only隱式虛實現:IB中的Test4函數接口 -- 使用抽象函數的方式 public abstract void Test5(); } class CD : CC { public new void Test1() // 使用new隱藏 { Console.WriteLine("DD Test1"); } public new void Test2() // 使用new隱藏 { Console.WriteLine("DD Test2"); } protected new void Test4() // 使用new隱藏,並降低Test4可見權限為protected { Console.WriteLine("DD Test4"); } public override void Test5() // 使用override重載 { Console.WriteLine("DD Test5"); } } CD od = new CD(); //od.Test(); // Test函數在CD中不可見 od.Test1();//打印DD Test1 od.Test2();//打印DD Test2 od.Test3();//打印CC Test3 od.Test4();//打印CC Test4 od.Test5();//打印DD Test5 CC oc = od as CC; //oc.Test(); // Test函數在CC中不可見 oc.Test1();//打印CC Test1 oc.Test2();//打印CC Test2 oc.Test3();//打印CC Test3 oc.Test4();//打印CC Test4 oc.Test5();//打印DD Test5 IA ia = od as IA; ia.Test();//打印IA.Test ia.Test1();//打印CC Test1 ia.Test2();//打印IA.Test2 IB ib = od as IB; ib.Test();//打印IB.Test ib.Test1();//打印CC Test1 ib.Test2();//打印IA.Test2 ib.Test3();//打印IB.Test3 ib.Test4();//打印CC Test4 ib.Test5();//打印DD Test5
.Net Framework中常用的接口有IDisposable、迭代器【IEnumerator、IEnumerable】、泛型版本迭代器【IEnumerator<T>、IEnumerable<T>】
// Disposable接口,通過顯示調用Dispose接口來進行清理工作 public interface IDisposable { void Dispose (); } // 迭代器 public interface IEnumerator { object Current { get; } bool MoveNext (); void Reset (); } public interface IEnumerable { IEnumerator GetEnumerator(); } // 泛型版本的迭代器 public interface IEnumerator<out T> : IDisposable, IEnumerator { T Current { get; } } public interface IEnumerable<out T> : IEnumerable { new IEnumerator<T> GetEnumerator(); }
委托
(1)委托的功能類似於c/c++里面的函數指針,但委托更安全,它會在編譯器期檢查函數的參數和返回值類型
(2)委托本質是一個類,在使用前需要聲明
靜態數組
用方括號聲明靜態數組編譯成字節碼后會創建一個派生於抽象基類Array的新類
static int[] ArraySum(int[] array1, int[] array2) { int[] result = new int[5]; for (int i = 0; i < 5; i++) { result[i] = array1[i] + array2[i]; } return result; } static int Array1PartSum(ArraySegment<int> array1) { int result = 0; for (int i = array1.Offset; i < array1.Offset+array1.Count; i++) { result += array1.Array[i]; } return result; } public class Person { public string FirstName, LastName; } int[] array1 = null; int[] array2 = new int[3]; int[] array3 = new int[3] { 1, 2, 3 }; int[] array4 = new int[] {1, 2, 3, 4, 5}; int[] array5 = { 1, 2, 3, 4, 5 }; int nLengthOfArray5 = array5.Length; // 5 int nArray5Sum = 0; foreach (int an in array5) // 繼承了IEnumerable接口,必須實現自己的IEnumerator GetEnumerator()方法和對應IEnumerator的數據類型 { nArray5Sum += an; // nArray5Sum = 1+2+3+4+5 = 15 } Person[] Persons1 = new Person[2]; Persons1[0] = new Person { FirstName = "Bruce", LastName = "Lee" }; Persons1[1] = new Person { FirstName = "Jack", LastName = "Wong" }; Person[] Persons2 = { new Person { FirstName = "Jim", LastName = "Green" }, new Person { FirstName = "Kate", LastName = "Green" }, new Person { FirstName = "Lei", LastName = "Li" } }; //2x2的二維數組 // det1 = | 1 2 | // | 3 4 | // int[,] det1 = new int[2,2]; det1[0,0] = 1; det1[0,1] = 2; det1[1,0] = 3; det1[1,1] = 4; int nLengthOfdet1 = det1.Length; // 4 long nLongLengthOfdet1 = det1.LongLength; // 4 個數>2147483647時,應使用LongLength獲取數組長度 int nRankOfdet1 = det1.Rank; // 2 數組的維度 // 3x3的二維數組 int[,] det2 = { {1,2,3}, {4,5,6}, {7,8,9} }; int edet2 = det2[2, 1]; //edet2=8 int nLengthOfdet2 = det2.Length; // 9 // 3x2x1的三維數組 int[,,] det3 = { {{1},{2}}, {{3},{4}}, {{5},{6}} }; int edet3 = det3[1, 1, 0]; //edet3=4 int nLengthOfdet3 = det3.Length; // 6 // 鋸齒數組 // jagged = | 1 2 | // | 3 4 5 6 7 8 | // | 9 10 11 | int[][] jagged = new int[3][]; jagged[0] = new int[]{1, 2}; int [] ar1 = { 3, 4, 5, 6, 7, 8 }; jagged[1] = ar1; jagged[2] = new int[3] { 9, 10, 11 }; int nLengthOfjagged = jagged.Length; // 3 // 數組調用和返回值 int[] sumarray = ArraySum(new int[5] { 1, 2, 3, 4, 5 }, new int[5] { 10, 5, 8, 3, 12 });// sum = {11,7,11,7,17} // 計算sumarray數組中從索引為1起后面3個元素之和 int sum = Array1PartSum(new ArraySegment<int>(sumarray, 1, 3)); // 7+11+7=25
Array類
Array intArray1 = Array.CreateInstance(typeof(int), 5);// 創建元素個數為5的一維數組 for (int i = 0; i < intArray1.Length; i++) { intArray1.SetValue(100, i);// 設置intArray1的每個元素為100 } for (int i = 0; i < intArray1.Length; i++) { Console.WriteLine(intArray1.GetValue(i)); } int[] intArray2 = (int[])intArray1; int[] intArray3 = (int[])intArray1.Clone();// 創建淺表副本(拷貝后,引用類型成員變量仍然指向同一塊內存區域) int[] intArray4 = new int[intArray1.Length]; Array.Copy(intArray1, intArray4, intArray1.Length);// 創建淺表副本(拷貝后,引用類型成員變量仍然指向同一塊內存區域) Array floatArray1 = Array.CreateInstance(typeof(float), 2, 3); // 創建一個起始索引為[3]長度為2的一維數組 Array floatArray2 = Array.CreateInstance(typeof(float), new int[] { 2, 3 }); // 創建一個起始索引為[0][0]的2x3的二維數組 Array floatArray3 = Array.CreateInstance(typeof(float), new int[] { 2, 3 }, new int[] { 1, 2 }); // 創建一個起始索引為[1][2]的2x3的二維數組
排序
1. 由於基本類型、String實現了IComparable接口,因此可以直接使用Array的Sort靜態函數來排序
2. 結構體、類須實現IComparable接口來支持Sort函數進行排序
3. Array的Sort有多個重載函數,static Void Sort<T>(T[] array, System.Comparison<T> comparison)允許傳入派生於IComparer<T>的對象來自定義排序行為
public struct Color : IComparable<Color> { public float R; public float G; public float B; public float A; public int CompareTo(Color other)//按照R、G、B、A的順序升序排列 { if (this.R > other.R) return 1; if (this.R < other.R) return -1;//小於則交換 if (this.G > other.G) return 1; if (this.G < other.G) return -1; if (this.B > other.B) return 1; if (this.B < other.B) return -1; if (this.A> other.A) return 1; if (this.A < other.A) return -1; return 0; } } public class ColorComparer : IComparer<Color> { public int Compare(Color c1, Color c2)//按照R、G、B、A的順序降序排列 { if (c1.R > c2.R) return -1;//大於則交換 if (c1.R < c2.R) return 1; if (c1.G > c2.G) return -1; if (c1.G < c2.G) return 1; if (c1.B > c2.B) return -1; if (c1.B < c2.B) return 1; if (c1.A > c2.A) return -1; if (c1.A < c2.A) return 1; return 0; } } String[] strArray = {"tencent", "Map", "Hello", "Test", "apple"}; Array.Sort(strArray, 1, 3); // 對數組strArray中從索引為1起的后3個元素進行排序 {"tencent", "Hello", "Map", "Test", "apple"} Color[] ClrArray = new Color[3]; ClrArray[0].R = 100; ClrArray[1].R = 100; ClrArray[1].G = 100; ClrArray[2].R = 80; ClrArray[2].G = 120; Array.Sort(ClrArray);//自定義類型結構體Color需要從IComparable<Color>接口派生,實現public int CompareTo(Color other)虛方法;否則會拋InvalidOperationException異常 Array.Sort(ClrArray, new ColorComparer());//使用ColorComparer(需從IComparer<Color>接口派生)實例中的int Compare(Color c1, Color c2)方法進行排序
元組(Tuple) 需.Net 4.0及以上
數組合並了相同類型的對象,而元組合並了不同類型的對象
元組默認支持1~7種類型,當類型大於7種時,需在第8個參數中傳入一個元組類型,通過嵌套來支持更多類型
Tuple<int, string> t0 = Tuple.Create<int, string>(18, "James"); Tuple<int, int, string> t1 = new Tuple<int, int, string>(1, 15, "Rose"); Tuple<bool, int, int, int, string, string, string> t2 = new Tuple<bool, int, int, int, string, string, string>(true, 1, 2, 3, "How", "are", "you"); Tuple<bool, int, int, int, string, string, string, Tuple<int, int>> t3 = new Tuple<bool, int, int, int, string, string, string, Tuple<int, int>>(true, 1, 2, 3, "How", "are", "you", new Tuple<int, int>(10, 20)); Console.WriteLine("Age:{0} Name:{1}", t0.Item1, t0.Item2); // Age:18 Name:James Console.WriteLine("Sex:{0} Code:{1}", t3.Item1, t3.Rest.Item2); // Sex:True Code:20
數組與元組內容相等性比較 需.Net 4.0及以上
數組與元組都實現了IStructuralEquatable接口,該接口是.Net4.0新增的,用於比較兩個數組元組是否有相同的內容
struct Student { public int Id; public string Name; public bool Sex; public Student(int InId, string InName, bool InSex) { Id = InId; Name = InName; Sex = InSex; } } class StdArrayEqualityComparer : EqualityComparer<Student> { public override bool Equals(Student x, Student y) { return (x.Id == y.Id) && (x.Name == y.Name) && (x.Sex == y.Sex); } public override int GetHashCode(Student obj) { int code = obj.GetHashCode(); int code1 = obj.Id.GetHashCode(); int code2 = obj.Name.GetHashCode(); int code3 = obj.Sex.GetHashCode(); if (code != code1) { code ^= code1;// 進行異或操作 } if (code != code2) { code ^= code2; } if (code != code3) { code ^= code3; } return code; } } class StdTupleEqualityComparer : IEqualityComparer { public new bool Equals(object x, object y)// new用於顯式地表明隱藏基類中的同名方法(object中定義了帶兩個參數的靜態Equals方法) { return x.Equals(y); } public int GetHashCode(object obj) { return obj.GetHashCode(); } } Student[] StArray1 = { new Student(0, "Jim", true), new Student(1, "Kate", false) }; Student[] StArray2 = { new Student(0, "Jim", true), new Student(1, "Kate", false) }; if (StArray1 == StArray2) { Console.WriteLine("StArray1 == StArray2"); // false } if (StArray1.Equals(StArray2)) { Console.WriteLine("StArray1 Equals StArray2"); // false } if ((StArray1 as IStructuralEquatable).Equals(StArray2))// IStructuralEquatable重寫過object的Equals函數,會調用object的Equals函數 { Console.WriteLine("StArray1 IStructuralEquatable Equals StArray2"); // false } if ((StArray1 as IStructuralEquatable).Equals(StArray2, EqualityComparer<Student>.Default))// IStructuralEquatable新增的Equals函數接口,傳入缺省比較對象實例 { Console.WriteLine("StArray1 IStructuralEquatable Equals Default StArray2"); // true } if ((StArray1 as IStructuralEquatable).Equals(StArray2, new StdArrayEqualityComparer()))// IStructuralEquatable新增的Equals函數接口,傳入自定義比較對象實例 { Console.WriteLine("StArray1 StdArrayEqualityComparer StArray2"); // true } Tuple<int, Student> StTp1 = new Tuple<int, Student>(100, new Student(0, "Jim", true)); Tuple<int, Student> StTp2 = new Tuple<int, Student>(100, new Student(0, "Jim", true)); if (StTp1 == StTp2) { Console.WriteLine("StTp1 == StTp2"); // false } if (StTp1.Equals(StTp2))// Tuple重寫過object的Equals函數,使得其進行值比較 { Console.WriteLine("StTp1 Equals StTp2"); // true } if ((StTp1 as IStructuralEquatable).Equals(StTp2)) // IStructuralEquatable重寫過object的Equals函數,會調用object的Equals函數 { Console.WriteLine("StTp1 IStructuralEquatable Equals StTp2"); // true } if ((StTp1 as IStructuralEquatable).Equals(StTp2, EqualityComparer<object>.Default))// IStructuralEquatable新增的Equals函數接口,傳入缺省比較對象實例 { Console.WriteLine("StTp1 IStructuralEquatable Equals Default StTp2"); // true } if ((StTp1 as IStructuralEquatable).Equals(StTp2, new StdTupleEqualityComparer()))// IStructuralEquatable新增的Equals函數接口,傳入自定義比較對象實例 { Console.WriteLine("StTp1 StdTupleEqualityComparer StTp2"); // true }
集合
注1:虛線框的為接口、紅色實線框的為功能類
注2:左邊為非泛型集合類、右邊為泛型集合類
.Net Framework1.0包含非泛型集合類,如:ArrayList、Hashtable、Queue、Stack、SortedList等
.Net Framework2.0添加了泛型和泛型集合類的支持,如:List<T>、Dictionary<TKey,TValue>、Queue<T>、Stack<T>、SortedList<TKey,TValue>等
大多數集合類都在System.Collections和System.Collections.Generic名稱空間中。非泛型集合類在System.Collections中,泛型集合類在System.Collections.Generic中。
專用於特定類型的非泛型集合類在System.Collections.Specialized中,線程安全的泛型集合類在System.Collections.Concurrent中。
ArrayList、List<T>
ArrayList是可變長數組,你可以將任意多的數據Add到ArrayList里面。其內部維護的數組,當長度不足時,會自動擴容為原來的兩倍。
但是ArrayList也有一個缺點,就是存入ArrayList里面的數據都是Object類型的,所以如果將值類型存入和取出的時候會發生裝箱、拆箱操作,這會影響程序性能。在.Net 2.0泛型出現以后,就提供了List<T>。
List<T>是ArrayList的泛型版本,它不再需要裝箱拆箱,直接取,直接用,它基本與ArrayList一致,不過在使用的時候要先設置好它的類型,而設置好類型之后,不是這種類型的數據,是不允許Add進去的。
就性能來說,如果要存進數組的只有一種數據,那么無疑List<T>是最優選擇。
List<int> ListInt = new List<int>();
如果一個變長數組,又要存int,又要存string。那么就只能用ArrayList。
StringCollection
字符串類型的可變長數組
Hashtable、Dictionary<TKey,TValue>、ConcurrentDictionary<TKey,TValue>
Hashtable(哈希表)是一種根據key查找非常快的鍵值數據結構,不能有重復key,而且由於其特點,其長度總是一個素數,所以擴容后容量會比2倍大一點點,加載因子為0.72f。
當要大量使用key來查找value的時候,HashTable無疑是最佳選擇,HashTable與ArrayList一樣,是非泛型的,value存進去是object,存取會發生裝箱、拆箱,所以出現了Dictionary<TKey,TValue>。
Dictionary<TKey,TValue>是HashTable的泛型版本,存取同樣快,但是不需要裝箱和拆箱了。而且,其優化了算法,Hashtable是0.72,它的浪費容量少了很多。
Dictionary<string,Person> Dic = new Dictionary<string,Person>();
Hashtable允許單線程寫入多線程讀取,對Hashtable進一步調用Synchronized()方法可以獲得完全線程安全的類型,而Dictionary非線程安全, 必須人為使用lock語句進行保護, 效率大減.
如果 HashTable要允許並發讀但只能一個線程寫, 要這么創建 HashTable實例:
System.Collections.Hashtable htSyn = System.Collections.Hashtable.Synchronized(new System.Collections.Hashtable());
Hashtable也可以使用lock語句進行保護,但要lock的不是HashTable,而是其成員變量SyncRoot
private static System.Collections.Hashtable htCache = new System.Collections.Hashtable(); public static void AccessCache () { lock ( htCache.SyncRoot ) { htCache.Add ( "key", "value" ); } } //也可以用Monitor.Enter和Exit函數達到與lock相同的結果 public static void AccessCache () { System.Threading.Monitor.Enter ( htCache.SyncRoot ); try { htCache.Add ( "key", "value" ); } finally { System.Threading.Monitor.Exit ( htCache.SyncRoot ); } }
ConcurrentDictionary<TKey,TValue>為線程安全的Dictionary<TKey,TValue>
注:StringDictionary: 字符串類型的哈希表
HashSet<T>、SortedSet<T>
算法,存儲結構都與哈希表相同,主要是設計用來做高性能集運算的,例如對兩個集合求交集、並集、差集等。
HashSet<T>:集合中包含一組不重復出現且無特定順序的元素。 .net 3.5加入
SortedSet<T>:集合中包含一組不重復出現且有序的元素。加入元素后,會自動排序 .net 4加入
Queue、Queue<T>、ConcurrentQueue<T>
Queue隊列,Queue<T>泛型隊列,ConcurrentQueue<T>線程安全版本泛型隊列 先進先出。enqueue方法入隊列,dequeue方法出隊列。
Stack、Stack<T>、ConcurrentStack<T>
Stack棧,Stack<T>泛型棧,ConcurrentStack<T>線程安全版本泛型棧 先進后出。push方法入棧,pop方法出棧。
SortedList、SortedList<TKey,TValue>
集合中的數據是有序的。可以通過key來匹配數據,也可以通過int下標來獲取數據。
添加操作比ArrayList,Hashtable略慢;查找、刪除操作比ArrayList快,比Hashtable慢。
SortedDictionary<TKey,TValue>
SortedDictionary<TKey,TValue>相比於SortedList<TKey,TValue>其性能優化了,SortedList<TKey,TValue>其內部維護的是數組,
而SortedDictionary<TKey,TValue>內部維護的是紅黑樹(平衡二叉樹)的一種,因此其占用的內存,性能都好於SortedDictionary<TKey,TValue>。唯一差在不能用下標取值。
ListDictionary、LinkedList<T>
ArrayList、List<T>、Hashtable等容器類,其內部維護的是數組Array來,ListDictionary和LinkedList<T>不用Array,而是用鏈表的形式來保存。鏈表最大的好處就是節約內存空間。
ListDictionary是單向鏈表。
LinkedList<T>雙向鏈表。雙向鏈表的優勢,可以插入到任意位置。
HybridDictionary
HybridDictionary類充分利用了Hashtable查詢效率高和ListDictionary占用內存空間少的優點,內置了Hashtable和ListDictionary兩個容器,添加數據時內部邏輯如下:
當數據量小於8時,Hashtable為null,用ListDictionary保存數據。
當數據量大於8時,實例化Hashtable,數據轉移到Hashtable中,然后將ListDictionary置為null。
BitArray、BitVector32
BitArray用於二進制運算,"或"、"非"、"與"、"異或非"等這種操作,只能存true或false;
BitVector32為結構體,速度非常快,只能存儲32位,存在一個整數里。可使用section能選擇片段的位置長度。