儘管C#和C++在語法上有不少類似之處,但C#是更高一級的編程語言,它提供了不少只有高級語言纔有的特性,好比屬性,委託和事件,這些都是在C#中常常用到的語言特徵。html
屬性(Property) 是類(class)、結構(structure)和接口(interface)的命名(named)成員。類或結構中的成員變量或方法稱爲 域(Field)。屬性(Property)是域(Field)的擴展,且可以使用相同的語法來訪問。它們使用 訪問器(accessors) 讓私有域的值可被讀寫或操做。
屬性(Property)不會肯定存儲位置。相反,它們具備可讀寫或計算它們值的 訪問器(accessors)。例如,有一個名爲 Student 的類,帶有 age、name 和 code 的私有域。咱們不能在類的範圍之外直接訪問這些域,可是咱們能夠擁有訪問這些私有域的屬性。
屬性(Property)的訪問器(accessor)包含有助於獲取(讀取或計算)或設置(寫入)屬性的可執行語句。訪問器(accessor)聲明可包含一個 get 訪問器、一個 set 訪問器,或者同時包含兩者。
在VS2015中添加屬性很簡單,只要定義好私有成員後,在成員上定位輸入點,按下Ctrl+.選擇添加屬性便可。不少的快捷函方式均可以經過按下Ctrl+.來實現。
抽象類也能夠具備屬性,稱之爲抽象屬性,這些屬性應在派生類中被實現。程序員
下面一個示例很好的演示了屬性的聲明,使用以及虛屬性繼承和實現:編程
class Animal { //聲明兩個私有成員 private string name = "Dolly"; private int age = 12; //使用屬性訪問其私有成員 public string Name { get { return name; } set { name = value; } } //僅開放get屬性,禁止使用set改變成員值 public int Age { get { return age; } } //定義自動屬性,不建立成員變量 public virtual int Cost { get; set; } } class Dog : Animal { private int money; private int cost; public int Money { get { return money; } set { money = value; } } //實現基類的屬性 public override int Cost { get { return cost; } set { cost = value; } } } Animal a = new Animal(); Console.WriteLine("name={0}, age={1}, cost={2}", a.Name, a.Age, a.Cost); //name=Dolly,age=12,cost=0 a.Name = "Joy"; //a.Age = 15; error CS0200: Property or indexer 'Animal.Age' cannot be assigned to -- it is read only a.Cost = 100; Console.WriteLine("name={0}, age={1}, cost={2}", a.Name, a.Age, a.Cost); //name=Joy,age=12,cost=100 Dog d = new Dog(); Animal a = d; d.Name = "Ani"; d.Age = 3; d.Money = 10; d.Cost = 100; Console.WriteLine("{0}-{1}-{2}-{3}", d.Name, d.Age, d.Money, d.Cost); //基類的虛屬性能夠被使用 Console.WriteLine("{0}-{1}-{2}", a.Name, a.Age, a.Cost); a.Cost = 500; Console.WriteLine("{0}-{1}-{2}", a.Name, a.Age, a.Cost);
C#委託(Delegate)相似於 C 或 C++ 中函數的指針,可是類型安全的。
C#委託(Delegate)是存有對某個方法的引用的一種引用類型變量。
C#委託(Delegate)特別用於實現事件和回調方法。
C#委託(Delegate)都派生自 System.Delegate 類。
C#委託可指向一個與其具備相同標籤的方法,在運行時能夠被替換。
C#委託能夠實現多播調用列表。
假若有一個委託的聲明以下:
public delegate int MyDelegate (string s);
那麼只要定義爲int func(string)的函數均可以使用這個委託來調用。這點和C/C++的函數指針徹底是同樣的,不過C#的委託是由類實現的引用類型變量,具備類型安全機制,可不是簡單的函數指針。
C#委託是一個class引用類型變量,所以在實例化時須要使用new來建立內存空間。簡單來講,能夠將委託的使用看做是委託是特殊的C#類,將委託的做用看做是委託是C++中的函數指針。數組
委託實例化
委託的實例化有不少種方法,最普通的就是建立委託類型,傳遞函數對象進去,這裏使用new或不使用new操做符都沒有太多關係。其次委託能夠和匿名函數聯合使用,若是你用過C++的Lambda表達式就知道匿名函數的好處啦!安全
//聲明委託 delegate void Del(string str); //函數聲明 static void Notify(string name) { Console.WriteLine("Notification received for: {0}", name); } // 一、建立委託實例,早期C#1.0的用法 Del del1 = new Del(Notify); // 二、建立委託實例,如今的用法,不使用new,C#2.0的用法 Del del2 = Notify; // 三、使用匿名方法來初始化委託 Del del3 = delegate (string name) { Console.WriteLine("Notification received for: {0}", name); }; // 四、使用lambda表達式實例化委託 Del del4 = name => { Console.WriteLine("Notification received for: {0}", name); };
多參數的委託實例化:框架
//聲明委託 delegate int Add(int a, int b); //匿名函數實例化委託,須要在delegate以後重寫參數列表 Add add = delegate (int a, int b) { return a * b; }; //Lambda表達式,=號以後直接寫參數列表,而後加上=>符號 Add add2 = (a, b) => { return a + b; }; Console.WriteLine("10 + 20 = {0}", add(10, 20)); Console.WriteLine("10 + 20 = {0}", add2(10, 20));
委託多播
委託對象可以使用 "+" 運算符進行合併。一個合併委託調用它所合併的兩個委託。只有相同類型的委託可被合併。"-" 運算符可用於從合併的委託中移除組件委託。
使用委託的這個有用的特色,您能夠建立一個委託被調用時要調用的方法的調用列表。這被稱爲委託的 多播(multicasting),也叫組播。
使用委託多播能夠建立一系列連續的操做,只要一個調用入口便可完成全部的調用操做,並且調用順序是按照組播的加入順序依次執行;使用委託多播和事件搭配,能夠在一個事件發生後,自動調用組播方法調用列表上的所有方法。這個有點相似QT中的信號槽機制,一個信號發射出去後,與之鏈接的全部槽函數都會被執行。編程語言
//聲明委託 delegate int NumberChanger(int n); class DeltegateTest { static int num = 10; //定義static函數 public static int AddNum(int p) { num += p; return num; } //定義static函數 public static int MultNum(int q) { num *= q; return num; } } //建立委託實例,分別代理兩個靜態函數 //委託的實例化和類的實例化同樣都須要new操做 NumberChanger nc1 = new NumberChanger(DeltegateTest.AddNum); NumberChanger nc2 = new NumberChanger(DeltegateTest.MultNum); // 使用委託對象調用方法 var v1 = nc1(25); var v2 = nc2(5); Console.WriteLine("V1={0}, V2={1}", v1, v2); //建立多播委託實例 NumberChanger nc; //建立調用列表,或者nc=nc1;nc+=nc2; nc = nc1+nc2; //調用多播,會依次調用nc1(5)和nc2(5) var v3 = nc(5); Console.WriteLine("V3={0}", v3);
事件在類中聲明且生成,且經過使用同一個類或其餘類中的委託與事件處理程序關聯。包含事件的類用於發佈事件。這被稱爲 發佈器(publisher) 類。其餘接受該事件的類被稱爲 訂閱器(subscriber) 類。事件使用 發佈-訂閱(publisher-subscriber) 模型。
發佈器(publisher) 是一個包含事件和委託定義的對象。事件和委託之間的聯繫也定義在這個對象中。發佈器(publisher)類的對象調用這個事件,並通知其餘的對象。
訂閱器(subscriber) 是一個接受事件並提供事件處理程序的對象。在發佈器(publisher)類中的委託調用訂閱器(subscriber)類中的方法(事件處理程序)。
下面的示例是典型的委託和事件最經常使用的方式:ide
//建立一個取水類 class FetchWater { //聲明一個事件的委託 public delegate void FullWaterHandle(string msg); //基於這個委託定義事件模型 public event FullWaterHandle FullWaterEvent; //建立取水方法 public void BeginFetchWater() { int length = 10; for (int i = 0; i < length; i++) { Thread.Sleep(1000); if (i >= 5) { //激活事件 if (FullWaterEvent != null) { FullWaterEvent(msg); } Console.WriteLine("取水完成"); return; } else { Console.WriteLine("正在取水,等待時間{0}秒", i + 1); } } } #region 字段 private string msg; //建立msg的屬性 public string Msg { get { return msg; } set { msg = value; } } #endregion } //事件響應類 class FullWaterACK { public void OnFullWater(string msg) { Console.WriteLine("取水完成,謝謝9527!"); } } public static void Main() { //使用匿名方法建立委託實例 FetchWater.FullWaterHandle handle1 = delegate (string msg) { Console.WriteLine("{0} : 1 號收到!", msg); }; //使用Lambda函數建立委託實例 FetchWater.FullWaterHandle handle2 = msg => { Console.WriteLine("{0} : 2 號收到!", msg); }; //建立取水實例 FetchWater w = new FetchWater(); w.Msg = "9527完成取水"; //建立取水完成響應實例 FullWaterACK ack = new FullWaterACK(); //將handle1和handle2和取水實例的事件關聯起來 w.FullWaterEvent += handle1; w.FullWaterEvent += handle2; //將取水響應和取水實例的事件關聯起來 //也能夠直接使用關聯:w.FullWaterEvent += ack.OnFullWater; w.FullWaterEvent += new FetchWater.FullWaterHandle(ack.OnFullWater); //調用取水函數,會自動調用相應的委託方法 w.BeginFetchWater(); Console.ReadKey(); }
特性(Attribute)是用於在運行時傳遞程序中各類元素(好比類、方法、結構、枚舉、組件等)的行爲信息的聲明性標籤,您能夠經過使用特性向程序添加聲明性信息。
有三種預約義特性:AttributeUsage,Conditional和Obsolete。函數
Conditional
這個預約義特性標記了一個條件方法,其執行依賴於指定的預處理標識符。若是指定的預處理標識符爲真,就執行條件方法中的語句,不然什麼也不作。
條件特性標識的函數必須是void,不能返回任何數值。
例如,當調試代碼時顯示變量的值:性能
#define DEBUG using System; using System.Diagnostics; public class Myclass { //條件特性關聯DEBUG預處理指令 [Conditional("DEBUG")] public static void Message(string msg) { //只有定義了預處理指令DEBUG時纔會執行這句代碼 Console.WriteLine(msg); } } class Test { static void function1() { Myclass.Message("In Function 1."); function2(); } static void function2() { Myclass.Message("In Function 2."); } public static void Main() { Myclass.Message("In Main function."); function1(); Console.ReadKey(); } }
這個有點相似C++日誌輸出時,爲了減小在Release模式下的調試代碼,一般會這樣定義日誌輸出宏:
#ifdef _DEBUG //輸出日誌到控制檯 #define LOGOUT(x) std::cout << x << std::endl; #else //空語句,什麼也不作 #define LOGOUT(x) #endif
這裏[Conditional]特性按照條件來編譯執行方法主體中的代碼。
Obsolete
這個預約義特性標記了不該被使用的程序實體。它可讓您通知編譯器丟棄某個特定的目標元素。例如,當一個新方法被用在一個類中,可是您仍然想要保持類中的舊方法,您能夠經過顯示一個應該使用新方法,而不是舊方法的消息,來把它標記爲 obsolete(過期的)。
[Obsolete("use NewPrint() instead", false)] public void Print() { Console.WriteLine("這個是老方法!"); } public void NewPrint() { Console.WriteLine("這個是新方法!"); }
[Obsolete]的第一個參數是描述文字,第二個爲false時會在編譯時產生編譯警告,信息就是提供的描述文字;若是設置爲true,在編譯時就會出現編譯錯誤。當用新的方法取代舊的方法時,用[Obsolete]能夠很容易的在編譯時刻就檢查出問題。
AttributeUsage
AttributeUsage是用來設計自定義特性的,能夠參考這些文章:
AttributeUsage使用說明
AttributeUsage的使用淺析
特性能夠給指定的類,方法,接口等增長額外的附加信息,而後在程序中能夠獲取到這裏有用的額外信息。
建立自定義特性(Attribute)
建立並使用自定義特性包含四個步驟:
一、聲明自定義特性
二、構建自定義特性
三、在目標程序元素上應用自定義特性
四、經過反射訪問特性
下面的代碼完成了前三部,後面將在反射一節用代碼說明如何經過反射來訪問特性:
// 一個自定義特性 BugFix 被賦給類及其成員 [AttributeUsage(AttributeTargets.Class | AttributeTargets.Constructor | AttributeTargets.Field | AttributeTargets.Method | AttributeTargets.Property, AllowMultiple = true)] //構建一個名爲 DeBugInfo 的自定義特性,該特性將存儲調試程序得到的信息。 //它存儲下面的信息: // : bug 的代碼編號 // : 辨認該 bug 的開發人員名字 // : 最後一次審查該代碼的日期 // : 一個存儲了開發人員標記的字符串消息 public class DeBugInfo : System.Attribute { private int bugNo; private string developer; private string lastReview; private string message; public DeBugInfo(int bg, string dev, string d) { this.bugNo = bg; this.developer = dev; this.lastReview = d; } public int BugNo { get { return bugNo; } } public string Developer { get { return developer; } } public string LastReview { get { return lastReview; } } public string Message { get { return message; } set { message = value; } } } //在目標代碼上應用自定義特性 [DeBugInfo(45, "Zara Ali", "12/8/2012", Message = "Return type mismatch")] [DeBugInfo(49, "Nuha Ali", "10/10/2012", Message = "Unused variable")] class Rectangle { // 成員變量 protected double length; protected double width; public Rectangle(double l, double w) { length = l; width = w; } [DeBugInfo(55, "Zara Ali", "19/10/2012", Message = "Return type mismatch")] public double GetArea() { return length * width; } [DeBugInfo(56, "Zara Ali", "19/10/2012")] public void Display() { Console.WriteLine("Length: {0}", length); Console.WriteLine("Width: {0}", width); Console.WriteLine("Area: {0}", GetArea()); } }
反射指程序能夠訪問、檢測和修改它自己狀態或行爲的一種能力。
程序集包含模塊,而模塊包含類型,類型又包含成員。反射則提供了封裝程序集、模塊和類型的對象。您可使用反射動態地建立類型的實例,將類型綁定到現有對象,或從現有對象中獲取類型。而後,能夠調用類型的方法或訪問其字段和屬性。
優勢:
一、反射提升了程序的靈活性和擴展性。
二、下降耦合性,提升自適應能力。
三、它容許程序建立和控制任何類的對象,無需提早硬編碼目標類。
缺點:
一、性能問題:使用反射基本上是一種解釋操做,用於字段和方法接入時要遠慢於直接代碼。所以反射機制主要應用在對靈活性和拓展性要求很高的系統框架上,普通程序不建議使用。
二、使用反射會模糊程序內部邏輯;程序員但願在源代碼中看到程序的邏輯,反射卻繞過了源代碼的技術,於是會帶來維護的問題,反射代碼比相應的直接代碼更復雜。
用途:
一、它容許在運行時查看特性(attribute)信息。
二、它容許審查集合中的各類類型,以及實例化這些類型。
三、它容許延遲綁定的方法和屬性(property)。
四、它容許在運行時建立新類型,而後使用這些類型執行一些任務。
接下來繼續完成上一節特性的代碼,使用反射查詢特性信息:
public static void Main() { Rectangle r = new Rectangle(4.5, 7.5); r.Display(); //經過MemberInfo能夠查詢到全部頂層的Attribute信息 System.Reflection.MemberInfo info = typeof(Rectangle); object[] attributes = info.GetCustomAttributes(true); for (int i = 0; i < attributes.Length; i++) { //這裏會打印Attribute特性的類名:DebugInfo System.Console.WriteLine(attributes[i]); } Type type = typeof(Rectangle); //遍歷Rectangle類的特性 //這裏將獲取到45,49兩個編號的DebugInfo信息 foreach (Object attr in type.GetCustomAttributes(false)) { //使用as轉換類型,若是失敗等於null DeBugInfo dbi = attr as DeBugInfo; if (null != dbi) { Console.WriteLine("Bug no: {0}", dbi.BugNo); Console.WriteLine("Developer: {0}", dbi.Developer); Console.WriteLine("Last Reviewed: {0}", dbi.LastReview); Console.WriteLine("Remarks: {0}", dbi.Message); } } //遍歷Rectangle類方法的特性 //先獲取Rectangle類的全部方法 foreach (MethodInfo m in type.GetMethods()) { //打印查詢到的全部類方法 Console.WriteLine("{0}", m); //查詢該方法中的全部Attribute特性 foreach (Attribute a in m.GetCustomAttributes(true)) { //使用as轉換類型,若是失敗等於null //這裏必定要判斷,會轉換失敗,不是全部的方法都用DebugInfo附加過信息 DeBugInfo dbi = a as DeBugInfo; if (null != dbi) { Console.WriteLine("Bug no: {0}, for Method: {1}", dbi.BugNo, m.Name); Console.WriteLine("Developer: {0}", dbi.Developer); Console.WriteLine("Last Reviewed: {0}", dbi.LastReview); Console.WriteLine("Remarks: {0}", dbi.Message); } } } Console.ReadKey(); }
下面的代碼利用反射抓取類型並調用其方法,全程都只知道類名和方法名:
//在其餘地方定義的類 namespace Office { class Printer { public void PrintMessage(string name, string msg) { Console.WriteLine("你好,{0}:\n{1}", name, msg); } } } public static void Main() { try { //定義類名稱和類方法 string className = "Office.Printer"; string methodName = "PrintMessage"; //獲取當前程序集的對象 System.Reflection.Assembly asm = Assembly.GetExecutingAssembly(); //若是須要反射類庫可使用Assembly.Load的方法 //System.Reflection.Assembly ass = Assembly.LoadFrom("xxx.dll"); //獲取類型信息,記住要判斷 System.Type t = asm.GetType(className); if (null == t) { throw new Exception(String.Format("{0} 類型不支持!", className)); } //獲取類方法,記住要判斷,重要的事情說三遍!!! System.Reflection.MethodInfo mi = t.GetMethod(methodName); if (null == mi) { throw new Exception(String.Format("{0}.{1} 類方法不支持!", className, methodName)); } //建立對象實例,記住要判斷 Object obj = System.Activator.CreateInstance(t); if (null == obj) { throw new Exception(String.Format("{0} 實例建立失敗!", className)); } //調用方法,這裏使用object[]來傳遞所有參數 mi.Invoke(obj, new object[] { "程序員", "我是反射示例代碼" }); } catch (Exception e) { Console.WriteLine(e); } Console.ReadKey(); }
索引器(Indexer) 容許一個對象能夠像數組同樣被索引訪問。當您爲類定義一個索引器時,該類的行爲就會像一個 虛擬數組(virtual array) 同樣。您可使用數組訪問運算符([ ])來訪問該類的數據。
索引器的定義有點像屬性的定義,有get也有set,同時又像是運算符重載,這個運算符就是下標符號[ ]。簡單來講,索引器就是從新定義了下標[ ]操做的功能,下標符號既能夠讀取,也能夠設置,天然索引器就須要提供get和set相似於屬性的定義方法。
//定義元素存儲數組和容量 private string[] namelist = new string[size]; static public int size = 10; public string this[int index] { get { string tmp; if (index >= 0 && index <= size - 1) { tmp = namelist[index]; } else { tmp = ""; } return (tmp); } set { if (index >= 0 && index <= size - 1) { namelist[index] = value; } } } IndexedNames names = new IndexedNames(); names[0] = "Zara"; names[1] = "Riz"; names[2] = "Nuha"; names[3] = "Asif";
索引器能夠被重載,一個類能夠定義多個索引器,只要函數的標識不同就能夠。
所以索引器更像是運算符重載,只是形式上看起來像屬性而已。
重載多個索引器的一個類:
//索引器經過[名字&課程編號]查找和保存成績 public int this[string stuName, int courseId] { get { } set { } } //索引器重載,根據[名字]查找全部成績 public List<Scores> this[string stuName] { get { List<Scores> tempList = listScores.FindAll(x => x.StuName == stuName); return tempList; } } //多參數索引器和索引器重載 FindScore fScore = new FindScore(); fScore["張三", 1] = 98; fScore["張三", 2] = 100; fScore["張三", 3] = 95; fScore["李四", 1] = 96; //查找全部張三的成績 List<Scores> listScores = fScore["張三"];
這個很C++中的泛型是同一個意思。C#中可使用泛型的地方不少,不只限定於類,方法,接口,還能夠在委託,事件中都使用泛型編程。
泛型的主要特徵:
一、它有助於您最大限度地重用代碼、保護類型的安全以及提升性能。
二、您能夠建立泛型集合類。.NET 框架類庫在 System.Collections.Generic 命名空間中包含了一些新的泛型集合類。您可使用這些泛型集合類來替代 System.Collections 中的集合類。
三、您能夠建立本身的泛型接口、泛型類、泛型方法、泛型事件和泛型委託。
四、您能夠對泛型類進行約束以訪問特定數據類型的方法。
五、泛型數據類型中使用的類型的信息可在運行時經過使用反射獲取。
//定義一個泛型類 public class MyGenericArray<T> { private T[] array; public MyGenericArray(int size) { array = new T[size + 1]; } public T getItem(int index) { return array[index]; } public void setItem(int index, T value) { array[index] = value; } } // 聲明一個整型數組 MyGenericArray<int> intArray = new MyGenericArray<int>(5); // 聲明一個字符數組 MyGenericArray<char> charArray = new MyGenericArray<char>(5);
使用委託時使用匿名函數和Labmda表達式能夠很方便:
delegate void Print(); delegate int Add(int a, int b); static void Main() { //不帶參數的Lambda表達式 Print p = () => { Console.WriteLine("Hello"); }; //帶參數的Lambda表達式,下面兩種均可以 Add fn = (int a, int b) => //Add fn = (a, b) => { return a + b; }; //不帶參數的匿名函數 Print p2 = delegate () { Console.WriteLine("Welcom"); }; //帶參數的匿名函數 Add fn2 = delegate (int a, int b) { return a + b; }; Console.WriteLine(fn(3, 4)); p(); Console.WriteLine(fn(6, 9)); p2(); Console.Read(); }