C # also has sliced grammar sugar. It's great

1: Background

1. Tell a story

Yesterday, I was looking for some new expressions of C# 9 on github. I found a strange expression in a document: foreach (var item in myArray[0..5]) haha, familiar and strange. My friends who have played python are too familiar with this [0.. 5], but I actually met it in C #. Happy ha, I saw 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 semantics of myArray[0..5] introduced earlier, we can also see that this is an operation of segmenting array. 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 #, you need 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 the three position elements with index = 4, 5 and 6 in the array

If you use linq, you need to use Skip + Take double combination. If you use slicing, it's 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,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)}");
}


From the output results of the above cutting interval [4.. 7], this is an interval closed on the left and open on the right, so we should pay special attention to it.

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. Get 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. The next step is to see what is used as the support inside. 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<string>(myarr, new Range(0, 3));

It can be seen from the compiled code that the original array to obtain slices is to call runtimehelpers Get getsubarray, and then I'll simplify this method. The code is as follows:

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

As can be seen from the above code, the last sub array is composed of buffer Memmove is completed, but the cutting position of the sub array is realized by the GetOffsetAndLength method. Continue to follow the code:

 public readonly struct Range : IEquatable<Range>
    {   
        public Index Start { get; }
        public Index End { get; }
 
  public Range(Index start, Index end)
  {
   this.Start = start;
   this.End = end;
  }
 
        public ValueTuple<int, int> 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<int, int>(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. It's 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 roughly: new range (1,3) - > operator index (int value) - > fromstart (value) - > new index (value). It can be seen that the optional parameters are 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<string>(myarr, Range.StartAt(new Index(3, true)));

You 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. Acting on arr can greatly reduce the use of skip & take, and acting on string can also greatly reduce the use of SubString, such as: "12345" [1.. 3] - > "12345" SubString (1, 2), hey hey, it's awesome! Or C# Dafa???

Tags: C# linq

Posted by akimm on Tue, 03 May 2022 10:54:07 +0300