天天看點

C#進階程式設計結構(四)

9 集合與泛型

解決記憶體中維護和操作一組資料點的問題

解決了.net1.1中裝箱和類型安全問題

  1. 集合類的動機

    非泛型集合:通常設計為操作System.Object System.Collections

  2. 泛型類型的優勢

    a. 性能更好,因為他們不會導緻裝箱和拆箱的損耗

    b. 更加類型安全,因為我們指定他們包含的類型

    c. 大幅減少了建構自定義集合類型的需要,因為建立泛型容器時指定了 類型的類型

  3. 泛型類型參數的作用

    讀作 of T

    隻有類,結構,接口和委托可以使用泛型,枚舉類型不可以

  4. System.Collection.Generic

    為大多數非泛型接口定義了泛型版本

    List, Stack, Queue, SortedSet

  5. System.Collection.ObjectModel

    ObservableColection, 内容發生變化時通知外部對象的動态資料集合

    ReadOnlyObservableColection

    ObservableCollection<Person> person1 = new ObservableCollection<Person>
            {
                new Person{Name="jack",Age=10 },
                new Person{Name="mary",Age=12 },
            };
            person1.CollectionChanged += delegate (object sender, NotifyCollectionChangedEventArgs e)
            {
                MessageBox.Show("observablecollection changed, {0}" + person1[0].Name);
            };
            person1[0].Name = "jack2";//useless
            person1.Add(new Person { Name = "jeff" });
               
  6. 建立自定義泛型方法
  7. 建立自定義泛型結構和類

    default和泛型一起使用時,表示一個類型參數的預設值

    數值預設為0,引用類型預設為null

  8. 類型參數的限制
    where T : struct   must have System.ValueType in its chain 
    where T : class    must be a reference type 
    where T : new()    must have a default constructor. 
    where T : NameOfBaseClass  must be derived from the class specified by NameOfBaseClass. 
    where T : NameOfInterface 
               
    ! Operator ‘+’ cannot be applied to operands of type ‘T’ and ‘T’

10 委托,事件和Lambda表達式

委托類型用來定義和響應應用程式中的回調..net委托類型是一個類型安全的對象,指向以後可以調用的方法.允許記憶體中的對象進行雙向對話

回調:對象與建立它的實體進行通信,低層函數執行調用高層的代碼

事件, event關鍵字

Lambda表達式,用代碼語句代替強類型委托.Lambda是匿名方法的一種僞裝

  1. 委托類型

    組成:調用的方法的名稱,該方法的參數,該方法的傳回值類型

    .net委托可以指向靜态方法,也可以指向執行個體方法

    定義: 委托的名稱可以自由選擇,但是必須比對它指向的方法的簽名(參數類型和個數)

    C#委托類型定義會生成一個密封類,繼承自System.MulticastDelegate,預設含有3個方法

    // 僞代碼
    public sealed class DelegateName : System.MulticastDelegate
    {
        public delegateReturnValue Invoke(allDelegateInputRefAndOutParams);
        public IAsyncResult BeginInvoke(allDelegateInputRefAndOutParams,
        AsyncCallback cb, object state);
        public delegateReturnValue EndInvoke(allDelegateRefAndOutParams,
        IAsyncResult result);
    }
               
  2. 簡單示例
    public delegate int BinaryOP(int x, int y);
        public class DelegateClass
        {
            public int Add(int x, int y)
            {
                return x + y;
            }
            public static int Add2(int x, int y)
            {
                return x + y;
            }
        }
        DelegateClass dg = new DelegateClass();
        BinaryOP binaryOP = new BinaryOP(dg.Add); 
        BinaryOP binaryOP2 = new BinaryOP(DelegateClass.Add2);
        Console.WriteLine("the sum is "+binaryOP.Invoke(1,2));
        Console.WriteLine("the sum is " + binaryOP2(1, 2)); 
               
  3. 使用委托發送對象狀态通知

    定義發送給調用者的委托類型

    私有委托類型變量,建立委托對象注冊方法

    建立調用委托對象的方法

    //this.listOfhandlers => OnCarEngineEvent
        car.RegisterCarEngine(new Car.CarEngineHandler(OnCarEngineEvent));
        //Accelerate => OnCarEngineEvent
        for (int i = 0; i < 6; i++)
            car.Accelerate(20);
               

    多路廣播:

    委托對象維護一個可調用方法的清單

    +=: Delegate.Combine() this.listOfhandlers += methodToCall; <=> if (listOfHandlers == null) listOfHandlers = methodToCall; else Delegate.Combine(listOfHandlers, methodToCall); -=: Delegate.Remove()

    方法組轉換:調用以委托作為參數的方法時直接提供方法的名稱,而不用建立委托對象

    car.RegisterCarEngine(new Car.CarEngineHandler(OnCarEngineEvent)); <=> car.RegisterCarEngine(OnCarEngineEvent);

  4. 泛型委托

    使用委托在應用程式中進行回調的步驟

    自定義與要指向的的方法的格式相比對的委托

    建立自定義委托的執行個體,方法名作為其構造函數的參數

    通過Invoke()來調用方法

    Action<>, Func<>

    接受一組參數并傳回一個值(或void)的委托.

    Action隻能指向void方法

    Func可以指向具有傳回值的方法

    //action func Action<string, string, int> action = new Action<string, string, int>(DisplayAction); action(" action", " message", 2); //func Func<string, string, string> func = new Func<string, string, string>(DisplayFunc); Console.WriteLine("func is {0}", func(" func", " message")); private static void DisplayAction(string v1, string v2, int v3) { Console.WriteLine("DisplayAction" + v1 + v2 + v3); } private static string DisplayFunc(string v1, string v2) { return "DisplayFunc" + v1 + v2; }

  5. 事件 event

    公共的委托成員打破了封裝,而且我們不希望給其他應用程式改變委托指向及未經許可直接調用成員的權力

    event關鍵字,編譯器會自動提供注冊和登出方法及必要的委托類型成員變量.event關鍵字隻是節省了打字的時間.

    public delegate void CarEngineHandler(string msgForCaller);
        public event CarEngineHandler Exploded;
        public event CarEngineHandler AbouttoBlow;
               

    自定義事件參數

    低層委托的第一個參數是System.Object,表示對發送事件的對象的引用

    第二個參數是System.EventArgs的子類型,表示該事件相關的資訊

    匿名方法: 事件注冊時直接将一個委托與一段代碼關聯.匿名方法最後一個大括号以分号結束

    匿名方法不能通路定義方法中的ref或是out參數

    public delegate void CarEngineHandlerEvent(object sender, CarEventArgs e);
    public event CarEngineHandlerEvent ExplodedEvent;   
    public class CarEventArgs : EventArgs
    {
        public readonly string msg;
        public CarEventArgs(string message)
        { msg = message; }
    }   
     car.ExplodedEvent += delegate (object sender, CarEventArgs e)
     {
         Console.WriteLine("object is {0}, careventargs is {1}",sender,e.msg);
     };          
               
  6. Lambda表達式

    要處理的參數 => {處理參數的語句; 語句2; 語句3 }

    Lambda表達式隻是用更簡單的方法來寫匿名方法,可應用于任何匿名方法或者強類型委托

car.ExplodedEvent += delegate (object sender, CarEventArgs e)
            {
                Console.WriteLine("object is {0}, careventargs is {1}", sender, e.msg);
            };
            car.ExplodedEvent += (sender, e) =>
            {
                Console.WriteLine("object is {0}, careventargs is {1}", sender, e.msg);
                Console.WriteLine("object2 is {0}, car2 eventargs is {1}", sender, e.msg);
            };  
           

11 進階c#語言特性

  1. 索引器方法

    索引器的實作基于集合類型如何實作調用者查詢子項

    允許我們建構以類似通路數組的文法來通路内部子類型的自定義類型

private ArrayList arPeople = new ArrayList();
           public Person this[int index]
           {
               get { return (Person)arPeople[index]; }
               set { arPeople.Insert(index, value); }
           }
           public IEnumerator GetEnumerator()
           {
               return arPeople.GetEnumerator();
           } 
           

使用字元串值索引對象,可直接獲得索引器方法功能,而不用建構自定義的非泛型類來支援字元串索引.

private Dictionary<string, Person> dictionaryPerson = new Dictionary<string, Person>();
    public Person this[string name]
    {
        get { return dictionaryPerson[name]; }
        set { dictionaryPerson.Add(name, value); }
    }
           

重載索引器方法

索引器方法支援多元

接口類型上定義索引器

索引器可以在.net接口上定義,這樣實作類型就可以提供自定義實作

public interface IStringContainer
    {
        string this[int index] { get; set; }
    }
           

2. 操作符重載

通常建構原子資料類型時才有用

操作符對不同類型做出不同反應, +: 對數字類型求值,對字元串類型串聯

重載二進制操作符 +

// operator ‘Point.operator +(Point, Point)’ must be declared static and public

public static Point operator +(Point p1, Point p2)

{

return new Point { X = p1.X + p2.X, Y = p1.Y + p2.Y };

}

簡寫指派操作符會根據相關二進制操作符自動具有相關功能 += / +   

        point1 += point2;
        Console.WriteLine("point1 is {0}", point1);
    can be overloaded. +, -,! , ~, ++, --, true, false   
can be overloaded. +, -, *, /, %, &, |, ^, <<, >>  
can be overloaded 對應的操作符要一起重載 (i.e., < and >, <= and >=, == and !=)  ==,!=, <, >, <=, >= 
cannot be overloaded. [] The [] operator.  () The () operator 
簡寫指派操作符會根據相關二進制操作符自動具有相關功能 `+=, -=, *=, /=, %=, &=, |=, ^=, <<=,>>= `
           
  1. 自定義類型轉換

    類似操作符重載,使用關鍵字implicit/explicit

    public static implicit operator Rectangle(Square square)
    {
        return new Rectangle { Height = square.Length, Width = 2 * square.Length };
    }
    public static explicit operator Square(Rectangle rectangle)
    {
        Square square = new Square { Length = rectangle.Width < rectangle.Height ? rectangle.Width : rectangle.Height };
        return square;
    }
        Rectangle rectangle = new Rectangle(15,4); 
        Square square = (Square)rectangle;
        square.Draw(); 
        rectangle = square;
        rectangle.Draw();
               
  2. 拓展方法

    不改變原始類型的情況下,為類或結構添加新的方法或屬性

    定義: 1, 拓展方法必須是靜态方法并定義在靜态類中

    2, 用this限定表示被拓展的項,this關鍵字修飾且僅修飾第一個

static class MyExtension
    {
        public static int ReverseDigits(this int i)
        {
            char[] digits = i.ToString().ToCharArray();
            int begin = ;
            int end = digits.Length - ;
            char flag;
            while (begin < end)
            {
                flag = digits[end];
                digits[end] = digits[begin];
                digits[begin] = flag;
                begin++;
                end--;
            }
            return Int32.Parse(new string(digits));
        }
        public static string ToStringEx(this char[] digits)
        {
            string str = string.Empty;
            if (digits == null)
                return str;
            foreach (char digit in digits)
                str += digit;
            return str;
        }
    }
   int i = ;
   int j = i.ReverseDigits();
   Console.WriteLine("Extension Method int reverse is {0} ", j);
   char[] charArray = new char[] {'1','2','e','5' };
   charArray.ReverseDigits();
   Console.WriteLine("Extension Method int reverse is {0} ", charArray.ToStringEx());
           
  1. 匿名類型

    定義匿名類型 var

    同樣值的匿名類型,按值比較相等.按引用不一樣.預設的類型一緻

    var car = new { Name = "car", Color = "red" };
        var mycar = new { Name = "car", Color = "red" };
    
        //same Equals
        //not same ==
        //same GetType<> f__AnonymousType0`2
        if (car.Equals(mycar))
            Console.WriteLine("same Equals");
        else
            Console.WriteLine("not same Equals");
    
        if (car==mycar)
            Console.WriteLine("same ==");
        else
            Console.WriteLine("not same ==");
    
        if (car.GetType().Name==(mycar.GetType().Name))
            Console.WriteLine("same GetType "+ car.GetType().Name);
        else
            Console.WriteLine("not same GetType");
               
  2. 指針類型

    第三種資料類型.繞開CLR的記憶體管理.

    VS: Properties=> Build => Allow Unsafe Code

    unsafe
        {
            int  right = 10;
            int* ptr = &right;
            *ptr = 123; 
            Console.WriteLine(right); 
        }
               

    stackalloc關鍵字,直接從調用棧配置設定記憶體

    fixed關鍵字,變量位址語句在執行過程中保持不變

    sizeof關鍵字, 擷取值類型的位元組大小,可用在unsafe上下文中

12 LINQ to Object

.net 3.5 引入了LINQ Language-Integrated Query

LINQ操作符隻是System.Linq.Enumerable類型靜态方法調用的快捷形式.

  1. LINQ 特有的程式設計結構

    LINQ可以了解為直接嵌入c#文法的強類型查詢語言

    LINQ相關的特性

    隐式類型本地變量 var

    對象/集合初始化文法 new Class{}

    lambda表達式 =>

    拓展方法

    匿名類型 var car = new {};

  2. 作用

    LINQ的API意圖提供一種統一操作各種資料類型的方式: database,xml, array, collection

  3. 用于原始數組
    string[] currVideos = new string[] { "morrowind", "red", "gone with the wind", "fallout", "modern family" };
        var subset = from g in currVideos
                     where g.Contains(" ")
                     orderby g
                     select g;
        foreach (var v in subset)
            Console.WriteLine(v);
               
    延遲執行的作用在疊代内容之前,他們不會真正的進行計算立即執行

    ToArray<T>(),ToDictionary<TSource,TKey>(), and ToList<T>()

    這些方法将會立即執行LINQ查詢并取得一份資料快照
  4. 用于集合對象
    List<Car> cars = new List<Car>() {
            new Car { Name = "car1",maxSpeed=70},
            new Car { Name = "car2",maxSpeed=130},
            new Car { Name = "car3",maxSpeed=140},
            new Car { Name = "car4",maxSpeed=150}};
        var fastcars = from g in cars where g.maxSpeed > 100 && g.maxSpeed < 143 orderby g.Name select g;
        foreach (var car in fastcars)
            Console.WriteLine(car.Name + car.maxSpeed + ";");
               
    用于非泛型集合 OfType對包含在非泛型集合裡的資料授予資料結構進行疊代操作
    ArrayList cars2 = new ArrayList() {
            new Car { Name = "car1",maxSpeed=70},
            new Car { Name = "car2",maxSpeed=130},
            new Car { Name = "car3",maxSpeed=140},
            new Car { Name = "car4",maxSpeed=150},
            new Car { Name = "car5",maxSpeed=110},
            new Car { Name = "car6",maxSpeed=120}};
        var carsEnum = cars2.OfType<Car>();
        var fastcars2 = from f in carsEnum
                        where f.maxSpeed > 100 &  f.maxSpeed < 150
                        orderby f.maxSpeed
                        select f;
        foreach (var car in fastcars2)
            Console.WriteLine(car.Name + " " + car.maxSpeed + ";");
               
    OfType篩選資料
    ArrayList myStuff = new ArrayList();
        myStuff.AddRange(new object[] { 1, 2, 4, 3, "as", "ds", 'w', 'e', new Car() });
        var myInts = myStuff.OfType<int>();
        foreach (int i in myInts)
            Console.WriteLine(" OfType " + i + ";");
               
  5. LINQ查詢操作符

    from in 定義LINQ表達式的主幹,允許從合适的容器中提取資料子集

    where 對提取出的項的限制條件

    select 從容器中選擇出序列

    join,on, equals, into 基于制定的鍵做關聯操作

    orderby, ascending/descending 排列子集進行升序或是降序

    group by 基于特定的值進行分組

    LINQ查詢表達式在編譯時校驗,操作符的順序很重要

    from後的項用來比對查詢條件及選擇出的項

    in後面的項表示要查詢的資料容器

    where後預期接一個結果為bool值得表達式

    select後投影出新的資料類型

    (LINQ set)

    Count() 傳回資料項的總數,Average(),Max()..

    orderby 排序

    Except() 傳回兩個容器的不同之處

    Intersect() 傳回兩個容器的共同資料項

    Union() 合并容器并去除重複項

    Concat() 連接配接兩個容器

    Distinct() 去除重複資料項

  6. 查詢語句的内部表示

    編譯器将所有的LINQ操作符都翻譯為對Enumerable類中方法的調用

    var fastcars2 = from f in carsEnum
                        where f.maxSpeed > 10 & f.maxSpeed < 150
                        orderby f.maxSpeed
                        select new { f.Name, f.maxSpeed };//select f;
        var fastcarEnum = carsEnum.Where(f => f.maxSpeed > 10 & f.maxSpeed < 150)
            .OrderByDescending(a => a.maxSpeed)
            .Select(a => new { a.Name, a.maxSpeed });
               

13 對象的生命周期

CLR通過GC(garbage collection)來管理已配置設定的類執行個體(對象)

.net對象被配置設定到一塊叫托管堆(managed heap)的記憶體區域上,GC會自動管理這塊記憶體

  1. 類,對象和引用

    類:隻是一個藍圖,描述了這個類的執行個體在記憶體中看起來是什麼樣子

    對象:類執行個體,配置設定在托管堆上.一個類可以有任意個對象

    引用:new關鍵字傳回的指向堆上對象的引用(指針)

    點操作符可以調用對象的成員.

  2. 對象生命周期的基礎

    當對象從代碼庫的任何部分都不可通路時,GC會進行回收.GC機制是獨立于代碼庫的

    将對象設定為null也不會觸發GC回收

  3. 應用程式根

    根是一個存儲位置,儲存着對托管對上一個對象的引用.

    GC通過根判斷對象是否可被通路,進一步标記是否回收

  4. 對象的代

    代的設計思路:對象在堆存在時間越長就越可能應該保留

    第0代: 從沒被标記為回收的新配置設定的對象

    第1代: 上一次垃圾回收中被标記為回收但沒有被回收的對象

    第2代: 一次以上的垃圾回收後仍沒有被回收的對象

  5. .net 1.0 - 3.5 并發進行垃圾回收

    GC會挂起所有活動程序

  6. 4.0以後

    背景垃圾回收用于第二代,回收時間較長

    專用背景線程回收暫時代(0/1),存在時間短

    Console.WriteLine("GC GetTotalMemory" + GC.GetTotalMemory(false) + ";");
        Console.WriteLine("GC MaxGeneration" + GC.MaxGeneration + ";");
        //強制回收記憶體
        GC.Collect();
        GC.WaitForPendingFinalizers();
               
  7. 建構可終結對象

    重寫Finalize()方法,會在垃圾回收之前調用,System.Object中的虛方法,但可能不存在

  8. 建構可處置對象

    實作IDidposable接口,其中定義了一個Dispose()的方法

    結構和類都可以實作IDisposeable

    隻有類可實作Finalize()

    using(){} 會自動調用Dispose()

  9. 延遲對象執行個體化

    System.Lazy<>: 提供懶加載.定義的資料在代碼庫實際使用之前是不會被建立的.

    //private AllTracks allSongs = new AllTracks();

    //public AllTracks GetAllTracks()

    //{ return allSongs; }

    private Lazy<AllTracks> allSongs = new Lazy<AllTracks>();
    public AllTracks GetAllTracks()
    {
        return allSongs.Value;
    }