C#高级编程(第10版) C# 6 & .NET Core 1.0 (.NET开发经典名著)
上QQ阅读APP看书,第一时间看更新

8.6 实现自定义的索引运算符

自定义索引器不能使用运算符重载语法来实现,但是它们可以用与属性非常相似的语法来实现。

首先看看数组元素的访问。这里创建一个int元素数组。第二行代码使用索引器来访问第二个元素,并给它传递42。第三行使用索引器来访问第三个元素,并给该元素传递变量x。

        int[] arr1 = {1, 2, 3};
        arr1[1] = 42;
        int x = arr1[2];

注意:数组在第7章阐述。

CustomIndexerSample使用如下依赖项和名称空间:

依赖项:

        NETStandard.Library

名称空间:

        System
        System.Collections.Generic
        System.Linq
        static System.Console

要创建自定义索引器,首先要创建一个Person类,其中包含FirstName、LastName和Birthday只读属性(代码文件CustomIndexerSample/Person.cs):

        public class Person
        {
          public DateTime Birthday { get; }
          public string FirstName { get;  }
          public string LastName { get;  }
          public Person(string firstName, string lastName, DateTime birthDay)
          {
            FirstName = firstName;
            LastName = lastName;
            Birthday = birthDay;
          }
          public override string ToString() => $"{FirstName} {LastName}";
        }

类PersonCollection定义了一个包含Person元素的私有数组字段,以及一个可以传递许多Person对象的构造函数(代码文件CustomIndexerSample/PersonCollection.cs):

        public class PersonCollection
        {
          private Person[] _people;
          public PersonCollection(params Person[] people)
          {
            _people = people.ToArray();
          }
        }

为了允许使用索引器语法访问PersonCollection并返回Person对象,可以创建一个索引器。索引器看起来非常类似于属性,因为它也包含get和set访问器。两者的不同之处是名称。指定索引器要使用this关键字。this关键字后面的括号指定索引使用的类型。数组提供int类型的索引器,所以这里使用int类型直接把信息传递给被包含的数组people。get和set访问器的使用非常类似于属性。检索值时调用get访问器,在右边传递Person对象时调用set访问器。

        public Person this[int index]
        {
          get { return _people[index]; }
          set { _people[index] = value; }
        }

对于索引器,不能仅定义int类型作为索引类型。任何类型都是有效的,如下面的代码所示,其中把DateTime结构作为索引类型。这个索引器用来返回有指定生日的每个人。因为多个人员可以有相同的生日,所以不是返回一个Person对象,而是用接口IEnumerable < Person>返回一个Person对象列表。所使用的Where方法根据lambda表达式进行过滤。Where方法在名称空间System.Linq中定义:

        public IEnumerable<Person> this[DateTime birthDay]
        {
          get { return _people.Where(p => p.Birthday == birthDay); }
        }

使用DateTime类型的索引器检索人员对象,但不允许把人员对象设置为只有get访问器,而没有set访问器。在C# 6中有一个速记符号,可使用表达式主体的成员创建相同的代码(属性也可使用该语法):

        public IEnumerable<Person> this[DateTime birthDay] =>
          _people.Where(p => p.Birthday == birthDay);

示例应用程序的Main方法创建一个PersonCollection对象,给构造函数传递四个Person对象。在第一个WriteLine方法中,使用索引器的get访问器和int参数访问第三个元素。在foreach循环中,带有DateTime参数的索引器用来传递指定的日期(代码文件CustomIndexerSample/Program.cs):

        static void Main()
        {
          var p1 = new Person("Ayrton", "Senna", new DateTime(1960, 3, 21));
          var p2 = new Person("Ronnie", "Peterson", new DateTime(1944, 2, 14));
          var p3 = new Person("Jochen", "Rindt", new DateTime(1942, 4, 18));
          var p4 = new Person("Francois", "Cevert", new DateTime(1944, 2, 25));
          var coll = new PersonCollection(p1, p2, p3, p4);
          WriteLine(coll[2]);
          foreach (var r in coll[new DateTime(1960, 3, 21)])
          {
            WriteLine(r);
          }
          ReadLine();
        }

运行程序,第一个WriteLine方法把Jochen Rindt写到控制台;foreach循环的结果是Ayrton Senna,因为他的生日是第二个索引器中指定的日期。