概述
此部分内容引用自MSDN文档
使用索引器可以用类似于数组的方式为对象建立索引。
get
取值函数返回值。set
取值函数分配值。this
关键字用于定义索引器。value
关键字用于定义set
索引器所赋的值。索引器不必根据整数值进行索引;由你决定如何定义特定的查找机制。
索引器可被重载。
索引器可以有多个形参,例如当访问二维数组时。
我对索引器的理解就是,他是一个读写自定义类中的数据集合的接口,连接自定义类中的数据集合,并可对其进行读写操作
通过该接口简化或者丰富对自定义类中数据集合的操作方式
索引器实际上相当于一个方法,支持多个及多种类型的参数,不同的是,其返回值不可为
void
,并且索引器除可传入参数外,还可对其进行赋值,即it[0] = "测试数据0"
创建索引器时,其返回值类型亦为其
value
关键字所使用的类型,即定义了返回值类型的同时,也定义了其可接受的值类型
索引器使用要素
创建索引器时有几部分内容是必须的:
必须先创建索引器所需要的容器(我把它称为容器,暂时还没看到有对它的具体定义)
创建索引器需要使用
this
关键字索引器中必须要包含
get
和set
访问器,在C#7.0可以使用表达式主体(=>
)简化在使用表达式主体成员实现索引器时,必须额外提供容器的修改接口,因为通过表达式主体实现的索引器是不包含
set
关键字的
单参数索引器
此索引器使用简单的string
数组作为容器,此索引器使用int
类型的i
进行索引,返回值为string
类型。
class SampleIndxer
{
//可供索引器使用的容器,暂用数组
private string[] sampleStrArr = new string[10];
//创建索引器
public string this[int i]
{
get { return sampleStrArr[i]; }
set { sampleStrArr[i] = value; }
}
}
class Test
{
public static void test()
{
//简单索引器测试
SampleIndxer it = new SampleIndxer();
it[0] = "测试数据0";
it[1] = "测试数据1";
Console.WriteLine("it[0]:" + it[0]);
Console.WriteLine("it[1]:" + it[1]);
Console.ReadLine();
}
}
索引器中同时也可以使用泛型作为参数
class SampleGenericIndexer<T>
{
//可供索引器使用的主体变量,暂用泛型数组代替
private T[] sampleGenericStrArr = new T[10];
public T this[int i]
{
get { return sampleGenericStrArr[i]; }
set { sampleGenericStrArr[i] = value; }
}
}
class Test
{
public static void test()
{
//泛型索引器测试
SampleGenericIndexer<string> it = new SampleGenericIndexer<string>();
it[0] = "测试数据0";
it[1] = "测试数据1";
Console.WriteLine("it[0]:" + it[0]);
Console.WriteLine("it[1]:" + it[1]);
Console.ReadLine();
}
}
在C#7.0之后可以通过表达式主体实现索引器,需要注意的是,通过表达式主体实现索引器时,必须提供数据修改的接口,因为通过表达式主体实现索引时仅提供了get
访问器,并未提供set
访问器。或者将容器的可访问性设置为使用该类的地方可以访问,直接对容器进行数据操作,仅使用索引器进行数据的读取。
class ExpressionBodyIndexer<T>
{
//可供索引器使用的主体变量,暂用泛型数组代替
private T[] expressionBodyStrArr = new T[10];
//标记当前索引器的中已初始化数据的索引位置
int nextIndex = 0;
// 使用表达式主体(ExpressionBody)定义简化定义索引器
public T this[int i] => expressionBodyStrArr[i];
/// <summary>
/// 表达式主体方式定义的索引器无法通过索引值设置其中的值
/// 因为此状态下,索引器的数据为只读状态
/// 必须向外提供赋值的方法
/// </summary>
/// <param name="value"></param>
public void Add(T value)
{
if(nextIndex >= expressionBodyStrArr.Length)
{
throw new IndexOutOfRangeException($"当前集合数据已满,共{expressionBodyStrArr.Length}组数据");
}
expressionBodyStrArr[nextIndex++] = value;
}
}
class Test
{
public static void test()
{
//泛型索引器测试
ExpressionBodyIndexer<string> it = new ExpressionBodyIndexer<string>();
//此条件下不可通过it[0]索引方式进行数据添加,因为他是只读的
//必须通过提供的Add方法添加数据
it.Add("测试数据0");
it.Add("测试数据1");
it.Add("测试数据2");
Console.WriteLine("it[0]:" + it[0]);
Console.WriteLine("it[1]:" + it[1]);
Console.WriteLine("it[2]:" + it[2]);
Console.ReadLine();
}
}
索引器既然是可以简化或者丰富对自定义类中数据集合的操作方式,那么自然也可以使用稍微复杂点的数据集合作为索引器的容器。本例中使用Dictionary作为容器。
class VariableLengthIndexer
{
/// <summary>
/// 可供索引器使用的容器,此处使用Dictionary代替,
/// 实现使用string类型数据当作索引器的指针,同时实现索引器的可变长度
/// </summary>
private Dictionary<string, string> dic = new Dictionary<string, string>();
/// <summary>
/// 使用表达式主体创建索引器
/// </summary>
/// <param name="s"></param>
/// <returns></returns>
public string this[string s] => dic[s];
public void Add(string key,string value)
{
if (dic.ContainsKey(key))
{
dic[key] = value;
}
else
{
dic.Add(key, value);
}
}
}
class Test
{
public static void test()
{
//泛型索引器测试
VariableLengthIndexer it = new VariableLengthIndexer();
//此条件下不可通过it[0]索引方式进行数据添加,因为他是只读的
//必须通过提供的Add方法添加数据
it.Add("数据0", "测试数据0");
it.Add("数据1", "测试数据1");
it.Add("数据2", "测试数据2");
Console.WriteLine("it[数据1]:" + it["数据1"]);
Console.WriteLine("it[数据2]:" + it["数据2"]);
Console.WriteLine("it[数据3]:" + it["数据3"]);
Console.ReadLine();
}
}
前面的几个例子中,仅仅是对于索引器的认识,实际工作中并没有使用价值,因为所作的操作完全可以使用 .NET 中预定义的数据集合完成。个人觉得C#7.0之后提供的表达式主体实际作用并不大,甚至没有必要。个人认为索引器最大价值存在于get
和set
访问器中对于数据操作的自定义处理,可以在访问器中对数据进行修正或者过滤,这才是其比较好的价值体现。
通过在索引器中对数据处理做封装,可以简化平常大部分的操作,此类也可根据实际情况嵌入到数据库访问实体类中。
/// <summary>
/// 本实例通过考试成绩的处理演示索引器对数据处理的过程
/// </summary>
class TestScore
{
private Dictionary<string, int> scores = new Dictionary<string, int>();
public string this[string s]
{
get
{
if (!scores.ContainsKey(s))
{
return $"非常抱歉,{s}的成绩尚未录入";
}
switch (scores[s])
{
case 10:
case 20:
case 30:
case 40:
case 50:
return $"很遗憾,{s}不及格,分数仅为{scores[s]}";
case 60:
case 70:
return $"考的不错,{s}已及格,分数为{scores[s]}";
case 80:
case 90:
return $"成绩优秀,{s}成绩优秀,分数为{scores[s]}";
case 100:
return $"非常优秀,{s}获取满分{scores[s]}分";
default:
return $"{s}的成绩可能存在异常,分数为{scores[s]}";
}
}
set
{
if (int.TryParse(value, out int v))
{
//对分数做四舍五入处理
v = (int)Math.Round(v * 0.1) * 10;
if (!scores.ContainsKey(s))
{
scores.Add(s, v);
}
else
{
scores[s] = v;
}
}
}
}
}
class Test
{
public static void test()
{
TestScore ts = new TestScore();
ts["张三"] = "23";
ts["李四"] = "54";
ts["王二"] = "66";
ts["麻子"] = "89";
ts["王朝"] = "100";
ts["马汉"] = "5";
ts["老王"] = "";
Console.WriteLine(ts["张三"]);
Console.WriteLine(ts["李四"]);
Console.WriteLine(ts["王二"]);
Console.WriteLine(ts["麻子"]);
Console.WriteLine(ts["王朝"]);
Console.WriteLine(ts["马汉"]);
Console.WriteLine(ts["老王"]);
Console.ReadLine();
}
}
多参数索引器
前面通过单参数所以其的实现分析了索引器的使用方式即可能的使用范围,下面进行下简单的拓展,分析多参数索引器的使用方式,依旧使用上面分数的例子做演示。
struct Student
{
public string Name;
public string Classes;
public string Grade;
public int Score;
public override string ToString()
{
return $"{this.Grade}\t{this.Classes}\t{this.Name}\t{this.Score}";
}
}
public class ArrayList1 : ArrayList
{
public override bool Contains(object item)
{
if (item.GetType().ToString() == "Student")
{
foreach (var a in this)
{
if (a.GetType().ToString() == "Student")
{
var s1 = (Student)a;
var s2 = (Student)item;
if (s1.Name == s2.Name && s1.Classes == s2.Classes && s1.Grade == s2.Grade)
{
return true;
}
return false;
}
}
}
return base.Contains(item);
}
}
class TestScore
{
public ArrayList1 ArrList = new ArrayList1();
public string this[string name, string grade, string classes]
{
get
{
string rtn = "";
foreach (Student a in ArrList)
{
if (a.Name == name && a.Classes == classes && a.Grade == grade)
{
switch (a.Score)
{
case 10:
case 20:
case 30:
case 40:
case 50:
rtn = $"很遗憾,{name}不及格,分数仅为{a.Score}";
break;
case 60:
case 70:
rtn = $"考的不错,{name}已及格,分数为{a.Score}";
break;
case 80:
case 90:
rtn = $"成绩优秀,{name}成绩优秀,分数为{a.Score}";
break;
case 100:
rtn = $"非常优秀,{name}获取满分{a.Score}分";
break;
default:
rtn = $"{name}的成绩可能存在异常,分数为{a.Score}";
break;
}
}
}
if (rtn == "")
{
return $"非常抱歉,{name}的成绩尚未录入";
}
return rtn;
}
set
{
if (int.TryParse(value, out int v))
{
//对分数做四舍五入处理
v = (int)Math.Round(v * 0.1) * 10;
Student st = new Student
{
Name = name,
Grade = grade,
Classes = classes,
Score = v
};
//重复项,不再插入,避免查找时出现重复
if (!ArrList.Contains(st))
{
ArrList.Add(st);
}
}
}
}
}
class Test
{
public static void test()
{
TestScore ts = new TestScore();
ts["张三", "三年级", "二班"] = "23";
ts["李四", "三年级", "二班"] = "54";
ts["王二", "三年级", "二班"] = "66";
ts["麻子", "三年级", "二班"] = "89";
ts["王朝", "三年级", "二班"] = "100";
ts["马汉", "三年级", "二班"] = "5";
ts["老王", "三年级", "二班"] = "";
Console.WriteLine("查看存入的数据:");
Console.WriteLine($"共存入了:{ts.ArrList.Count}组数据");
Console.WriteLine();
//不使用索引器,直接访问实例中的容器
foreach (Student s in ts.ArrList)
{
Console.WriteLine(s.ToString());
}
Console.WriteLine();
Console.WriteLine(ts["张三", "三年级", "二班"]);
Console.WriteLine(ts["李四", "三年级", "二班"]);
Console.WriteLine(ts["王二", "三年级", "二班"]);
Console.WriteLine(ts["麻子", "三年级", "二班"]);
Console.WriteLine(ts["王朝", "三年级", "二班"]);
Console.WriteLine(ts["马汉", "三年级", "二班"]);
Console.WriteLine(ts["老王", "三年级", "二班"]);
Console.ReadLine();
}
}
同时二维数组中多个参数的实现方式,同样也支持二维数组
public string[,] sampleStrArr = new string[10,10];
public string this[int x,int y]
{
get { return sampleStrArr[x, y]; }
set { sampleStrArr[x, y] = value; }
}
public static void test()
{
SampleIndxer it = new SampleIndxer();
it[0, 0] = "测试数据0,0";
it[0, 1] = "测试数据0,1";
it[1, 1] = "测试数据1,1";
it[1, 2] = "测试数据1,2";
it[3, 3] = "测试数据3,3";
Console.WriteLine("it[0,0]:" + it[0, 0]);
Console.WriteLine("it[0,1]:" + it[0, 1]);
Console.WriteLine("it[1,1]:" + it[1, 1]);
Console.WriteLine("it[1,2]:" + it[1, 2]);
Console.WriteLine("it[3,3]:" + it[3, 3]);
Console.ReadLine();
}
索引器的重载
前面说过,索引器相当于一个方法,他们同样都支持重载。与方法不同的是,索引器没有独立的名称,只能通过返回值的不同和参数的不同来区分不同的签名,从而实现重载。
class VariableLengthIndexer
{
private Dictionary<string, int> dic = new Dictionary<string, int>();
//通过Key,查找Value
public int this[string s]
{
get { return dic[s]; }
}
//通过Value查找Key
public string this[int num]
{
get { return dic.Where(x => x.Value == num).Last().Key; }
}
//通过Value查找Key,添加无效参数num1演示重载
public string this[int num, int num1]
{
get { return dic.Where(x => x.Value == num).Last().Key; }
}
public void Add(string key, int value)
{
if (dic.ContainsKey(key))
{
dic[key] = value;
}
else
{
dic.Add(key, value);
}
}
}
class Test
{
public static void test()
{
//泛型索引器测试
VariableLengthIndexer it = new VariableLengthIndexer();
it.Add("测试数据1", 1);
it.Add("测试数据2", 2);
it.Add("测试数据3", 3);
it.Add("测试数据4", 4);
//通过Key查找Value
Console.WriteLine("通过Key查找Value");
Console.WriteLine("Key:测试数据1,Value:" + it["测试数据1"]);
Console.WriteLine("Key:测试数据2,Value:" + it["测试数据2"]);
Console.WriteLine("Key:测试数据3,Value:" + it["测试数据3"]);
Console.WriteLine("Key:测试数据4,Value:" + it["测试数据4"]);
//通过Value查找Key
Console.WriteLine("通过Value查找Key");
Console.WriteLine("Value:1,Key:" + it[1]);
Console.WriteLine("Value:2,Key:" + it[2]);
Console.WriteLine("Value:3,Key:" + it[3]);
Console.WriteLine("Value:4,Key:" + it[4]);
//通过Value查找Key,并添加无效参数传入
Console.WriteLine("通过Value查找Key,并添加无效参数传入");
Console.WriteLine("Value:1,Key:" + it[1, 1]);
Console.WriteLine("Value:2,Key:" + it[2, 2]);
Console.WriteLine("Value:3,Key:" + it[3, 3]);
Console.WriteLine("Value:4,Key:" + it[4, 4]);
Console.ReadLine();
}
}
参考文献:
1 C# 中常用的索引器 https://www.cnblogs.com/daimajun/p/6819081.html
2 索引器(C# 编程指南)https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/indexers/