مقدمه

در برنامه‌نویسی، گاهی اوقات نیاز داریم مجموعه‌ای از داده‌های مرتبط و هم‌نوع را ذخیره و مدیریت کنیم. در چنین شرایطی، به جای تعریف تعداد زیادی متغیر مجزا، از ساختارهای داده‌ای به نام کالکشن (Collection) استفاده می‌کنیم. این ساختارها به ما امکان می‌دهند چندین مقدار را در قالب یک متغیر واحد تعریف و ذخیره کنیم. آرایه‌ها (Arrays) یکی از بنیادی‌ترین، مرسوم‌ترین و پرکاربردترین ساختارهای کالکشنال در اکثر زبان‌های برنامه‌نویسی، از جمله C#، هستند. در این درس به جزئیات مربوط به تعریف، مقداردهی و کار با انواع آرایه‌ها در C# می‌پردازیم.

تعریف آرایه در C#

یک آرایه در زبان C# ساختاری است که می‌تواند تعداد مشخص و ثابتی از مقادیر هم‌نوع را در خود ذخیره کند. فرض کنید بخواهیم نمرات 5 دانش‌آموز را ذخیره کنیم. به جای تعریف 5 متغیر جداگانه (مثلاً score1، score2 و...)، می‌توانیم یک آرایه تعریف کنیم که 5 مقدار از نوع double را در خود نگه دارد. برای تعریف یک آرایه، ابتدا نوع داده‌ی عناصر و سپس براکت [] و در نهایت نام آرایه را مشخص می‌کنیم.

Copy Icon Program.cs
double[] scores = new double[5];

در کد بالا، ما یک آرایه با نام scores تعریف کرده‌ایم که با استفاده از کلمه‌ی کلیدی new ایجاد شده و می‌تواند 5 مقدار از نوع double را ذخیره کند. عددی که در براکت جلوی نوع داده قرار می‌گیرد، طول (Length) یا تعداد عناصر آرایه را مشخص می‌کند. وقتی آرایه‌ای به این شکل ایجاد می‌شود، عناصر آن با مقدار پیش‌فرضِ نوع داده‌ی مربوطه مقداردهی اولیه می‌شوند (برای انواع عددی صفر، برای bool مقدار false و برای انواع ارجاعی مانند string مقدار null).

اگر بخواهیم مقادیر اولیه را در زمان تعریف آرایه به آن اختصاص دهیم، می‌توانیم از سینتکس مقداردهی اولیه (initializer syntax) استفاده کنیم.

Copy Icon Program.cs
double[] scores = new double[] { 18.5, 19.0, 15.75, 20.0, 12.5 };

در این حالت، نیازی به تعیین طول آرایه در براکت نیست، زیرا کامپایلر به طور خودکار طول آرایه را بر اساس تعداد عناصر داخل آکولاد تشخیص می‌دهد. البته این کار را می‌توان به فرم خلاصه‌تری که در زیر می‌بینید هم انجام داد.

Copy Icon Program.cs
double[] scores = { 18.5, 19.0, 15.75, 20.0, 12.5 };

این سینتکس کوتاه‌تر و خواناتر است و در عمل بیشتر مورد استفاده قرار می‌گیرد. کامپایلر C# نوع آرایه را از روی نوع مقادیر داخل آکولاد استنباط می‌کند.

دسترسی به عناصر آرایه

برای دسترسی به عناصر یک آرایه از اندیس (index) آن عناصر استفاده می‌شود. اندیس‌ها در C# (و بسیاری از زبان‌های دیگر) مبتنی بر صفر (zero-based) هستند. این یعنی اولین عنصر آرایه در اندیس 0، دومین عنصر در اندیس 1، و به همین ترتیب عنصر n-ام در اندیس n-1 قرار دارد. برای دسترسی یا تغییر مقدار یک عنصر، نام آرایه و سپس اندیس مورد نظر را داخل براکت قرار می‌دهیم.

Copy Icon Program.cs
double[] scores = {18.5, 19.0, 15.75, 20.0, 12.5};

// Accessing and printing the first element (index 0)
Console.WriteLine(scores[0]); // Output: 18.5

// Accessing and printing the third element (index 2)
Console.WriteLine(scores[2]); // Output: 15.75

// Modifying the value of the third element
scores[2] = 16.0; 
Console.WriteLine(scores[2]); // Output: 16.0

همانطور که در مثال مشاهده می‌کنید، می‌توانیم به راحتی مقادیر عناصر آرایه را بخوانیم و آن‌ها را تغییر دهیم. تلاش برای دسترسی به یک اندیس خارج از محدوده مجاز (مثلاً scores[5] در مثال بالا که طول آن 5 است و آخرین اندیس معتبر آن 4 است) منجر به یک خطای زمان اجرا از نوع IndexOutOfRangeException خواهد شد.

پیمایش آرایه با حلقه

یکی از رایج‌ترین کارها با آرایه‌ها، پیمایش یا تکرار (iteration) روی تمام عناصر آن برای انجام یک عملیات خاص است. در C#، متداول‌ترین روش برای این کار استفاده از حلقه‌های for یا foreach است. حلقه‌ی for زمانی مناسب است که به اندیس هر عنصر نیاز داشته باشید. اما اگر فقط می‌خواهید روی عناصر آرایه حرکت کنید بدون اینکه اندیس آن‌ها برایتان مهم باشد، حلقه‌ی foreach خواناتر و ساده‌تر است.

Copy Icon Program.cs
double[] scores = {18.5, 19.0, 15.75, 20.0, 12.5};

// Iterating with a for loop
for (int i = 0; i < scores.Length; i++)
{
    Console.WriteLine($"Score at index {i}: {scores[i]}");
}

Console.WriteLine("---");

// Iterating with a foreach loop
foreach (double score in scores)
{
    Console.WriteLine($"Score: {score}");
}

در حلقه‌ی for، از پراپرتی Length آرایه برای تعیین شرط پایان حلقه استفاده کرده‌ایم. این پراپرتی تعداد کل عناصر آرایه را برمی‌گرداند. در حلقه‌ی foreach، کد ساده‌تر است؛ این حلقه به ترتیب روی هر عنصر از کالکشن (در اینجا آرایه‌ی scores) حرکت کرده و در هر تکرار، مقدار عنصر جاری را در متغیر score قرار می‌دهد.

ویژگی‌های مهم آرایه‌ها

آرایه‌ها در C# ویژگی‌های کلیدی خاصی دارند که باید حتماً به آن‌ها توجه داشته باشید. اولین ویژگی این است که طول آرایه ثابت است (Fixed-Size). یعنی پس از تعریف و ایجاد آرایه، نمی‌توان به آن عنصر جدیدی اضافه کرد یا از آن حذف کرد. ویژگی دوم، نوع یکنواخت (Homogeneous) است. یعنی اگر آرایه‌ای از نوع string تعریف کرده باشید، نمی‌توانید مقداری از نوع int یا bool در آن قرار دهید. این محدودیت‌ها باعث می‌شود که آرایه‌ها در دسترسی به عناصر بسیار سریع و بهینه باشند، اما در عین حال انعطاف‌پذیری محدودتری نسبت به ساختارهای داده‌ای دیگری مثل List<T> داشته باشند که در فصول آینده با آنها آشنا خواهیم شد.

یک مثال عملی: محاسبه میانگین نمرات

در این مثال عملی، آرایه‌ای از نمرات تعریف می‌کنیم، سپس با استفاده از یک حلقه‌ی foreach، مجموع آن‌ها را محاسبه کرده و در پایان میانگین را در خروجی چاپ می‌کنیم. این مثال ساده و کاربردی نشان می‌دهد که چگونه می‌توان با آرایه‌ها عملیات آماری و پردازشی انجام داد.

Copy Icon Program.cs
int[] grades = {85, 92, 78, 95, 88};
int sum = 0;

foreach (int grade in grades)
{
    sum += grade; // Equivalent to sum = sum + grade;
}

// We cast sum to double to get a precise floating-point division
double average = (double)sum / grades.Length;
Console.WriteLine($"Average grade: {average}"); // Output: Average grade: 87.6

در این کد، ابتدا یک آرایه از نوع int برای ذخیره نمرات ایجاد کرده‌ایم. سپس با یک حلقه foreach تمام نمرات را با هم جمع و در متغیر sum ذخیره کرده‌ایم. در نهایت برای محاسبه میانگین، مجموع نمرات را بر تعداد آنها (grades.Length) تقسیم کرده‌ایم. توجه کنید که برای جلوگیری از تقسیم صحیح (integer division) که بخش اعشاری را حذف می‌کند، متغیر sum را به نوع double تبدیل (cast) کرده‌ایم تا نتیجه با دقت اعشاری محاسبه شود.

کلاس System.Array

در C# آرایه‌ها فقط یک ساختار داده‌ی ساده نیستند، بلکه در سطح پایین‌تر، اشیائی هستند که به طور ضمنی از کلاس System.Array ارث‌بری می‌کنند. این کلاس، مجموعه‌ای از متدها (methods) و پراپرتی‌های (properties) استاتیک و مفید را فراهم می‌کند که به شما امکان می‌دهد تا عملیات مختلفی مثل مرتب‌سازی، جستجو، کپی‌کردن، و معکوس‌کردن را به سادگی روی آرایه‌ها انجام دهید.

هر آرایه‌ای که شما تعریف می‌کنید، فارغ از اینکه از نوع int[] یا string[] یا هر نوع دیگری باشد، یک شیء از کلاس System.Array محسوب می‌شود. به همین دلیل است که کد زیر کاملاً مجاز است و کار می‌کند.

Copy Icon Program.cs
int[] numbers = {10, 20, 30, 40, 50};
Array array = numbers;

اما موضوع مهم‌تر این است که ما می‌توانیم از متدها و پراپرتی‌های استاتیک کلاس Array برای کار با آرایه‌های خود استفاده کنیم. در ادامه، تعدادی از مهم‌ترین و پرکاربردترین متدهای این کلاس را بررسی می‌کنیم.

متد Array.Sort()

متد Array.Sort() برای مرتب‌سازی عناصر یک آرایه به کار می‌رود. این متد عناصر آرایه را در جای خود (in-place) مرتب می‌کند؛ یعنی خود آرایه‌ی اصلی تغییر می‌کند. به صورت پیش‌فرض، عناصر به ترتیب صعودی (از کوچک به بزرگ برای اعداد و بر اساس ترتیب الفبایی برای رشته‌ها) مرتب می‌شوند.

Copy Icon Program.cs
int[] numbers = {5, 2, 8, 1, 4};
Array.Sort(numbers);

// The original array is now sorted.
foreach (int number in numbers)
{
    Console.Write(number + " "); // Output: 1 2 4 5 8 
}

در این مثال، آرایه‌ای از اعداد تعریف شده و سپس با استفاده از متد استاتیک Array.Sort() مرتب‌سازی می‌شود. در نهایت، با استفاده از حلقه‌ی foreach، عناصر مرتب‌شده در خروجی چاپ می‌شوند.

متد Array.Reverse()

متد Array.Reverse() برای معکوس کردن ترتیب عناصر یک آرایه استفاده می‌شود. این متد نیز مانند Sort، آرایه را در جای خود تغییر می‌دهد.

Copy Icon Program.cs
string[] names = {"Alice", "Bob", "Charlie"};
Array.Reverse(names);

foreach (string name in names)
{
    Console.Write(name + " "); // Output: Charlie Bob Alice 
}

در این مثال، آرایه‌ای از رشته‌ها تعریف شده و سپس با استفاده از متد Array.Reverse() ترتیب عناصر آن معکوس می‌شود.

با استفاده از ترکیبی از دو متد Sort() و Reverse() می‌توانیم عناصر یک آرایه را به صورت نزولی (از بزرگ به کوچک) مرتب کنیم. ابتدا آرایه را با Sort() مرتب کرده و سپس با Reverse() آن را معکوس می‌کنیم.

متد Array.IndexOf()

متد Array.IndexOf() برای پیدا کردن اندیس اولین رخداد (occurrence) یک عنصر خاص در آرایه استفاده می‌شود. این متد، آرایه و مقدار مورد جستجو را به عنوان ورودی دریافت کرده و اندیس اولین عنصری که با مقدار مورد نظر برابر است را برمی‌گرداند. اگر عنصر در آرایه وجود نداشته باشد، مقدار -1 را برمی‌گرداند که نشان‌دهنده‌ی عدم موفقیت در جستجو است.

Copy Icon Program.cs
int[] numbers = {10, 20, 30, 40, 20, 50};

// Find the index of the first occurrence of 20
int index1 = Array.IndexOf(numbers, 20);
Console.WriteLine("First index of 20: " + index1); // Output: 1

// Find the index of 99 (which does not exist)
int index2 = Array.IndexOf(numbers, 99);
Console.WriteLine("Index of 99: " + index2); // Output: -1

در اینجا، جستجو برای عدد 20 منجر به بازگشت اندیس 1 می‌شود، چون اولین باری که عدد 20 در آرایه ظاهر شده، در این موقعیت است. اما جستجو برای عدد 99 باعث بازگشت مقدار -1 می‌شود، چون این مقدار در آرایه وجود ندارد.

آرایه‌های چند بعدی

آرایه‌های یک بعدی برای ذخیره لیستی از مقادیر عالی هستند، اما گاهی نیاز داریم داده‌هایی با ساختار جدولی یا ماتریسی را ذخیره کنیم. برای این منظور، از آرایه‌های چند بعدی (Multi-dimensional) استفاده می‌کنیم. پرکاربردترین نوع آرایه چند بعدی، آرایه دو بعدی است که می‌توان آن را به عنوان یک جدول با سطر و ستون در نظر گرفت.

برای تعریف یک آرایه دو بعدی، به جای یک جفت براکت، از یک کاما در داخل براکت‌ها استفاده می‌کنیم. در هنگام ایجاد آرایه نیز، تعداد سطرها و ستون‌ها را مشخص می‌کنیم.

Copy Icon Program.cs
// Declaring a 2D array (matrix) with 3 rows and 4 columns
int[,] matrix = new int[3, 4];

اگر بخواهیم هنگام تعریف، آرایه را مقداردهی هم بکنیم، از آکولادهای تودرتو استفاده می‌کنیم که هر آکولاد داخلی نماینده یک سطر از ماتریس است.

Copy Icon Program.cs
int[,] matrix = 
{ 
    { 1, 2, 3, 4 },  // row 0
    { 5, 6, 7, 8 },  // row 1
    { 9, 10, 11, 12 } // row 2
};

در یک آرایه دو بعدی، هر عنصر دارای دو اندیس است: اندیس اول نمایانگر سطر و اندیس دوم نمایانگر ستونی است که عنصر به آن تعلق دارد. برای دسترسی به عناصر یک آرایه دو بعدی به صورت زیر عمل می‌کنیم.

Copy Icon Program.cs
int[,] matrix = { { 1, 2, 3 }, { 4, 5, 6 } };
Console.WriteLine(matrix[0, 0]);  // Output: 1 (element at row 0, column 0)
Console.WriteLine(matrix[1, 2]);  // Output: 6 (element at row 1, column 2)

// Change an element
matrix[0, 1] = 20;
Console.WriteLine(matrix[0, 1]);  // Output: 20

پیمایش روی آرایه‌های چند بعدی

همانطور که برای پیمایش روی آرایه‌های یک بعدی از یک حلقه استفاده کردیم، برای پیمایش روی آرایه‌های دو بعدی باید از حلقه‌های تودرتو (nested loops) استفاده کنیم. معمولاً یک حلقه بیرونی برای سطرها و یک حلقه درونی برای ستون‌ها به کار می‌رود.

Copy Icon Program.cs
int[,] matrix = { { 1, 2, 3 }, { 4, 5, 6 } };

// Outer loop for rows
for (int i = 0; i < matrix.GetLength(0); i++)
{
    // Inner loop for columns
    for (int j = 0; j < matrix.GetLength(1); j++)
    {
        Console.Write(matrix[i, j] + "\t"); // Use \t for tab spacing
    }
    Console.WriteLine(); // Move to the next line after each row
}

متد GetLength(dimension) طول بعد مشخص شده را برمی‌گرداند. در آرایه دو بعدی، GetLength(0) تعداد سطرها (طول بعد اول) را برمی‌گرداند و GetLength(1) تعداد ستون‌ها (طول بعد دوم) را برمی‌گرداند. خروجی کد بالا به صورت یک جدول مرتب خواهد بود.