CH04-集合

本章目标

  1. 理解集合的概念
  2. 会使用集合初始化器
  3. 熟练使用集合访问数据理
  4. 解泛型的概念
  5. 熟练使用各种泛型集合

本章内容

1、理解集合的概念

  1. 为什么要使用集合?

    回顾:当需要保存多个相同类型数据的时候,之前使用的是数组,那么数组有什么特点呢?

    1. 存储数据个数固定,当需要在数组中添加新元素时,则需要重新添加长度更大的突间,从数组中间删除数据的时候,则需要大量做数据移动,不然浪费空间。
    2. 有没有更好的方式解决以上问题呢?集合
  2. 什么是集合?

    集合(Collection)类是专门用于数据存储和检索的类。

2、动态数组集合:ArrayList

  1. 概念:动态数组(ArrayList)代表了可被单独索引的对象的有序集合。它基本上可以替代一个数组。但是,与数组不同的是,您可以使用索引在指定的位置添加和移除项目,动态数组会自动重新调整它的大小。它也允许在列表中进行动态内存分配、增加、搜索、排序各项。

  2. ArrayList常用属性和方法:

    1. 常用属性:

      属性 描述
      Capacity 获取或设置 ArrayList 可以包含的元素个数。
      Count 获取 ArrayList 中实际包含的元素个数。
    2. 常用方法:

      序号 方法名 & 描述
      1 public virtual int Add( object value ); 在 ArrayList 的末尾添加一个对象。
      2 public virtual void AddRange( ICollection c ); 在 ArrayList 的末尾添加 ICollection 的元素。
      3 public virtual void Clear(); 从 ArrayList 中移除所有的元素。
      4 public virtual bool Contains( object item ); 判断某个元素是否在 ArrayList 中。
      5 public virtual ArrayList GetRange( int index, int count ); 返回一个 ArrayList,表示源 ArrayList 中元素的子集。
      6 public virtual int IndexOf(object); 返回某个值在 ArrayList 中第一次出现的索引,索引从零开始。
      7 public virtual void Insert( int index, object value ); 在 ArrayList 的指定索引处,插入一个元素。
      8 public virtual void InsertRange( int index, ICollection c ); 在 ArrayList 的指定索引处,插入某个集合的元素。
      9 public virtual void Remove( object obj ); 从 ArrayList 中移除第一次出现的指定对象。
      10 public virtual void RemoveAt( int index ); 移除 ArrayList 的指定索引处的元素。
      11 public virtual void RemoveRange( int index, int count ); 从 ArrayList 中移除某个范围的元素。
      12 public virtual void Reverse(); 逆转 ArrayList 中元素的顺序。
      13 public virtual void SetRange( int index, ICollection c ); 复制某个集合的元素到 ArrayList 中某个范围的元素上。
      14 public virtual void Sort(); 对 ArrayList 中的元素进行排序。
      15 public virtual void TrimToSize(); 设置容量为 ArrayList 中元素的实际个数。
    3. ArrayList的使用步骤

      1. 引入命名空间:System.Collections

        1
        using System.Collections;
      2. 创建ArrayList对象:

        1
        2
        ArrayList  engineers = new ArrayList();
        ArrayList engineers = new ArrayList(5)//指定集合容量为5
      3. 使用具体属性和方法:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36


        //1.在集合中添加元素,Add方法是添加的object类型
        //所以会进行装箱操作
        ArrayList arrayList = new ArrayList(4);
        Console.WriteLine("容量:{0}", arrayList.Capacity);
        //1.调用添加的方法,Add方法参数为object类型,所以将进行装箱操作
        arrayList.Add("张三");
        arrayList.Add("李四");
        arrayList.Add("王五");
        arrayList.Add("张小丽");
        arrayList.Add("李大大");

        Console.WriteLine("容量:{0}", arrayList.Capacity);//输出8
        Console.WriteLine("集合中元素数量:{0}",arrayList.Count);//输出5
        //2.遍历集合里的元素
        Console.WriteLine("foreach遍历:");
        foreach (var item in arrayList)
        {
        Console.Write(item+" ");
        }
        Console.WriteLine("\nfor遍历:");
        for (int i = 0; i < arrayList.Count; i++)
        {
        Console.Write(arrayList[i]+" ");
        }
        //3、常用方法
        arrayList.Remove("王五");//移除对象
        arrayList.RemoveAt(0);//根据索引移除
        arrayList.Reverse();//逆转 ArrayList 中元素的顺序
        arrayList.AddRange(new string[] {"a","b","c" });//添加集合
        Console.WriteLine("\nfor遍历:");
        for (int i = 0; i < arrayList.Count; i++)
        {
        Console.Write(arrayList[i] + " ");
        }
    4. ArrayList集合初始化器

      1. 概念:

        C#3.0语言的新特性之一由一系列元素初始化器构成,包围在“{”和“}”之间,并使用逗号进行分隔

      2. 作用:

        对集合数据作初始化操作

      3. 语法:

        1
        ArrayList arrayList2 = new ArrayList() {"张三","李四","王五" };
      4. 通过对象演示集合初始化器

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
         static void Main(string[] args)
        {
        Student stu1 = new Student();
        stu1.StudentName = "张三";
        stu1.Gender = Gender.男;

        Student stu2 = new Student() { StudentName = "李四", Gender = Gender.男 };
        Student stu3 = new Student() { StudentName = "小莉", Gender = Gender.女 };

        //通过集合初始化器,将Student对象添加到集合中
        ArrayList arrayList = new ArrayList() { stu1, stu2, stu3 };
        //循环遍历
        foreach (var item in arrayList)
        {
        Student stu = (Student)item;//拆箱操作,将object对象转换成Student对象
        stu.SayHi();
        }
        }

        public enum Gender
        {
        男, 女
        }
        public class Student
        {
        public string StudentName { get; set; }
        public Gender Gender { get; set; }//定义性别的枚举

        public void SayHi()
        {
        Console.WriteLine("姓名:{0},性别:{1}", StudentName, Gender);
        }
        }

3、哈希表:HashTable

  1. 为什么要使用HashTable?

    ArrayList是通过索引方式获取数据,当索引发生变化,要精确获取到对应数据就比较麻烦,能否有一种方式不管索引怎么发生变化,都可以通过名称或键找到对应的数据呢?

    HashTable则可以解决此类型问题

    根据键(Key)可以查找到相应的值 (Value)

    1721615104700

  2. 什么是HashTable?

    Hashtable 类代表了一系列基于键的哈希代码组织起来的键/值对。

    它使用键来访问集合中的元素。

    当您使用键访问元素时,则使用哈希表,而且您可以识别一个有用的键值。

    哈希表中的每一项都有一个键/值对。

    键用于访问集合中的项目。

  3. HashTable的属性和方法:

    1. 常用属性:

      属性 描述
      Count 获取 Hashtable 中包含的键值对个数。
      IsFixedSize 获取一个值,表示 Hashtable 是否具有固定大小。
      IsReadOnly 获取一个值,表示 Hashtable 是否只读。
      Item 获取或设置与指定的键相关的值。
      Keys 获取一个 ICollection,包含 Hashtable 中的键。
      Values 获取一个 ICollection,包含 Hashtable 中的值。
    2. 常用方法:

      序号 方法名 & 描述
      1 public virtual void Add( object key, object value ); 向 Hashtable 添加一个带有指定的键和值的元素。
      2 public virtual void Clear(); 从 Hashtable 中移除所有的元素。
      3 public virtual bool ContainsKey( object key ); 判断 Hashtable 是否包含指定的键。
      4 public virtual bool ContainsValue( object value ); 判断 Hashtable 是否包含指定的值。
      5 public virtual void Remove( object key ); 从 Hashtable 中移除带有指定的键的元素。
    3. 使用HashTable:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      //1、创建对象
      Hashtable hashtable = new Hashtable();
      //2、通过键值对,添加对象
      hashtable.Add("zs", "张三");
      hashtable.Add("ls", "李四");
      hashtable.Add("xl", "小利");

      //3、获取对象,必须通过键获取,而且要将object转换成具体对象
      string name = (string)hashtable["ls"];
      //4、循环遍历数据,可通过HashTable的键的集合
      foreach (object key in hashtable.Keys)
      {
      Console.WriteLine("键:{0},值:{1}", key, hashtable[key]);
      }
      //5、可直接获取值的集合
      foreach (object value in hashtable.Values)
      {
      Console.WriteLine(value);
      }
      //6、根据键移除对象
      hashtable.Remove("zs");
      hashtable.Clear();//清空集合

4、List泛型集合

  1. 为什么要使用泛型?

    使用集合存储数据时容易出现的问题:

    (1)对象存储不易控制,添加时为object对象

    (2)类型转换容易出错,会将object对象转换成具体对象,因为添加时未进行类型检查,则容易出错

  2. 什么是泛型集合?

    1. 概念:

      泛型最常见的用途是创建集合类

      泛型集合可以约束集合内的元素类型

      典型泛型集合List、Dictionary<K,V>、<K,V>表示该泛型集合中的元素类型

      1721616819706

  3. 使用List泛型集合:

    1. 命名空间:System.Collections.Generic(通常情况下,创建类文件时默认此命名空间)

    2. 创建List对象:

      1
      List<类型> list=new List<类型>();
    3. 演示案例:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      //创建string的泛型集合
      List<string> list = new List<string>();
      //添加操作时,必须为string类型,约束了相应类型
      list.Add("张三");
      list.Add("李四");

      string name = list[0];//通过索引获取,不需要类型转换

      //遍历数据时,直接可用string类型接受,不需类型转换
      foreach (string str in list)
      {
      Console.WriteLine(str);
      }

    4. List的访问方式与ArrayList类似,可对比学习

      1721617190680

5、Dictionary<K,V>泛型集合

  1. 概念:

    Dictionary<K,V>通常称为字典<K,V>

    约束集合中元素类型

    编译时检查类型约束,无需装箱拆箱操作

    与Hashtable操作类似。

  2. 演示案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    //创建对象,指定键值对都为String类型
    Dictionary<string ,string > dic=new Dictionary<string ,string>();
    //添加元素
    dic.Add("zs", "张三");
    dic.Add("ls", "李四");

    //根据键获取值,不需要类型转换
    string val = dic["ls"];
    //遍历数据
    foreach (string key in dic.Keys)
    {
    Console.WriteLine("key:{0},value:{1}", key, dic[key]);
    }
    foreach (string value in dic.Values)
    {
    Console.WriteLine("value:{0}",value);
    }
    foreach (var item in dic)
    {
    Console.WriteLine(item.Key+":"+item.Value);
    }

    //初始化器
    Dictionary<int, string> dics = new Dictionary<int, string>()
    {
    {1,"星期一"},{2 ,"星期二"},{3,"星期三"}
    };
    Dictionary<string, Student> stus = new Dictionary<string, Student>
    {
    {"zs",new Student{StudentName="张三",Gender=Gender.男 } },
    {"ls",new Student{StudentName="李四",Gender=Gender.男 } },
    {"xl",new Student{StudentName="小莉",Gender=Gender.女 } }
    };
  3. 访问 Dictionary<K,V> 与 Hashtable 的对比:

    1721617684165

6、泛型方法

  1. 什么是泛型?

    泛型是C#2.0推出的新语法,不是语法糖,而是2.0由框架升级提供的功能。

    我们在编程程序时,经常会遇到功能非常相似的模块,只是它们处理的数据不一样。但我们没有办法,只能分别写多个方法来处理不同的数据类型。这个时候,那么问题来了,有没有一种办法,用同一个方法来处理传入不同种类型参数的办法呢?泛型的出现就是专门来解决这个问题的。

  2. 为什么使用泛型?

    【案例】:在一个类中,添加方法来实现根据传入不一样类型的参数,获取相应的数据类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    public class TestMethod
    {
    public static void ShowInt(int param)
    {
    Console.WriteLine("类名:{0},参数类型:{1},参数值:{2}", typeof(TestMethod).Name, param.GetType().Name, param);
    }
    public static void ShowString(string param)
    {
    Console.WriteLine("类名:{0},参数类型:{1},参数值:{2}", typeof(TestMethod).Name, param.GetType().Name, param);
    }
    public static void ShowDateTime(DateTime param)
    {
    Console.WriteLine("类名:{0},参数类型:{1},参数值:{2}", typeof(TestMethod).Name, param.GetType().Name, param);
    }
    }

    static void Main(string[] args)
    {
    int intParam = 10;
    string stringParam = "张三";
    DateTime dateTimeParam = DateTime.Now;

    TestMethod.ShowInt(intParam);
    TestMethod.ShowString(stringParam);
    TestMethod.ShowDateTime(dateTimeParam);
    Console.ReadLine();
    }

    运行结果:

    1721619202108

    问题:

    从上面的结果中我们可以看出这三个方法,除了传入的参数不同外,其里面实现的功能都是一样的。在1.0版的时候,还没有泛型这个概念,那么怎么办呢。相信很多人会想到了OOP三大特性之一的继承,我们知道,C#语言中,object是所有类型的基类,将上面的代码进行以下优化:

    优化后的代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    public class TestMethod
    {
    public static void ShowObject(object param)
    {
    Console.WriteLine("类名:{0},参数类型:{1},参数值:{2}", typeof(TestMethod).Name, param.GetType().Name, param);
    }
    }

    static void Main(string[] args)
    {
    int intParam = 10;
    string stringParam = "张三";
    DateTime dateTimeParam = DateTime.Now;

    TestMethod.ShowObject(intParam);
    TestMethod.ShowObject(stringParam);
    TestMethod.ShowObject(dateTimeParam);
    Console.ReadLine();
    }

    输出结果:

    1721619400613

    从上面的结果中我们可以看出,使用Object类型达到了我们的要求,解决了代码的可复用。可能有人会问定义的是object类型的,为什么可以传入int、string等类型呢?

    原因有二:

    1、object类型是一切类型的父类。

    2、通过继承,子类拥有父类的一切属性和行为,任何父类出现的地方,都可以用子类来代替。

    但是上面object类型的方法又会带来另外一个问题:装箱和拆箱,会损耗程序的性能。

    微软在C#2.0的时候推出了泛型,可以很好的解决上面的问题。

  3. 通过泛型,优化代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public static void Show<T>(T param)
    {
    Console.WriteLine("类名:{0},参数类型:{1},参数值:{2}", typeof(TestMethod).Name, param.GetType().Name, param);
    }
    static void Main(string[] args)
    {
    int intParam = 10;
    string stringParam = "张三";
    DateTime dateTimeParam = DateTime.Now;

    TestMethod.Show<int>(intParam);
    TestMethod.Show<string>(stringParam);
    TestMethod.Show<DateTime>(dateTimeParam)
    Console.ReadLine();
    }

    运行如果:

    1721619643258

7、泛型类

  1. 语法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class 类名<T>{
    //可以指定泛型属性
    public T _T;

    //可以指定方法类型
    public T TestMethod(T param){
    ......
    }
    }
  2. 演示案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    public class TestGeneric<T>
    {
    public T MyProperty { get; set; }

    public void TestMethod(T obj)
    {
    Console.WriteLine("属性值:{0},传入参数类型:{1}", MyProperty, obj.GetType().Name);
    }
    }
    public class Test{
    void Main(){
    TestGeneric<int> testInt = new TestGeneric<int>();
    testInt.MyProperty = 100;
    testInt.TestMethod(20);
    Console.ReadLine();
    }
    }

    输出结果 :

    1721620750736

8、泛型总结优点:

  1. 实现代码重用,未来的主流技术
  2. 性能高,避免繁琐的装箱拆箱
  3. 提供了更好的类型安全性
  4. CLR支持泛型

本章总结

  1. 集合的概念是什么?

  2. List泛型集合常用的方法

  3. Dictionary<TKey,TValue>泛型常用的方法

    1721633038224

本章作业

  1. 实现考勤信息管理,具体说明:使用集合保存数据

    需求说明:

    1、实现员工信息和考勤管理系统

    1. 实现新增员工(员工ID唯一)
    2. 使用DataGridView控件展示员工列表

    2、使用DataGridView展示集合的数据,参考代码如下:

    1
    this.dgvProgrammer.DataSource = new BindingList<SE>(list);

    1721632526003

  2. 员工信息查询和删除:

    需求说明:

    1. 实现根据员工工号进行模糊查询
    2. 实现删除员工信息

    1721632748939

  3. 实现员工的签到和签退功能

    需求说明:

    实现员工签到和签退

    1. 每天只能签到1次
    2. 签退前必须已经签到

    实现思路:

    编写考勤记录类

    1. RecordRecord属性:员工ID、员工姓名、签到时间、签退时间
    2. 使用Dictionary<string,Record>保存考勤记录
    3. 编写签到和签退响应,并给出相应提示

    1721632831253

  4. 显示员工考勤记录

    需求说明:

    使用DataGridView控件显示员工考勤记录

    提示:DataGridView显示字典泛型集合数据

    1
    2
    3
    BindingSource bs = new BindingSource();  //创建BindingSource对象
    bs.DataSource = recordList.Values; //设置DataSource为字典Values
    this.dgvRecords.DataSource = bs; //设置dgvRecords的DataSource

    1721632941926