基礎系列(3)—— 高級數據類型

1、數組

(一)簡單數組(一維數組)
  數組是一種數據結構,它能夠包含同一個類型的多個元素。
  1.數組的聲明
  在聲明數組時,先定義數組中的元素類型,其後是一對空方括號和一個變量名。
html

 int[] myArray;

  2.數組的初始化
  聲明瞭數組以後,就必須爲數組分配內存,以保存數組的全部元素。數組是引用類型,因此必須給它分配堆上的內存。爲此,應使用new運算符,指定數組中元素的類型和數量來初始化數組的變量。  
程序員

myArray = new int[4];

  在聲明和初始化數組後,變量myArray就引用了4個整數值,它們位於託管堆上:
  
  在指定了數組的大小後,就不能從新設置數組的大小。若是事先不知道數組中應包含多少個元素,就可使用集合。除了在兩個語句中聲明和初始化數組以外,還能夠在一個語句中聲明和初始化數組:
算法

int[] myArray = new int[4];

  還可使用數組初始化器爲數組的每一個元素複製。數組初始化器只能在聲明數組變量時使用,不能在聲明數組以後使用。
數據庫

 int[] myArray = new int[4]{1357};

  若是用花括號初始化數組,能夠不指定數組的大小,由於編譯器會自動統計元素的個數: 編程

int[] myArray = new int[]{1357};

  也可使用更簡單的形式:
c#

 int[] myArray = {1357};

  3.訪問數組元素
  在聲明和初始化數組以後,就可使用索引器訪問其中的元素了。數組只支持有整型參數的索引器。索引器老是以0開頭,表示第一個元素。能夠傳遞給索引器的最大值是元素個數減1,由於索引從0開始:
數組

   int[] myArray = {1357};
  int v1 = myArray[0];
  int v2 = myArray[1];
  myArray[3] = 4;

  可使用數組的Length屬性獲取元素的個數。
  4.數組中使用引用類型
  數組除了能聲明預約義類型的數組,還能夠聲明自定義類型的數組。
安全

  public class Person
  {
    public string FirstName { get; set;

    public string LastName { get; set; }

    public override string ToString()
    {
      return String.Format("{0} {1}", FirstName, LastName);
    }
  }

  Person[] myPersons = new Person[2];
  myPersons[0] = new Person { FirstName = "Ayrton", LastName = "Senna" };
  myPersons[1] = new Person { FirstName = "Michael", LastName = "Schumacher" };

  若是數組中的元素是引用類型,就必須爲每一個數組元素分配內存。若是使用了數組中未分配內存的元素,就會拋出NullReferenceException類型的異常。
  下面是內存狀況:
  
  對自定義類型也可使用數組初始化器:
數據結構

Person[] myPersons2 =
  {
    new Person { FirstName="Ayrton", LastName="Senna"},
    new Person { FirstName="Michael", LastName="Schumacher"}
  };

(二).多維數組
  多維數組用兩個或多個整數來索引。在C#中聲明多維數組,須要在方括號中加上逗號。數組在初始化時應指定每一維的大小(也稱爲階)。   
ide

   int[,] twoDim = new int[3,3];
  twoDim[0,0] = 1;
  twoDim[0,1] = 2;
  twoDim[0,2] = 3;
  twoDim[1,0] = 4;
  twoDim[1,1] = 5;
  twoDim[1,2] = 6;
  twoDim[2,0] = 7;
  twoDim[2,1] = 8;
  twoDim[2,2] = 9;

  聲明數組以後,就不能修改其階數了。也可使用初始化器來初始化多維數組:

 int[,] twoDim ={
    {1,2,3},
    {4,5,6},
    {7,8,9}
    };

  使用數組初始化器時,必須初始化數組的每一個元素,不能遺漏任何元素。聲明一個三位數組:  

int[,,] threeDim ={
    {{1,2},{3,4}},
    {{5,6},{7,8}},
    {{9,10},{11,12}}
    };
  Console.WriteLine(threeDim[0,1,1]);

(三).鋸齒數組
  二維數組的大小對應於一個矩形,而鋸齒數組的大小設置比較靈活,在鋸齒數組中,每一行均可以有不一樣的大小。
  在聲明鋸齒數組時,要依次放置左右括號。在初始化鋸齒數組時,只在第一對方括號中設置該數組包含的行數。定義各行中元素個數的第二個方括號設置爲空,由於這類數組的每一行包含不一樣的元素個數。以後,爲每一行指定行中的元素個數: 

   int[][] jagged = new int[3][];
  jagged[0] = new int[2]{1,2};
  jagged[1] = new int[4]{3,4,5,6};
  jagged[2] = new int[3]{7,8};

  迭代鋸齒數組中的全部元素的代碼能夠放在嵌套的for循環中。在外層的for循環中迭代每一行,在內層的for循環中迭代一行中的每一個元素:

 for(int row = 0;row<jagged.Length;row++)
  {
    for(int element = 0;element<jagged[row].Length;element++)
    {
      Console.WriteLine("row:{0}, element:{1},value:{2}",row,element,jagged[row][element]);
    }
  }

(四).Array類
  用方括號聲明數組是C#中使用Array類的表示法。在後臺使用C#語法,會建立一個派生自抽象基類Array的新類。這樣,就可使用Array類爲每一個C#數組定義的方法和屬性了。
  Array類實現的其它屬性有LongLength和Rank。若是數組包含的元素個數超出了整數的取值範圍,就可使用LongLength屬性來得到元素個數。使用Rank屬性能夠得到數組的維數。
  1.建立數組
  Array類是一個抽象類,因此不能使用構造函數來建立數組。但除了使用C#語法建立數組實例以外,還可使用靜態方法CreateInstance()建立數組。若是事先不知道元素的類型,該靜態方法就頗有用,由於類型能夠做爲Type對象傳遞給CreateInstance()方法。
  CreateInstance()方法的第一個參數是元素的類型,第二個參數定義數組的大小。可使用SetValue()方法設置對應元素的值,用GetValue()方法讀取對應元素的值。  

Array intArray1 = Array.CreateInstance(typeof(int), 5);
  for (int i = 0; i < 5; i++)
  {
    intArray1.SetValue(33, i);
  }

  for (int i = 0; i < 5; i++)
  {
    Console.WriteLine(intArray1.GetValue(i));
  }

  還能夠將已經建立的數組強制轉換稱聲明爲int[]的數組:

 int[] intArray2 = (int[])intArray1;

  CreateInstance()方法有許多重載版本,能夠建立多維數組和索引不基於0的數組。  

//建立一個2X3的二維數組,第一維基於1,第二維基於10:
  int[] lengths = { 2, 3 };
  int[] lowerBounds = { 1, 10 };
  Array racers = Array.CreateInstance(typeof(Person), lengths, lowerBounds);

  racers.SetValue(new Person { FirstName = "Alain", LastName = "Prost" }, index1: 1, index2: 10);
  racers.SetValue(new Person
  {
    FirstName = "Emerson",
    LastName = "Fittipaldi"
  }, 1, 11);
  racers.SetValue(new Person { FirstName = "Ayrton", LastName = "Senna" }, 1, 12);
  racers.SetValue(new Person { FirstName = "Michael", LastName = "Schumacher" }, 2, 10);
  racers.SetValue(new Person { FirstName = "Fernando", LastName = "Alonso" }, 2, 11);
  racers.SetValue(new Person { FirstName = "Jenson", LastName = "Button" }, 2, 12);

  Person[,] racers2 = (Person[,])racers;
  Person first = racers2[1, 10];
  Person last = racers2[2, 12];

  2.複製數組
  由於數組是引用類型,因此將一個數組變量賦予另外一個數組變量,就會獲得兩個引用同一數組的變量。數組實現ICloneable接口。這個接口定義的Clone()方法會複製數組,建立數組的淺表副本。若是數組的元素是值類型,Clone()方法會複製全部值:

   int[] a1 = {1,2};
  int[] a2 = (int[])a1.Clone();

  若是數組包含引用類型,只複製引用。除了使用Clone()方法以外,還可使用Array.Copy()方法建立淺表副本。

 Person[] beatles = {
    new Person { FirstName="John", LastName="Lennon" },
    new Person { FirstName="Paul", LastName="McCartney" }
  };

  Person[] beatlesClone = (Person[])beatles.Clone();
  Person[] beatlesClone2 = new Person[2];
  Array.Copy(beatlesClone,beatlesClone2,2);//注意與Clone的語法區別,Copy須要傳遞階數相同的已有數組。(還可使用CopyTo()方法)

  3.排序
  Array類使用快速排序算法對數組中的元素進行排序。Sort()方法須要數組中的元素實現IComparable接口。由於簡單類型(如String,Int32)實現IComparable接口,因此能夠對包含這些類型的元素排序。   

string[] names = {
    "Christina Aguilera",
    "Shakira",
    "Beyonce",
    "Gwen Stefani"
    };

    Array.Sort(names);
    foreach (string name in names)
    {
      Console.WriteLine(name);
    }

  若是對數組使用使用自定義類,就必須實現IComparable接口。這個接口只定義了一個方法CompareTo()方法,若是要比較的對象相等,該方法就返回0.若是該實例應排在參數對象的前面,該方法就返回小於i0de值。若是該實例應排在參數對象的後面,該方法就返回大於0的值。 

public class Person : IComparable<Person>
  {
    public string FirstName { get; set; }

    public string LastName { get; set; }

    public override string ToString()
    {
      return String.Format("{0} {1}",
      FirstName, LastName);
    }

    public int CompareTo(Person other)
    {
      if (other == null) throw new ArgumentNullException("other");

      int result = this.LastName.CompareTo(other.LastName);
      if (result == 0)
      {
        result = this.FirstName.CompareTo(other.FirstName);
      }

      return result;
    }

  }

  //客戶端代碼:
  Person[] persons = {
  new Person { FirstName="Damon", LastName="Hill" },
  new Person { FirstName="Niki", LastName="Lauda" },
  new Person { FirstName="Ayrton", LastName="Senna" },
  new Person { FirstName="Graham", LastName="Hill" }
  };
  Array.Sort(persons);
  foreach (Person p in persons)
  {
    Console.WriteLine(p);
  }

  若是Person對象的排序方式與上述不一樣,或者不能修改在數組中用做元素的類,就能夠實現IComparer接口或IComparer<T>接口。這兩個接口定義了方法Compare()方法。機型比較的類必須實現這兩個接口之一。 

public enum PersonCompareType
  {
    FirstName,
    LastName
  }
  //經過使用實現了IComparer<T> 泛型接口的PersonComparer類比較Person對象數組。
  public class PersonComparer : IComparer<Person>
  {
    private PersonCompareType compareType;

    public PersonComparer(PersonCompareType compareType)
    {
      this.compareType = compareType;
    }


    #region IComparer<Person> Members

    public int Compare(Person x, Person y)
    {
        if (x == null) throw new ArgumentNullException("x");
        if (y == null) throw new ArgumentNullException("y");

      switch (compareType)
      {
        case PersonCompareType.FirstName:
          return x.FirstName.CompareTo(y.FirstName);
        case PersonCompareType.LastName:
          return x.LastName.CompareTo(y.LastName);
        default:
          throw new ArgumentException(
          "unexpected compare type");
      }
    }

    #endregion
  }
  客戶端代碼:
  Person[] persons = {
  new Person { FirstName="Damon", LastName="Hill" },
  new Person { FirstName="Niki", LastName="Lauda" },
  new Person { FirstName="Ayrton", LastName="Senna" },
  new Person { FirstName="Graham", LastName="Hill" }
  };
  Array.Sort(persons,
  new PersonComparer(PersonCompareType.FirstName));

  foreach (Person p in persons)
  {
    Console.WriteLine(p);
  }

(五).數組做爲參數
  數組能夠做爲參數傳遞給方法,也能夠從方法中返回。
  1.數組協變
  數組支持協變。這表示數組能夠聲明爲基類,其派生類型的元素能夠賦值於數組元素

 static void DisPlay(object[] o)
  {
    //..
  }

  能夠給該方法傳遞一個Person[]。數組協變只能用於引用類型,不能用於值類型
  2.ArraySegment<T>
  結構ArraySegment<T>表示數組的一段。若是須要使用不一樣的方法處理某個大型數組的不一樣部分,那麼能夠把相應的數組部分複製到各個方法。
  ArraySegment<T>結構包含了關於數組段的信息(偏移量和元素個數)。 
  數組段不復制原數組的元素,但原數組能夠經過ArraySegment<T>訪問。若是數組段中的元素改變了,這些變化就會反映到原數組中。

static void Main()
  {
    int[] ar1 = { 1, 4, 5, 11, 13, 18 };
    int[] ar2 = { 3, 4, 5, 18, 21, 27, 33 };
    var segments = new ArraySegment<int>[2]
    {
      new ArraySegment<int>(ar1, 0, 3),
      new ArraySegment<int>(ar2, 3, 3)
    };


    var sum = SumOfSegments(segments);
    Console.WriteLine("sum of all segments: {0}", sum);

  }

  static int SumOfSegments(ArraySegment<int>[] segments)
  {
  int sum = 0;
  foreach (var segment in segments)
  {
    for (int i = segment.Offset; i < segment.Offset + segment.Count; i++)
    {
        sum += segment.Array[i];
    }

  }
  return sum;
  }

2、 枚舉

 (一) 枚舉的定義

        枚舉類型聲明爲一組相關的符號常數定義了一個類型名稱。枚舉用於「多項選擇」場合,就是程序運行時從編譯時已經設定的固定數目的「選擇」中作出決定。枚舉是由程序員定義的類型,與結構和類具備很強的類似性。

   枚舉類型(也稱爲枚舉)爲定義一組能夠賦給變量的命名整數常量提供了一種有效的方法。例如,假設您必須定義一個變量,該變量的值表示一週中的一天。該變量只能存儲七個有意義的值。若要定義這些值,可使用枚舉類型。枚舉類型是使用 enum 關鍵字聲明的。

        默認狀況下,枚舉中每一個元素的基礎類型是 int。可使用冒號指定另外一種整數值類型。若是不爲枚舉數列表中的元素指定值,則它們的值將以 1爲增量自動遞增。在前面的示例中,Days.Sunday 的值爲 0,Days.Monday 的值爲 1,依此類推。建立新的 Days 對象時,若是不顯式爲其賦值,則它將具備默認值 Days.Sunday (0)。建立枚舉時,應選擇最合理的默認值並賦給它一個零值。這便使得只要在建立枚舉時未爲其顯式賦值,則所建立的所有枚舉都將具備該默認值。枚舉中大小寫敏感,可是建議不要這樣。 

enum Days { Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday };

(二)枚舉的優勢

        一、枚舉可以使代碼更加清晰,它容許使用描述性的名稱表示整數值。

  二、枚舉使代碼更易於維護,有助於確保給變量指定合法的、指望的值。

  三、枚舉使代碼更易輸入。

 (三) 枚舉的說明

  • 枚舉使用enum關鍵字來聲明,與類同級。枚舉自己能夠有修飾符,但枚舉的成員始終是公共的,不能有訪問修飾符。枚舉自己的修飾符僅能使用public和internal。
  • 枚舉是值類型,隱式繼承自System.Enum,不能手動修改。System.Enum自己是引用類型,繼承自System.ValueType。
  • 枚舉都是隱式密封的,不容許做爲基類派生子類。
  • 枚舉類型的枚舉成員均爲靜態,且默認爲Int32類型。
  • 每一個枚舉成員均具備相關聯的常數值。此值的類型就是枚舉的底層數據類型。每一個枚舉成員的常數值必須在該枚舉的底層數據類型的範圍以內。若是沒有明確指定底層數據類型則默認的數據類型是int類型。
  • 枚舉成員不能相同,但枚舉的值能夠相同。
  • 枚舉最後一個成員的逗號和大括號後面的分號能夠省略

(四) 枚舉的例子

複製代碼
 enum TimeOfDay { Moning=0, Afternoon=1, Evening=2 } class Program { public static string getTimeOfDay(TimeOfDay time) { string result = string.Empty; switch (time) { case TimeOfDay.Moning: result="上午"; break; case TimeOfDay.Afternoon: result = "下午"; break; case TimeOfDay.Evening: result = "晚上"; break; default: result = "未知"; break; } return result; } static void Main(string[] args) { string time= getTimeOfDay(TimeOfDay.Evening); Console.Write(time); Console.Read(); //輸出晚上 }
複製代碼

(五) 枚舉的方法

一、獲取枚舉字符串

TimeOfDay time = TimeOfDay.Afternoon;

Console.WriteLine(time.ToString());//輸出:Afternoon

 

二、Enum.Parse()方法。

   這個方法帶3個參數,第一個參數是要使用的枚舉類型。其語法是關鍵字typeof後跟放在括號中的枚舉類名。typeof運算符將在第5章詳細論述。第二個參數是要轉換的字符串,第三個參數是一個bool,指定在進行轉換時是否忽略大小寫。最後,注意Enum.Parse()方法實際上返回一個對象引用——咱們須要把這個字符串顯式轉換爲須要的枚舉類型(這是一個取消裝箱操做的例子)。對於上面的代碼,將返回1,做爲一個對象,對應於TimeOfDay.Afternoon的枚舉值。在顯式轉換爲int時,會再次生成1。

TimeOfDay time2 = (TimeOfDay) Enum.Parse(typeof(TimeOfDay), "afternoon", true); Console.WriteLine((int)time2);//輸出1

三、獲得枚舉的某一值對應的名稱

lbOne.Text = Enum.GetName(typeof(TimeOfDay), 0); lbOne.Text = ((TimeOfDay)0).ToString();//返回:Morning

四、獲得枚舉的全部的值

foreach (int i in Enum.GetValues(typeof(TimeOfDay))) lbValues.Text += i.ToString();

五、枚舉全部的名稱

foreach(string temp in Enum.GetNames(typeof(TimeOfDay))) lbNames.Text+=temp;

3、IEnumerable和IEnumerator

      初學C#的時候,總是被IEnumerable、IEnumerator、ICollection等這樣的接口弄的糊里糊塗,我以爲有必要切底的弄清楚IEnumerable和IEnumerator的本質。

      下面咱們先看IEnumerable和IEnumerator兩個接口的語法定義。其實IEnumerable接口是很是的簡單,只包含一個抽象的方法GetEnumerator(),它返回一個可用於循環訪問集合的IEnumerator對象。IEnumerator對象有什麼呢?它是一個真正的集合訪問器,沒有它,就不能使用foreach語句遍歷集合或數組,由於只有IEnumerator對象才能訪問集合中的項,假如連集合中的項都訪問不了,那麼進行集合的循環遍歷是不可能的事情了。那麼讓咱們看看IEnumerator接口有定義了什麼東西。看下圖咱們知道IEnumerator接口定義了一個Current屬性,MoveNext和Reset兩個方法,這是多麼的簡約。既然IEnumerator對象時一個訪問器,那至少應該有一個Current屬性,來獲取當前集合中的項吧。MoveNext方法只是將遊標的內部位置向前移動(就是移到一下個元素而已),要想進行循環遍歷,不向前移動一下怎麼行呢?

(一)詳細講解:

說到IEnumerable老是會和IEnumerator、foreach聯繫在一塊兒。C# 支持關鍵字foreach,容許咱們遍歷任何數組類型的內容:

int[] myArrayOfInts = {10,20,30,40};
foreach(int i in my myArrayOfInts) 
{
    Console.WirteLine(i); 
}

雖然看上去只有數組纔可使用這個結構,其實任何支持GetEnumerator()方法的類型均可以經過foreach結構進行運算。

    public class Garage  
    {  
        Car[] carArray = new Car[4];  //在Garage中定義一個Car類型的數組carArray,其實carArray在這裏的本質是一個數組字段        
        //啓動時填充一些Car對象  
        public Garage()  
        {  
            //爲數組字段賦值  
            carArray[0] = new Car("Rusty", 30);  
            carArray[1] = new Car("Clunker", 50);  
            carArray[2] = new Car("Zippy", 30);  
            carArray[3] = new Car("Fred", 45);  
        }  
    }  

理想狀況下,與數據值數組同樣,使用foreach構造迭代Garage對象中的每個子項比較方便:

    //這看起來好像是可行的  
    lass Program  
       {  
           static void Main(string[] args)  
           {  
               Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n");  
               Garage carLot = new Garage();  
      
               //交出集合中的每一Car對象嗎  
                foreach (Car c in carLot)  
               {  
                   Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed);  
               }  
      
               Console.ReadLine();  
           }  
       }  

讓人沮喪的是,編譯器通知咱們Garage類沒有實現名爲GetEnumerator()的方法(顯然用foreach遍歷Garage對象是不可能的事情,由於Garage類沒有實現GetEnumerator()方法,Garage對象就不可能返回一個IEnumerator對象,沒有IEnumerator對象,就不可能調用方法MoveNext(),調用不了MoveNext,就不可能循環的了)。這個方法是有隱藏在System.collections命名空間中的IEnumerable接口定義的。(特別注意,其實咱們循環遍歷的都是對象而不是類,只是這個對象是一個集合對象),支持這種行爲的類或結構其實是宣告它們向調用者公開所包含的子項:

//這個接口告知調方對象的子項能夠枚舉
public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

能夠看到,GetEnumerator方法返回對另外一個接口System.Collections.IEnumerator的引用。這個接口提供了基礎設施,調用方能夠用來移動IEnumerable兼容容器包含的內部對象。

//這個接口容許調用方獲取一個容器的子項
public interface IEnumerator
{
    bool MoveNext();             //將遊標的內部位置向前移動
    object Current{get;}       //獲取當前的項(只讀屬性)
    void Reset();                 //將遊標重置到第一個成員前面
}

因此,要想Garage類也可使用foreach遍歷其中的項,那咱們就要修改Garage類型使之支持這些接口,能夠手工實現每個方法,不過這得花費很多功夫。雖然本身開發GetEnumerator()、MoveNext()、Current和Reset()也沒有問題,但有一個更簡單的辦法。由於System.Array類型和其餘許多類型(如List)已經實現了IEnumerable和IEnumerator接口,你能夠簡單委託請求到System.Array,以下所示:

namespace MyCarIEnumerator  
{  
    public class Garage:IEnumerable  
    {  
        Car[] carArray = new Car[4];  
  
        //啓動時填充一些Car對象  
        public Garage()  
        {  
            carArray[0] = new Car("Rusty", 30);  
            carArray[1] = new Car("Clunker", 50);  
            carArray[2] = new Car("Zippy", 30);  
            carArray[3] = new Car("Fred", 45);  
        }  
        public IEnumerator GetEnumerator()  
        {  
            return this.carArray.GetEnumerator();  
        }  
    }  
}  
    //除此以外,GetEnumerator()被定義爲公開的,對象用戶能夠與IEnumerator類型交互:   
    namespace MyCarIEnumerator  
    {  
        class Program  
        {  
            static void Main(string[] args)  
            {  
                Console.WriteLine("*********Fun with IEnumberable/IEnumerator************\n");  
                Garage carLot = new Garage();  
      
                //交出集合中的每一Car對象嗎  
                foreach (Car c in carLot)  //之因此遍歷carLot,是由於carLot.GetEnumerator()返回的項時Car類型,這個十分重要  
                {  
                    Console.WriteLine("{0} is going {1} MPH", c.CarName, c.CurrentSpeed);  
                }  
      
                Console.WriteLine("GetEnumerator被定義爲公開的,對象用戶能夠與IEnumerator類型交互,下面的結果與上面是一致的");  
                //手動與IEnumerator協做  
                IEnumerator i = carLot.GetEnumerator();  
                while (i.MoveNext())  
                {   
                    Car myCar = (Car)i.Current;  
                    Console.WriteLine("{0} is going {1} MPH", myCar.CarName, myCar.CurrentSpeed);  
                }  
                Console.ReadLine();  
            }  
        }  
    }  

下面咱們來看看手工實現IEnumberable接口和IEnumerator接口中的方法:

    namespace ForeachTestCase  
    {  
          //繼承IEnumerable接口,其實也能夠不繼承這個接口,只要類裏面含有返回IEnumberator引用的GetEnumerator()方法便可  
        class ForeachTest:IEnumerable     {  
            private string[] elements;  //裝載字符串的數組  
            private int ctr = 0;  //數組的下標計數器  
      
            /// <summary>  
            /// 初始化的字符串  
            /// </summary>  
            /// <param name="initialStrings"></param>  
            ForeachTest(params string[] initialStrings)  
            {   
                //爲字符串分配內存空間  
                elements = new String[8];  
                //複製傳遞給構造方法的字符串  
                foreach (string s in initialStrings)  
                {  
                    elements[ctr++] = s;   
                }  
            }  
      
            /// <summary>  
            ///  構造函數  
            /// </summary>  
            /// <param name="source">初始化的字符串</param>  
            /// <param name="delimiters">分隔符,能夠是一個或多個字符分隔</param>  
            ForeachTest(string initialStrings, char[] delimiters)   
            {  
                elements = initialStrings.Split(delimiters);  
            }  
      
            //實現接口中得方法  
            public IEnumerator GetEnumerator()  
            {  
                return  new ForeachTestEnumerator(this);  
            }  
      
            private class ForeachTestEnumerator : IEnumerator  
            {  
                private int position = -1;  
                private ForeachTest t;  
                public ForeachTestEnumerator(ForeachTest t)  
                {  
                    this.t = t;  
                }  
     
                #region 實現接口  
      
                public object Current  
                {  
                    get  
                    {  
                        return t.elements[position];  
                    }  
                }  
      
                public bool MoveNext()  
                {  
                    if (position < t.elements.Length - 1)  
                    {  
                        position++;  
                        return true;  
                    }  
                    else  
                    {  
                        return false;  
                    }  
                }  
      
                public void Reset()  
                {  
                    position = -1;  
                }  
     
                #endregion  
            }  
            static void Main(string[] args)  
            {  
                // ForeachTest f = new ForeachTest("This is a sample sentence.", new char[] { ' ', '-' });  
                ForeachTest f = new ForeachTest("This", "is", "a", "sample", "sentence.");  
                foreach (string item in f)  
                {  
                    System.Console.WriteLine(item);  
                }  
                Console.ReadKey();  
            }  
        }  
    }  

(二)IEnumerable<T>接口

實現了IEnmerable<T>接口的集合,是強類型的。它爲子對象的迭代提供類型更加安全的方式。

    public  class ListBoxTest:IEnumerable<String>  
       {  
           private string[] strings;  
           private int ctr = 0;  
          
           #region IEnumerable<string> 成員  
           //可枚舉的類能夠返回枚舉  
           public IEnumerator<string> GetEnumerator()  
           {  
               foreach (string s in strings)  
               {  
                   yield return s;  
               }  
           }  
     
           #endregion  
     
           #region IEnumerable 成員  
           //顯式實現接口  
           System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()  
           {  
               return GetEnumerator();  
           }  
     
           #endregion  
      
           //用字符串初始化列表框  
           public ListBoxTest(params string[] initialStrings)  
           {   
               //爲字符串分配內存空間  
               strings = new String[8];  
               //複製傳遞給構造方法的字符串  
               foreach (string s in initialStrings)  
               {  
                   strings[ctr++] = s;   
               }  
           }  
      
           //在列表框最後添加一個字符串  
           public void Add(string theString)  
           {   
               strings[ctr] = theString;  
               ctr++;  
           }  
      
           //容許數組式的訪問  
           public string this[int index]  
           {  
               get {  
                   if (index < 0 || index >= strings.Length)  
                   {   
                       //處理不良索引  
                   }  
                   return strings[index];  
               }  
               set {   
                   strings[index] = value;  
               }  
           }  
      
           //發佈擁有的字符串數  
           public int GetNumEntries()  
           {  
               return ctr;  
           }  
       }  
    class Program  
      {  
          static void Main(string[] args)  
          {  
              //建立一個新的列表框並初始化  
              ListBoxTest lbt = new ListBoxTest("Hello", "World");  
      
              //添加新的字符串  
              lbt.Add("Who");  
              lbt.Add("Is");  
              lbt.Add("Douglas");  
              lbt.Add("Adams");  
      
              //測試訪問  
              string subst = "Universe";  
              lbt[1] = subst;  
      
              //訪問全部的字符串  
              foreach (string s in lbt)  
              {  
                  Console.WriteLine("Value:{0}", s);  
              }  
              Console.ReadKey();  
          }  
      }   

方案1:讓這個類實現IEnumerable接口

方案2:這個類有一個public的GetEnumerator的實例方法,而且返回類型中有public 的bool MoveNext()實例方法和public的Current實例屬性。

 4、foreach和for的區別

(一)、foreach循環的優點

C#支持foreach關鍵字,foreach在處理集合和數組相對於for存在如下幾個優點:

一、foreach語句簡潔

二、效率比for要高(C#是強類型檢查,for循環對於數組訪問的時候,要對索引的有效值進行檢查)

三、不用關心數組的起始索引是幾(由於有不少開發者是從其餘語言轉到C#的,有些語言的起始索引多是1或者是0)

四、處理多維數組(不包括鋸齒數組)更加的方便,代碼以下:

int[,] nVisited ={
       {1,2,3},
       {4,5,6},
       {7,8,9}
};
// Use "for" to loop two-dimension array(使用for循環二維數組)
Console.WriteLine("User 'for' to loop two-dimension array");
for (int i = 0; i < nVisited.GetLength(0); i++)
    for (int j = 0; j < nVisited.GetLength(1); j++)
         Console.Write(nVisited[i, j]);
         Console.WriteLine();

//Use "foreach" to loop two-dimension array(使用foreach循環二維數組)
Console.WriteLine("User 'foreach' to loop two-dimension array");
foreach (var item in nVisited)
Console.Write(item.ToString());

foreach只用一行代碼就將全部元素循環了出來,而for循環則就須要不少行代碼才能夠.

注:foreach處理鋸齒數組需進行兩次foreach循環

int[][] nVisited = new int[3][];
nVisited[0] = new int[3] { 1, 2, 3 };
nVisited[1] = new int[3] { 4, 5, 6 };
nVisited[2] = new int[6] { 1, 2, 3, 4, 5, 6 };

//Use "foreach" to loop two-dimension array(使用foreach循環二維數組)
Console.WriteLine("User 'foreach' to loop two-dimension array");
foreach (var item in nVisited)
      foreach (var val in item)
          Console.WriteLine(val.ToString());

五、在類型轉換方面foreach不用顯示地進行類型轉換

int[] val = { 1, 2, 3 };
ArrayList list = new ArrayList();
list.AddRange(val);
foreach (int item in list)//在循環語句中指定當前正在循環的元素的類型,不須要進行拆箱轉換
{
Console.WriteLine((2*item));
}
Console.WriteLine();
for (int i = 0; i < list.Count; i++)
{
int item = (int)list[i];//for循環須要進行拆箱
Console.WriteLine(2 * item);
}

六、當集合元素如List<T>等在使用foreach進行循環時,每循環完一個元素,就會釋放對應的資源,代碼以下:

using (IEnumerator<T> enumerator = collection.GetEnumerator())
{
      while (enumerator.MoveNext())
      {
           this.Add(enumerator.Current);
      }
}

(二)、foreach循環的劣勢

一、上面說了foreach循環的時候會釋放使用完的資源,因此會形成額外的gc開銷,因此使用的時候,請酌情考慮

二、foreach也稱爲只讀循環,因此再循環數組/集合的時候,沒法對數組/集合進行修改。

三、數組中的每一項必須與其餘的項類型相等.

 5、索引器

    實際工做中咱們遇到索引器的地方有不少,咱們都是似曾類似,今天咱們一塊兒來揭開他們的神祕面紗項目開發中在得到DataGridView控件的某列值時:dgvlist.SelectedRows[0].Cells[0].Value;在得到ListView控件的某列值時:listView1.SelectedItems[0].SubItems[0].Text;

在讀取數據庫記錄給變量賦值時:result=dr["StudentName"].ToString();記得Dictionary中根據key值來獲取Value值時:dic["key"]等等

(一)、索引器的定義

C#中的類成員能夠是任意類型,包括數組和集合。當一個類包含了數組和集合成員時,索引器將大大簡化對數組或集合成員的存取操做。

定義索引器的方式與定義屬性有些相似,其通常形式以下:

 [修飾符] 數據類型 this[索引類型 index]
  {
    get{//得到屬性的代碼}                                              

    set{ //設置屬性的代碼}
  }

(二)、索引器的本質是屬性

下面咱們以一個例子來了解索引器的用法和原理:

1.建立一個Test類,裏面定義一個數組和一個索引器

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace 索引器
{
   public class Test
    {
       //01.首先定義一個數組
       private string[] name=new string[2];

       //02.根據建立索引器的語法定義一個索引器,給name數組賦值和取值
       public string this[int index]
       {
           get { return name[index]; }
           set { name[index] = value; }
       }
    }
}

2.在Main方法中經過索引器給Test類中的數組賦值

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace 索引器
{
    class Program
    {
        static void Main(string[] args)
        {
            //01.首先你得實例化Test類的對象
            Test test=new Test();
            //02.利用索引器給Test類中的數組賦值
            test[0] = "張總";
            test[1] = "吳總";
            //03.打印查看效果
            for (int i = 0; i < 2; i++)
            {
                Console.WriteLine(test[i]);
            }
            Console.ReadLine();
        }
    }
}

3.效果以下:

上面說到 索引器的本質是屬性。是的,咱們能夠發現上面定義索引器的代碼的IL(中間語言)是(屬性的格式):

(三)、實際開發例子:

設計一個關於班級的類MyClass,裏面有一個list用來記錄班級裏的全部學生,假設學生類已經定義爲Student,那麼MyClass的定義大體以下:

public class MyClass

{

private List<Student> allStudents;

//其餘字段方法

//....

}

如今想爲MyClass定義一個操做,由於學生都是有學號的,因此我想直接用學號來獲得某個具體學生的信息,固然了,你能夠設計一個public的方法,就像下面同樣:

public class MyClass

{

private List<Student> allStudents;

public Student GetStudentByID(int studentID)

{

        if(allStudents==null) return null;

        for(int i=0;i<allStudents.Count;i++)

        {

                if(allStudents[i].id==studentID)

                {

                        return allStudents[i];

                }

        }

        return null;

}

//其餘字段方法

//....

}

固然了,使用方法就是:

MyClass mc=new MyClass();

Student st=mc.GetStudentByID(123);

而除了這種添加public函數的方法,索引器也能作到相應的功能,好比我設計一個索引器以下:

public class MyClass

{

private List<Student> allStudents;

public this[int studentID]

{

        get

        {

                if(allStudents==null) return null;

                for(int i=0;i<allStudents.Count;i++)

                {

                        if(allStudents[i].id==studentID)

                        {

                                return allStudents[i];

                        }

                }

                return null;

        }

}

//其餘字段方法

//....

}

 

而使用方法就是:

MyClass mc=new MyClass();

Student st=mc[123];

總結一下,索引器其實就是利用索引(index)找出具體數據的方法(函數),在類的設計中它並非必須的,它是能夠用普通函數替代的,簡單點理解,其實就是函數的一種特殊寫法而已

(四)、無參屬性特徵

   因爲某些不恰當使用字段會破壞對象的狀態,因此通常會將全部字段都設爲private。要容許用戶或類型獲取或設置狀態信息,須要提供封裝了字段訪問的方法(訪問器)。

public class Employee
{
   private string name;
   public string GetName(){return name;}
   public string SetName(string value){name = value;}
}    
上面進行數據封裝不得不實現額外的方法,類型用戶必須調用方法,不能直接引用字段名。爲此CLR提供一個稱爲屬性的機制。
public class Employee
{
    private string name;
    public string Name
    {
       get{return name;}
       set{name=value;}                          
    }
}

   每一個屬性都有名稱和類型(類型不能是void)。屬性不能重載。C#編譯器發現獲取或設置屬性時,實際會生成以下代碼,在屬性名以前自動附加get_,set_前綴。

public class Employe
{
    private string name;
    public string get_Name()
    {
        return n_Name;
    }
    pubic void set_Name()
    {
       n_Name=value;
    }
}

 

針對源代碼中定義的每一個屬性,編譯器會在託管程序集的元數據中生成一個屬性定義項,包含一些標誌以及屬性類型。同時還引用了get和set訪問器方法。這些工做將屬性這種抽象概念與它的訪問器方法之間創建起一個聯繫。

(五)、自動實現屬性

     若是僅爲了封裝一個支持字段而建立屬性,C#提供了稱爲自動屬性的簡潔語法。public string Name{get;set;}C#會在內部自動聲明一個私有字段。

     優勢:訪問該屬性的任何代碼實際都會調用get和set方法。若是之後決定本身實現get和set方法,則訪問屬性的任何代碼都沒必要從新編譯。然而將Name聲明爲字段,之後想改成屬性,那麼訪問字段的全部代碼都必須從新編譯才能訪問屬性方法。

     缺點:AIP的支持字段名稱由編譯器決定,每次從新編譯均可能更更名稱。所以任何類型含有一個AIP,就沒辦法對該類型的實例進行反序列化。而運行時序列化引擎會將字段名持久存儲到序列化流中。任何想要序列化或反序列化的類型中都不要使用AIP功能。另外使用AIP不能加斷點,手動實現的屬性則是能夠,方便調試。使用AIP屬性必然是可讀可寫的,只寫字段不能讀取值有什麼用?只讀確定是有默認值,因此AIP做用於整個屬性的,不能顯式實現一個訪問器方法,而讓另外一個自動實現。

(六)、 屬性使用時需注意的點

  • 若是定義屬性,最好同時爲它提供get,set訪問器方法
  • 屬性方法可能拋出異常;字段訪問永遠不會
  • 屬性不能做爲out和ref參數傳給方法;字段能夠
  • 屬性方法可能花較長時間執行,可能須要額外內存
  • 屬性只是簡化了語法,不會提高代碼的性能,最好老老實實實現GetXXX和SetXXX方法。

(七)、 有參屬性

C#使用數組風格的語法公開有參屬性(索引器),可將索引器是C#開發人員對[]操做符的重載。好比定義一個類Student

public class Student
    {
        public string this[int n]
        {
           get { return Name; }
           set { Name = n > 0 ? "大於0" : "小於0"; }
        }
       private string Name;
     }

當執行Student stu = new Student();stu[4] = "NNN";時執行set方法,將Name設爲大於0;執行stu[4]時輸出大於0字樣。

(八) 調用屬性訪問器方法時的性能

      對於簡單的get和set訪問器方法,JIT編譯器會將代碼內聯(將方法也就是訪問器方法的代碼直接編譯到調用它的方法中),避免在運行時發出調用所產生的開銷,代價是編譯好的方法變得更大。注意:JIT在調試代碼時不會內聯屬性方法,由於這將難以調試,發行版本會使用內聯,性能會快。

    既然屬性本質上是方法,而C#和CLR支持泛型方法,可是C#不容許屬性引入本身的泛型類型參數,最主要緣由屬性原本應該表示可供查詢或設置某個對象特徵。一旦引入泛型類型參數就意味可能改變查詢/設置行爲,但屬性不該該和行爲沾邊,公開對象的行爲不管是否是泛型,都應該定義方法而非屬性。

(九) 屬性與索引器的區別

參考資料:《c#高級編程》

相關文章
相關標籤/搜索