概念:泛型程式設計是一種程式設計方式,它利用“參數化類型”将類型抽象化,進而實作更為靈活的服用,然後使用這些變量記錄不同類型的資料,這樣可以重複利用泛型來存儲不同類型的資料。
泛型用于處理算法,資料結構的一種程式設計方法。泛型的目标采用廣泛适用和可互動性的形式來表示算法和資料結構,以使他們能夠直接用于軟體構造。泛型類、結構、接口、委托和方法可以根據他們存儲和操作的資料類型來進行參數化。泛型能在編譯時提供強大的類型檢查,減少資料類型之間的顯示轉換、裝箱操作和運作時的類型檢查等。泛型類和泛型方法同時具備可重用性、類型安全和效率高等特性。這是非泛型類和非泛型方法無法具備的。
文法格式如下:
類修飾符 class 類名<類型參數T>
{
類體
}
泛型類的聲明比普通類多一個類型參數T,類型參數T樂意看作是一個占位符,他不是一種類型,它僅僅代表了某種可能的類型。在定義泛型類,T出現的位置可以在使用時,用任何類型來代替。類型參數T的命名規則如下:
使用描述名稱命名泛型類型參數,除非單個字母名稱完全可以讓人了解它表示的含義,而面熟性名稱不會有更多的意義。
将T作為面熟性類型參數名的字首。
例:使用泛型存儲不同類型的資料清單
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iZwUGMjZ2YwkDM2kjZ3MDO0cTN0EWYyMjMxgjMyYGZ38CX5d2bs92Yl1iclB3bsVmdlR2LcNWaw9CXt92Yu4GZjlGbh5yYjV3Lc9CX6MHc0RHaiojIsJye.png)
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):