天天看點

面向對象——泛型類

概念:泛型程式設計是一種程式設計方式,它利用“參數化類型”将類型抽象化,進而實作更為靈活的服用,然後使用這些變量記錄不同類型的資料,這樣可以重複利用泛型來存儲不同類型的資料。

      泛型用于處理算法,資料結構的一種程式設計方法。泛型的目标采用廣泛适用和可互動性的形式來表示算法和資料結構,以使他們能夠直接用于軟體構造。泛型類、結構、接口、委托和方法可以根據他們存儲和操作的資料類型來進行參數化。泛型能在編譯時提供強大的類型檢查,減少資料類型之間的顯示轉換、裝箱操作和運作時的類型檢查等。泛型類和泛型方法同時具備可重用性、類型安全和效率高等特性。這是非泛型類和非泛型方法無法具備的。

文法格式如下:

 類修飾符 class 類名<類型參數T>

      {

             類體

}

泛型類的聲明比普通類多一個類型參數T,類型參數T樂意看作是一個占位符,他不是一種類型,它僅僅代表了某種可能的類型。在定義泛型類,T出現的位置可以在使用時,用任何類型來代替。類型參數T的命名規則如下:

使用描述名稱命名泛型類型參數,除非單個字母名稱完全可以讓人了解它表示的含義,而面熟性名稱不會有更多的意義。

将T作為面熟性類型參數名的字首。

例:使用泛型存儲不同類型的資料清單

面向對象——泛型類

namespace ExtensiveList

{

   public partial class Form1 : Form

   {

       public Form1()

       {

           InitializeComponent();

       }

       class Types<T>

           public T Num; //聲明編号字段

           public T Name; //聲明姓名字段

           public T Sex; //聲明性别字段

           public T Age; //聲明年齡字段

           public T Birthday; //聲明生日字段

           public T Salary; //聲明薪水字段

       private void button1_Click(object sender, EventArgs e)

           Types<object> Exte = new Types<object>();//執行個體化泛型類對象

           //為泛型類中聲明的字段進行指派,存儲不同類型的值

           Exte.Num = 1;

           Exte.Name = "王老師";

           Exte.Sex = "男";

           Exte.Age = 25;

           Exte.Birthday = Convert.ToDateTime("1986-06-08");

           Exte.Salary = 1500.45F;

           //将泛型類中各字段的值顯示在文本框中

           textBox1.Text = Exte.Num.ToString();

           textBox2.Text = Exte.Name.ToString();

           textBox3.Text = Exte.Sex.ToString();

           textBox4.Text = Exte.Age.ToString();

           textBox5.Text = Exte.Birthday.ToString();

           textBox6.Text = Exte.Salary.ToString();

   }

例2:泛型的繼承:泛型繼承類與普通類是類似的,隻是在繼承的時候多個T,格式如下:

 class DerivedClass<參數類型T>:BaseClass<參數類型T>

舉例:  class BStuInfo<T>

           public T ID; //聲明學生編号字段

           public T Grade; //聲明班級字段

       class HStuInfo<T> : BStuInfo<T>//繼承自BStuInfo泛型類

           public T Chinese; //聲明國文成績字段

           public T Math; //聲明數學成績字段

           public T English; //聲明英語成績字段

泛型方法是在聲明中包括了類型參數T的方法。泛型方法可以在類、結構或接口中聲明,這些類、結構或接口本身可以是泛型或非泛型。如果在泛型類型聲明中聲明泛型方法,則方法可以同時引用該方法的類型參數T和包含該方法聲明的類型參數T。泛型方法的文法如下:

[修飾符]  [傳回值類型]  [方法名]  <參數類型T>()

      方法體

例:通過泛型查找不同的值

面向對象——泛型類

namespace ArrayInfo

       public class Finder

           // 定義一個泛型方法,用來查找指定值在數組中的索引

           public static int Find<T>(T[] items, T item)

           {

               for (int i = 0; i < items.Length; i++)//周遊泛型數組

               {

                   if (items[i].Equals(item))//判斷是否找到了指定值

                   {

                       return i;//傳回指定值在數組中的索引

                   }

               }

               return -1;//如果沒有找到,傳回-1

           }

           string[] Str = new string[] { "一", "二", "三", "四", "五", "六", "七", "八", "九" };//聲明一個字元串類型的數組

           MessageBox.Show(Finder.Find<string>(Str, "三").ToString());//查找字元串“三”在數組中的索引

       private void button2_Click(object sender, EventArgs e)

           int[] IntArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };//聲明一個整數類型的數組

           MessageBox.Show(Finder.Find<int>(IntArray, 5).ToString());//查找數字5在數組中的索引

       private void button3_Click(object sender, EventArgs e)

           bool[] IntArray = new bool[] { true, false};//聲明一個布爾類型的數組

           MessageBox.Show(Finder.Find<bool>(IntArray, false).ToString());//查找false在數組中的索引

例2: 泛型作為傳回值,例如Spring.Net中擷取類的方法

      public static T GetObject(string name)

           try

               IApplicationContext ctx = ContextRegistry.GetContext();

               return ctx.GetObject(name) as T;

           catch (Exception ex)

               logger.Error(ex.Message);

               return default(T);

泛型方法的重載:與普通的方法類似,隻是聲明時,添加了一個類型參數T

例:通過泛型實作子窗體的不同的操作。

面向對象——泛型類

FormDisOperate

       public void FormOperate<T>()

           Form2 Frm_2 = new Form2();//執行個體化Form2窗體對象

           Frm_2.ShowDialog();//以對話框形式顯示Form2窗體

       public void FormOperate<T>(string strError)

       {//執行個體化提示框中顯示圖示對象

MessageBoxIcon messIcon = MessageBoxIcon.Error;            MessageBox.Show(strError, "提示", MessageBoxButtons.OK, messIcon);//顯示錯誤提示框

           FormOperate<object>();//調用FormOperate方法的第一種重載形式對窗體操作

           FormOperate<object>("資料庫連接配接失敗。");//調用FormOperate方法的第二種重載形式對窗體操作

使用泛型集合:通常情況下,建議開發人員使用泛型集合,因為這樣可以獲得類型安全的直接優點,而不需要從基集合類型派生并實作類型特定的成員。此外,如果集合元素為值類型,泛型集合類型的性能通常優于對應的非泛型集合類型,并優于從非泛型基集合類型派生的類型,因為使用泛型時不必對元素進行封裝。

常用的集合:ArrayList、Hashtable、Stack、Queue。需要添加命名空間using System.Collections

ArrayList使用舉例:添加單個元素

static void Main(string[] args)

         ArrayList list=new ArrayList();

           list.Add(12);

           list.Add("ASsa");

           list.Add(false);

           foreach(var i in list)

               Console.WriteLine(i.ToString());

輸出結果:

12

ASsa

False

添加數組:

         static void Main(string[] args)

         int[] arr = { 1, 2, 3 };//第一種添加數組的方法

         list.AddRange(arr);

         int[] arr1 = { 4, 5, 6 };//第二種添加數組的方法

         ArrayList list1 = new ArrayList(arr1);

ArrayList常用的一些方法:

         int[] arr = { 1, 2, 3 };

         list.Clear();//清除所有資料

         list.Contains(2);//判斷是否包含這個資料,包含輸出true,不包含輸出false

         int cap = list.Capacity;//得到目前最大容量

         int num = list.Count;//得到實際元素的個數

         int b = list.Count;//傳回元素的下标,從左向右查

         list.Insert(0, 12);//在下邊為0處插入12,其後面的,後移一位,下标不能超過list的最大長度

         list.Remove(2);//删除一個元素,存在則删除,不存在就不執行。

         list.RemoveAt(0);//删除下标的元素,括号為下标

HashTable常用的方法:

  static void Main(string[] args)

           Hashtable table = new Hashtable();

           table.Add("1", 10);

           table.Add("2", 20);

           table.Add("3", 30);//添加資料

           object i = table["1"];//根據Key,擷取對應的Value

           table.Clear();//删除表

           bool key = table.Contains("1");//是否包含某個鍵,若包含則傳回true

           bool key1 = table.ContainsKey("1");//是否包含某個鍵,若包含則傳回true

           bool val = table.ContainsValue(10);//是否存在10.存在傳回true

           int ct = table.Count;//傳回元素的個數

           ICollection ic = table.Keys;//擷取所有的鍵值

           foreach (object o in ic)

               Console.WriteLine(0);//結果:1,2,3

           ic = table.Values;

               Console.WriteLine(o);//結果:10、20、30

           foreach (DictionaryEntry d in table)

               Console.WriteLine("{0},{1}",d.Key,d.Value);

//結果:

             //1,10

             //2,20

             //3,30

棧是一種先進後出的一種資料結構,類似口向上的井

棧舉例:

                Stack st = new Stack();

           st.Push(1);

           st.Push(2);//壓棧

           object o = st.Pop();//出棧,并把棧頂删除

           object ob = st.Peek();//出棧,并不删除棧頂元素

隊列是一種先進先出的資料結構

隊列舉例:   Queue q = new Queue();

           q.Enqueue(1);

           q.Enqueue(2);//添加資料

           object o = q.Dequeue();//出隊,并把隊頂元素删除

           object ob = q.Peek();//出隊,并不删除隊頂元素

常用的泛型:

List<資料類型>

格式:List<s資料類型> list=new List<資料類型>();取代了集合中的ArrayList

Dictionary<資料類型,資料類型>

格式:Dictionary<資料類型,資料類型> dic=new Dictionary<資料類型,資料類型>();取代了集合中的HashTable

Stack<資料類型>

格式Stack<資料類型> s=new Stack<資料類型>();取代了集合中Stack

Queue<資料類型>

格式Queue <資料類型> s=new Queue <資料類型>();取代了集合中Queue

舉例:從字典中同時取出,key和對應的value

Dictionary<int, string> dic = new Dictionary<int, string>();

          dic.Add(1, "10");

          dic.Add(2, "20");

          string s = dic[1];//取出key為1的值。

          Dictionary<int, string>.KeyCollection keys = dic.Keys;//擷取所有的鍵值

          foreach (int i in keys)//周遊Key

          {

               Console.WriteLine(i);

          }

         Dictionary<int, string>.ValueCollection vals = dic.Values;//擷取所有的Value值

          foreach (string t in vals)//周遊Value

               Console.WriteLine(t);

           foreach (KeyValuePair<int, string> k in dic)//周遊鍵值對

               Console.WriteLine(k.Key+"....."+k.Value);

字典和List嵌套綜合應用:字典中嵌套List,按字典的key進行分類放入List中。比如按照名字進行分類

private void BindGrid()

      List<MemberInfo> memberList=_bll.getList();//擷取資料放入清單

Dictionary<string,List<MemberInfo>> memberNameDic=new Dictionary<string,List<MemberInfo>>();//建立嵌套List

Foreach(Member memberInfo in memberList)

      if(memberNameDic.ContainsKey(memberInfo.MemberName)==false)    

      memberNameDic.Add(memberInfo.MemberName , new List<MemberInfo>())

memberNameDic[memberInfo.MemberName].Add(memberInfo);//向字典中按姓名相同進行分類

Foreach( string key in memberNameDic.Keys)

      GroupNameSame(key , memberNameDic[key]);

Private void GroupNameSame(string name,IList<MemberInfo> memberNameList)

      Foreach(MemberInfo memberInfo in memberNameList)

{}//讀取List

泛型排序清單,SortList<Tkey,TValue> 表示按照鍵進行的排序的鍵/值對的集合,鍵/值對是KeyValuePair<TKey,TValue>類型。泛型排序清單具有以下3個特點:

、将添加到泛型排序的表的元素自動按照鍵進行排序。

、泛型排序清單中的鍵不能修改,不能為空,不能重複。

、泛型排序清單的值樂意修改,可以為空,可以重複。

 public class UserInfo

       public int UserCode { get; set; }

       public string UserName { get; set; }

       public string PassWord { get; set; }

       public UserInfo(int userCode, string userName, string passWord)

           UserCode = userCode;

           UserName = userName;

           PassWord = passWord;

   class Program

   {    

       static void Main(string[] args)

           SortedList<int, UserInfo> uses = new SortedList<int, UserInfo>();

           uses.Add(2, new UserInfo(2, "User02", "02"));

           uses.Add(3, new UserInfo(3, "User03", "03"));

           uses.Add(1, new UserInfo(1, "User01", "01"));

           foreach (var item in uses)

               Console.WriteLine("{0},{1}",item.Key,item.Value.UserName);

           }//按照key的順序從小到大排序

1,User01

2,User02

3,User03

利用比較器來定義排序規則,将排序規則改為大到小順序:

   public class ListComparer : IComparer<int>

       #region IComparer<int> 成員

       public int Compare(int x, int y)

           if (x > y)

               return -1;

           else

               return 1;

       #endregion

   public class UserInfo

           SortedList<int, UserInfo> uses = new SortedList<int, UserInfo>(new ListComparer());

泛型排序字典

SortedDictionary<string, string> sortDic = new SortedDictionary<string, string>();

           sortDic.Add("qw", "1qwa");

           sortDic.Add("as", "sdfsa");

           foreach (var i in sortDic.Keys)

           }//預設按照字典序排序、

as

qw

IList和List的差別

首先IList 泛型接口是 ICollection 泛型接口的子代,并且是所有泛型清單的基接口。它僅僅是所有泛型類型的接口,并沒有太多方法可以友善實用,如果僅僅是作為集合資料的承載體,确實,IList可以勝任。

其次, IList <> 是在 .net framework 2.0裡面才支援的

1. 不過,更多的時候,我們要對集合資料進行處理,從中篩選資料或者排序。這個時候IList就不太好使了。因為他的效率要慢。後面會一一講到。

2、IList <>是個接口,定義了一些操作方法這些方法要你自己去實作,List <>是泛型類,它已經實作了IList <>定義的那些方法

IList IList11 =new List ();

List List11 =new List ();

這兩行代碼,從操作上來看,實際上都是建立了一個List對象的執行個體,也就是說,他們的操作沒有差別。隻是用于儲存這個操作的傳回值變量類型不一樣而已。

那麼,我們可以這麼了解,這兩行代碼的目的不一樣。

是想建立一個List,而且需要使用到List的功能,進行相關操作。

而IList IList11 =new List ();

隻是想建立一個基于接口IList的對象的執行個體,隻是這個接口是由List實作的。是以它隻是希望使用到IList接口規定的功能而已。

3.接口實作松耦合...有利于系統的維護與重構...優化系統流程...鼓勵使用接口,這樣可以實作功能和具體實作的分離.

這些說的都是有道理的,那麼接剛才第一點的話題說,為什麼說用到資料處理,或者排序IList就不太好使了呢。這個也是要根據資料量來的。我做了如下測試

       public class TestClass

           public int Id

           {  get;  set; }

           public string Name

           { get; set;}

           ListTest();

       private static void ListTest()

           Stopwatch timer = new Stopwatch();

           timer.Start();

           List<TestClass> list1 = new List<TestClass>();

           for (int i = 0; i < 10000000; i++)

               TestClass tc = new TestClass();

               tc.Id = i;

               tc.Name = "Test Data" + i;

               list1.Add(tc);

           int count = 0;

           foreach (var tc in list1)

               if (tc.Id >= 1 && tc.Id < 1000)

                   count++;

           //list1.OrderBy(i => i.Id);

           timer.Stop();

           Console.Write("Count:" + count + ", List time:");

           Console.WriteLine(timer.Elapsed.Ticks);

           timer = new Stopwatch();

           IList<TestClass> list2 = new List<TestClass>();

               list2.Add(tc);

           int count2 = 0;

           foreach (var tc in list2)

                   count2++;

           //list2.OrderBy(i => i.Id);

           Console.Write("Count:" + count2 + ", IList time:");

           Console.Read();

當我們都去周遊IList和List的時候,注意我取的資料是1~1000之間,經過反複測試,IList的效率确實是要低一些。那就更不用說資料量更大的時候,請看輸出框:

面向對象——泛型類

但是,當我取的資料是1~500的時候, IList似乎效率還是要慢一些。

另外,可能有的朋友會說,你把前面的for循環放在外面比較呢,這個我也做過測試,結果還是一樣,List效率要好于IList

同樣的方法,我測試了,IList和List的OrderBy的效率,List均有勝面,高效一籌。是以,什麼時候用IList和List自己去斟酌,當你用到設計的時候當然是IList合理一些。用做資料容器周遊或者排序,還是選擇List好一點。

數組、ArrayList、List差別:

     數組

   數組在C#中最早出現的。在記憶體中是連續存儲的,是以它的索引速度非常快,而且指派與修改元素也很簡單。

   但是數組存在一些不足的地方。在數組的兩個資料間插入資料是很麻煩的,而且在聲明數組的時候必須指定數組的長度,數組的長度過長,會造成記憶體浪費,過段會造成資料溢出的錯誤。如果在聲明數組時我們不清楚數組的長度,就會變得很麻煩。

   針對數組的這些缺點,C#中最先提供了ArrayList對象來克服這些缺點。

ArrayList

   ArrayList是命名空間System.Collections下的一部分,在使用該類時必須進行引用,同時繼承了IList接口,提供了資料存儲和檢索。ArrayList對象的大小是按照其中存儲的資料來動态擴充與收縮的。是以,在聲明ArrayList對象時并不需要指定它的長度。

ArrayList中插入不同類型的資料是允許的。因為ArrayList會把所有插入其中的資料當作為object類型來處理,在我們使用ArrayList處理資料時,很可能會報類型不比對的錯誤,也就是ArrayList不是類型安全的。在存儲或檢索值類型時通常發生裝箱和取消裝箱操作,帶來很大的性能耗損。

泛型List

   因為ArrayList存在不安全類型與裝箱拆箱的缺點,是以出現了泛型的概念。List類是ArrayList類的泛型等效類,它的大部分用法都與ArrayList相似,因為List類也繼承了IList接口。最關鍵的差別在于,在聲明List集合時,我們同時需要為其聲明List集合内資料的對象類型。

比如:

List<string> list = new List<string>();

list.Add(“abc”);  //新增資料

list[0] = “def”;   //修改資料

list.RemoveAt(0);  //移除資料

   上例中,如果我們往List集合中插入int數組123,IDE就會報錯,且不能通過編譯。這樣就避免了前面講的類型安全問題與裝箱拆箱的性能問題了。

總結:

   數組的容量是固定的,您隻能一次擷取或設定一個元素的值,而ArrayList或List<T>的容量可根據需要自動擴充、修改、删除或插入資料。

   數組可以具有多個次元,而 ArrayList或 List< T> 始終隻具有一個次元。但是,您可以輕松建立數組清單或清單的清單。特定類型(Object 除外)的數組 的性能優于 ArrayList的性能。 這是因為 ArrayList的元素屬于 Object 類型;是以在存儲或檢索值類型時通常發生裝箱和取消裝箱操作。不過,在不需要重新配置設定時(即最初的容量十分接近清單的最大容量),List< T> 的性能與同類型的數組十分相近。

   在決定使用 List<T> 還是使用ArrayList 類(兩者具有類似的功能)時,記住List<T> 類在大多數情況下執行得更好并且是類型安全的。如果對List< T> 類的類型T 使用引用類型,則兩個類的行為是完全相同的。但是,如果對類型T使用值類型,則需要考慮實作和裝箱問題。

C# 集合性能 總結                          标記說明:

O(1) 表示無論集合中有多少項,這個操作需要的時間都不變,例如,ArraryLIst的Add(方法就O(1),無論集合中有多少元素,在清單尾部添加一個新的元素的時間都是相同的.

O(n)表示對于集合中的每個元素,需要增加的時間量都是相同的,如果需要重新給

O(log n)表示操作需要的時間随着集合中元素的增加和增加,但每個元素增加的時間不是線性的.而是呈對數曲線,在集合中插入操作時,SortedDictionary<Tkey,Tvalue>就是O(log n),而SortedList<Tkey,Tvalue> 就是O(n),這裡SortedDictionary<Tkey,Tvalue>要快的多.因為它在樹形結構中插入元素的效率比清單高的多.下表顯示各種集合的操作時間:

集合

Add

Insert

Remove

Item

Sort

Find

List<T>

如果集合必須重置大小就是O(1)或O(n)

O(n)

O(1)

O(n log n)最壞情況O(n^2)

Stack<T>(棧)

Push(),如果棧必須重置大小,就是O(1)或O(n)

no

Pop(),O(1)

Queue<T>(列隊)

Enqueue(),如果棧必須重置大小,就是O(1)或O(n)

Dequeu(),O(1)

HastSet<T>(無序清單)

如果棧必須重置大小,就是O(1)或O(n)

Add()

O(1)或O(n)

LinkedList<T>(連結清單)

AddLast(),O(1)

AddAfter(),O(1)

Dictionary<Tkey,TValue>

O(1) 或 O(n)

SortedDictionary<Tkey,Tvalue>

O(log n)

SortedList<Tkey,Tvalue>

無序資料為O(n),如果必選重置大小,到清單的尾部就是

讀寫是O(log n),如果鍵在清單中,就是O(log n),如果鍵不在清單中就是O(n).

 注:如果單元格中有多個大O值,表示集合需要重置大小,該操作需要一定的時間

     如果單元格内容是no,就表示不支援這種操作.

小結:

    數組的大小是固定的,但可以使用清單作為動态增長集合,列隊以先進先出的方式通路元素,棧以後進先出的方式通路元素,

連結清單可以快速的插入和删除元素,但搜尋比較慢,通過鍵和值可以使用字典,它的搜尋和插入操作比較快,集(Hashset<T>) 是用于無序的唯一項.

可觀察集合

如果需要集合中的元素合适删除或添加的資訊,就可以使用ObserVableCollection<T>類。這樣UI就可以知集合的變化。這個類的名命空間SystemCollections.ObjectModel.

ObserableCollection<T>類派生自Collection<T>積累。該基類可以用于建立自定義集合,并在内部使用List<T>類。重寫了基類中的虛方法SetITem()和RemoveItem(),以出發CollectionChanged事件。這個類的使用者可以使用InotifyCollectionChanged接口注冊事件。

using System.Collections.ObjectModel;

namespace Wrox.ProCSharp.Collections

       static void Main()

           var data = new ObservableCollection<string>();

           data.CollectionChanged += Data_CollectionChanged;

           data.Add("One");

           data.Add("Two");

           data.Insert(1, "Three");

           data.Remove("One");

       static void Data_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)

           Console.WriteLine("action: {0}", e.Action.ToString());

           if (e.OldItems != null)

               Console.WriteLine("starting index for old item(s): {0}", e.OldStartingIndex);

               Console.WriteLine("old item(s):");

               foreach (var item in e.OldItems)

                   Console.WriteLine(item);

           if (e.NewItems != null)

               Console.WriteLine("starting index for new item(s): {0}", e.NewStartingIndex);

               Console.WriteLine("new item(s): ");

               foreach (var item in e.NewItems)

           Console.WriteLine();

action: Add

starting index for new item(s): 0

new item(s):

One

starting index for new item(s): 1

Two

Three

action: Remove

starting index for old item(s): 0

old item(s):

繼續閱讀