学习 C# (第 8 天):C# 中的索引器 (实用方法)






4.93/5 (44投票s)
OOP:C# 中的索引器(实践方法)
目录
- 目录
- 引言
- C# 中的索引器(定义)
- 路线图
- 索引器(解释)
- 索引器中的数据类型
- 接口中的索引器
- 抽象类中的索引器
- 索引器重载
- 静态索引器?
- 索引器中的继承/多态
- .NET Framework 和索引器
- 属性与索引器
- 结论
引言
在本系列的上篇文章中,我们学习了 C# 中的属性。本系列文章“深入 OOP”将解释 C# 中索引器的所有内容、其用途和实际实现。我们将遵循相同的学习方式,即少理论多实践。我将尝试深入解释这个概念。
C# 中的索引器(定义)
让我们从 https://msdn.microsoft.com/en-us/library/6x16t2tx.aspx 获取定义,
“索引器允许像数组一样对类或结构体的实例进行索引。索引器类似于 属性,只是它们的访问器带有参数。”
路线图
让我们回顾一下我们的路线图,
- 深入 OOP(第一天):多态与继承(早期绑定/编译时多态)
- 深入OOP(第2天):多态性和继承(继承)
- 深入OOP(第3天):多态性和继承(动态绑定/运行时多态)
- 深入 OOP (第四天):多态与继承 (关于 C# 中的抽象类)
- 深入OOP(第5天):C#中访问修饰符的一切(Public/Private/Protected/Internal/Sealed/Constants/Readonly字段)
- 学习 C#(第 6 天):理解 C# 中的枚举(一种实用方法)
- 学习 C# (第 7 天):C# 中的属性 (实用方法)
- 学习 C# (第 8 天):C# 中的索引器 (实用方法)
- 学习 C#(第 9 天):理解 C# 中的事件(深入探讨)
- 学习C#(第10天):C#中的委托(一种实用方法)
- 学习C#(第11天):C#中的事件(一种实用方法)
索引器(解释)
正如定义所说,索引器允许我们利用像数组一样访问类对象的能力。
为了更好地理解,创建一个名为 Indexers 的控制台应用程序,并向其中添加一个名为 Indexer 的类。我们将使用这个类和项目来学习索引器。将类设置为 public,暂时不添加任何代码,并在 Program.cs 中添加以下代码:
实验 1
namespace Indexers
{
class Program
{
static void Main(string[] args)
{
Indexer indexer=new Indexer();
indexer[1] = 50;
}
}
}
编译代码。我们得到:
错误 无法对 'Indexers.Indexer' 类型的表达式应用带 [] 的索引
我只是创建了一个 Indexer 类的对象,并尝试像数组一样使用该对象。由于它实际上不是一个数组,因此导致编译时错误。
实验 2
Indexer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Indexers
{
public class Indexer
{
public int this[int indexValue]
{
set
{
Console.WriteLine("I am in set : Value is " + value + " and indexValue is " + indexValue);
Console.ReadLine();
}
}
}
}
Program.cs
namespace Indexers
{
class Program
{
static void Main(string[] args)
{
Indexer indexer=new Indexer();
indexer[1] = 50;
}
}
}
输出
在这里,我们只是使用索引器来索引 Indexer 类的对象。现在,我的对象可以像数组一样用于访问不同的对象值。
索引器的实现源自一个名为“this”的属性。它接受一个整数参数 indexValue。索引器与属性不同。在属性中,当我们想要初始化或赋值时,如果定义了“set”访问器,它会自动被调用。“set”访问器中的关键字“value”用于保存或跟踪分配给属性的值。在上面的例子中,indexer[1] = 50;
调用“this”属性的“set”访问器,即索引器,因此 50 成为值,1 成为该值的索引。
实验 3
Indexer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Indexers
{
public class Indexer
{
public int this[int indexValue]
{
set
{
Console.WriteLine("I am in set : Value is " + value + " and indexValue is " + indexValue);
}
get
{
Console.WriteLine("I am in get and indexValue is " + indexValue);
return 30;
}
}
}
}
Program.cs
using System;
namespace Indexers
{
class Program
{
static void Main(string[] args)
{
Indexer indexer=new Indexer();
Console.WriteLine(indexer[1]);
Console.ReadKey();
}
}
}
输出
在上面的代码片段中,我也使用了 get 来访问索引器的值。属性和索引器遵循相同的规则集。它们的使用方式略有不同。当我们执行 indexer[1] 时,表示调用了“get”访问器;当我们给 indexer[1] 赋值时,表示调用了“set”访问器。在实现索引器代码时,我们必须注意,当访问索引器时,它以变量形式访问,并且也是数组参数。
索引器中的数据类型
实验 1
Indexer.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Indexers
{
public class Indexer
{
public int Index;
public int this[string indexValue]
{
set
{
Console.WriteLine("I am in set : Value is " + value + " and indexValue is " + indexValue);
Index = value;
}
get
{
Console.WriteLine("I am in get and indexValue is " + indexValue);
return Index;
}
}
}
}
Program.cs
using System;
namespace Indexers
{
class Program
{
static void Main(string[] args)
{
Indexer indexer=new Indexer();
indexer["name"]=20;
Console.WriteLine(indexer["name"]);
Console.ReadKey();
}
}
}
输出
“this
”属性,即索引器,具有返回值。在我们的例子中,返回值是整数。方括号和“this
”也可以保存其他数据类型,而不仅仅是整数。在上面提到的例子中,我试图通过使用字符串参数类型来解释这一点,例如:public int this[string indexValue],
字符串参数“indexValue
”的值是“name”,就像我们在 Program.cs 的 Main 方法中传递的那样。因此,一个类可以有多个索引器,用于决定数组参数值的数据类型。索引器,像属性一样,遵循相同的继承和多态规则。
接口中的索引器
像属性和方法一样,索引器也可以在接口中声明。
为了实际实现,只需创建一个名为 IIndexers
的接口,其代码如下:
namespace Indexers
{
interface IIndexers
{
string this[int indexerValue] { get; set; }
}
}
这里,声明了一个带有空 get 和 set 访问器的索引器,它返回字符串值。
现在我们需要一个实现此接口的类。您可以定义一个您选择的类并通过 IIndexers
接口实现它,
Indexer.cs
using System;
namespace Indexers
{
public class IndexerClass:IIndexers
{
readonly string[] _nameList = { "AKhil","Bob","Shawn","Sandra" };
public string this[int indexerValue]
{
get
{
return _nameList[indexerValue];
}
set
{
_nameList[indexerValue] = value;
}
}
}
}
该类有一个默认的字符串数组,用于存储名称。现在我们可以在这个类中实现接口定义的索引器,以编写我们的自定义逻辑,根据 indexerValue
获取名称。让我们在 main 方法中调用它,
Program.cs
using System;
namespace Indexers
{
class Program
{
static void Main(string[] args)
{
IIndexers iIndexer=new IndexerClass();
Console.WriteLine(iIndexer[0]);
Console.WriteLine(iIndexer[1]);
Console.WriteLine(iIndexer[2]);
Console.WriteLine(iIndexer[3]);
Console.ReadLine();
}
}
}
运行应用程序。输出:
在 main 方法中,我们使用接口引用创建了一个 IndexerClass
的对象,并通过索引器值像数组一样访问该对象数组。它逐个给出名称。
现在,如果我也想访问“set”访问器,我可以很容易地做到。要检查这一点,只需在索引器中设置值的地方再添加两行代码,
iIndexer[2] = "Akhil Mittal";
Console.WriteLine(iIndexer[2]);
我将第二个元素的值设置为新名称,让我们看看输出,
抽象类中的索引器
就像我们在接口中使用索引器一样,我们也可以在抽象类中使用索引器。我将使用与接口相同的源代码逻辑,这样您就可以理解它在抽象类中是如何工作的。只需定义一个新的抽象类,它应该包含一个带有空 get 和 set 的抽象索引器,
抽象基类
namespace Indexers
{
public abstract class AbstractBaseClass
{
public abstract string this[int indexerValue] { get; set; }
}
}
定义派生类,继承自抽象类,
索引器类
我们在这里使用 override 关键字来重写在抽象类中声明的抽象索引器。
using System;
namespace Indexers
{
public class IndexerClass:AbstractBaseClass
{
readonly string[] _nameList = { "AKhil","Bob","Shawn","Sandra" };
public override string this[int indexerValue]
{
get
{
return _nameList[indexerValue];
}
set
{
_nameList[indexerValue] = value;
}
}
}
}
Program.cs
我们将使用抽象类的引用来创建 Indexer
类的对象。
using System;
namespace Indexers
{
class Program
{
static void Main(string[] args)
{
AbstractBaseClass absIndexer=new IndexerClass();
Console.WriteLine(absIndexer[0]);
Console.WriteLine(absIndexer[1]);
Console.WriteLine(absIndexer[2]);
Console.WriteLine(absIndexer[3]);
absIndexer[2] = "Akhil Mittal";
Console.WriteLine(absIndexer[2]);
Console.ReadLine();
}
}
}
输出
以上所有代码都是不言自明的。您可以自行探索更多场景以获得更详细的理解。
索引器重载
Indexer.cs
using System;
namespace Indexers
{
public class Indexer
{
public int this[int indexerValue]
{
set
{
Console.WriteLine("Integer value " + indexerValue + " " + value);
}
}
public int this[string indexerValue]
{
set
{
Console.WriteLine("String value " + indexerValue + " " + value);
}
}
public int this[string indexerValue, int indexerintValue]
{
set
{
Console.WriteLine("String and integer value " + indexerValue + " " + indexerintValue + " " + value);
}
}
}
}
Program.cs
using System;
namespace Indexers
{
class Program
{
static void Main(string[] args)
{
Indexer indexer=new Indexer();
indexer[1] = 30;
indexer["name"]=20;
indexer["address",2] = 40;
Console.ReadLine();
}
}
}
输出
在上面的例子中,我们看到索引器的签名实际上是实际参数的数量和数据类型,与参数的名称或索引器的返回值无关。这允许我们像方法重载一样重载索引器。您可以在 https://codeproject.org.cn/Articles/771455/Diving-in-OOP-Day-Polymorphism-and-Inheritance-Ear 中阅读更多关于方法重载的内容。现在我们已经重载了索引器,它接受整数、字符串整数和字符串的组合作为实际参数。就像方法不能基于返回类型重载一样,索引器遵循与方法相同的重载方法。
要记住的要点
与索引器不同,我们不能重载属性。属性更像是通过名称识别,而索引器更像是通过签名识别。
静态索引器?
在我们上一节讨论的例子中,只需在索引器签名中添加一个 static 关键字,
public static int this[int indexerValue]
{
set
{
Console.WriteLine("Integer value " + indexerValue + " " + value);
}
}
编译程序;我们得到一个编译时错误,
错误 修饰符 'static' 对此项无效
错误清楚地表明索引器不能标记为静态。索引器只能是类实例成员,而不能是静态的,另一方面,属性也可以是静态的。
要记住的要点
属性可以是静态的,但索引器不能是。
索引器中的继承/多态
Indexer.cs
using System;
namespace Indexers
{
public class IndexerBaseClass
{
public virtual int this[int indexerValue]
{
get
{
Console.WriteLine("Get of IndexerBaseClass; indexer value: " + indexerValue);
return 100;
}
set
{
Console.WriteLine("Set of IndexerBaseClass; indexer value: " + indexerValue + " set value " + value);
}
}
}
public class IndexerDerivedClass:IndexerBaseClass
{
public override int this[int indexerValue]
{
get
{
int dValue = base[indexerValue];
Console.WriteLine("Get of IndexerDerivedClass; indexer value: " + indexerValue + " dValue from base class indexer: " + dValue);
return 500;
}
set
{
Console.WriteLine("Set of IndexerDerivedClass; indexer value: " + indexerValue + " set value " + value);
base[indexerValue] = value;
}
}
}
}
Program.cs
using System;
namespace Indexers
{
class Program
{
static void Main(string[] args)
{
IndexerDerivedClass indexDerived=new IndexerDerivedClass();
indexDerived[2] = 300;
Console.WriteLine(indexDerived[2]);
Console.ReadLine();
}
}
}
输出
上面提供的示例代码解释了索引器中的运行时多态性和继承。我创建了一个名为 IndexerBaseClass
的基类,它包含一个带有自己的 get 和 set 的索引器,就像我们之前讨论的示例一样。之后,创建了一个名为 IndexerDerivedClass
的派生类,它继承自 IndexerBaseClass
并覆盖了基类中的“this”索引器,请注意基类索引器被标记为 virtual,因此我们可以在派生类中通过将其标记为“override”来覆盖它。该示例调用了基类的索引器。有时,当我们需要在派生类中覆盖代码时,我们可能需要首先调用基类索引器。这只是一种情况。相同的运行时多态性规则也适用于此,我们将基类索引器声明为 virtual,将派生类索引器声明为 override。在派生类的“set”访问器中,我们可以调用基类索引器,如 base[indexerValue]
。此外,此值也用于初始化派生类索引器。因此,该值也存储在“value”关键字中。因此,在 Program.cs 的 Main()
方法中,indexDerived[2]
被替换为“set”访问器中的 base[2]
。而在“get”访问器中则相反,我们需要将 base[indexerValue]
放在等号的右侧。“get”访问器在基类中返回一个值,即 100,我们在 dValue
变量中获取该值。
.NET Framework 和索引器
索引器在 .NET 框架中扮演着至关重要的角色。索引器广泛应用于 .NET Framework 内置类、库(例如集合和可枚举对象)。索引器用于可搜索的集合,如 Dictionary、Hashtable、List、ArrayList 等。
要记住的要点
C# 中的 Dictionary 大量使用索引器,将起始参数作为索引器参数。
ArrayList 和 List 等类在内部使用索引器,为获取和使用元素提供数组功能。
属性与索引器
我已经详细解释了属性和索引器,总结一下,为了更好地理解,让我指出一个 MSDN 链接:
属性 | 索引器 |
允许像调用公共数据成员一样调用方法。 | 允许通过在对象本身上使用数组表示法来访问对象的内部集合的元素。 |
通过简单名称访问。 | 通过索引访问。 |
可以是静态成员或实例成员。 | 必须是实例成员。 |
属性的 get 访问器没有参数。 | 索引器的 get 访问器具有与索引器相同的形式参数列表。 |
属性的 set 访问器包含隐式 value 参数。 | 索引器的 set 访问器具有与索引器相同的形式参数列表,并且还包含 value 参数。 |
支持使用 自动实现的属性(C# 编程指南) 简化语法。 | 不支持简化语法。 |
结论
通过本文,我们几乎完成了与索引器相关的所有场景。我们进行了大量实践操作来澄清我们的概念。我希望我的读者现在能够牢记这些基本概念,并且永远不会忘记它们。这些也可能帮助您通过 C# 面试。
继续编程,享受阅读
如果我的文章对您有任何帮助,请不要忘记点赞/评论/分享我的文章,这会激励我并鼓励我写更多文章。
我的其他系列文章
MVC: https://codeproject.org.cn/Articles/620195/Learning-MVC-Part-Introduction-to-MVC-Architectu
RESTful WebAPIs: https://codeproject.org.cn/Articles/990492/RESTful-Day-sharp-Enterprise-Level-Application
祝您编码愉快!