CodeGym /课程 /C# SELF /认识索引器

认识索引器

C# SELF
第 18 级 , 课程 0
可用

1. 关于集合的几句话

到现在为止我们一直在用数组——这是存储同一类型多个元素最简单的方式。但C#里其实有很多更方便更强大的集合,能帮你解决各种问题。

集合就是能存一组其他对象的对象。和数组不一样,集合通常能动态改变大小,提供了很多方便操作数据的方法,还针对不同场景做了优化。

主要集合类型(简单了解下):

List<T> —— 动态数组,可以自动扩容或缩小:

List<string> names = new List<string>();
names.Add("亚历克斯");
names.Add("玛丽亚");
Console.WriteLine(names[0]); // 亚历克斯
Console.WriteLine(names.Count); // 2

Dictionary<TKey, TValue> —— “键-值”对的集合,每个key对应一个value:

Dictionary<string, int> ages = new Dictionary<string, int>();
ages["亚历克斯"] = 25;
ages["玛丽亚"] = 30;
Console.WriteLine(ages["亚历克斯"]); // 25

注意:上面例子里我们用[]方括号访问集合元素——和数组一模一样!这就是索引器的功劳,今天我们就来聊聊它。

这里只是初步认识集合——后面几节课会详细讲它们的功能、区别和用法。现在你只要知道,集合能用方括号访问元素,这不是魔法,而是语言自带的特殊机制。

2. 索引器

普通C#对象一般通过属性和方法来操作。但如果你的对象本身就是个小集合怎么办?比如:

  • 你写了个Week类,想通过数字拿到星期几的名字:week[0] → "星期一"
  • 或者写了个Library类,想通过编号拿到书名:library[3] → "朝向斯万"

是不是很方便?每次都写library.GetBookByIndex(3)太麻烦了——当然想像数组一样直接访问!

这时候就轮到索引器登场了。

索引器就是类里的特殊成员,让你能用方括号语法像数组一样操作对象: obj[0]obj["key"]等等。

索引器外观看起来像属性,但没有名字,参数写在方括号里。就像你写.Name,但这里是[i]

3. 带索引器的简单集合

来写个小类,存你最喜欢的颜色。我们用字符串数组存。如果没有索引器,就得写个GetColor(int i)方法。有了索引器就简单多了:


using System;

public class FavoriteColors
{
   // 用来存颜色的私有字段
   private string[] colors = new string[5];

   // 索引器:
   public string this[int index]
   {
       get
       {
           // 检查数组边界(封装体现!)
           if (index < 0 || index >= colors.Length)
               throw new IndexOutOfRangeException("颜色索引不对!");

           return colors[index];
       }
       set
       {
           if (index < 0 || index >= colors.Length)
               throw new IndexOutOfRangeException("颜色索引不对!");

           colors[index] = value ?? throw new ArgumentNullException(nameof(value));
       }
   }
}

class Program
{
   static void Main()
   {
       FavoriteColors favorites = new FavoriteColors();

       favorites[0] = "绿色";
       favorites[1] = "蓝色";
       favorites[2] = "红色";
       favorites[10] = "紫色"; // 会抛异常!

       Console.WriteLine(favorites[1]); // 蓝色
   }
}

这里发生了什么?

  • 我们创建了个私有数组,外部不能直接乱改。
  • 索引器写成public string this[int index]this表示是当前对象的索引器。
  • getset里做了边界检查,不让越界/写入null
  • 最后就能像数组一样用favorites[0]了。

4. 索引器语法:细节

语法和属性很像,但名字(比如Age)换成了this加参数:


// 索引器签名(通用模板)
[修饰符] 返回类型 this[索引类型 索引名]
{
   get { ... }
   set { ... }
}

例子:经典写法

public class MyCollection
{
   private int[] data = new int[10];

   // 可读可写的索引器
   public int this[int index]
   {
       get { return data[index]; }
       set { data[index] = value; }
   }
}

索引器不一定只能用int

重点:索引器参数不一定只能是int,你可以用任何类型(只要key有意义):

public string this[string colorName]
{
   get { /* ... */ }
   set { /* ... */ }
}

比如电话本类,按名字查最合理:

public class PhoneBook
{
   private Dictionary<string, int> entries = new Dictionary<string, int>();

   public int this[string name]
   {
       get
       {
           if (entries.ContainsKey(name))
               return entries[name];
           return null;
       }
       set
       {
           entries[name] = value;
       }
   }
}

关于集合和Dictionary<string, string>怎么用,以后还会详细讲 :P

5. 实战例子:文本里的单词计数器

继续完善我们的应用。假设现在有个类,能统计文本里每个单词出现了多少次。最好让用户能直接用方括号查单词出现次数:


using System.Collections.Generic;

public class WordCounter
{
   private Dictionary<string, int> counter = new Dictionary<string, int>();

   // 按字符串(单词)索引
   public int this[string word]
   {
       get
       {
           if (counter.ContainsKey(word))
               return counter[word];
           return 0; // 没有这个单词就返回0
       }
       set
       {
           counter[word] = value;
       }
   }

   // 从字符串统计单词
   public void AddWords(string text)
   {
       foreach (var word in text.Split(' ', System.StringSplitOptions.RemoveEmptyEntries))
       {
           if (counter.ContainsKey(word))
               counter[word]++;
           else
               counter[word] = 1;
       }
   }
}

// 在Main里:
var wc = new WordCounter();
wc.AddWords("妈妈 洗 框 洗 妈妈 爸爸");
Console.WriteLine($"'妈妈' 出现了 {wc["妈妈"]} 次");
Console.WriteLine($"'框' 出现了 {wc["框"]} 次");
Console.WriteLine($"'猫' 出现了 {wc["猫"]} 次"); // 0

实际有什么用? 这种写法常用来实现自定义集合、内存库、映射(map和index)甚至DSL(C#里的小型专用语言)。

6. 限制和细节

索引器很强大,但也有一些规则和坑(哪有没坑的~)。

索引器没有名字

和属性不一样,索引器没有名字,只有this[参数类型]这种签名。你要是写public int MyIndexer[int i],编译器会懵逼。只能用this

不能有静态索引器

索引器只能属于类的实例,不能是static成员。你不能写static int this[int i]——因为this永远指的是某个对象。

可以重载参数类型/数量

你可以在一个类里写多个索引器,只要参数类型或数量不一样。例如:

public string this[int i] { get { ... } set { ... } }
public string this[string key] { get { ... } set { ... } }

这样没问题,编译器不会搞混——如果参数重复就会报错。

必须有get或set

索引器必须有get或set。只读就去掉set,只写就去掉get。一般都写俩。

7. 实用价值和为什么要学

  • 索引器在数据集合里用得超多。很多.NET类都带索引器,比如List<T>Dictionary<TKey,TValue>。你写list[2]其实就是用的索引器!
  • 索引器能隐藏内部实现(封装!),但给用户一个熟悉又方便的接口。用你类的人不用管你怎么存数据,直接用[index]就行。
  • 你的代码会变得简洁直观——面试官(还有你未来的同事)都很喜欢。

属性 vs 索引器:对比

属性 索引器
名字 有(比如Name) 没有(用this[参数]代替)
访问方式 按名字 按索引或key
静态 可以是static 只能实例用
一个类里能有多少 随便几个 可以有多个,但参数签名要不一样
用途 存/取数据 小型集合、关联数据

8. 常见错误和建议

错误1: 想写静态索引器。
不行——this[...]只能用在对象上。

错误2: 忘了检查索引。
如果在getset里不检查边界,程序可能直接崩掉。

错误3: 参数类型搞混了。
如果写了两个参数一样的索引器,编译器会报错。

错误4: 忘了写getset
想读写都能用,两个都得写。

建议: 如果你的类只是包了个数组——直接把数组访问转给索引器就行。这样又快又直观。

9. 为什么要学这个

索引器让对象接口变得简单、直观又好用。它能隐藏内部实现,但给开发者一个超级方便的数据访问方式。

内置集合就是这么做的:stringList<T>Dictionary<TKey, TValue>Span<T>等等。你写array[2]text[0],其实就是在用索引器。

最重要的是——你现在也能写出自己的类,像这些内置集合一样灵活简洁。也就是说——你离写出专业、易读的代码又近了一步!

2
任务
C# SELF, 第 18 级, 课程 0
已锁定
创建一个带有星期几索引器的 "Week" 类
创建一个带有星期几索引器的 "Week" 类
2
任务
C# SELF, 第 18 级, 课程 0
已锁定
带有按姓名索引器的电话簿
带有按姓名索引器的电话簿
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION