C# 學習筆記

引用類型是類型安全的指針,它們的內存是分配在堆(保存指針地址)上的。
String、數組、類、接口和委託都是引用類型。


強制類型轉換與as類型轉換的區別:當類型轉換非法時,強制類型轉換將拋出一個System.InvalidCastException異常,
而as不會拋出異常,它返回一個null值。

用using創建別名:using console = System.Console;

訪問限定符:
public 該成員可以被其他任何類訪問
protected 該成員只能被其派生類訪問
private 該成員只能被本類的其他成員訪問
internal 該成員只能在當前編譯單元的其他成員訪問

帶參數列表和返回值的Main方法:
class Test
{
  public static int Main(string[] args)
  {
   foreach (string arg in args)
   {
   ...
   }
  }
}


構造函數(constructor)包括實例構造函數和靜態構造函數。
構造函數與類名相同,且不能有返回值。例:
class TestClass
{
  TestClass() //實例構造函數:可以訪問靜態成員和實例成員,用於初始化實例成員
  {
  ...
  }

  static TestClass() //靜態構造函數:只能訪問靜態成員,用於初始化靜態成員
  {
  ...
  }
}

類的靜態成員屬於類所有,不必生成實例就可以訪問,它是在載入包含類的應用程序時創建的,
但靜態方法不能訪問類的實例變量和方法。通常,靜態變量是在定義時就賦初始值的。
類的實例成員屬於類的實例所有,不創建實例對象就無法對其進行訪問,實例成員可以訪問類的
靜態成員和其它實例成員。


調用基類的析構函數:
class A
{
  public A()
  {
  ...
  }
}

class B
{
  public B(): base() //調用基類的析構函數
  {
  ...
  }
}


常量:其值是在編譯時設定的,必須是數值文字。默認狀態下常量是靜態的。例:
class A
{
  public const double pi = 3.1415;
}


常量是編譯時就確定的值,只讀字段是在運行才能確定的值。比如運行時才能確定的屏幕分辨率。
只讀字段只能在類的析構函數中賦值。

靜態只讀字段:
class A
{
  public static readonly int ScreenWidth; //靜態只讀字段
  static A()  //靜態析構函數
  {
   ScreenWidth = 1024; //在靜態析構函數中初始化
  }
}


在類的繼承中,類的析構函數是不會被繼承的。
一個派生類只能從一個基類繼承,不能同時從多個基類繼承,但可以通過繼承多個接口來
達到相同目的。實現多繼承的唯一方法就是使用接口。例:
class MyFancyGrid: Control, ISerializable, IDataBound
{
...
}


密封類是不能繼承的類,抽象類不能被定義爲密封類,且密封類的私有成員不能用protected修飾,
只能用private。例:
sealed class A
{
...
}


關鍵字ref和out用於指定用引用方式傳遞方法的參數。
它們的區別是:ref參數必須初始化,而out參數不需要初始化。所以在方法處理代碼依賴參數的
初始化值時使用ref,不依賴初始化值時使用out。
對out參數即使在傳遞前對其進行了初始化,其值也不會傳遞到方法處理函數內部。傳遞時系統
會將其設爲未初始化。所以在方法內部必須對out參數進行初始化。


方法重載時,必須參數數目和參數類型其中之一不同,返回值不同不能作爲重載。
C#不支持方法的默認值,只能通過方法重載來實現。例:
class A
{
  int Method(int a)
  {
  ...
  }

  void Method(int a, int b) //參數數目不同
  {  //返回值不同不能作爲重載
  ...
  }
}


params參數用於一個不定數目參數的方法,一般後面跟一個數組。例:
class A
{
  public void Method(params int[] i)
  {
  ...
  }
}


方法的覆蓋:指派生類覆蓋基類的同名方法,有二種方法
1)第一種是在派生類要覆蓋的方法前面加new修飾,而基類不需要作任何改動。
這種方法的缺點是不能實現多態。例:
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修飾。
這樣就能實現多態,例:

class A
{
  virtual public void Method()  //基類定義虛方法
  {   //虛擬方法不能定義爲private,因爲private成員對派生類是無法訪問的
  ...
  }
}

class B: A   //從基類繼承
{
  override public void Method()  //派生類覆蓋基類的同名虛方法
  {
  ...
  }
}

class TestClass
{
  protected void Test()
  {
   A Instance = new B();  //定義一個實例,類型爲基類,從派生類創建
   //派生類總是能夠向上轉換爲其基類
   Instance.Method();  //將調用派生類B的Method方法,而不是基類的,這就是多態
  }
}

說明:new修飾的方法覆蓋不能實現多態的原因,是因爲使用new時編譯器只會實現早期綁定(early binding)。
即調用的方法在編譯時就決定了:編譯器看到Instance.Method()而Instance的類是A,就會調用類A的Method()方法。
override修飾的方法覆蓋可以實現多態的原因,是因爲實現了後期綁定(late binding)。
使用override時強制編譯器在運行時根據類的真正類型正確調用相應的方法,而不是在編譯時。
而基類的同名方法必須加virtual修飾。


類的靜態方法可能通過 類名.靜態方法名 這種格式來調用,不能使用 實例名.靜態方法名 這種方法調用。
因爲類的靜態方法爲類所有(是屬於類本身的),而非實例所有(不是屬於類的實例的)。
類的靜態方法可以訪問類的任何靜態成員,但不能訪問類的實例成員。


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)。即在需要類成員時纔對它們進行
初始化。如果類中包含了很少被引用的成員,而這些成員的初始化又會花費大量的時候和系統
資源的話,懶惰的初始化就很有用了。


C#中數組對象共同的基類是System.Array。
將數組聲明爲類的一個成員時,聲明數組與實例化數組必須分開,這是因爲只能在運行時創建了
類的實例對象之後,才能實例化數組元素值。
聲明:
int[] intArray; //一維數組
int[,,] int3Array; //三維數組
初始化:
intArray = new int[3] {1,2,3};
int[,] int2Array = new int[2,3] {{1,2,3},{4,5,6}}; //聲明時可以初始化
遍歷:
1)一維數組
for (int i = 0; i < intArray.Length; i++); //Array.Length返回數組所有元素的個數
foreach (int i in intArray);
for (int i = 0; i < intArray.GetLength(0); i++);//Array.GetLength(0)返回數組第一維的個數
2)多維數組
for (int i = 0; i < int3Array.GetLength(0); i++) //遍歷三維數組
  for (int j = 0; j < int3Array.GetLength(1); j++)
   for (int k = 0; k < int3Array.GetLength(2); k++)
   {
   ...
   }
數組的維數就是該數組的秩(Rank)。Array.Rank可以返回數據的秩。
鋸齒數組(jagged Array)是元素爲數組的數組,例:
int[][] jaggedArray = new int[2][]; //包含二個元素,每個元素是個數組
jaggedArray[0] = new int[2]; //每個元素必須初始化
jaggedArray[1] = new int[3];
for (int i = 0; i < jaggedArray.Length; i++) //遍歷鋸齒數組
  for (int j = 0; j < jaggedArray[i].Length; j++)
  {
  ...
  }


類的屬性稱爲智能字段,類的索引器稱爲智能數組。由於類本身作數組使用,所以用
this作索引器的名稱,索引器有索引參數值。例:
using System;
using System.Collections;

class MyListBox
{
  protected ArrayList data = new ArrayList();
  public object this[int idx] //this作索引器名稱,idx是索引參數
  {
   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
    {
     //拋出一個異常
    }
   }
  }
}


接口是二段不同代碼之間約定,通過約定實現彼此之間的相互訪問。
C#並不支持多繼承,但通過接口可實現相同功能。
當在接口中指定了實現這個接口的類時,我們就稱這個類“實現了該接口”或“從接口繼承”。
一個接口基本上就是一個抽象類,這個抽象類中除了聲明C#類的其他成員類型——例如屬性、
事件和索引器之外,只聲明瞭純虛擬方法。
接口中可以包含方法、屬性、索引器和事件——其中任何一種都不是在接口自身中來實現的。例:
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類型的。


因爲接口定義了一個約定,任何實現一個接口的類都必須定義那個接口中每一個成員,否則將編譯失敗。例:
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() //實現接口
  {
   if (data == "my control data")
    return true;
   else
    return false;
  }
}

class InterfaceApp
{
  MyControl myControl = new MyControl();
 
  IValidate val = (IValidate)myControl; //可以將一個實現某接口的類,轉換成該接口
  bool success = val.Validate(); //然後可調用該接口的方法
}

也可以用:
bool success = myControl.Validate();
這種方法來調用Validate方法,因爲Validate在類MyControl中是被定義成public的,如果去除public,Validate方法被隱藏,
就不能用這種方法調用了,這樣隱藏接口方法稱爲名字隱藏(name hiding)。

可以用:類實例 is 接口名 來判斷某個類是否實現了某接口,例:
myControl is IValidate //MyControl類的實例myControl是否實現了IValidate接口
當然,也可用as來作轉換,根據轉換結果是否爲null來判斷某個類是否實現了某接口,例:
IValidate val = myControl as IValidate;
if (null == val)
{
... //沒有實現IValidate接口
}
else
{
... //實現了IValidate接口
}

如果一個類從多個接口繼承,而這些接口中如果定義的同名的方法,則實現接口的方法時,必須加接口名來區別,
寫成 接口名.方法名。假設Test類從IDataStore和ISerializable二個接口繼承,而這二個接口都有SaveData()方法,
實現SaveData()方法時必須寫成:
class Test: ISerializable, IDataStore
{
  void ISerializable.SaveData()
  {
  ...
  }

  void IDataStore.SaveData()
  {
  ...
  }
}

如果一個類從多個接口繼承,爲了方便可以定義一個新的接口,這個接口繼續多個接口,然後類直接從這個接口繼承就
可以了,這個叫合併接口。例:
interface ISaveData: ISerializable, IDataStore
{ //不需要定義任何方法或成員,只是用作合併
}
class Test: ISaveData //只要繼承ISaveData就可以了
{
...
}


C# 操作符優先級(從高到低)
初級操作符 () x.y f(x) a[x] x++ x-- new typeof sizeof checked unchecked
一元操作符 + - | ~ ++x --x (T)x
乘除操作符 * / %
加減操作符 + -
位移操作符 << >>
關係操作符 < > <= >= is
等於操作符 ==
邏輯與 &
邏輯異或 ^
邏輯或 |
條件與 &&
條件或 ||
條件操作符 ?:
賦值操作符 = *= /= %= += -= <<= >>= &= ^= |=


所有的二元操作符除賦值符外都是左聯合的,即從左到右計算。

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()操作符用來計算值類型變量在內存中佔用的字節數(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) //輸出斐波那契數列
 Console.Write("{0} ", i);

在switch語句中執行一個分支的代碼後還想執行另一個分支的代碼,可以用:
goto case 分支;


操作符重載是爲了讓程序更加自然,容易理解。想要爲一個類重新定義一個操作符,使用以下語法:
public static 返回值 operator 操作符 (操作對象1[,操作對象2])
說明:
1)所有重載的操作符方法都必須定義爲public和static
2)從技術上說返回值可以是任何類型,但通常是返回所定義方法使用的類型
3)操作對象的數目取決於重載是一元操作符還是二元操作符,一元操作符只要一個操作對象,二元操作符則需要二個。
4)不管重載是一元操作符還是二元操作符,第一個操作對象的類型都必須與返回值的類型一致;而對於二元操作符的第二個
操作對象的類型則可以是任何類型。
5)只有下列操作符可以被重載:
一元:+ - ! ~ ++ -- true false
二元:+ - * / % & | ^ << >> == != > < >= <=
賦值操作符(+=,-=,*-,/=,%=等等)無法被重載。
[]和()操作符也無法被重載。
6)操作符的優先級是無法改變的,運算優先級的規則是靜態的。

例:假設一個Invoice發票類由多個InvoiceDetailLine類(成員只有一個Double類型的Amount金額屬性)組成,
我們重載+操作符,使之可以將InvoiceDetailLine類的內容(注意不是金額合計)加在一起。
class Invoice
{
  public ArrayList DetailLine;
 
  public Invoice  //類的析構函數
  {
   DetailLine = new ArrayList(); //ArrayList存放多個InvoiceDetailLine類的實例
  }

  public static Invoice operator+ (Invoice Invoice1, Invoice Invoice2) //參數與返回值的類型一致
  {
   //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)轉換方法必須是靜態的。
2)implicite表示隱式轉換,explicite表示顯式轉換。
3)輸入類型和輸出類型其中之一必須與包含轉換的類或結構體類型。即轉換必須與本類相關。
例:
struct Celisus
{
  public float t;

  public Celisus(float t)
  {
   this.t = t;  //this.t是結構體的字段,t是參數
  }

  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++中的函數指針相同,代表不是在編譯時被定義的,而是在運行時被定義的。
代表主要有二個用途:回調(Callback)和事件處理(event)
回調通常用於異步處理和自定義處理。例:
class DBManager
{
  static DBConnection[] activeConnections;
  //聲明回調函數
  public void delegate EnumConnectionCallback(DBConnection connection);

  public static void EnumConnections(EnumConnectionCallback callback)
  {
   foreach (DBConnection connection in activeConnections)
   {
    callback(connection); //執行回調函數
   }
  }
}

//調用
class DelegateApp
{
  public static void ActiveConncetionCallback(DBConnection connection) //處理函數
  {
  ...
  }

  public void main()
  {
   //創建指向具體處理函數的代表實例(新建一個代表,讓它指向具體的處理函數)
   DBManager.EmnuConnectionCallback myCallback = new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
   DBManager.EnumConnections(myCallback);
  }
}

//使用靜態代表,上面的調用改爲
class DelegateApp
{
  //創建一個指向處理函數的靜態代表
  public static DBManager.EmnuConnectionCallback myCallback
   = new DBManager.EmnuConnectionCallback(ActiveConncetionCallback);
  public static void ActiveConncetionCallback(DBConnection connection)
  {
  ...
  }

  public void main()
  {
   DBManager.EnumConnections(myCallback);
  }
}

//在需要時才創建代表,上面的調用改爲
class DelegateApp
{
  //將創建代表放在屬性的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(); //創建應用程序
   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);
   //整合爲一個代表,注意後加的代表先執行(這裏是先執行LogEventCallback)
   InventoryManager.OutOfStockExceptionMethod onHandExceptionEventsCallback
    = EmailPurchasingMgrCallback + LogEventCallback;
   //調用代表
   InventoryManager mgr = new InventoryManager();
   mgr.ProcessInventory(onHandExceptionEventsCallback);
   //InventoryManager類的ProcessInventory方法的原型爲:
   //public void ProcessInventory(OutOfStockExceptionMethod exception);
  }
}

可以根據需要將多個代表自由地組合成單個代表,例:
class CompositeDelegateApp
{
  //代表指向的處理函數(三個代表三個函數)
  public static void LogEvent(Part part)
  {
  ...
  }

  public static void EmailPurchasingMgr(Part part)
  {
  ...
  }

  public static void EmailStoreMgr(Part part)
  {
  ...
  }

  public static void Main()
  {
   //通過數組定義三個代表
   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;
   //根據需要組合
   if (location = 2)
   {
    compositeDelegate = exceptionMethods[0] + exceptionMethods[1];
   }
   else
   {
    compositeDelegate = exceptionMethods[0] + exceptionMethods[2];
   }
   //調用代表
   InventoryManager mgr = new InventoryManager();
   mgr.ProcessInventory(compositeDelegate);
  }
}


C#的事件遵循“發佈——預訂”的設計模式。在這種模式中,一個類公佈能夠出現的所有事件,
然後任何的類都可以預訂這些事件。一旦事件產生,運行環境就負責通知每個訂戶事件已經發生了。
當代表作爲事件的處理結果時(或者說定義具有代表的事件),定義的代表必須指向二個參數的方法:
一個參數是引發事件的對象(發佈者),另一個是事件信息對象(這個對象必須從EventArgs類中派生)。
例:
using System;

class InventoryChangeEventArgs: EventArgs //事件信息對象,從EventArgs類派生
{
... //假設定義二個public屬性string Sku和int Change
}

class InventoryManager  //事件的發佈者
{
  //聲明代表
  public delegate void InventoryChangeEventHander(object source, InventoryChangeEventArgs e);
  //發佈事件,event關鍵字可將一個代表指向多個處理函數
  public event InventoryChangeEventHandler onInventoryChangeHander;
 
  public void UpdateInventory(string sku, int change)
  {
   if (change == 0)
    return;
   InventoryChangeEventArgs e = new InventoryChangeEventArgs(sku, change);
   //觸發事件
   if (onInventoryChangeHandler != null) //如果有預訂者就觸發
    onInventoryChangeHandler(this, e); //執行代表指向的處理函數
  }
}

class InventoryWatcher  //事件的預訂者
{
  public InventoryWatcher(InventoryManager mgr) //mgr參數用於聯結髮布者
  {
   this.inventoryManager = mgr;
   //預訂事件,用 += 調用多個處理函數
   mgr.onInventroyChangeHandler += new InventoryManager.InventoryChangeEventHandler(onInventoryChange);
   //事件處理函數
   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等操作系統都支持佔先型多任務。在佔先型多任務執行中,處理器負責
給每個線程分配一定量的運行時間——一個時間片(timeslice)。處理器接着在不同的線程之間進行切換,
執行相應的處理。在單處理器的計算機上,並不能真正實現多個線程的同時運行,除非運行在多個處理器
的計算機上。操作系統調度的多線程只是根據分配給每個線程時間片進行切換執行,感覺上就像同時執行。

上下文切換(context switching)是線程運行的一部分,處理器使用一個硬件時間來判斷一個指定線程的時間片
何時結束。當這個硬件計時器給出中斷信號時,處理器把當前運行的線程所用的所有寄存器(registers)數據
存儲到堆棧中。然後,處理器把堆棧裏那些相同的寄存器信息存放到一種被稱爲“上下文結構”的數據結構中。
當處理器要切換回原來執行的線程時,它反向執行這個過程,利用與該線程相關的上下文結構,在寄存器裏
重新恢復與這一線程相關的信息。這樣的一個完整過程稱爲“上下文切換”。

多線程允許應用程序把任務分割爲多個線程,它們彼此之間可以獨立地工作,最大限度地利用了處理器時間。

using System;
using System.Threading;

class SimpleThreadApp
{
  public static void WorkerThreadMethod() //線程的執行體
  {
  ...   //執行一些操作
  }

  public static void Main()
  {
   //創建一個線程代表指向線程的執行體,ThreadStart是創建新線程必須用到的代表
   ThreadStart worker = new ThreadStart(WorkerThreadMethod);
   Thread t = new Thread(worker); //用線程代表創建線程
   t.Start();   //執行線程
  }
}

可以通過兩種方式來得到一個Thread對象:一種是通過創建一個新線程來得到,如上例;另一種在正在執行的線程調用
靜態的Thread.CurrentThread方法。
靜態方法Thread.Sleep(int ms)可以讓當前線程(它自動調用Thread.CurrentThread)暫停指定毫秒的時間。
如果使用Thread.Sleep(0)那麼當前線程將一直處於等待中,直到另一個線程調用這個線程的實例方法Thread.Interrupt方法,
等待纔會結束。
使用Thread.Suspend方法也能掛起線程,Thread.Suspend方法可以被當前線程或其他線程調用,而Thread.Sleep(0)
只能由當前線程在執行體中調用。當線程用Thread.Suspend掛起時,必須用Thread.Resume方法恢復。不論Thread.Suspend
方法調用了多少次,只要調用Thread.Resume方法一次就可以線程恢復執行。用Thread.Suspend方法並不會阻塞線程,
調用立即返回。而Thread.Sleep(0)則會阻塞線程。所以確切地說Thread.Sleep(0)暫停線程,而不是掛起線程。
使用Thread.Abort方法可以終止正在執行的線程。當Thread.Abort方法被調用時,線程不會立即終止執行。運行環境將會
等待,直到線程到達文檔中所描述的“安全點”。如果要確保線程已經完全停止,可以使用Thread.Join方法。這是一個同步
調用,同步調用意味着直到線程完全停止,調用纔會返回。
Thread.Priority屬性用於設置的線程的優先級。其值是Thread.ThreadPriority枚舉值,可以設爲Highest, AboveNormal,
Normal, BelowNormal, Lowest。缺省值是Thread.ThreadPriority.Normal。

線程的同步是爲了解決多個線程同時使用同一對象產生的一些問題。通過同步,可以指定代碼的臨界區(critical section),
一次只有一個線程可以進入臨界區。

使用System.Monitor類(鎖定與信號量)進行線程同步:
using System;
using System.Threading;

public void SaveData(string text) //線程執行函數或線程執行函數調用的對象的方法
{
  ...  //執行其他一些不需要同步的處理

  Monitor.Enter(this); //獲取對象的Monitor鎖
  ...  //執行需要同步的處理
  Monitor.Exit(this); //釋放對象的Monitor鎖

  ...  //執行其他一些不需要同步的處理
}

說明:當執行Monitor.Enter方法時。這個方法會試圖獲取對象上的Monitor鎖,如果另一個線程已經擁有了
這個鎖,這個方法將會阻塞(block),直到這個鎖被釋放。
也可用C#的lock語句來獲得和釋放一個Monitor鎖。上面同步寫成:
public void SaveData(string text) //線程執行函數或線程執行函數調用的對象的方法
{
  ...  //執行其他一些不需要同步的處理

  lock(this) //獲取對象的Monitor鎖,代碼塊執行完成後釋放Monitor鎖
  {
  ...  //執行需要同步的處理
  }

  ...  //執行其他一些不需要同步的處理
}

也可以使用System.Threading名稱空間的Mutex類(互斥類)進行線程同步。與Monitor鎖一樣,一次只有一個線程
能獲得一個給定的互斥。但Mutex要慢得多,但它增加了靈活性。例:
using System;
using System.Threading;

class Database
{
  Mutex mutex = new Mutex(false); //創建一個互斥,但不立即獲得它
   //注意:創建互斥在需要同步的方法之外,實際上它只要創建一個實例
  public void SaveData(string text) //需要同步的方法
  {
   mutex.WaitOne(); //等待獲得互斥
   ...  //需要同步的處理
   mntex.Close(); //釋放互斥
  }
}

Mutex類重載了三個構造函數:
Mutex()    //創建並使創建類立即獲得互斥
Mutex(bool initiallyOwned)  //創建時可指定是否要立即獲得互斥
Mutex(bool initiallyOwned, string muterName) //還可以指定互斥的名稱

Mutex.WaitOne方法也重載了三次:
Mutex.WaitOne()   //一直等待
Mutex.WaitOne(TimeSpan time, bool exitContext) //等待TimeSpan指定的時間
Mutex.WaitOne(int milliseconds, bool exitContext) //等待指定的毫秒

線程的用法:
1)併發操作:比如一個程序監視多個COM口,當每個COM接到信息時執行一段處理時。
2)複雜長時間操作:一個長時間的複雜操作可能會使界面停滯,停止用戶響應,如果還允許用戶停止它,
或者顯示進度條、顯示操作執行進程信息時。

 


反射(Reflection)就是能夠在運行時查找類型信息,這是因爲.NET編譯的可執行(PE)文件中包括MSIL和元數據(metadata)。
反射的中心是類System.Type。System.Type是一個抽象類,代表公用類型系統(Common Type System, CTS)中的一種類型。

using System;
using System.Reflection; //反射命名空間,必須引用

public static void Main(string[] args)
{
  int i = 6;
  Type t = i.GetType();  //根據實例得到類型
  t = Type.GetType("System.Int32"); //根據類型的字符名稱得到類型
}

通過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) //如果參數爲空,取當前進程的名稱
   {
    Process p = Process.GetCurrentProcess();
    assemblyName = p.ProcessName + ".exe";
   }
   else
    assemblyName = args[0]; //取第一個參數,即當前運行程序名

   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)  //遍歷類型數組
   {
   ... //取得t.FullName,t.BaseType.FullName等類型信息
   }
  }
}

一個應用程序可以包括多個代碼模塊。若要將一個cs文件編譯一個模塊,只要執行下面的命令:
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);  //調用方法
   }
  }
}

//帶參數的例子
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);

    // 定義參數類型
    Type[] paramTypes = new Type[1];
    paramTypes[0]= Type.GetType("System.Double");

    MethodInfo CosineInfo = t.GetMethod("Cos", paramTypes);

    //設置參數數據
    Object[] parameters = new Object[1];
    parameters[0] = 45;

    //執行方法
    Object returnVal = CosineInfo.Invoke(o, parameters);
    Console.WriteLine("The cosine of a 45 degree angle {0}", returnVal);
   }
  }
}


動態生成代碼和動態調用的完整例子:
//動態生成代碼的部分
using System;
using System.Reflection;
using System.Reflection.Emit; //動態生成代碼必須引用

namespace ILGenServer
{
  public class CodeGenerator
  {
   public CodeGenerator()
   {
    currentDomain = AppDomain.CurrentDomain; //得到當前域
    assemblyName = new AssemblyName(); //從域創建一個程序集
    assemblyName.Name = "TempAssembly";
    //得到一個動態編譯生成器,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;
    }
   }
  }
}

//動態調用的部分
using System;
using System.Reflection;
using ILGenServer; //引用動態生成代碼的類

public class ILGenClientApp
{
  public static void Main(
  {
   CodeGenerator gen = new CodeGenerator(); //創建動態生成類
   Type t = gen.T;
   if (null != t)
   {
    object o = Activator.CreateInstance(t);
    MethodInfo helloWorld = t.GetMethod("HelloWorld"); //爲調用方法創建一個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版本的函數(MessageBoxA),CharSet.Unicode指定Unicode版本的函數(MessageBoxW)
  static extern int MessageBox(int hWnd, string msg, string caption, int type); //聲明DLL中的函數
 
  //[DllImport("user32.dll", EntryPoint="MessageBoxA")] //用這種方法使用不同的函數名
  //static extern int MsgBox(int hWnd, string msg, string caption, int type);
 
  //[DllImport("user32.dll", CharSet=CharSet.Unicode)] //調用Unicode版的DLL函數
  //static extern int MessageBox(int hWnd, [MarshalAs(UnmanagedType.LPWStr)]string msg,
  // [MarshalAs(UnmanagedType.LPWStr)]string caption, int type); //將LPWStr翻譯爲string型,缺省情況系統只將LPStr翻譯成string
  public static void Main()
  {
   MessageBox(0, "Hello, World!", "CaptionString", 0); //調用DLL中的函數
  }
}

例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指定標記塊在非控環境中運行。該關鍵字可以用於所有的方法,包括構造函數和屬性,
甚至還有方法中的代碼塊。關鍵字fixed負責受控對象的固定(pinning)。Pinning是一種動作,向
垃圾收集器(Garbage Collector, GC)指定一些不能被移動的對象。爲了不在內存中產生碎片,.NET
運行環境把對象四處移動,以便於最有效地利用內存。使用fixed後指定對象將不會被移動,所以就
可以用指針來訪問它。
C#中只能得到值類型、數組和字符串的指針。在數組的情況下,第一個元素必須是值類型,因爲C#
實際上是返回一個指向數組第一個元素的指針,而不是返回數組自身。
& 取一個變量的內存地址(即指向該變量的指針)
* 取指針所指變量的值
-> 取成員
例:
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)
}


傳統的COM組件可以通過互操作層(COM Interop)與.NET運行環境交互。互操作層處理在託管運行環境和非託管區域
中的COM組件操作之間傳遞所有的消息。
要使COM組件能在.NET環境中使用,必須爲COM組件生成元數據。.NET運行環境用元數據層業判斷類型信息。在運行時刻
使用類型信息,以便生成RCW(Runtime Callable Wrapper,運行時可調用包裝)。當.NET應用程序與COM對象交互時,
RCW處理對COM對象的裝載和調用。RCW還完成許多其他的工作,如管理對象標識、對象生存週期以及接口緩衝區。
對象生存週期管理十分關鍵,因爲.NET GC把對象到處移動,並且當對象不再使用時,自動處理這些對象。RCW服務告訴
.NET,應用程序正與託管.NET組件交互,同時又使非託管COM組件“覺得”COM對象是被傳統的COM客戶端調用的。

爲了爲COM組件生成元數據包裝,必須使用tlbimp.exe(TypeLib Importer)工具:
tlbimp some_COM.tlb /out:som_COM.dll

發佈了37 篇原創文章 · 獲贊 0 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章