C#8.0本质论第十七章--构建自定义集合

C#8.0本质论第十七章–构建自定义集合

17.1更多集合接口

17.1.1IList< T >和IDictionary< TKey , TValue >

这两个接口决定了集合类型是侧重于通过位置索引来获取值,还是侧重于通过键来获取值。

实现这两个接口的类都必须提供索引器。

17.1.2ICollection< T >

IList< T >和IDictionary< TKey , TValue >都实现了ICollection< T >

17.2主要集合类

17.2.1列表集合:List< T >

List< T >类的性质和数组相似,关键区别就是随着元素增多,这种类会自动扩展。此外,列表可通过显示调用TrimToSize()或Capacity来缩小。

**IComparable< T >和IComparer< T >**的区别很细微,却很重要。前者说“我知道如何将我自己和我的类型的另一个实例进行比较”,后者说“我知道如何比较给定类型的两个实例”。

using System;
using System.Collections.Generic;
// ...
public class Contact
{
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
 
    public Contact(string firstName, string lastName)
    {
        this.FirstName = firstName;
        this.LastName = lastName;
    }
}
public class NameComparison : IComparer
{
    public int Compare(Contact? x, Contact? y)
    {
        if(Object.ReferenceEquals(x, y))
            return 0;
        if(x == null)
            return 1;
        if(y == null)
            return -1;
        int result = StringCompare(x.LastName, y.LastName);
        if(result == 0)
            result = StringCompare(x.FirstName, y.FirstName);
        return result;
    }
 
    private static int StringCompare(string? x, string? y)
    {
        if(Object.ReferenceEquals(x, y))
            return 0;
        if(x == null)
            return 1;
        if(y == null)
            return -1;
        return x.CompareTo(y);
    }
}
17.2.2全序

实现IComparable< T >和IComparer< T >时必须生成一个全序(total order),必须为任何可能的数据项排列组合提供一致的排序结果。例如上面代码中连实参是null的情况都考虑到了,不能任何一个元素为null就返回0,否则可能出现两个非null元素等于null但不相等的情况。

17.2.3搜索List< T >

可以使用Contains(),Indexof(),LastIndexOf()和BinarySearch()方法。

BinarySearch()要求有序,如果没有找到,会返回一个负整数。该值的取反结果是“大于被查找元素的下一个元素”的索引。没有更大的则是元素总数。

17.2.4字典集合:Dictionary< TKey , TValue >

可利用Keys和Values属性只处理字典类中的键或值。返回ICollection< T >类型,返回的是对原始字典集合中的数据的引用,而不是返回拷贝。

17.2.5已排序集合:SortedDictionary< TKey , TValue >和SortedList< T >

元素是按照键排序的

17.2.6栈集合:Stack< T >
17.2.7队列集合:Queue< T >
17.2.8链表:LinkedList< T >

链表集合,允许正向和反向遍历。(所以是双向链表)

17.3提供索引器

数组,字典和列表都提供了索引器(indexer)以便根据键或索引来获取/设置成员。

interface IPair
{
    T First { get; }
    T Second { get; }
    T this[PairItem index] { get; }
}

public enum PairItem
{
    First,
    Second
}
 
public struct Pair : IPair
{
    public Pair(T first, T second)
    {
        First = first;
        Second = second;
    }
 
    public T First { get; }
    public T Second { get; }
 
    public T this[PairItem index]
    {
        get
        {
            switch (index)
            {
                case PairItem.First:
                    return First;
                case PairItem.Second:
                    return Second;
                default:
                    throw new NotImplementedException(
                        $"The enum { index.ToString() } has not been implemented");
 
            }
        }
    }
}

索引器的声明和属性很相似,但不是使用属性名,而是使用关键字this,后跟方括号中的参数列表。主题也像属性,有get和set块。索引可获得多个参数,甚至可以重载。

17.4返回null或者空集合

返回数组和集合时允许返回null,更好的选择是返回不含任何数据项的集合实例。可避免强迫调用者在便利集合前检查null值。

17.5迭代器

本节讨论如何利用迭代器(iterator)为自定义集合实现自己的IEnumerator< T >,IEnumerable< T >和对应的非泛型接口。迭代器使集合的用户能遍历集合的内部结构,同时不必了解结构的内部实现。

类要支持用foreach进行迭代,就必须实现枚举数(enumerator)模式,如第15章所述,C#的foreach循环结构被编译器扩展成while循环结构,它以从IEnumerable< T >接口获取的IEnumerator< T >接口为基础。

17.5.1定义迭代器
17.5.2迭代器语法

迭代器提供了迭代器接口(IEnumerable< T >和IEnumerator< T >)的一个快捷实现。

using System.Collections;
using System.Collections.Generic;

public class BinaryTree : IEnumerable
{
    public BinaryTree(T value)
    {
        Value = value;
    }

    #region IEnumerable
    public IEnumerator GetEnumerator()
    {
        // ...
    }
    #endregion IEnumerable

    public T Value { get; }
    public Pair> SubItems { get; set; }
}

public struct Pair
{
    public Pair(T first, T second) : this()
    {
        First = first;
        Second = second;
    }
    public T First { get; }
    public T Second { get; }
}

要为GetEnumerator()提供一个实现。

17.5.3从迭代器生成值

迭代器类似于函数,但它不是返回(return)一个值,而是**生成(yield)**一系列值。

每次迭代器遇到yield return语句都生成一个值,之后控制立即回到请求数据项的调用者。当调用者请求下一项时,慧紧接着在上一个yield return语句之后执行。

17.5.4迭代器和状态

GetEnumerator()在foreach语句中被首次调用时,慧创建一个迭代器对象,其状态被初始化为特殊的“起始”状态,表示迭代器尚未执行代码,所以尚未生成任何值。

只要foreach继续,迭代器就会一直持续其状态。循环每一次请求下一个值,控制就会一直维持其状态。循环每一次请求下一个值,控制就会进入迭代器,从上一次离开的位置继续。该位置是根据迭代器对象中存储的状态信息来判断的。foreach终止,迭代器的状态就不再保存了。

17.5.5更多的迭代器例子
17.5.6将yield return语句放到循环中
17.5.7取消更多的迭代:yield break

可以使用yield break使MoveNext()返回false,使控制立即回到调用者并终止循环。

C#编译器遇到一个迭代器时,会根据枚举数模式将代码展开成恰当的CIL,在生成的CIL代码中,C#编译器首先创建一个嵌套的私有类来实现IEnumerator< T >接口,以及它的Current熟悉和MoveNext()方法。Current属性返回与迭代器的返回类型对应的一个类型。

using System;
using System.Collections;
using System.Collections.Generic;
// ...
    [NullableContext(1)]
    [Nullable(0)]
    public struct Pair<[Nullable(2)] T> : IPair, IEnumerable, IEnumerable
    {
        public Pair(T first, T second)
        {
            First = first;
            Second = second;
        }
 
        public T First { get; }
 
        public T Second { get; }
 
        public T this[PairItem index]
        {
            get
            {
                PairItem pairItem = index;
                PairItem pairItem2 = pairItem;
                T result;
                if (pairItem2 != PairItem.First)
                {
                    if (pairItem2 != PairItem.Second)
                    {
                        throw new NotImplementedException(
                            string.Format(
                                "The enum {0} has not been implemented", 
                                index.ToString()));
                    }
                    result = Second;
                }
                else
                {
                    result = First;
                }
                return result;
            }
        }
 
        public IEnumerator GetEnumerator()
        {
            yield return First;
            yield return Second;
            yield break;
        }
 
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

yield关键字是上下文关键字,不是保留关键字。所以可以合法地声明名为yield的局部变量。

事实上,C#1.0之后加入的所有关键字都是上下文关键字,这是为了防止升级老程序来使用语言的新版本时出问题。

17.5.8在一个类中创建多个迭代器
17.5.9yield语句的要求

只有在返回IEnumerator< T >或者IEnumerable< T >类型的成员中,才能使用yield return语句。

主体包含yield return语句的成员不能包含简单return语句。

yield语句只能在方法,用户自定义操作符或者索引器/属性的get访问器方法中出现。成员不可获取任何ref或者out参数。

yield语句不能在匿名方法或Lambda表达式中出现。

yield语句不能在try语句的catch和finally块中出现。此外,yield语句在try块中出现的前提是没有catch块。

你可能感兴趣的:(C#学习笔记,c#,开发语言,.net,学习,笔记)