C#入門03:招牌菜-C#高級功能

儘管C#和C++在語法上有很多相似之處,但C#是更高一級的編程語言,它提供了很多隻有高級語言纔有的特性,比如屬性,委託和事件,這些都是在C#中經常用到的語言特徵。

屬性

屬性(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);
    }

    // 1、創建委託實例,早期C#1.0的用法
    Del del1 = new Del(Notify);

    // 2、創建委託實例,現在的用法,不使用new,C#2.0的用法
    Del del2 = Notify;

    // 3、使用匿名方法來初始化委託
    Del del3 = delegate (string name)
    {
        Console.WriteLine("Notification received for: {0}", name);
    };

    // 4、使用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)類中的方法(事件處理程序)。
下面的示例是典型的委託和事件最常用的方式:

    //創建一個取水類
    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)
創建並使用自定義特性包含四個步驟:
1、聲明自定義特性
2、構建自定義特性
3、在目標程序元素上應用自定義特性
4、通過反射訪問特性

下面的代碼完成了前三部,後面將在反射一節用代碼說明如何通過反射來訪問特性:

    // 一個自定義特性 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());
        }
    }

反射

反射指程序可以訪問、檢測和修改它本身狀態或行爲的一種能力。
程序集包含模塊,而模塊包含類型,類型又包含成員。反射則提供了封裝程序集、模塊和類型的對象。您可以使用反射動態地創建類型的實例,將類型綁定到現有對象,或從現有對象中獲取類型。然後,可以調用類型的方法或訪問其字段和屬性。

優點:
1、反射提高了程序的靈活性和擴展性。
2、降低耦合性,提高自適應能力。
3、它允許程序創建和控制任何類的對象,無需提前硬編碼目標類。

缺點:
1、性能問題:使用反射基本上是一種解釋操作,用於字段和方法接入時要遠慢於直接代碼。因此反射機制主要應用在對靈活性和拓展性要求很高的系統框架上,普通程序不建議使用。
2、使用反射會模糊程序內部邏輯;程序員希望在源代碼中看到程序的邏輯,反射卻繞過了源代碼的技術,因而會帶來維護的問題,反射代碼比相應的直接代碼更復雜。

用途:
1、它允許在運行時查看特性(attribute)信息。
2、它允許審查集合中的各種類型,以及實例化這些類型。
3、它允許延遲綁定的方法和屬性(property)。
4、它允許在運行時創建新類型,然後使用這些類型執行一些任務。

接下來繼續完成上一節特性的代碼,使用反射查詢特性信息:

    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#中可以使用泛型的地方很多,不僅限定於類,方法,接口,還可以在委託,事件中都使用泛型編程。
泛型的主要特徵:
1、它有助於您最大限度地重用代碼、保護類型的安全以及提高性能。
2、您可以創建泛型集合類。.NET 框架類庫在 System.Collections.Generic 命名空間中包含了一些新的泛型集合類。您可以使用這些泛型集合類來替代 System.Collections 中的集合類。
3、您可以創建自己的泛型接口、泛型類、泛型方法、泛型事件和泛型委託。
4、您可以對泛型類進行約束以訪問特定數據類型的方法。
5、泛型數據類型中使用的類型的信息可在運行時通過使用反射獲取。

    //定義一個泛型類
    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);

匿名函數與Lambda表達式

使用委託時使用匿名函數和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();
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章