C#8.0特性篇之索引和范围

开篇解读:

        索引和范围的特性,可以使我们很方便的获取序列(比如数组)中的某一段数据或者某个位置的数据,跟python的索引差不多。为此,C#添加了两个新的类型和两个新的运算符以达到此目的。

一、定义

索引和范围为访问序列中的单个元素或范围提供了简洁的语法。

此语言支持依赖于两个新类型和两个新运算符:

  • System.Index 表示一个序列索引。
  • 来自末尾运算符 ^ 的索引,指定一个索引与序列末尾相关。
  • System.Range 表示序列的子范围。
  • 范围运算符 ..,用于指定范围的开始和末尾,就像操作数一样。

二、使用

2.1 索引

string[] words = new string[]
{
                // index from start    index from end
    "The",      // 0                   ^9
    "quick",    // 1                   ^8
    "brown",    // 2                   ^7
    "fox",      // 3                   ^6
    "jumped",   // 4                   ^5
    "over",     // 5                   ^4
    "the",      // 6                   ^3
    "lazy",     // 7                   ^2
    "dog"       // 8                   ^1
};              // 9 (or words.Length) ^0

//1. 正向索引是从0~9,负向索引是从^9~^0  其中9跟^0等同于Length,无效的索引,同样会引发异常。
var dogStr = words[^1];    // "dog"
var quickStr = words[1];   // "quick"  等同于 words[^8]
Index the = ^3;
var theStr = words[the];   // "the"

//2. 对于任何数字 n, words[^n] = words[words.Length - n]

2.2 范围

//3. 范围Range表示:n..m, 是前闭后开的一个范围[1,3)
string[] quickBrownFox = words[1..4];  // 含有quick,Brown,Fox三个字符串的数组,不包含索引4的值
string[] allWords = words[..];         // contains "The" through "dog".
string[] firstPhrase = words[..4];     // contains "The" through "fox"
string[] lastPhrase = words[6..];      // contains "the, "lazy" and "dog"

//4. 直接声明变量
Range phrase = 1..4;
string[] text = words[phrase];

2.3 类比其他语言

C#表示一个索引范围:      0..^0

Python表示一个索引范围:0:-0

三、目的以及实现原理

3.1 目的:为访问序列中的单个元素或范围提供了简洁的语法。

3.2 实现原理

新的索引器将通过将类型的参数转换 System.Index 为 int 并发出对基于索引器的调用来实现 int。

其实就是编译后对索引进行了额外处理。

// 字符串:编译前
string str = "hello world";
var s2 = str[0..^1];
// 字符串:编译后(计算0..^1 对应的"真实"索引)
int num = 0;
int num2 = length - 1 - num;
string s2 = text.Substring(num, num2);

// List:编译前
List<string> m_List = new List<string>();
var l1 = m_List[^1];
// List:编译后(计算^1 对应的"真实"索引)
num2 = list.Count - 1;
string l = list[num2];

// 数组:编译前
var strs = words[^3..2];
// 数组:编译后
string[] str = RuntimeHelpers.GetSubArray<string>(words, new Range(new Index(3, true), 2));

关于System.Index和system.Range类型的定义如下:

 

 

   说明:

        1、从上面的定义中就可以看到,Index类型的实现实际上就是存了一个int的值,在构造函数中,根据传入的fromEnd的值,if true,则会将value取反,否则正常存为一个int。

        当然还有一个重要的属性,IsFromEnd(是否是从后开始),取决于这个值的正负。如果是正,则是正向索引,如果是负(因为取反了),则是逆向索引。如果是负的,当取值的时候会再次取反。

        2、Range就是持有了两个Index,一个start,一个end。   

3.3 有哪些类型支持索引和范围?

1、若任何类型提供带 Index 或 Range 参数的索引器,则该类型可分别显式支持索引或范围

2、若类型包含名称为 Length 或 Count 的属性,属性有可访问的 Getter 并且其返回类型为 int,则此类型为可计数类型。 不显式支持索引或范围的可计数类型可能为它们提供隐式支持。

比如:

以下 .NET 类型同时支持索引和范围:StringSpan<T> 和 ReadOnlySpan<T>。 List<T> 支持索引,但不支持范围。

Array 具有更多的微妙行为。 单个维度数组同时支持索引和范围。 多维数组不支持索引器或范围。

详情参考:索引和范围的类型支持

3.4 自己定义的类型也想支持索引和范围怎么做

1、显示支持索引或范围

public class MyClass
    {
        List<int> m_List = new List<int>();

        public int this[Index index]
        {
            get 
            {
                var idx = index.IsFromEnd ? m_List.Count - index.Value : index.Value;
                if (idx < 0 || idx >= m_List.Count)
                {
                    return -1;
                }

                return m_List[idx]; 
            }
        }

        public List<int> this[Range range]
        {
            get
            {
                var sIdx = range.Start;
                var eIdx = range.End;
                var startIndex = sIdx.IsFromEnd ? m_List.Count - sIdx.Value : sIdx.Value;
                var endIndex = eIdx.IsFromEnd ? m_List.Count - eIdx.Value : eIdx.Value;
                var newList = new List<int>();
                for (int i = startIndex; i < endIndex; i++)
                {
                    newList.Add(m_List[i]);
                }

                return newList;
            }
        }
}

2、满足以下条件,会隐式提供支持

  • 类型为可数(提供名为Length或Count的属性,并开放Getter,返回值为int)
  • 该类型具有可访问的实例索引器,该索引器采用单个 int 作为参数。

其实就是通过长度和int的索引器实现,来支撑查找逆序索引,就可以实现Index的索引器

    public class MyClass
    {
        List<int> m_List = new List<int>();

        public int Length
        {
            get { return m_List.Count; }
        }

        public int this[int index]
        {
            get
            {
                var idx = index;
                if (idx < 0 || idx >= m_List.Count)
                {
                    return -1;
                }

                return m_List[idx];
            }
        }
}

隐式索引支持

隐式范围支持

四、 总结

4.1 优点

方便访问序列中的单个元素或范围

4.2 限制

有一些类型不支持,自己要实现的话有一些限制条件

4.3 注意事项

注意性能问题,比如:

在所有情况下,Array 的范围运算符都会分配一个新的数组来存储返回的元素。

public static T[] GetSubArray<[Nullable(2)] T>(T[] array, Range 
{
    if (array == null)
	{
		ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
	}
	ValueTuple<int, int> offsetAndLength = range.GetOffsetAndLength(array.Length);
	int item = offsetAndLength.Item1;
	int item2 = offsetAndLength.Item2;
	if (default(T) == null && !(typeof(T[]) == array.GetType()))
	{
		T[] array2 = (T[])Array.CreateInstance(array.GetType().GetElementType(), item2);
		Array.Copy(array, item, array2, 0, item2);
		return array2;
	}
	if (item2 == 0)
	{
		return Array.Empty<T>();
	}
	T[] array3 = new T[item2];

    // 从array里第item个元素开始,copy到array3里,长度为item2
	Buffer.Memmove<T>(Unsafe.As<byte, T>(array3.GetRawSzArrayData()), Unsafe.Add<T>(Unsafe.As<byte, T>(array.GetRawSzArrayData()), item), (ulong)item2);
	return array3;
}

参考:官方文档

使用索引和范围探索数据范围 | Microsoft Docs

Ranges and indices - C# 8.0 specification proposals | Microsoft Docs

时刻保持对代码的敬畏之心,我是一个游戏码农,一个追梦的人。
喜欢就点个关注吧。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值