1.索引器的使用以及理解索引器的重载,索引器的链式调用
在C#中,索引器(Indexer)是一种特殊的属性,它允许您使用数组的索引方式来访问类的实例。索引器使得类的对象可以像数组一样通过索引来访问其元素。这在处理集合或列表时特别有用,因为它提供了一种统一的方式来访问集合中的元素。
索引器的语法如下:
public class MyClass
{
private int[] _items; // 假设我们有一个整数数组
// 索引器的定义
public int this[int index]
{
get
{
// 获取操作
if (index < 0 || index >= _items.Length)
{
throw new IndexOutOfRangeException("索引超出范围。");
}
return _items[index];
}
set
{
// 设置操作
if (index < 0 || index >= _items.Length)
{
throw new IndexOutOfRangeException("索引超出范围。");
}
_items[index] = value;
}
}
}
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass._items = new int[] { 1, 2, 3, 4, 5 };
// 使用索引器访问元素
Console.WriteLine(myClass[2]); // 输出: 3
// 使用索引器设置元素值
myClass[2] = 10;
Console.WriteLine(myClass[2]); // 输出: 10
}
}
在这个例子中,MyClass
有一个名为 _items
的私有整数数组。我们定义了一个名为 this
的索引器,它允许我们通过索引来访问和设置 _items
数组的元素。索引器的 get
访问器用于获取指定索引处的值,而 set
访问器用于设置该索引处的值。
用户可以通过 myClass[2]
这样的语法来访问或设置 _items
数组的第三个元素(索引从0开始)。这种方式与访问数组元素的方式非常相似,使得类的对象可以像数组一样被操作。
需要注意的是,索引器的名称 this
是特殊的,它表示索引器。在索引器的定义中,我们使用 int index
作为参数来表示索引,您可以根据需要使用不同的数据类型作为索引。此外,索引器可以有多个参数,以便实现多维索引。
如果您的属性是一个三维数组,您需要在索引器中定义三个维度的索引。以下是一个简单的例子,展示了如何为三维数组定义索引器:
public class MyClass
{
private int[,,] _items; // 三维整数数组
// 三维索引器的定义,使用index1, index2, index3作为参数名
public int this[int index1, int index2, int index3]
{
get
{
// 获取操作
if (index1 < 0 || index1 >= _items.GetLength(0) ||
index2 < 0 || index2 >= _items.GetLength(1) ||
index3 < 0 || index3 >= _items.GetLength(2))
{
throw new IndexOutOfRangeException("索引超出范围。");
}
return _items[index1, index2, index3];
}
set
{
// 设置操作
if (index1 < 0 || index1 >= _items.GetLength(0) ||
index2 < 0 || index2 >= _items.GetLength(1) ||
index3 < 0 || index3 >= _items.GetLength(2))
{
throw new IndexOutOfRangeException("索引超出范围。");
}
_items[index1, index2, index3] = value;
}
}
// 初始化三维数组的方法
public void InitializeArray(int size1, int size2, int size3)
{
_items = new int[size1, size2, size3];
// 初始化数组或执行其他操作
}
}
class Program
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.InitializeArray(3, 3, 3); // 初始化一个3x3x3的三维数组
// 使用三维索引器访问元素
Console.WriteLine(myClass[1, 1, 1]); // 输出数组中的某个元素
// 使用三维索引器设置元素值
myClass[1, 1, 1] = 10;
Console.WriteLine(myClass[1, 1, 1]); // 输出修改后的元素值
}
}
在这个例子中,MyClass
有一个名为 _items
的三维整数数组。我们定义了一个三维索引器,它接受三个整数参数:index1
、index2
和 index3
。这些参数分别代表三维数组的三个维度。在 get
和 set
访问器中,我们使用这些参数来访问或设置数组中的特定元素。
InitializeArray
方法用于初始化三维数组,您可以根据需要设置数组的大小。在 Main
方法中,我们创建了 MyClass
的实例,初始化了三维数组,并使用三维索引器来访问和设置数组中的元素。
请注意,三维数组的索引器需要检查每个维度的索引是否在数组的边界内。我们使用 GetLength
方法来获取每个维度的大小,并在索引超出范围时抛出 IndexOutOfRangeException
异常。
上面的代码定义了一个名为 MyClass
的类,它包含了一个三维整数数组作为其属性,并为这个数组提供了一个三维索引器。下面是对代码的详细解释:
- 三维数组的声明:
private int[,,] _items; // 三维整数数组
这里声明了一个名为 _items
的三维整数数组。在C#中,三维数组使用2个逗号来分隔维度。
- 三维索引器的定义:
public int this[int index1, int index2, int index3]
{
// ...
}
索引器使用 this
关键字定义,它允许我们使用类似数组的语法来访问类的实例。在这个例子中,索引器接受三个参数:index1
、index2
和 index3
,分别对应三维数组的三个维度。
-
索引器的
get
访问器:
get
{
// ...
}
get
访问器用于从数组中检索值。首先,它检查提供的索引是否在数组的边界内。这是通过调用 GetLength
方法并传递相应的维度索引来完成的。如果索引有效,它返回数组中指定位置的值。如果索引无效,它抛出 IndexOutOfRangeException
异常。
-
索引器的
set
访问器:
set
{
// ...
}
set
访问器用于在数组的指定位置设置值。与 get
访问器类似,它也检查索引的有效性。如果索引有效,它将值赋给数组的指定位置。如果索引无效,同样抛出 IndexOutOfRangeException
异常。
- 初始化三维数组的方法:
public void InitializeArray(int size1, int size2, int size3)
{
_items = new int[size1, size2, size3];
// 初始化数组或执行其他操作
}
这个方法用于初始化三维数组。它接受三个参数,分别代表数组在每个维度上的大小,并创建一个新的三维数组。您可以在这个方法中添加额外的逻辑来初始化数组的值或执行其他操作。
-
在
Main
方法中使用索引器:
MyClass myClass = new MyClass();
myClass.InitializeArray(3, 3, 3); // 初始化一个3x3x3的三维数组
// 使用三维索引器访问和设置元素
在 Main
方法中,我们创建了 MyClass
的一个实例,并调用 InitializeArray
方法来初始化三维数组。然后,我们使用三维索引器来访问和设置数组中的元素,就像操作普通数组一样。
这个例子展示了如何在C#中为三维数组创建索引器,并提供了一种直观的方式来访问和修改数组的元素。
定义了一个名为 Person
的类,其中包含了三个字符数组字段:_show
、_info
和 _music
。类中定义了两个索引器,一个是基本的一维索引器,另一个是重载后的索引器,它接受一个字符串参数 type
和一个整数索引 index
。
class Person
{
// 字段:数组一
private char[] _show = { '就', '是', '有', '这', '种', '操', '作', '!' };
// 字段:数组二
private char[] _info = { '我', '是', '练', '习', '时', '长', '两', '年', '半', '的', '偶', '像', '练', '习', '生' };
// 字段:数组三
private char[] _music = { '音', '乐', '响', '起', '!', '鸡', '你', '太', '美' };
// 基本索引器
public char this[int index]
{
get
{
if (index >= 0 && index < _show.Length)
{
return _show[index];
}
else
{
Console.WriteLine("取值的索引超出范围");
return '\0';
}
}
set
{
if (index >= 0 && index < _show.Length)
{
_show[index] = value;
}
else
{
Console.WriteLine("赋值的索引超出范围");
}
}
}
// 重载索引器
public char this[string type, int index]
{
get
{
char result = '\0';
switch (type)
{
case "数组一":
if (index >= 0 && index < _show.Length)
{
return _show[index];
}
break;
case "数组二":
if (index >= 0 && index < _info.Length)
{
return _info[index];
}
break;
case "数组三":
if (index >= 0 && index < _music.Length)
{
return _music[index];
}
break;
default:
Console.WriteLine("不在数组范围!请输入:数组一/数组二/数组三");
break;
}
return result;
}
set
{
switch (type)
{
case "数组一":
if (index >= 0 && index < _show.Length)
{
_show[index] = value;
}
else
{
Console.WriteLine("赋值的索引超出范围");
}
break;
// 为数组二和数组三添加类似的逻辑
default:
Console.WriteLine("不在数组范围!请输入:数组一/数组二/数组三");
break;
}
}
}
}
-
基本索引器:
这个索引器允许您通过整数索引来访问和设置_show
数组中的字符。如果索引超出了_show
数组的范围,它会提示用户索引超出范围,并返回一个空字符'\0'
。 -
重载索引器:
这个索引器允许您通过指定的数组类型("数组一"、"数组二" 或 "数组三")和整数索引来访问和设置对应的数组。它使用switch
语句来判断用户输入的type
,并根据type
返回对应的数组中的字符。如果索引超出了指定数组的范围,它会提示用户重新输入。
以下是对重载索引器的一些改进和解释:
- 在
get
访问器中,如果type
不匹配任何已知的数组类型,应该返回一个默认值(例如'\0'
),而不是result
,因为result
被初始化为'\0'
。 - 在
set
访问器中,您可能也想添加类似的逻辑,以允许用户通过重载的索引器设置数组的值。 - 在
set
访问器中,您应该在所有情况下返回void
,因为您正在设置值而不是返回值。 - 为重载索引器的
set
访问器添加了与get
访问器相似的逻辑。这样,用户就可以通过指定数组类型和索引来设置数组中的值了。如果索引超出范围,它会提示用户索引超出范围。
索引器的重载:
public char this[string type, int index]
{
get
{
// ...(索引器的代码)
}
set
{
// ...(索引器的代码)
}
}
这段代码定义了一个名为 this
的索引器,它是 Person
类的一部分。这个索引器是重载的,意味着它有两个参数:一个字符串 type
和一个整数 index
。索引器允许您使用类似于数组的语法来访问和设置类的内部数组的元素。
-
get
访问器:当您尝试从索引器获取值时(例如person["数组一", 0]
),get
访问器会被调用。它根据传入的type
参数确定要访问哪个数组,然后使用index
参数来获取数组中特定位置的元素。 -
set
访问器:当您尝试设置索引器的值时(例如person["数组一", 0] = 'A'
),set
访问器会被调用。它同样根据type
参数确定要操作哪个数组,并将新的值设置到指定的index
位置。
<u>这里是 get
和 set
访问器的内部逻辑的简化版本:</u>
public char this[string type, int index]
{
get
{
// 根据type参数选择正确的数组
char[] selectedArray;
switch (type)
{
case "数组一":
selectedArray = _show;
break;
case "数组二":
selectedArray = _info;
break;
case "数组三":
selectedArray = _music;
break;
default:
Console.WriteLine("无效的数组类型。");
return '\0'; // 返回一个空字符
}
// 检查索引是否在数组的有效范围内
if (index >= 0 && index < selectedArray.Length)
{
return selectedArray[index];
}
else
{
Console.WriteLine("索引超出范围。");
return '\0'; // 返回一个空字符
}
}
set
{
// 根据type参数选择正确的数组
char[] selectedArray;
switch (type)
{
case "数组一":
selectedArray = _show;
break;
case "数组二":
selectedArray = _info;
break;
case "数组三":
selectedArray = _music;
break;
default:
Console.WriteLine("无效的数组类型。");
return;
}
// 检查索引是否在数组的有效范围内
if (index >= 0 && index < selectedArray.Length)
{
selectedArray[index] = value;
}
else
{
Console.WriteLine("索引超出范围。");
}
}
}
在这个简化的例子中,get
和 set
访问器都使用 switch
语句来确定要操作的数组。然后,它们检查索引是否在数组的有效范围内。如果是,get
访问器返回数组中指定索引的元素,而 set
访问器将新值设置到指定索引。如果索引无效,它们会在控制台输出错误消息。
在 Main
方法中,您可以通过传递数组类型和索引来调用这个索引器,例如:
Person person = new Person();
Console.WriteLine(person["数组一", 0]); // 输出数组一的第一个元素
person["数组一", 0] = 'A'; // 将数组一的第一个元素设置为 'A'
这样,您就可以通过索引器来访问和修改 Person
类中定义的数组了。
<u>public char this[string type, int index]
</u> 这部分代码定义了一个索引器,它不是固定必须这样写,但它遵循了C#中索引器的语法规则。索引器的语法允许您定义一个类似于数组的索引访问模式,但您可以根据需要自定义参数和返回类型。
在这个特定的例子中:
-
public
是访问修饰符,表示这个索引器可以在类的外部被访问。 -
char
是返回类型,表示这个索引器返回一个字符(char
类型)。 -
this
是索引器的关键字,它在这里用作方法名,表示这是一个索引器。 -
[string type, int index]
是索引器的参数列表,其中type
是一个字符串参数,用于指定要访问的数组类型;index
是一个整数参数,用于指定数组中的索引位置。
您可以选择不同的参数类型和数量来满足您的具体需求。例如,如果您的类中只有一个数组,或者您希望索引器只接受一个参数,您可以简化索引器的定义。但在这个例子中,由于有三个不同的数组,所以需要一个额外的参数来区分它们。
如果您不希望使用字符串来指定数组类型,您也可以定义三个不同的索引器,每个索引器对应一个数组,如下所示:
public char this[int index] // 用于数组一
{
get { /* ... */ }
set { /* ... */ }
}
public char this[string type, int index] // 用于数组二和数组三
{
get { /* ... */ }
set { /* ... */ }
}
在这个例子中,我们为每个数组定义了一个单独的索引器。这样,您可以根据数组的类型直接调用相应的索引器,而不需要传递额外的类型参数。
总之,索引器的写法不是固定的,您可以根据实际情况和需求来设计索引器的签名。
下面是一个例子,展示了如何为一个包含多个不同类型属性的类定义两个不同类型的索引器重载。假设我们有一个名为 Bookshelf
的类,它用来表示书架,书架上可以放不同类型的书籍(例如小说、教科书和杂志)。我们想要通过索引器来访问特定类型的书籍。
using System;
public class Bookshelf
{
// 字段:存储不同类型的书籍
private List<Book> novels;
private List<教科书> textbooks;
private List<Magazine> magazines;
// 构造函数,初始化书籍列表
public Bookshelf()
{
novels = new List<Book>();
textbooks = new List<教科书>();
magazines = new List<Magazine>();
}
// 索引器重载1:用于访问小说
public Book this[int index, BookType type]
{
get
{
if (type == BookType.Novel)
{
if (index >= 0 && index < novels.Count)
{
return novels[index];
}
}
else
{
throw new ArgumentException("Invalid book type for this indexer.");
}
}
set
{
if (type == BookType.Novel)
{
if (index >= 0 && index < novels.Count)
{
novels[index] = value;
}
else
{
throw new IndexOutOfRangeException("Index out of range.");
}
}
else
{
throw new ArgumentException("Invalid book type for this indexer.");
}
}
}
// 索引器重载2:用于访问教科书和杂志
public Book this[int index, BookType type]
{
get
{
switch (type)
{
case BookType.Textbook:
if (index >= 0 && index < textbooks.Count)
{
return textbooks[index];
}
break;
case BookType.Magazine:
if (index >= 0 && index < magazines.Count)
{
return magazines[index];
}
break;
default:
throw new ArgumentException("Invalid book type for this indexer.");
}
}
set
{
switch (type)
{
case BookType.Textbook:
if (index >= 0 && index < textbooks.Count)
{
textbooks[index] = (教科书) value;
}
else
{
throw new IndexOutOfRangeException("Index out of range.");
}
break;
case BookType.Magazine:
if (index >= 0 && index < magazines.Count)
{
magazines[index] = (Magazine) value;
}
else
{
throw new IndexOutOfRangeException("Index out of range.");
}
break;
default:
throw new ArgumentException("Invalid book type for this indexer.");
}
}
}
}
// 假设这些是书籍的不同类型
public class Book { }
public class 教科书 : Book { }
public class Magazine : Book { }
// 枚举,表示书籍的类型
public enum BookType
{
Novel,
Tutorial,
Magazine
}
class Program
{
static void Main(string[] args)
{
Bookshelf shelf = new Bookshelf();
// 添加一些书籍到书架
shelf.novels.Add(new Book());
shelf.textbooks.Add(new 教科书());
shelf.magazines.Add(new Magazine());
// 使用索引器访问和设置小说
Book novel = shelf[0, BookType.Novel];
shelf[0, BookType.Novel] = new Book();
// 使用索引器访问和设置教科书
教科书 textbook = shelf[0, BookType.Tutorial];
shelf[0, BookType.Tutorial] = new 教科书();
// 使用索引器访问和设置杂志
Magazine magazine = shelf[0, BookType.Magazine];
shelf[0, BookType.Magazine] = new Magazine();
}
}
在这个例子中,我们定义了两个索引器重载:
- 第一个索引器
this[int index, BookType type]
专门用于访问和设置小说(Book
类型)。 - 第二个索引器
this[int index, BookType type]
用于访问和设置教科书(教科书
类型)和杂志(Magazine
类型)。这里我们使用了switch
语句来根据书籍类型选择正确的列表。
我们还定义了一个 BookType
枚举,用于表示书籍的类型。这样,当我们使用索引器时,可以通过传递 BookType
枚举值来指定要访问的书籍类型。
请注意,这个例子中的索引器重载可能会导致一些混淆,因为它们有相同的签名。在实际应用中,为了避免这种情况,您可能需要为每个索引器重载定义不同的参数列表。例如,您可以为每种书籍类型定义一个单独的索引器,或者使用不同的方法名。
索引器(Indexer)在C#中通常用于以下几种场景:
-
模拟数组或集合的访问:
当您想要提供一个类,使其行为类似于数组或集合,允许用户通过索引来访问或修改元素时,索引器非常有用。例如,自定义集合类、列表或字典。 -
封装数据结构:
如果您的类封装了一个复杂的数据结构(如二维数组、链表、树等),索引器可以提供一个简单的接口来访问这些数据结构中的元素。 -
数据库操作:
在与数据库交互时,索引器可以用来模拟数据库表的行和列。例如,您可以创建一个类来表示数据库中的表,索引器可以用于访问特定行和列的数据。 -
文件系统操作:
在处理文件系统时,索引器可以用来访问文件或目录列表。例如,一个类可以表示一个目录,索引器可以用于获取目录中的文件或子目录。 -
游戏开发:
在游戏开发中,索引器可以用来访问游戏对象的集合,如角色、敌人、子弹等。这使得代码更易于理解和维护。 -
数据绑定:
在用户界面编程中,索引器常用于实现数据绑定。例如,当您需要将一个列表绑定到用户界面的某个部分时,索引器可以简化数据的访问。 -
简化API:
索引器可以提供一个简洁的API,使得外部代码可以通过索引来操作类的状态,而不需要暴露内部的实现细节。 -
多维数据结构:
对于多维数据结构,如三维数组或自定义的多维集合,索引器可以提供一种直观的方式来访问特定维度的元素。
使用索引器时,您应该考虑其性能影响。索引器在每次访问时都会执行额外的代码,这可能会影响性能。因此,在性能敏感的应用中,您可能需要权衡索引器的便利性和性能开销。
索引器通常在以下情况下需要重载:
-
支持多种数据类型:
当您需要一个索引器来处理不同类型的数据时,您可能需要重载索引器。例如,如果您的类管理了多种类型的对象(如字符串、整数、自定义对象等),您可能需要为每种类型提供一个专门的索引器。 -
不同的访问模式:
如果您的类需要支持不同的访问模式,例如,一个索引器用于只读访问,另一个用于读写访问,您可能需要重载索引器以提供不同的行为。 -
不同的参数数量或类型:
当您需要根据不同的参数数量或类型来访问数据时,索引器重载是必要的。例如,一个索引器可能接受一个整数索引来访问一维数组,而另一个可能接受两个整数索引来访问二维数组。 -
不同的数据结构:
如果您的类封装了不同的数据结构(如列表、字典、集合等),您可能需要为每种数据结构提供一个专门的索引器。 -
提供更灵活的接口:
为了提供更灵活的接口,您可能需要重载索引器以支持不同的参数组合。例如,一个索引器可能接受一个字符串和一个整数来访问字典中的键值对,而另一个可能只接受一个字符串来访问特定的属性。 -
遵循特定的设计模式:
在某些设计模式中,如外观模式(Facade Pattern),您可能需要重载索引器来提供一个统一的接口来访问底层系统的多个组件。 -
满足特定的业务逻辑:
如果业务逻辑要求根据不同的条件来返回不同的数据,您可能需要重载索引器来实现这些条件逻辑。
在设计索引器重载时,您应该确保重载的索引器之间有明确的区分,以避免混淆。同时,您应该考虑索引器的使用场景和性能影响,确保索引器的使用不会对性能产生负面影响。如果可能,最好通过方法重载或属性来代替索引器重载,以保持代码的清晰和简洁。
索引器的链式调用:
namespace 索引器的理解
{
class Student
{
//我是三好学生
private char[] _info = null;
public Student(char[] info)
{
this._info = info;
}
//构建索引器
public char this[int index]
{
get
{
if (_info != null)
{
if (index >= 0 && index <= _info.Length)
{
return this._info[index];
}
else
{
//提示不在数组的范围
Console.WriteLine("不在数组的索引范围[{0},{1}],请重新输入!", 0, _info.Length);
return '\0';
}
}
else
{
return '\0';
}
}
set
{
if (_info != null)
{
if (index >= 0 && index <= _info.Length)
{
_info[index] = value;
}
else
{
//提示不在范围
Console.WriteLine("不在数组的索引范围[{0},{1}],请重新输入!", 0, _info.Length);
}
}
else
{
//提示不在范围
Console.WriteLine("不在数组的索引范围[{0},{1}],请重新输入!", 0, _info.Length);
}
}
}
}
class School
{
//字段:数组四
private Student[] _student =
{
new Student(new char[] { '蔡', '徐', '坤' }),
new Student(new char[] { '洪', '世', '贤' }),
new Student(new char[] { '断', '水', '流','大','师','兄' }),
};
public Student this[int index]
{
get
{
if (index >= 0 && index < _student.Length)
{
return _student[index];
}
else
{
//提示不在数组的索引范围请重新输入
Console.WriteLine("不在数组的索引范围[{0},{1}],请重新输入!", 0, _student.Length);
return null;
}
}
set
{
if (index >= 0 && index < _student.Length)
{
_student[index] = value;
}
else
{
//提示不在数组的索引范围请重新输入
Console.WriteLine("不在数组的索引范围[{0},{1}],请重新输入!", 0, _student.Length);
}
}
}
}
internal class Program
{
static void Main(string[] args)
{
//打印对象数组当中的内容
School school = new School();
Console.Write(school[0][0]);
Console.Write(school[0][1]);
Console.Write(school[0][2]);
Console.WriteLine();
Console.Write(school[1][0]);
Console.Write(school[1][1]);
Console.Write(school[1][2]);
Console.WriteLine();
Console.Write(school[2][0]);
Console.Write(school[2][1]);
Console.Write(school[2][2]);
Console.Write(school[2][3]);
Console.Write(school[2][4]);
Console.Write(school[2][5]);
Console.ReadKey();
}
}
}
这段代码定义了两个类:Student
和 School
,以及一个 Program
类,它包含了程序的入口点 Main
方法。下面是对这段代码的详细解释:
- Student 类:
-
_info
字段是一个字符数组,用于存储学生的姓名信息。 -
Student
类的构造函数接受一个字符数组作为参数,并将其赋值给_info
字段。 - 类还定义了一个索引器,允许通过索引访问和设置
_info
字段中的单个字符。索引器的get
和set
访问器都包含了边界检查,以确保索引值在有效范围内。如果索引超出范围,会在控制台输出错误消息,并返回一个空字符'\0'
(在get
访问器中)或不执行任何操作(在set
访问器中)。
- School 类:
-
_student
字段是一个Student
对象数组,每个对象代表一个学生。 - 类同样定义了一个索引器,允许通过索引访问和设置
_student
数组中的Student
对象。这个索引器也包含了边界检查,如果索引超出范围,会在控制台输出错误消息,并返回null
(在get
访问器中)或不执行任何操作(在set
访问器中)。
- Program 类:
-
Main
方法是程序的入口点。 - 创建了一个
School
类的实例school
。 - 使用索引器访问
school
数组中的每个学生,并打印出学生的姓名。由于Student
类的索引器允许访问_info
字段中的字符,这里实际上是在打印每个学生的姓名的每个字符。
在 Main
方法中,程序尝试打印出每个学生的姓名。例如,school[0][0]
会打印出第一个学生姓名的第一个字符('蔡'),school[0][1]
会打印出第二个字符('徐'),依此类推。对于第三个学生,由于其姓名有六个字符,所以 school[2][5]
会打印出第六个字符('兄')。
这段代码演示了如何在类中使用索引器来模拟数组的索引访问,并提供了一种方式来处理索引超出范围的情况。同时,它也展示了如何在类的实例之间共享数据(在这个例子中是学生姓名)并对其进行操作。
打印学生名字的代码:
School school = new School();
Console.Write(school[0][0]);
Console.Write(school[0][1]);
// ...
这段代码创建了一个 School
对象 school
,然后通过索引器访问每个学生的名字的每个字符,并将其打印到控制台。例如,school[0][0]
访问第一个学生的名字的第一个字符。
school[0][0]
这样的表达式能够正常工作,是因为在 School
类和 Student
类中都定义了索引器。这里的索引器允许您通过方括号 []
的语法来访问对象的属性或字段。这种语法通常用于数组或集合类型,但也可以用于任何定义了索引器的类。
让我们逐步分解 school[0][0]
这个表达式:
-
school
是School
类的一个实例。 -
school[0]
是对School
类索引器的调用,它使用索引0
来获取_student
数组中的第一个Student
对象。这个索引器返回的是Student
类型的对象。 -
school[0][0]
中的第二个索引器调用实际上是对Student
类索引器的调用。由于Student
类的索引器接受一个整数索引,它尝试获取Student
对象的_info
字段中的第一个字符(索引为0
)。
这种嵌套的索引器调用是可能的,因为 School
类的索引器返回了一个 Student
对象,而 Student
类也定义了自己的索引器。当您对 School
类的索引器返回的对象进行索引时,实际上是在调用那个对象的索引器。
这里是一个简化的例子来说明这个过程:
// School 类的索引器
public Student this[int index]
{
get { return _student[index]; }
}
// Student 类的索引器
public char this[int index]
{
get { return _info[index]; }
}
当您执行 school[0][0]
时:
-
school[0]
返回_student
数组的第一个Student
对象。 -
school[0][0]
实际上是(school[0])[0]
,即对第一个Student
对象调用索引器,获取其_info
字段的第一个字符。
这种索引器的链式调用在C#中是允许的,只要每个类都正确地定义了索引器,并且返回的类型支持进一步的索引访问。
让我们通过一个简单的例子来理解索引器的链式调用。假设我们有一个表示二维网格的类,这个类有两个索引器:一个用于访问网格的行(二维数组的一维表示),另一个用于访问行中的元素(二维数组的二维表示)。
首先,我们定义一个 Grid
类来表示这个二维网格:
public class Grid
{
// 字段:二维整数数组
private int[,] grid = new int[3, 3];
// 索引器:用于访问网格的行
public int[] this[int rowIndex]
{
get
{
// 返回指定行的数组
return new int[] { grid[rowIndex, 0], grid[rowIndex, 1], grid[rowIndex, 2] };
}
set
{
// 设置指定行的元素
grid[rowIndex, 0] = value[0];
grid[rowIndex, 1] = value[1];
grid[rowIndex, 2] = value[2];
}
}
// 索引器:用于访问网格中的单个元素
public int this[int rowIndex, int columnIndex]
{
get
{
// 返回指定行和列的元素
return grid[rowIndex, columnIndex];
}
set
{
// 设置指定行和列的元素
grid[rowIndex, columnIndex] = value;
}
}
}
在这个 Grid
类中,我们有两个索引器:
-
第一个索引器接受一个整数
rowIndex
作为参数,用于获取或设置网格的某一行。这个索引器返回一个整数数组,代表了网格的一行。 -
第二个索引器接受两个整数参数
rowIndex
和columnIndex
,用于直接获取或设置网格中特定行和列的元素。
现在,我们可以创建一个 Grid
类的实例,并使用链式索引器来访问和设置网格的元素:
class Program
{
static void Main(string[] args)
{
Grid myGrid = new Grid();
// 使用链式索引器设置第一行的元素
myGrid[0] = new int[] { 1, 2, 3 };
// 使用链式索引器设置第二行,第三列的元素
myGrid[1, 2] = 4;
// 使用链式索引器获取并打印第二行的内容
int[] row = myGrid[1];
foreach (int value in row)
{
Console.Write(value + " ");
}
Console.WriteLine();
}
}
在这个例子中,我们首先创建了一个 Grid
类的实例 myGrid
。然后,我们使用链式索引器来设置网格的第一行和第二行第三列的值。接着,我们使用第二个索引器来获取第二行的内容,并将其打印出来。
这个例子展示了如何通过索引器的链式调用来操作一个类的不同层级的属性。这种模式在处理复杂的数据结构时非常有用,尤其是当你需要提供一个直观的方式来访问嵌套的数据时。
网友评论