C # also has sliced grammar candy. That’s great

Time:2021-12-27

1: Background

1. Tell a story

Yesterday, I was going to look for c# 9 on GitHub to try out some new expressions. I didn’t realize that I saw a very strange expression in a document:foreach (var item in myArray[0..5])Ha ha, familiar and strange. Friends who have played Python are interested in this[0..5]I’m so familiar with C # that I actually met it in C # 8. I’m happy. After reading the new grammar of C # 8, it’s ironic. I made 9 before I was familiar with 8. I have a strong desire to explore. I always want to see what supports the bottom of this thing.

2: Usage of grammar sugar

From the previous introductionmyArray[0..5]Semantically, it can also be seen that this is a segmentation array operation. How many segmentation methods are there? Let’s introduce one by one. To facilitate the demonstration, I first define an array. The code is as follows:

var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };

1. Extract the first three elements of arr

If LINQ is used, take (3) can be used. If slicing is used, it is [0.. 3]. The code is as follows:

static void Main(string[] args)
        {
            var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };

            //1.  Get the first 3 elements of the array
            var query1 = myarr[0..3];

            var query2 = myarr.Take(3).ToList();

            Console.WriteLine($"query1={string.Join(",", query1)}");
            Console.WriteLine($"query2={string.Join(",", query2)}");
        }

2. Extract the last three elements of arr

How to extract this? In Python, it’s OK to use – 3 directly. In C #, it’s necessary to use ^ to indicate starting from the end. The code is as follows:

static void Main(string[] args)
        {
            var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };

            //1.  Get the last 3 elements of the array
            var query1 = myarr[^3..];

            var query2 = myarr.Skip(myarr.Length - 3).ToList();

            Console.WriteLine($"query1={string.Join(",", query1)}");
            Console.WriteLine($"query2={string.Join(",", query2)}");
        }

3. Extract three position elements with index = 4, 5 and 6 in the array

If you use LINQ, you need to useSkip + TakeDouble combination, if slicing operation is used, it is too simple…

static void Main(string[] args)
        {
            var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };

            //1.  Get the elements at index = 4, 5 and 6 in the array
            var query1 = myarr[4..7];

            var query2 = myarr.Skip(4).Take(3).ToList();

            Console.WriteLine($"query1={string.Join(",", query1)}");
            Console.WriteLine($"query2={string.Join(",", query2)}");
        }

Cut section from above[4..7]From the output results, this is aLeft closed right openSo pay special attention.

4. Get the penultimate and second elements in the array

In terms of requirements, it is to obtain elements 80 and 90. If you understand the above two usages, I believe you will write this quickly. The code is as follows:

static void Main(string[] args)
        {
            var myarr = new string[] { "10", "20", "30", "40", "50", "60", "70", "80", "90", "100" };

            //1.  Gets the penultimate and second elements in the array
            var query1 = myarr[^3..^1];

            var query2 = myarr.Skip(myarr.Length - 3).Take(2).ToList();

            Console.WriteLine($"query1={string.Join(",", query1)}");
            Console.WriteLine($"query2={string.Join(",", query2)}");
        }

III Inquiry principle

Through the above four examples, I think everyone knows how to play. Next, let’s see what the internal support is made of. Here, use dnspy to dig.

1. From myarr [0.. 3]

Decompile the code with dnspy as follows:

//Before Compilation
    var query1 = myarr[0..3];

    //After compilation:
	string[] query = RuntimeHelpers.GetSubArray(myarr, new Range(0, 3));

From the compiled code, it can be seen that the original array for obtaining slices is a callRuntimeHelpers.GetSubArrayI got it, and then I simplified the method. The code is as follows:

public static T[] GetSubArray(T[] array, Range range)
        {
            ValueTuple offsetAndLength = range.GetOffsetAndLength(array.Length);
            int item = offsetAndLength.Item1;
            int item2 = offsetAndLength.Item2;
            T[] array3 = new T[item2];
            Buffer.Memmove(Unsafe.As(array3.GetRawSzArrayData()), Unsafe.Add(Unsafe.As(array.GetRawSzArrayData()), item), (ulong)item2);
            return array3;
        }

As can be seen from the above code, the last sub array is composed ofBuffer.MemmoveComplete, but the cutting position for the sub array is determined byGetOffsetAndLengthMethod implementation, continue to trace the code:

public readonly struct Range : IEquatable
    {   
        public Index Start { get; }
        public Index End { get; }

		public Range(Index start, Index end)
		{
			this.Start = start;
			this.End = end;
		}

        public ValueTuple GetOffsetAndLength(int length)
        {
            Index start = this.Start;
            int num;
            if (start.IsFromEnd)
            {
                num = length - start.Value;
            }
            else
            {
                num = start.Value;
            }
            Index end = this.End;
            int num2;
            if (end.IsFromEnd)
            {
                num2 = length - end.Value;
            }
            else
            {
                num2 = end.Value;
            }
            return new ValueTuple(num, num2 - num);
        }
    }

After reading the above code, you may have two doubts:

1) start. Isfromend and end What does isfromend mean.

In fact, after reading the above code logic, you will understand that isfromend indicates whether the starting point starts from the left or the right, which is so simple.

2) I didn’t see start Isfromend and end How isfromend is assigned a value.

In the constructor of index class, it depends on how the upper layer inserts true or false when going to new index, as shown in the following code:

The process of this example is as follows:new Range(1,3) -> operator Index(int value) -> FromStart(value) -> new Index(value), you can see that the optional parameter is not assigned at the time of new.

2. Explore myarr [^ 3…]

The example just now did not assign values to optional parameters, so let’s see if this example is the time to assign values to new index?

//Before compilation:
var query1 = myarr[^3..];

//After compilation:
string[] query = RuntimeHelpers.GetSubArray(myarr, Range.StartAt(new Index(3, true)));

See, this time when you use the new index, you give isfromend = true, which means that the calculation starts from the end. Combined with the getoffsetandlength method just now, I think you should straighten out the logic.

4: Summary

Generally speaking, this slicing operation is very practical. It can greatly reduce the use of skip & take when applied to arr and substring when applied to string, such as:"12345"[1..3] -> "12345".Substring(1, 2), hey, hey, awesome! Or c# Dafa

More high quality dry goods: see my GitHub:dotnetfly

图片名称