c# 高级编程 10章205页 【集合】【列表 List<T>】【ArrayList】

161 阅读4分钟

List<T>

  • 实现了IList, ICollection, IEnumerable, IList<T>, ICollection<T>, IEnumerable<T>
  • 元素类型必须为T
  • 其实现中,其实是在用一个T类型的数组

ArrayList(与List<T>对比)

  • 非泛型列表
  • 可以将任何Object类型作为元素

List<T>的初始化

//创建一个空列表
var racers = new List<Racer>();

List<T>的容量

  • Capacity属性可以查看容量
  • 每次,会将列表容量重设为原来的2倍
    • 空列表添加进1元素,容量变为4
    • 添加到第5个元素,容量变为8
    • 添加到第9个元素,容量变为16
  • 每次容量改变,都要重新分配到一个新的内存块中。这时,会创建一个新数组,将Array.Copy()将List内部的旧数组复制到新数组

为了节省复制数组的时间消耗,若提前知道List的元素个数,就可以用构造函数指定其容量

var racers = new List<Racer>(10);

为了节省多余开辟出来的内存,若已经将所有元素加入列表,并不再加了,就可以调用TrimExcess(),去除不需要的容量

racers.TrimExcess();

List<T> 添加元素

方法一:初始化器:

var graham = new Racer(7, "Graham", "Hill", "UK", 14);
var emerson = new Racer(13, "Emerson", "Fittipaldi", "Brazil", 14);
var mario = new Racer(16, "Mario", "Andretti", "USA", 12);

var racers = new List<Racer> { graham, emerson, mario };

方法二:Add()

racers.Add(new Racer(24, "Michael", "Schumacher", "Germany", 91));
racers.Add(new Racer(27, "Mika", "Hakkinen", "Finland", 20));

方法三:AddRange()

  • 参数是IEnumerable<T>对象,所以数组可以。
racers.AddRange(new Racer[] {
     new Racer(14, "Niki", "Lauda", "Austria", 25),
     new Racer(21, "Alain", "Prost", "France", 51)});

方法四:传递IEnumerable<T>给构造函数:

var racers = new List<Racer>{
    new Racer[] {
         new Racer(14, "Niki", "Lauda", "Austria", 25),
         new Racer(21, "Alain", "Prost", "France", 51)};

List<T> 插入元素

方法一Insert()

racers.Insert(3, new Racer(6, "Phil", "Hill", "USA", 3));

方法二InsertRange()

List<T> 访问元素

方法一索引器

  • 因为实现了IListIList<T>
for (int i = 0; i < racers.Count; i++)
{
    Console.WriteLine(racers[i]);
}

方法二foreach

  • 因为实现了IEnumerable接口
foreach (var r in racers)
{
    Console.WriteLine(r);
}

List<T> 删除元素

方法一:利用索引 RemoveAt()

  • 比较快速
//删除第三个元素
racers.RemoveAt(3)

方法二:利用索引 RemoveRange(开始索引,元素个数)

racers.RemoveAt(3, 5)

方法三:传递要删除的元素

  1. IndexOf(传递的元素)获取,与之相等的元素,的索引
    • IndexOf()在集合中搜索,与传递的元素,相等的元素
      • 检查元素类型是否实现了IEquatable<T>接口。
      • 如果是,则调用这个接口的Equals方法。
      • 如果否,则调用Object类的Equals方法。如此找到相等的元素。
  2. 再用该索引,删除该元素

方法四:删除满足特定条件的元素

  • RemoveAll( Predicate<T> )

方法五:删除所有元素

  • ICollection<T>定义的Clear()方法

List<T> 搜索元素

获得要查找的元素的索引:(第一个匹配的)(可以搜索相等的

  • IndexOf(元素)从前向后查找)
  • LastIndexOf(元素)从后向前查找) 获得要查找的元素的索引:(第一个匹配的)(可以搜索有某个特性的
  • FindIndex(Predicate<元素类型> match)从前向后查找)
  • FindLastIndex(Predicate<元素类型> match)从后向前查找) 获得要查找的元素:(第一个匹配的)(可以搜索有某个特性的
  • Find(Predicate<元素类型> match)从前向后查找)
  • FindLast(Predicate<元素类型> match)从后向前查找) 获得要查找的元素:(所有匹配的)(可以搜索有某个特性的
  • FindAll(Predicate<元素类型> match)从前向后查找)

IndexOf(元素)详解

  • 若找到,返回元素索引
  • 若没找到,返回-1 判定是否找到:
  • 检查元素类型是否实现了IEquatable<T>接口。
  • 如果是,则调用这个接口的Equals方法。
  • 如果否,则调用Object类的Equals方法。如此找到相等的元素。

FindIndex(Predicate<元素类型> match)详解: 对每一个元素,执行委托所代表的方法

  • 返回true, 表示匹配到满足这个特定条件的元素
  • 返回false, 表示没匹配到满足这个特定条件的元素

Predicate<T>是个委托, public delegate bool Predicate<T>(T obj);

 int index2 = racers.FindIndex(new FindCountry("Finland").FindCountryPredicate);
    public class FindCountry
    {
        public FindCountry(string country) => _country = country;

        private readonly string _country;

        public bool FindCountryPredicate(Racer racer) => racer?.Country == _country;
    }

或者Predicate<元素类型> 用lamda表达式:

int index3 = racers.FindIndex(r => r.Country == "Finland");

FindAll(Predicate<元素类型> match) 示例:

      List<Racer> bigWinners = racers.FindAll(r => r.Wins > 20);
      foreach (Racer r in bigWinners)
      {
          Console. WriteLine($"{r:A}");
      }

List<T> 排序

方法一:public void List<T>.Sort()

  • 元素类型T只有实现了IComparable<T>接口,才能使用这个不带参数的Sort()方法
    public class Racer : IComparable<Racer>
    {
        //...
        public int CompareTo(Racer other)
        {
            int compare = LastName?.CompareTo(other?.LastName) ?? -1;
            if (compare == 0)
            {
                return FirstName?.CompareTo(other?.FirstName) ?? -1;
            }
            return compare;
        }
    }
  • 如果不想用元素类型T默认支持的排序方式,那就要用其他方法:

方法二:public void List<T>.Sort(Comparison<T>)

  • Comparison<T>是一个委托,public delegate int Comparison<T>(T x, T y)
    • 第一个参数比第二个参数小,就返回一个小于0的值
    • 第一个参数比第二个参数大,就返回一个大于0的值
    • 两个参数一样,就返回0
racers.Sort((r1, r2) => r2.Wins.CompareTo(r1.Wins))

方法三:public void List<T>.Sort(IComparer<T>)

  • 返回值小于0,代表第一个参数小于第二个参数
  • 返回值大于0,代表第一个参数大于第二个参数

null的位置在其他任何元素之前

  • 第一个参数为null, 返回值为-1
  • 第二个参数为null, 返回值为1
    public class RacerComparer : IComparer<Racer>
    {
        private CompareType _compareType;
        public RacerComparer(CompareType compareType) =>
          _compareType = compareType;

        public int Compare(Racer x, Racer y)
        {
            if (x == null && y == null) return 0;
            if (x == null) return -1;
            if (y == null) return 1;

            int result;
            switch (_compareType)
            {
                case CompareType.FirstName:
                    result = string.Compare(x.FirstName, y.FirstName);
                    break;
                case CompareType.LastName:
                    result = string.Compare(x.LastName, y.LastName);
                    break;
                case CompareType.Country:
                    result = string.Compare(x.Country, y.Country);
                    if (result == 0)
                    {
                        result = string.Compare(x.LastName, y.LastName);
                    }
                    break;
                case CompareType.Wins:
                    result = x.Wins.CompareTo(y.Wins);
                    break;
                default:
                    throw new ArgumentException("Invalid Compare Type");
            }
            return result;
        }
    }

方法四:public void List<T>.Sort(Int32, Int32, IComparer<T>)

方法五: Reverse()方法来逆转整个集合的顺序


List<T>转换成只读集合ReadOnlyCollection<T>

  • List<T>AsReadOnly()方法,返回ReadOnlyCollection<T>类型对象。

示例 Racer类

    public class Racer : IComparable<Racer>, IFormattable
    {
        public Racer(int id, string firstName, string lastName, string country, int wins)
        {
            Id = id;
            FirstName = firstName;
            LastName = lastName;
            Country = country;
            Wins = wins;
        }

        public Racer(int id, string firstName, string lastName, string country)
          : this(id, firstName, lastName, country, wins: 0)
        { }

        public int Id { get; }
        public string FirstName { get; }
        public string LastName { get; }
        public string Country { get; }
        public int Wins { get; set; }

        public override string ToString() => $"{FirstName} {LastName}";


        public string ToString(string format, IFormatProvider formatProvider)
        {
            if (format == null) format = "N";
            switch (format.ToUpper())
            {
                case null:
                case "N": // name
                    return ToString();
                case "F": // first name
                    return FirstName;
                case "L": // last name
                    return LastName;
                case "W": // Wins
                    return $"{ToString()}, Wins: {Wins}";
                case "C": // Country
                    return $"{ToString()}, Country: {Country}";
                case "A": // All
                    return $"{ToString()}, Country: {Country} Wins: {Wins}";
                default:
                    throw new FormatException(String.Format(formatProvider,
                          "Format {0} is not supported", format));
            }
        }

        public string ToString(string format) => ToString(format, null);

        public int CompareTo(Racer other)
        {
            int compare = LastName?.CompareTo(other?.LastName) ?? -1;
            if (compare == 0)
            {
                return FirstName?.CompareTo(other?.FirstName) ?? -1;
            }
            return compare;
        }
    }