靜態(tài)方法只能訪問類中的靜態(tài)成員。 構造函數(shù)和析構函數(shù)不能被繼承。 在派生類中覆蓋基類中的方法時,在方法前面加上new關鍵字可關閉編譯器的警告。 base關鍵字主要是為派生類調用基類成員提供一個簡寫的方法。 C#中的多態(tài)性有兩種:編譯時的多態(tài)性和運行時的多態(tài)性;編譯時的多態(tài)性是通過重載實現(xiàn)的,運行時的多態(tài)性是通過虛成員實現(xiàn)的;編譯時的多態(tài)性為我們提供了運行速度快的特點,而運行時的多態(tài)性則帶來了高度的靈活和抽象的特點。 方法在使用了virtual修飾符之后,不允許再有static,abstract或override修飾符。 在派生類中聲明對虛方法的重載,要求在聲明中加上override關鍵字,而且不能有new,static或virtual修飾符. 抽象類只能作為其他類的基類,不能被實例化。 抽象類中的抽象方法在派生類中不能使用base關鍵字來進行訪問。 密封類(使用sealed修飾符的類)不能被其他類繼承。 將方法密封可防止方法所在的類的派生類對該方法重載。 是類的每個成員方法都可以作為密封方法,密封方法必須對基類的虛方法進行重載,提供具體的實現(xiàn)方法。
引用類型是類型安全的指針,它們的內存是分配在堆(保存指針地址)上的。 String、數(shù)組、類、接口和委托都是引用類型。 強制類型轉換與as類型轉換的區(qū)別:當類型轉換非法時,強制類型轉換將拋出一個System.InvalidCastException異常, 而as不會拋出異常,它返回一個null值。 用using創(chuàng)建別名:using console = System.Console; 訪問限定符: public 該成員可以被其他任何類訪問 protected 該成員只能被其派生類訪問 private 該成員只能被本類的其他成員訪問 internal 該成員只能在當前編譯單元的其他成員訪問 帶參數(shù)列表和返回值的Main方法: class Test { public static int Main(string[] args) { foreach (string arg in args) { ... } } } 構造函數(shù)(constructor)包括實例構造函數(shù)和靜態(tài)構造函數(shù)。 構造函數(shù)與類名相同,且不能有返回值。例: class TestClass { TestClass() //實例構造函數(shù):可以訪問靜態(tài)成員和實例成員,用于初始化實例成員 { ... }
static TestClass() //靜態(tài)構造函數(shù):只能訪問靜態(tài)成員,用于初始化靜態(tài)成員 { ... } } 類的靜態(tài)成員屬于類所有,不必生成實例就可以訪問,它是在載入包含類的應用程序時創(chuàng)建的, 但靜態(tài)方法不能訪問類的實例變量和方法。通常,靜態(tài)變量是在定義時就賦初始值的。 類的實例成員屬于類的實例所有,不創(chuàng)建實例對象就無法對其進行訪問,實例成員可以訪問類的 靜態(tài)成員和其它實例成員。 調用基類的析構函數(shù): class A { public A() { ... } } class B { public B(): base() //調用基類的析構函數(shù) { ... } } 常量:其值是在編譯時設定的,必須是數(shù)值文字。默認狀態(tài)下常量是靜態(tài)的。例: class A { public const double pi = 3.1415; } 常量是編譯時就確定的值,只讀字段是在運行才能確定的值。比如運行時才能確定的屏幕分辨率。 只讀字段只能在類的析構函數(shù)中賦值。 靜態(tài)只讀字段: class A { public static readonly int ScreenWidth; //靜態(tài)只讀字段 static A() //靜態(tài)析構函數(shù) { ScreenWidth = 1024; //在靜態(tài)析構函數(shù)中初始化 } } 在類的繼承中,類的析構函數(shù)是不會被繼承的。 一個派生類只能從一個基類繼承,不能同時從多個基類繼承,但可以通過繼承多個接口來 達到相同目的。實現(xiàn)多繼承的唯一方法就是使用接口。例: class MyFancyGrid: Control, ISerializable, IDataBound { ... } 密封類是不能繼承的類,抽象類不能被定義為密封類,且密封類的私有成員不能用protected修飾, 只能用private。例: sealed class A { ... } 關鍵字ref和out用于指定用引用方式傳遞方法的參數(shù)。 它們的區(qū)別是:ref參數(shù)必須初始化,而out參數(shù)不需要初始化。所以在方法處理代碼依賴參數(shù)的 初始化值時使用ref,不依賴初始化值時使用out。 對out參數(shù)即使在傳遞前對其進行了初始化,其值也不會傳遞到方法處理函數(shù)內部。傳遞時系統(tǒng) 會將其設為未初始化。所以在方法內部必須對out參數(shù)進行初始化。 方法重載時,必須參數(shù)數(shù)目和參數(shù)類型其中之一不同,返回值不同不能作為重載。 C#不支持方法的默認值,只能通過方法重載來實現(xiàn)。例: class A { int Method(int a) { ... }
void Method(int a, int b) //參數(shù)數(shù)目不同 { //返回值不同不能作為重載 ... } } params參數(shù)用于一個不定數(shù)目參數(shù)的方法,一般后面跟一個數(shù)組。例: class A { public void Method(params int[] i) { ... } } 方法的覆蓋:指派生類覆蓋基類的同名方法,有二種方法 1)第一種是在派生類要覆蓋的方法前面加new修飾,而基類不需要作任何改動。 這種方法的缺點是不能實現(xiàn)多態(tài)。例: class A { public void Method() //無需任何修飾 { ... } }
class B: A //從基類繼承 { new public void Method() //覆蓋基類的同名方法 { ... } }
class TestClass { A Instance = new B(); Instance.Method(); //這時將調用類A的Method方法,而不是類B的Method方法 }
2)第二種是在派生類要覆蓋的方法前面加override修飾,而基類的同名方法前面加virtual修飾。 這樣就能實現(xiàn)多態(tài),例:
class A { virtual public void Method() //基類定義虛方法 { //虛擬方法不能定義為private,因為private成員對派生類是無法訪問的 ... } }
class B: A //從基類繼承 { override public void Method() //派生類覆蓋基類的同名虛方法 { ... } }
class TestClass { protected void Test() { A Instance = new B(); //定義一個實例,類型為基類,從派生類創(chuàng)建 //派生類總是能夠向上轉換為其基類 Instance.Method(); //將調用派生類B的Method方法,而不是基類的,這就是多態(tài) } }
說明:new修飾的方法覆蓋不能實現(xiàn)多態(tài)的原因,是因為使用new時編譯器只會實現(xiàn)早期綁定(early binding)。 即調用的方法在編譯時就決定了:編譯器看到Instance.Method()而Instance的類是A,就會調用類A的Method()方法。 override修飾的方法覆蓋可以實現(xiàn)多態(tài)的原因,是因為實現(xiàn)了后期綁定(late binding)。 使用override時強制編譯器在運行時根據(jù)類的真正類型正確調用相應的方法,而不是在編譯時。 而基類的同名方法必須加virtual修飾。 類的靜態(tài)方法可能通過 類名.靜態(tài)方法名 這種格式來調用,不能使用 實例名.靜態(tài)方法名 這種方法調用。 因為類的靜態(tài)方法為類所有(是屬于類本身的),而非實例所有(不是屬于類的實例的)。 類的靜態(tài)方法可以訪問類的任何靜態(tài)成員,但不能訪問類的實例成員。 C#中類的變量稱為字段。類的public變量稱為類的公共字段。 類的屬性由一個protected(也可以是private)字段和getter和setter方法構成: class Address { protected string zipCode; //protected字段,注意大小寫 public string ZipCode { get //getter方法 { return zipCode; } set //setter方法 { zipCode = value; //被傳遞的值自動被在這個value變量中 } }; } 只讀屬性是指省略setter方法的屬性,只讀屬性只能讀取,不能設置。 屬性也可以用限定符virtual,override和abstract修飾,功能同其他類的方法。 屬性有一個用處稱為懶惰的初始化(lazy initialization)。即在需要類成員時才對它們進行 初始化。如果類中包含了很少被引用的成員,而這些成員的初始化又會花費大量的時候和系統(tǒng) 資源的話,懶惰的初始化就很有用了。 C#中數(shù)組對象共同的基類是System.Array。 將數(shù)組聲明為類的一個成員時,聲明數(shù)組與實例化數(shù)組必須分開,這是因為只能在運行時創(chuàng)建了 類的實例對象之后,才能實例化數(shù)組元素值。 聲明: int[] intArray; //一維數(shù)組 int[,,] int3Array; //三維數(shù)組 初始化: intArray = new int[3] {1,2,3}; int[,] int2Array = new int[2,3] {{1,2,3},{4,5,6}}; //聲明時可以初始化 遍歷: 1)一維數(shù)組 for (int i = 0; i < intArray.Length; i++); //Array.Length返回數(shù)組所有元素的個數(shù) foreach (int i in intArray); for (int i = 0; i < intArray.GetLength(0); i++);//Array.GetLength(0)返回數(shù)組第一維的個數(shù) 2)多維數(shù)組 for (int i = 0; i < int3Array.GetLength(0); i++) //遍歷三維數(shù)組 for (int j = 0; j < int3Array.GetLength(1); j++) for (int k = 0; k < int3Array.GetLength(2); k++) { ... } 數(shù)組的維數(shù)就是該數(shù)組的秩(Rank)。Array.Rank可以返回數(shù)據(jù)的秩。 鋸齒數(shù)組(jagged Array)是元素為數(shù)組的數(shù)組,例: int[][] jaggedArray = new int[2][]; //包含二個元素,每個元素是個數(shù)組 jaggedArray[0] = new int[2]; //每個元素必須初始化 jaggedArray[1] = new int[3]; for (int i = 0; i < jaggedArray.Length; i++) //遍歷鋸齒數(shù)組 for (int j = 0; j < jaggedArray[i].Length; j++) { ... } 類的屬性稱為智能字段,類的索引器稱為智能數(shù)組。由于類本身作數(shù)組使用,所以用 this作索引器的名稱,索引器有索引參數(shù)值。例: using System; using System.Collections; class MyListBox { protected ArrayList data = new ArrayList(); public object this[int idx] //this作索引器名稱,idx是索引參數(shù) { get { if (idx > -1 && idx < data.Count) { return data[idx]; } else { return null; } } set { if (idx > -1 && idx < data.Count) { data[idx] = value; } else if (idx = data.Count) { data.Add(value); } else { //拋出一個異常 } } } } 接口是二段不同代碼之間約定,通過約定實現(xiàn)彼此之間的相互訪問。 C#并不支持多繼承,但通過接口可實現(xiàn)相同功能。 當在接口中指定了實現(xiàn)這個接口的類時,我們就稱這個類“實現(xiàn)了該接口”或“從接口繼承”。 一個接口基本上就是一個抽象類,這個抽象類中除了聲明C#類的其他成員類型——例如屬性、 事件和索引器之外,只聲明了純虛擬方法。 接口中可以包含方法、屬性、索引器和事件——其中任何一種都不是在接口自身中來實現(xiàn)的。例: interface IExampleInterface { //property declaration int testProperty { get; }
//event declaration event testEvevnt Changed;
//mothed declaration function void testMothed();
//indexer declaration string this[int index] { get; set; } } 說明:定義接口時,在方法、屬性、事件和索引器所有這些接口成員都不能用public之類的訪問限定符, 因為所有接口成員都是public類型的。 因為接口定義了一個約定,任何實現(xiàn)一個接口的類都必須定義那個接口中每一個成員,否則將編譯失敗。例: using System; public class FancyControl { protected string data; public string Data { get {return this.data;} set {data = value;} } }
interface IValidate { bool Validate(); //接口方法 }
public class MyControl: FancyControl, IValidate { public MyControl() { data = "my control data"; }
public bool Validate() //實現(xiàn)接口 { if (data == "my control data") return true; else return false; } } class InterfaceApp { MyControl myControl = new MyControl(); IValidate val = (IValidate)myControl; //可以將一個實現(xiàn)某接口的類,轉換成該接口 bool success = val.Validate(); //然后可調用該接口的方法 } 也可以用: bool success = myControl.Validate(); 這種方法來調用Validate方法,因為Validate在類MyControl中是被定義成public的,如果去除public,Validate方法被隱藏, 就不能用這種方法調用了,這樣隱藏接口方法稱為名字隱藏(name hiding)。 可以用:類實例 is 接口名 來判斷某個類是否實現(xiàn)了某接口,例: myControl is IValidate //MyControl類的實例myControl是否實現(xiàn)了IValidate接口 當然,也可用as來作轉換,根據(jù)轉換結果是否為null來判斷某個類是否實現(xiàn)了某接口,例: IValidate val = myControl as IValidate; if (null == val) { ... //沒有實現(xiàn)IValidate接口 } else { ... //實現(xiàn)了IValidate接口 }
如果一個類從多個接口繼承,而這些接口中如果定義的同名的方法,則實現(xiàn)接口的方法時,必須加接口名來區(qū)別, 寫成 接口名.方法名。假設Test類從IDataStore和ISerializable二個接口繼承,而這二個接口都有SaveData()方法, 實現(xiàn)SaveData()方法時必須寫成: class Test: ISerializable, IDataStore { void ISerializable.SaveData() { ... }
void IDataStore.SaveData() { ... } }
如果一個類從多個接口繼承,為了方便可以定義一個新的接口,這個接口繼續(xù)多個接口,然后類直接從這個接口繼承就 可以了,這個叫合并接口。例: interface ISaveData: ISerializable, IDataStore { //不需要定義任何方法或成員,只是用作合并 } class Test: ISaveData //只要繼承ISaveData就可以了 { ... } C# 操作符優(yōu)先級(從高到低) 初級操作符 () x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked 一元操作符 + - | ~ ++x --x (T)x 乘除操作符 * / % 加減操作符 + - 位移操作符 << >> 關系操作符 < > <= >= is 等于操作符 == 邏輯與 & 邏輯異或 ^ 邏輯或 | 條件與 && 條件或 || 條件操作符 ?: 賦值操作符 = *= /= %= += -= <<= >>= &= ^= |= 所有的二元操作符除賦值符外都是左聯(lián)合的,即從左到右計算。 typeof()運算符可以從一個類名得到一個System.Type對象,而從System.Object對象繼承來的GetType()方法 則可從一個類實例來得到一個System.Type對象。例: Type t1 = typeof(Apple); //Apple是一個類名 Apple apple = new Apple(); //apple是Apple類的一個實例 Type t2 = apple.GetType(); //t1與t2是相同的 通過反射得到一個類的所有成員和方法: Type t = typeof(Apple); string className = t.ToString(); //得到類名 MethodInfo[] methods = t.GetMethods(); //得到所有方法 foreach (MethodInfo method in methods) { //用method.ToString()得到方法名 } MemberInfo[] members = t.GetMembers(); //得到所有成員 foreach (MemberInfo member in members) { //用member.ToString()得到成員名 } sizeof()操作符用來計算值類型變量在內存中占用的字節(jié)數(shù)(Bytes),并且它只能在unsafe(非安全) 代碼中使用。例: static unsafe public void ShowSizes() { int i, j; j = sizeof(short); j = sizeof(i); } 盡可能使用復合賦值操作符,它比不用復合賦值操作符的效率高。 for語句的語法為: for (initialization; Boolean-expression; step) embedded-statement 在initialization和step部份還可以使用逗號操作符,例: for (int i = '0', j = 1; i <= '\xFF'; i++, j++) for (int i = 1, j = 1; i < 1000; i += j, j = i - j) //輸出斐波那契數(shù)列 Console.Write("{0} ", i); 在switch語句中執(zhí)行一個分支的代碼后還想執(zhí)行另一個分支的代碼,可以用: goto case 分支; 操作符重載是為了讓程序更加自然,容易理解。想要為一個類重新定義一個操作符,使用以下語法: public static 返回值 operator 操作符 (操作對象1[,操作對象2]) 說明: 1)所有重載的操作符方法都必須定義為public和static 2)從技術上說返回值可以是任何類型,但通常是返回所定義方法使用的類型 3)操作對象的數(shù)目取決于重載是一元操作符還是二元操作符,一元操作符只要一個操作對象,二元操作符則需要二個。 4)不管重載是一元操作符還是二元操作符,第一個操作對象的類型都必須與返回值的類型一致;而對于二元操作符的第二個 操作對象的類型則可以是任何類型。 5)只有下列操作符可以被重載: 一元:+ - ! ~ ++ -- true false 二元:+ - * / % & | ^ << >> == != > < >= <= 賦值操作符(+=,-=,*-,/=,%=等等)無法被重載。 []和()操作符也無法被重載。 6)操作符的優(yōu)先級是無法改變的,運算優(yōu)先級的規(guī)則是靜態(tài)的。
例:假設一個Invoice發(fā)票類由多個InvoiceDetailLine類(成員只有一個Double類型的Amount金額屬性)組成, 我們重載+操作符,使之可以將InvoiceDetailLine類的內容(注意不是金額合計)加在一起。 class Invoice { public ArrayList DetailLine; public Invoice //類的析構函數(shù) { DetailLine = new ArrayList(); //ArrayList存放多個InvoiceDetailLine類的實例 }
public static Invoice operator+ (Invoice Invoice1, Invoice Invoice2) //參數(shù)與返回值的類型一致 { //Invoice1與Invoice2的內容合并 Invoice ReturnInvoice = new Invoice(); foreach(InvoiceDetailLine detailLine in Invoice1.DetailLines) ReturnInvoice.DetailLine.Add(detailLine); foreach(InvoiceDetailLine detailLine in Invoice2.DetailLines) ReturnInvoice.DetailLine.Add(detailLine); return ReturnInvoice; } }
class InvoiceAddApp //調用示例 { public static void main() { Invoice i1 = new Invoice(); for(int i = 0; i < 3; i++) i1.DetailLine.Add(new InvoiceDetailLine(i + 1));
Invoice i2 = new Invoice(); for(int i = 0; i < 3; i++) i2.DetailLine.Add(new InvoiceDetailLine(i + 1));
Invoice summaryInvoice = i1 + i2; //調用重載的操作符+方法 } }
自定義類型轉換可以編寫代碼實際二個不同的類、結構體之間的轉換。 語法:public static implicite/explicite operator 輸出類型 (輸入類型) 說明: 1)轉換方法必須是靜態(tài)的。 2)implicite表示隱式轉換,explicite表示顯式轉換。 3)輸入類型和輸出類型其中之一必須與包含轉換的類或結構體類型。即轉換必須與本類相關。 例: struct Celisus { public float t;
public Celisus(float t) { this.t = t; //this.t是結構體的字段,t是參數(shù) }
public static implicite operator Celisus(float t) //float=>Celisus { return new Celisus(t); }
public static implicite operator float(Celisus c) //Celisus=>float { return ((c.t - 32) / 9) * 5; } }
代表的(delegate)目的與C++中的函數(shù)指針相同,代表不是在編譯時被定義的,而是在運行時被定義的。 代表主要有二個用途:回調(Callback)和事件處理(event) 回調通常用于異步處理和自定義處理。例: class DBManager { static DBConnection[] activeConnections; //聲明回調函數(shù) public void delegate EnumConnectionCallback(DBConnection connection);
public static void EnumConnections(EnumConnectionCallback callback) { foreach (DBConnection connection in activeConnections) { callback(connection); //執(zhí)行回調函數(shù) } } }
//調用 class DelegateApp { public static void ActiveConncetionCallback(DBConnection connection) //處理函數(shù) { ... }
public void main() { //創(chuàng)建指向具體處理函數(shù)的代表實例(新建一個代表,讓它指向具體的處理函數(shù)) DBManager.EmnuConnectionCallback myCallback = new DBManager.EmnuConnectionCallback(ActiveConncetionCallback); DBManager.EnumConnections(myCallback); } }
//使用靜態(tài)代表,上面的調用改為 class DelegateApp { //創(chuàng)建一個指向處理函數(shù)的靜態(tài)代表 public static DBManager.EmnuConnectionCallback myCallback = new DBManager.EmnuConnectionCallback(ActiveConncetionCallback); public static void ActiveConncetionCallback(DBConnection connection) { ... }
public void main() { DBManager.EnumConnections(myCallback); } }
//在需要時才創(chuàng)建代表,上面的調用改為 class DelegateApp { //將創(chuàng)建代表放在屬性的getter方法中 public static DBManager.EmnuConnectionCallback myCallback { get { retun new DBManager.EmnuConnectionCallback(ActiveConncetionCallback); } } public static void ActiveConncetionCallback(DBConnection connection) { ... }
public void main() { DelegateApp app = new DelegateApp(); //創(chuàng)建應用程序 DBManager.EnumConnections(myCallback); } }
可以將多個代表整合成單個代表,例: class CompositeDelegateApp { public static void LogEvent(Part part) { ... }
public static void EmailPurchasingMgr(Part part) { ... }
public static void Main() { //定義二個代表 InventoryManager.OutOfStockExceptionMethod LogEventCallback = new InventoryManager.OutOfStockExceptionMethod(LogEvent); InventoryManager.OutOfStockExceptionMethod EmailPurchasingMgrCallback = new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr); //整合為一個代表,注意后加的代表先執(zhí)行(這里是先執(zhí)行LogEventCallback) InventoryManager.OutOfStockExceptionMethod onHandExceptionEventsCallback = EmailPurchasingMgrCallback + LogEventCallback; //調用代表 InventoryManager mgr = new InventoryManager(); mgr.ProcessInventory(onHandExceptionEventsCallback); //InventoryManager類的ProcessInventory方法的原型為: //public void ProcessInventory(OutOfStockExceptionMethod exception); } }
可以根據(jù)需要將多個代表自由地組合成單個代表,例: class CompositeDelegateApp { //代表指向的處理函數(shù)(三個代表三個函數(shù)) public static void LogEvent(Part part) { ... }
public static void EmailPurchasingMgr(Part part) { ... }
public static void EmailStoreMgr(Part part) { ... }
public static void Main() { //通過數(shù)組定義三個代表 InventoryManager.OutOfStockExceptionMethod[] exceptionMethods = new InventoryManager.OutOfStockExceptionMethod[3]; exceptionMethods[0] = new InventoryManager.OutOfStockExceptionMethod(LogEvent); exceptionMethods[1] = new InventoryManager.OutOfStockExceptionMethod(EmailPurchasingMgr); exceptionMethods[2] = new InventoryManager.OutOfStockExceptionMethod(EmailStoreMgr);
int location = 1; //再定義一個代表(用于組合成單代表) InventoryManager.OutOfStockExceptionMethod compositeDelegate; //根據(jù)需要組合 if (location = 2) { compositeDelegate = exceptionMethods[0] + exceptionMethods[1]; } else { compositeDelegate = exceptionMethods[0] + exceptionMethods[2]; } //調用代表 InventoryManager mgr = new InventoryManager(); mgr.ProcessInventory(compositeDelegate); } } C#的事件遵循“發(fā)布——預訂”的設計模式。在這種模式中,一個類公布能夠出現(xiàn)的所有事件, 然后任何的類都可以預訂這些事件。一旦事件產生,運行環(huán)境就負責通知每個訂戶事件已經發(fā)生了。 當代表作為事件的處理結果時(或者說定義具有代表的事件),定義的代表必須指向二個參數(shù)的方法: 一個參數(shù)是引發(fā)事件的對象(發(fā)布者),另一個是事件信息對象(這個對象必須從EventArgs類中派生)。 例: using System;
class InventoryChangeEventArgs: EventArgs //事件信息對象,從EventArgs類派生 { ... //假設定義二個public屬性string Sku和int Change }
class InventoryManager //事件的發(fā)布者 { //聲明代表 public delegate void InventoryChangeEventHander(object source, InventoryChangeEventArgs e); //發(fā)布事件,event關鍵字可將一個代表指向多個處理函數(shù) public event InventoryChangeEventHandler onInventoryChangeHander; public void UpdateInventory(string sku, int change) { if (change == 0) return; InventoryChangeEventArgs e = new InventoryChangeEventArgs(sku, change); //觸發(fā)事件 if (onInventoryChangeHandler != null) //如果有預訂者就觸發(fā) onInventoryChangeHandler(this, e); //執(zhí)行代表指向的處理函數(shù) } }
class InventoryWatcher //事件的預訂者 { public InventoryWatcher(InventoryManager mgr) //mgr參數(shù)用于聯(lián)結發(fā)布者 { this.inventoryManager = mgr; //預訂事件,用 += 調用多個處理函數(shù) mgr.onInventroyChangeHandler += new InventoryManager.InventoryChangeEventHandler(onInventoryChange); //事件處理函數(shù) void onInventroyChange(object source, InventroyChangeEventArgs e) { ... }
InventoryManager inventoryManager; } }
class EventsApp //主程序 { public static void Main() { InventoryManager inventoryManager = new InventoryManager(); InventoryWatcher inventoryWatcher = new InventoryWatcher(inventoryManager);
inventoryManager.UpdateInventory("111 006 116", -2); inventoryManager.UpdateInventory("111 006 116", 5); } }
Microsoft Windows NT和IBM OS/2等操作系統(tǒng)都支持占先型多任務。在占先型多任務執(zhí)行中,處理器負責 給每個線程分配一定量的運行時間——一個時間片(timeslice)。處理器接著在不同的線程之間進行切換, 執(zhí)行相應的處理。在單處理器的計算機上,并不能真正實現(xiàn)多個線程的同時運行,除非運行在多個處理器 的計算機上。操作系統(tǒng)調度的多線程只是根據(jù)分配給每個線程時間片進行切換執(zhí)行,感覺上就像同時執(zhí)行。
上下文切換(context switching)是線程運行的一部分,處理器使用一個硬件時間來判斷一個指定線程的時間片 何時結束。當這個硬件計時器給出中斷信號時,處理器把當前運行的線程所用的所有寄存器(registers)數(shù)據(jù) 存儲到堆棧中。然后,處理器把堆棧里那些相同的寄存器信息存放到一種被稱為“上下文結構”的數(shù)據(jù)結構中。 當處理器要切換回原來執(zhí)行的線程時,它反向執(zhí)行這個過程,利用與該線程相關的上下文結構,在寄存器里 重新恢復與這一線程相關的信息。這樣的一個完整過程稱為“上下文切換”。
多線程允許應用程序把任務分割為多個線程,它們彼此之間可以獨立地工作,最大限度地利用了處理器時間。
using System; using System.Threading;
class SimpleThreadApp { public static void WorkerThreadMethod() //線程的執(zhí)行體 { ... //執(zhí)行一些操作 }
public static void Main() { //創(chuàng)建一個線程代表指向線程的執(zhí)行體,ThreadStart是創(chuàng)建新線程必須用到的代表 ThreadStart worker = new ThreadStart(WorkerThreadMethod); Thread t = new Thread(worker); //用線程代表創(chuàng)建線程 t.Start(); //執(zhí)行線程 } }
可以通過兩種方式來得到一個Thread對象:一種是通過創(chuàng)建一個新線程來得到,如上例;另一種在正在執(zhí)行的線程調用 靜態(tài)的Thread.CurrentThread方法。 靜態(tài)方法Thread.Sleep(int ms)可以讓當前線程(它自動調用Thread.CurrentThread)暫停指定毫秒的時間。 如果使用Thread.Sleep(0)那么當前線程將一直處于等待中,直到另一個線程調用這個線程的實例方法Thread.Interrupt方法, 等待才會結束。 使用Thread.Suspend方法也能掛起線程,Thread.Suspend方法可以被當前線程或其他線程調用,而Thread.Sleep(0) 只能由當前線程在執(zhí)行體中調用。當線程用Thread.Suspend掛起時,必須用Thread.Resume方法恢復。不論Thread.Suspend 方法調用了多少次,只要調用Thread.Resume方法一次就可以線程恢復執(zhí)行。用Thread.Suspend方法并不會阻塞線程, 調用立即返回。而Thread.Sleep(0)則會阻塞線程。所以確切地說Thread.Sleep(0)暫停線程,而不是掛起線程。 使用Thread.Abort方法可以終止正在執(zhí)行的線程。當Thread.Abort方法被調用時,線程不會立即終止執(zhí)行。運行環(huán)境將會 等待,直到線程到達文檔中所描述的“安全點”。如果要確保線程已經完全停止,可以使用Thread.Join方法。這是一個同步 調用,同步調用意味著直到線程完全停止,調用才會返回。 Thread.Priority屬性用于設置的線程的優(yōu)先級。其值是Thread.ThreadPriority枚舉值,可以設為Highest, AboveNormal, Normal, BelowNormal, Lowest。缺省值是Thread.ThreadPriority.Normal。 線程的同步是為了解決多個線程同時使用同一對象產生的一些問題。通過同步,可以指定代碼的臨界區(qū)(critical section), 一次只有一個線程可以進入臨界區(qū)。 使用System.Monitor類(鎖定與信號量)進行線程同步: using System; using System.Threading;
public void SaveData(string text) //線程執(zhí)行函數(shù)或線程執(zhí)行函數(shù)調用的對象的方法 { ... //執(zhí)行其他一些不需要同步的處理
Monitor.Enter(this); //獲取對象的Monitor鎖 ... //執(zhí)行需要同步的處理 Monitor.Exit(this); //釋放對象的Monitor鎖
... //執(zhí)行其他一些不需要同步的處理 } 說明:當執(zhí)行Monitor.Enter方法時。這個方法會試圖獲取對象上的Monitor鎖,如果另一個線程已經擁有了 這個鎖,這個方法將會阻塞(block),直到這個鎖被釋放。 也可用C#的lock語句來獲得和釋放一個Monitor鎖。上面同步寫成: public void SaveData(string text) //線程執(zhí)行函數(shù)或線程執(zhí)行函數(shù)調用的對象的方法 { ... //執(zhí)行其他一些不需要同步的處理
lock(this) //獲取對象的Monitor鎖,代碼塊執(zhí)行完成后釋放Monitor鎖 { ... //執(zhí)行需要同步的處理 }
... //執(zhí)行其他一些不需要同步的處理 }
也可以使用System.Threading名稱空間的Mutex類(互斥類)進行線程同步。與Monitor鎖一樣,一次只有一個線程 能獲得一個給定的互斥。但Mutex要慢得多,但它增加了靈活性。例: using System; using System.Threading; class Database { Mutex mutex = new Mutex(false); //創(chuàng)建一個互斥,但不立即獲得它 //注意:創(chuàng)建互斥在需要同步的方法之外,實際上它只要創(chuàng)建一個實例 public void SaveData(string text) //需要同步的方法 { mutex.WaitOne(); //等待獲得互斥 ... //需要同步的處理 mntex.Close(); //釋放互斥 } }
Mutex類重載了三個構造函數(shù): Mutex() //創(chuàng)建并使創(chuàng)建類立即獲得互斥 Mutex(bool initiallyOwned) //創(chuàng)建時可指定是否要立即獲得互斥 Mutex(bool initiallyOwned, string muterName) //還可以指定互斥的名稱 Mutex.WaitOne方法也重載了三次: Mutex.WaitOne() //一直等待 Mutex.WaitOne(TimeSpan time, bool exitContext) //等待TimeSpan指定的時間 Mutex.WaitOne(int milliseconds, bool exitContext) //等待指定的毫秒 線程的用法: 1)并發(fā)操作:比如一個程序監(jiān)視多個COM口,當每個COM接到信息時執(zhí)行一段處理時。 2)復雜長時間操作:一個長時間的復雜操作可能會使界面停滯,停止用戶響應,如果還允許用戶停止它, 或者顯示進度條、顯示操作執(zhí)行進程信息時。 反射(Reflection)就是能夠在運行時查找類型信息,這是因為.NET編譯的可執(zhí)行(PE)文件中包括MSIL和元數(shù)據(jù)(metadata)。 反射的中心是類System.Type。System.Type是一個抽象類,代表公用類型系統(tǒng)(Common Type System, CTS)中的一種類型。 using System; using System.Reflection; //反射命名空間,必須引用
public static void Main(string[] args) { int i = 6; Type t = i.GetType(); //根據(jù)實例得到類型 t = Type.GetType("System.Int32"); //根據(jù)類型的字符名稱得到類型 }
通過Assembly類可以得到已經編譯.NET Framework程序的中所有類型,例: using System; using System.Diagnostics; //為了使用Process類 using System.Reflection; //為了使用Assembly類
class GetTypesApp { protected static string GetAssemblyName(string[] args) { string assemblyName; if (0 == args.Length) //如果參數(shù)為空,取當前進程的名稱 { Process p = Process.GetCurrentProcess(); assemblyName = p.ProcessName + ".exe"; } else assemblyName = args[0]; //取第一個參數(shù),即當前運行程序名
return assemblyName; }
public static void Main(string[] args) { string assemblyName = GetAssemblyName(args); Assembly a = Assembly.LoadFrom(assemblyName); //調用編譯程序集 Type[] types = a.GetTypes(); //得到多個類型 foreach (Type t in types) //遍歷類型數(shù)組 { ... //取得t.FullName,t.BaseType.FullName等類型信息 } } } 一個應用程序可以包括多個代碼模塊。若要將一個cs文件編譯一個模塊,只要執(zhí)行下面的命令: csc /target:module 要編譯的模塊.cs //csc是C Sharp Compiler(C#編譯器) 然后在應用程序中using編譯的模塊.cs中的NameSpace即可應用了。 要反射應用程序中所有代碼模塊(Module),只要: Assembly a = Assembly.LoadFrom(assemblyName); //應用程序的物理文件名 Module[] modules = a.GetModules(); foreach(Module m in modules) { ... //顯示m.Name等 } 后期綁定(latebinding),例: string[] fileNames = Directory.GetFiles(Environment.CurrentDirectory, "*.dll"); foreach (string fileName in fileNames) { Assembly a = Assembly.LoadFrom(fileName); Type[] types = a.GetTypes(); foreach(Type t in types) { if (t.IsSubclassOf(typeof(CommProtocol))) //判斷是否有CommProtocol的派生類 { object o = Activator.CreateInstance(t); //生成實例 MethodInfo mi = t.GetMethod("DisplayName"); mi.Invoke(o, null); //調用方法 } } } //帶參數(shù)的例子 namespace Programming_CSharp { using System; using System.Reflection; public class Tester { public static void Main( ) { Type t = Type.GetType("System.Math"); Object o = Activator.CreateInstance(t);
// 定義參數(shù)類型 Type[] paramTypes = new Type[1]; paramTypes[0]= Type.GetType("System.Double");
MethodInfo CosineInfo = t.GetMethod("Cos", paramTypes);
//設置參數(shù)數(shù)據(jù) Object[] parameters = new Object[1]; parameters[0] = 45;
//執(zhí)行方法 Object returnVal = CosineInfo.Invoke(o, parameters); Console.WriteLine("The cosine of a 45 degree angle {0}", returnVal); } } } 動態(tài)生成代碼和動態(tài)調用的完整例子: //動態(tài)生成代碼的部分 using System; using System.Reflection; using System.Reflection.Emit; //動態(tài)生成代碼必須引用
namespace ILGenServer { public class CodeGenerator { public CodeGenerator() { currentDomain = AppDomain.CurrentDomain; //得到當前域 assemblyName = new AssemblyName(); //從域創(chuàng)建一個程序集 assemblyName.Name = "TempAssembly"; //得到一個動態(tài)編譯生成器,AssemblyBuilerAccess.Run表示只在內存中運行,不能保存 assemblyBuilder = currentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilerAccess.Run); //從編譯生成器得到一個模塊生成器 moduleBuilder = assemblyBuilder.DefineDynamicModule("TempModule"); //模塊生成器得到類生成器 typeBuilder = moduleBuilder.DefineType("TempClass", TypeAttributes.Public); //為類添加一個方法 methodBuilder = typeBuilder.DefineMethod("HelloWord", MethodAttributes.Public, null, null); //為方法寫入代碼,生成代碼必須使用到IL生成器 msil = methodBuilder.GetILGenerator(); msil.EmitWriteLine("Hello World"); msil.Emit(OpCodes.Ret); //最后還需要編譯(build)一下類 t = typeBuilder.CreateType(); } AppDomain currentDomain; AssemblyName assemblyName; AssemblyBuilder assemblyBuilder; ModuleBuilder moduleBuilder; TypeBuilder typeBuilder; MethodBuilder methodBuilder; ILGenerator msil; object o; Type t; public Type T { get { return this.t; } } } } //動態(tài)調用的部分 using System; using System.Reflection; using ILGenServer; //引用動態(tài)生成代碼的類 public class ILGenClientApp { public static void Main( { CodeGenerator gen = new CodeGenerator(); //創(chuàng)建動態(tài)生成類 Type t = gen.T; if (null != t) { object o = Activator.CreateInstance(t); MethodInfo helloWorld = t.GetMethod("HelloWorld"); //為調用方法創(chuàng)建一個MethodInfo if (null != helloWorld) { helloWorld.Invoke(o, null); //調用方法 } } } } 調用DLL using System; using System.Runtime.InteropServices; //為了使用DLLImport特性
class PInvokeApp { [DllImport("user32.dll", CharSet=CharSet.Ansi)] //CharSet.Ansi指定Ansi版本的函數(shù)(MessageBoxA),CharSet.Unicode指定Unicode版本的函數(shù)(MessageBoxW) static extern int MessageBox(int hWnd, string msg, string caption, int type); //聲明DLL中的函數(shù) //[DllImport("user32.dll", EntryPoint="MessageBoxA")] //用這種方法使用不同的函數(shù)名 //static extern int MsgBox(int hWnd, string msg, string caption, int type); //[DllImport("user32.dll", CharSet=CharSet.Unicode)] //調用Unicode版的DLL函數(shù) //static extern int MessageBox(int hWnd, [MarshalAs(UnmanagedType.LPWStr)]string msg, // [MarshalAs(UnmanagedType.LPWStr)]string caption, int type); //將LPWStr翻譯為string型,缺省情況系統(tǒng)只將LPStr翻譯成string public static void Main() { MessageBox(0, "Hello, World!", "CaptionString", 0); //調用DLL中的函數(shù) } } 例2,使用回調: class CallbackApp { [DllImport("user32.dll")] static extern int GetWindowText(int hWnd, StringBuilder text, int count);
delegate bool CallbackDef(int hWnd, int lParam);
[DllImport("user32.dll")] static extern int EnumWindows(CallbackDef callback, int lParam);
static bool PrintWindow(int hWnd, int lParam) { StringBuilder text = new StringBuilder(255); GetWindowText(hWnd, text, 255); Console.WriteLine("Window Caption: {0}", text); return true; }
static void Main() { CallbackDef callback = new CallbackDef(PrintWindow); EnumWindows(callback, 0); } } 關鍵字unsafe指定標記塊在非控環(huán)境中運行。該關鍵字可以用于所有的方法,包括構造函數(shù)和屬性, 甚至還有方法中的代碼塊。關鍵字fixed負責受控對象的固定(pinning)。Pinning是一種動作,向 垃圾收集器(Garbage Collector, GC)指定一些不能被移動的對象。為了不在內存中產生碎片,.NET 運行環(huán)境把對象四處移動,以便于最有效地利用內存。使用fixed后指定對象將不會被移動,所以就 可以用指針來訪問它。 C#中只能得到值類型、數(shù)組和字符串的指針。在數(shù)組的情況下,第一個元素必須是值類型,因為C# 實際上是返回一個指向數(shù)組第一個元素的指針,而不是返回數(shù)組自身。 & 取一個變量的內存地址(即指向該變量的指針) * 取指針所指變量的值 -> 取成員 例: using System; class UnsafeApp { public static unsafe void GetValues(int* x, int* y) { *x = 6; *y = 42; }
public static unsafe void Main() { int a = 1; int b = 2; GetValues(&a, &b); } } fixed語法為:fixed(type* ptr = expression) statements 其中type也可以為非控類型,也可是void;expression是任何產生一個type指針的表達式; statements是應用的代碼塊。例: fixed (int* f = &foo.x) //foo是Foo類的一個實例,x是Foo類的一個int屬性 { SetFooValue(f); //SetFooValue方法的定義為unsafe static void SetFooValue(int* x) } 傳統(tǒng)的COM組件可以通過互操作層(COM Interop)與.NET運行環(huán)境交互?;ゲ僮鲗犹幚碓谕泄苓\行環(huán)境和非托管區(qū)域 中的COM組件操作之間傳遞所有的消息。 要使COM組件能在.NET環(huán)境中使用,必須為COM組件生成元數(shù)據(jù)。.NET運行環(huán)境用元數(shù)據(jù)層業(yè)判斷類型信息。在運行時刻 使用類型信息,以便生成RCW(Runtime Callable Wrapper,運行時可調用包裝)。當.NET應用程序與COM對象交互時, RCW處理對COM對象的裝載和調用。RCW還完成許多其他的工作,如管理對象標識、對象生存周期以及接口緩沖區(qū)。 對象生存周期管理十分關鍵,因為.NET GC把對象到處移動,并且當對象不再使用時,自動處理這些對象。RCW服務告訴 .NET,應用程序正與托管.NET組件交互,同時又使非托管COM組件“覺得”COM對象是被傳統(tǒng)的COM客戶端調用的。 為了為COM組件生成元數(shù)據(jù)包裝,必須使用tlbimp.exe(TypeLib Importer)工具: tlbimp some_COM.tlb /out:som_COM.dll
|