C#筆記(2)-高級特性

[TOC]

  • 一 委託
  • 二 事件
    • 標準事件模式
  • 三 匿名方法
  • 四 擴展方法
  • 五 匿名類型
  • 六 元組
  • 七 動態綁定
    • 自定義綁定
  • 八 運算符函數
  • 九 unsafe
  • 十 數組
    • stackalloc 關鍵字
    • fixed 關鍵字
  • 十一 預處理指令

一 委託

委託類似代理,也類似別名,使用關鍵字delegate定義,例如:

        // 定義一個委託類型, 它兼容任何返回類型是 int 並有一個int 類型的參數的方法,比如下面的 Square
        delegate int Transformer(int x);
        static int Square(int x) => x * x;
        static int AddOne(int x) => x + 1;
        public static void Test1()
        {
            // 將方法賦值給委託變量就能創建一個委託實例
            Transformer t = Square;
            // 委託就類似於別名一樣
            int answer = t(12);
            Console.WriteLine(answer); // 144
            Console.WriteLine(t.Method); // Int32 Square(Int32)
            t += AddOne;
            // 多播委託可以聯結多個委託實例, t 先調用 Square 再調用 AddOne 並返回最後一個委託實例返回的值
            answer = t(12);
            Console.WriteLine(answer); // 13
            Console.WriteLine(t.Method); // Int32 AddOne(Int32)
        }
    

委託都可以用接口來替代實現。

二 事件

使用委託可以實現觀察者模式,即廣播和訂閱。

下面是一個監聽數字變化的例子:

        // 聲明委託
        public delegate void NumberChangedHandler(int oldNumber, int newNumber);
        
        public class FunnyNumber
        {
            private string symbol;
            private int number;

            public FunnyNumber(string symbol)
            {
                this.symbol = symbol;
            }
            
            // 聲明事件,在委託成員前面加 event 關鍵字即可
            public event NumberChangedHandler NumberChanged;

            public int Number
            {
                get => number;
                set
                {
                    if (number == value)
                    {
                        // 數字無變化
                        return;

                    }

                    int oldNumber = number;
                    number = value;
                    if (NumberChanged != null)
                    {
                        // 所有委託的方法會被調用,這裏實現了觀察着模式
                        NumberChanged(oldNumber, number);
                    }
                }
            }
        }

標準事件模式

        /**
         * 標準事件模式的委託必須以 void 爲返回值;
         * 委託必須接受兩個參數,object 類型的事件廣播者 和 傳遞的信息;
         * 委託名稱必須以 EventHandler 結尾。
         * 下面是C#2.0及以後內置的源碼:
         */
        // public delegate void EventHandler<TEventArgs>(object source, TEventArgs e) where TEventArgs : EventArgs;

標準觀察者模式的應用:

       public class NumberChangedEventArgs:System.EventArgs
        {
            public readonly int LastNumber;
            public readonly int NewNumber;

            public NumberChangedEventArgs(int lastNumber, int newNumber)
            {
                LastNumber = lastNumber;
                NewNumber = newNumber;
            }
            
        }
        
        public class FoodNumber
        {
            private string symbol;
            private int number;

            public FoodNumber(string symbol)
            {
                this.symbol = symbol;
            }
            
            public event EventHandler<NumberChangedEventArgs> NumberChanged;

            /**
             * 該模式需要編寫一個 protected 的虛方法來觸發事件,方法名是 On 加上事件名,並接受唯一參數 EventArgs
             */
            protected virtual void OnNumberChanged(NumberChangedEventArgs e)
            {
                NumberChanged?.Invoke(this, e);
            }

            public int Number
            {
                get => number;
                set
                {
                    if (number == value)
                    {
                        return;
                    }

                    int oldNumber = number;
                    number = value;
                    OnNumberChanged(new NumberChangedEventArgs(oldNumber, number));
                }
            }
        }
        
        public static void Test2()
        {
            FoodNumber foodNumber = new FoodNumber("Afra55");
            foodNumber.Number = 100;
            // 註冊事件
            foodNumber.NumberChanged += food_NumberChanged;
            foodNumber.Number = 200;
            foodNumber.Number = 300;
            foodNumber.Number = 400;
        }

        static void food_NumberChanged(object sender, NumberChangedEventArgs e)
        {
            Console.WriteLine($"{e.LastNumber} -> {e.NewNumber}");       
        }

三 匿名方法

delegate 關鍵字後加上參數的聲明和方法體:

            Transformer sqr = delegate(int i) { return i * i; };
            Console.WriteLine(sqr(12));
            Transformer sqrLambda = x => x * x;
            Console.WriteLine(sqrLambda(2));

四 擴展方法

擴展方法允許在現有類型上擴展新的方法而無須修改原始類型的定義,是靜態類的靜態方法.

    public static class StringHelper
    {
        public static bool isCapitalized(this string s)
        {
            if (string.IsNullOrEmpty(s)) return false;
            return char.IsUpper(s[0]);
        }
    }
    
    ...
    
    Console.WriteLine("Afra55".isCapitalized()); // True
    Console.WriteLine("afra55".isCapitalized()); // False


五 匿名類型

new 關鍵字加上對象初始化器:

            // 只能通過 var 引用
            var person = new {Name = "Afra55", Age = 20};
            var person1 = new {Name = "Afra55", Age = 20};
            Console.WriteLine(person  == person1); // False
            Console.WriteLine(person.Equals(person1)); // True
            Console.WriteLine(person.GetType() == person1.GetType()); // True

六 元組

            // 元組元素可以簡單的在括號中初始化值
            var user = ("Bfra55", 10);
            Console.WriteLine(user.Item1);  // Bfra55
            Console.WriteLine(user.Item2);  // 10

            // 元組是值類型,可以指定元組元素類型
            (string, long) u = ("Bfra55", 20);

            // 可以爲元素定義名字
            var u1 = (Name: "Cfra55", Age: 22);
            Console.WriteLine(u1.Name);
            Console.WriteLine(u1.Age);

            (string Name, int Age) u2 = ("Dfra55", 22);
            Console.WriteLine(u2.Equals(u1)); // True

七 動態綁定

動態綁定將解析類型、成員和操作的過程從編譯時延遲到運行時。

        class User
        {
            private int age;
            private string name;

            public int Age
            {
                get => age;
                set => age = value;
            }

            public string Name
            {
                get => name;
                set => name = value;
            }

            public void ShowInfo()
            {
                Console.WriteLine( $"{Age}, {Name}");
            }
        }

        public static void Test5()
        {
            // 動態綁定
            dynamic d = new User();
            // 編譯時並不知道 d 有 ShowInfo 方法,在運行時纔會去查找這個方法
            d.Name = "Afra55";
            d.Age = 123;
            d.ShowInfo(); // 123, Afra55
            // 當方法不存在時就會拋出異常
            d.WhatDoYouDo(); // Microsoft.CSharp.RuntimeBinder.RuntimeBinderException:
        }

自定義綁定

避免方法不存在時拋出異常可以讓類繼承 DynamicObject, 並重寫 TryInvokeMember 方法:

       class User : DynamicObject
        {
            private int age;
            private string name;

            public int Age
            {
                get => age;
                set => age = value;
            }

            public string Name
            {
                get => name;
                set => name = value;
            }

            public void ShowInfo()
            {
                Console.WriteLine( $"{Age}, {Name}");
            }

            public override bool TryInvokeMember(InvokeMemberBinder binder, object[] args, out object result)
            {
                Console.WriteLine(binder.Name + " method was called");
                result = null;
                return true;
                // return base.TryInvokeMember(binder, args, out result);
            }
        }

        public static void Test5()
        {
            // 動態綁定
            dynamic d = new User();
            // 編譯時並不知道 d 有 ShowInfo 方法,在運行時纔會去查找這個方法
            d.Name = "Afra55";
            d.Age = 123;
            d.ShowInfo();
            d.WhatDoYouDo(); // WhatDoYouDo method was called
        }

動態綁定失去了靜態綁定時的類型安全保證,可能出現運行時異常:

        // 參數動態綁定,返回值也動態綁定,失去了類型安全的保護,可能發生運行時異常
        static dynamic Mean(dynamic x, dynamic y) => (x + y) / 2;
        public static void Test6()
        {
            int x = 3, y = 4;
            Console.WriteLine(Mean(x, y));
            Console.WriteLine(Mean(x, "22")); // .RuntimeBinderException
        }

動態引用可以指向 指針類型以外的任何類型對象:

            dynamic x = "Afra55";
            Console.WriteLine(x.GetType().Name); // String
            x = 32;
            Console.WriteLine(x.GetType().Name); // Int32

var 是用編譯器確定類型;
dynamic 是運行時確定類型。

八 運算符函數

  • 函數名爲operator關鍵字跟上運算符符號。
  • 運算符函數必須是static和public的。
  • 運算符函數的參數即操作數。
  • 運算符函數的返回類型表示表達式的結果。
  • 運算符函數的操作數中至少有一個類型和聲明運算符函數的類型是一致的。
       public struct Lover
        {
            int age;
            string name;

            public Lover(int age, string name)
            {
                this.age = age;
                this.name = name;
            }

            public static Lover operator +(Lover a, Lover b)
            {
                return new Lover(a.age + b.age, a.name + " and " + b.name);
            }

            public int Age
            {
                get => age;
                set => age = value;
            }
            public string Name
            {
                get => name;
                set => name = value;
            } 
            
        }

        public static void Test7()
        {
            Lover A = new Lover(12, "Afra");
            Lover B = new Lover(32, "Bfra");
            Lover C = A + B;
            Console.WriteLine($"{C.Name} ~ {C.Age}"); // Afra and Bfra ~ 44
        }

九 unsafe

使用unsafe關鍵字修飾類型、類型成員或者語句塊,就可以在該範圍內使用指針類型並可以對作用域內的內存執行指針操作。

每一種值類型或引用類型類型,它都有對應的指針類型;

運算符 作用
& 取址運算符,返回變量地址的指針
* 解引用運算符,放回指針指向地址的變量
-> 指針取成員運算符, x->y 相當於 (*x).y

fixed語句則告訴垃圾回收器“鎖定”這個對象,而且不要移動它, 避免指向該對象的指針無效。

            class FixTest
            {
                public int number = 0;
            }
            
            // ...
            FixTest fixTest = new FixTest();
            unsafe
            {
                fixed (int* p = &fixTest.number)
                {
                    *p = 9;
                }
                Console.WriteLine(fixTest.number); // 9
            }

十 數組

stackalloc 關鍵字

staclalloc關鍵字將在棧上顯式分配一塊內存, 因爲是在棧內分配,因此生命週期和其他局部變量受限於法執行期。

            unsafe
            {
                int* a = stackalloc int[10];
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine(a[i]);
                }
            }

fixed 關鍵字

fixed 關鍵字在結構中創建固定大小的緩衝區:

        unsafe struct UnsafeUnicodeString
        {
            public short Length;
            public fixed byte Buffer[30]; // 分配30字節緩衝區
        }

十一 預處理指令

#define DEBUG
    class DebugTest
    {
        void Test()
        {
#if DEBUG
            //根據DEBUG符號定義與否來有條件地對其中語句進行編譯, 如果移除 #define DEBUG 就不會進行下面語句的編譯
            Console.WriteLine("Debug");
#endif
        }
    }
預處理指令 操作
#define symbol 定義 symbol 符號
#undef symbol 取消 symbol 符號的定義
#if symbol [operator symbol2]... 判斷 symbol 符號,其中。operator 可以是 `==, !=, &&, , #if 指令後可以跟#else, #elif, #endif`
#else 執行到下個 #endif 之前的代碼
#elif symbol [operator symbol2] 組合 #else 分支和 #if 判斷
#endif 角黍條件指令
#warning text 在編譯器輸出中顯示 text 警告
#error text 在編譯器輸出中顯示 text 錯誤信息
`#pragma warning [disable restore]` 禁用/恢復編譯器警告
`#line [[number['file']] hidden]` number 源代碼行號;file 輸出的文件名;hidden 調試器忽略此處到下一個 #line 指令之間的代碼
#region name 標記大綱開始
#endregion 結束一個大綱區域
class MainClass
{
    static void Main()
    {
#line 200 "Special"
        int i;
        int j;
#line default
        char c;
        float f;
#line hidden // numbering not affected
        string s;
        double d;
    }
}

#line輸出測試:

Special(200,13): warning CS0168: The variable 'i' is declared but never used
Special(201,13): warning CS0168: The variable 'j' is declared but never used
MainClass.cs(9,14): warning CS0168: The variable 'c' is declared but never used
MainClass.cs(10,15): warning CS0168: The variable 'f' is declared but never used
MainClass.cs(12,16): warning CS0168: The variable 's' is declared but never used
MainClass.cs(13,16): warning CS0168: The variable 'd' is declared but never used
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章