معرفی عملگر Hat
برای دسترسی به آخرین عضو یک آرایه عموما از روش زیر استفاده میشود:
var integerArray = new int[3]; var lastItem = integerArray[integerArray.Length - 1];
var integerList = integerArray.ToList(); integerList.Last();
var secondToLast = integerArray[integerArray.Length - 2];
این شمردنهای از آخر در C# 8.0 توسط ارائهی عملگر hat یا همان ^ که پیشتر کار xor را انجام میداد (و البته هنوز هم در جای خودش همین مفهوم را دارد)، میسر شدهاست:
var lastItem = integerArray[^1];
نکتهی مهم: کسانیکه شروع به آموزش برنامه نویسی میکنند، مدتی طول میکشد تا عادت کنند که اولین ایندکس یک آرایه از صفر شروع میشود. در اینجا باید درنظر داشت که با بکارگیری «عملگر کلاه»، آخرین ایندکس یک آرایه از «یک» شروع میشود و نه از صفر. برای نمونه در مثال زیر به خوبی تفاوت بین ایندکس از ابتدا و ایندکس از انتها را میتوانید مشاهده کنید:
var 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
در حالت کلی ایندکس n^ معادل sequence.Length - n است. بنابراین sequence[^0] به معنای sequence[sequence.Length] است و هر دو مورد یک index out of range exception را صادر میکنند.
IDE نیز با فعال سازی C# 8.0، زمانیکه به قطعه کد زیر میرسد، زیر words.Length - 1 خط کشیده و پیشنهاد میدهد که بهتر است از 1^ استفاده کنید:
Console.WriteLine($"The last word is {words[words.Length - 1]}");
معرفی نوع جدید Index
در C# 8.0 زمانیکه مینویسم 1^، در حقیقت قطعه کد زیر را ایجاد کردهایم:
var index = new Index(value: 1, fromEnd: true); Index indexStruct = ^1; var indexShortHand = ^1;
در سطر اول، پارامتر fromEnd نیز قابل تعریف است. این fromEnd با مقدار true، همان عملگر ^ در اینجا است و عدم ذکر این عملگر به معنای false بودن آن است:
int[] a = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; Console.WriteLine(a[a.Length – 2]); // will write 8 on console. Console.WriteLine(a[^2]); // will write 8 on console. Index i5 = 5; Console.WriteLine(a[i5]); //will write 5 on console. Index i2fromEnd = ^2; Console.WriteLine(a[i2fromEnd]); // will write 8 on console.
روش دسترسی به بازهای از اعضای یک آرایه تا پیش از C# 8.0
فرض کنید آرایهای از اعداد بین 1 تا 10 را به صورت زیر ایجاد کردهاید:
var numbers = Enumerable.Range(1, 10).ToArray();
var (start, end) = (1, 7); var length = end - start; // Using LINQ var subset1 = numbers.Skip(start).Take(length); // Or using Array.Copy var subset2 = new int[length]; Array.Copy(numbers, start, subset2, 0, length);
روش دسترسی به بازهای از اعضای یک آرایه در C# 8.0
در C# 8.0 برای دسترسی به بازهای از عناصر یک آرایه میتوان از range expression که به صورت x..y نوشته میشود، استفاده کرد. در ادامه، مثالهایی را از کاربردهای عبارت .. ملاحظه میکنید:
var myArray = new string[] { "Item1", "Item2", "Item3", "Item4", "Item5" };
var fromIndexToX = myArray[1..3]; // = [Item2, Item3]
var fromIndexToXFromTheEnd = myArray[1..^1]; // = [ "Item2", "Item3", "Item4" ]
var fromAnIndexToTheEnd = myArray[1..]; // = [ "Item2", "Item3", "Item4", "Item5" ]
var fromTheStartToAnIndex = myArray[..3]; // = [ "Item1", "Item2", "Item3" ]
var entireRange = myArray[..]; // = [ "Item1", "Item2", "Item3", "Item4", "Item5" ]
مثالی دیگر: بازنویسی یک حلقهی for با foreach
حلقهی for زیر را
var myArray = new string[] { "Item1", "Item2", "Item3", "Item4", "Item5" }; for (int i = 1; i <= 3; i++) { Console.WriteLine(myArray[i]); }
foreach (var item in myArray[1..4]) // = [ "Item2", "Item3", "Item4" ] { Console.WriteLine(item); }
یعنی ابتدای آن inclusive است و انتهای آن exclusive
چند مثال کاربردی و متداول از بازهها
using System; using System.Linq; namespace ConsoleApp { class Program { private static readonly int[] _numbers = Enumerable.Range(1, 10).ToArray(); static void Main() { var skip2CharactersAndTake2Characters = _numbers[2..4]; // صرفنظر کردن از دو عنصر اول و سپس انتخاب دو عنصر var skipFirstAndLastCharacter = _numbers[1..^1]; // صرفنظر کردن از دو عنصر اول و آخر var last3Characters = _numbers[^3..]; // انتخاب بازهای شامل سه عنصر آخر var first4Characters = _numbers[0..4]; // دریافت بازهای از 4 عنصر اول var rangeStartFrom2 = _numbers[2..]; // دریافت بازهای شروع شده از المان دوم تا آخر var skipLast3Characters = _numbers[..^3]; // صرفنظر کردن از سه المان آخر var rangeAll = _numbers[..]; // انتخاب کل بازه } } }
معرفی نوع جدید Range
در C# 8.0 زمانیکه مینویسم 4..1، در حقیقت قطعه کد زیر را ایجاد کردهایم:
var range = new Range(1, 4); Range rangeStruct = 1..4; var rangeShortHand = 1..4;
یک مثال: استفاده از نوع جدید Range به عنوان پارامتر یک متد
using System; using System.Linq; namespace ConsoleApp { class Program { private static readonly int[] _numbers = Enumerable.Range(1, 10).ToArray(); static void Print(Range range) => Console.WriteLine($"{range} => {string.Join(", ", _numbers[range])}"); static void Main() { Print(1..3); // 1..3 => 2, 3 Print(..3); // 0..3 => 1, 2, 3 Print(3..); // 3..^0 => 4, 5, 6, 7, 8, 9, 10 Print(1..^1); // 1..^1 => 2, 3, 4, 5, 6, 7, 8, 9 Print(^2..^1); // ^2..^1 => 9 } } }
مثالی دیگر: استفاده از Range به عنوان جایگزینی برای متد String.Substring
از Range میتوان برای کار بر روی رشتهها و انتخاب قسمتی از آنها نیز استفاده کرد:
Console.WriteLine("123456789"[1..4]); // Would output 234
var helloWorldStr = "Hello, World!"; var hello = helloWorldStr[..5]; Console.WriteLine(hello); // Output: Hello var world = helloWorldStr[7..]; Console.WriteLine(world); // Output: World! var world2 = helloWorldStr[^6..]; // Take the last 6 characters Console.WriteLine(world); // Output: World!
سؤال: زمانیکه بازهای از یک آرایه را انتخاب میکنیم، آیا یک آرایهی جدید ایجاد میشود، یا هنوز به همان آرایهی قبلی اشاره میکند؟
پاسخ: یک آرایهی جدید ایجاد میشود؛ اما میتوان با فراخوانی متد ()array.AsSpan پیش از انتخاب یک بازه، بازهای را تولید کرد که دقیقا به همان آرایهی اصلی اشاره میکند و یک کپی جدید نیست:
var arr = (new[] { 1, 4, 8, 11, 19, 31 }).AsSpan(); var range = arr[2..5]; ref int elt1 = ref range[1]; elt1 = -1; int copiedElement = range[2]; copiedElement = -2; Console.WriteLine($"range[1]: {range[1]}, range[2]: {range[2]}"); // output: range[1]: -1, range[2]: 19 Console.WriteLine($"arr[3]: {arr[3]}, arr[4]: {arr[4]}"); // output: arr[3]: -1, arr[4]: 19