مقدمه

در فصل‌های گذشته با آرایه‌ها به عنوان راهی برای ذخیره‌سازی گروهی از عناصر هم‌نوع آشنا شدیم. آرایه‌ها بسیار کارآمد و سریع هستند، اما یک محدودیت بزرگ دارند: اندازه‌ی آن‌ها ثابت است. پس از ایجاد یک آرایه، نمی‌توانید اندازه‌ی آن را تغییر دهید. در برنامه‌های واقعی، ما اغلب به ساختارهای داده‌ای نیاز داریم که بتوانند به صورت پویا رشد کرده و کوچک شوند. فریم‌ورک .NET برای پاسخ به این نیاز، مجموعه‌ای غنی از کلاس‌های تخصصی به نام کالکشن‌ها (Collections) را فراهم کرده است. کالکشن‌ها کلاس‌هایی هستند که برای ذخیره‌سازی، مدیریت و دستکاری گروه‌هایی از اشیاء طراحی شده‌اند و انعطاف‌پذیری بسیار بیشتری نسبت به آرایه‌ها ارائه می‌دهند.

نیاز به کالکشن‌ها: فراتر از آرایه‌ها

تصور کنید می‌خواهید لیستی از نام کاربران را که از یک فرم وب ثبت‌نام می‌کنند، ذخیره کنید. شما از ابتدا نمی‌دانید دقیقاً چند کاربر ثبت‌نام خواهند کرد. اگر از یک آرایه استفاده کنید، باید یک اندازه‌ی اولیه (مثلاً ۱۰۰) را حدس بزنید. اگر کمتر از ۱۰۰ نفر ثبت‌نام کنند، حافظه را هدر داده‌اید. اگر بیشتر از ۱۰۰ نفر ثبت‌نام کنند، با خطای IndexOutOfRangeException مواجه می‌شوید یا مجبور می‌شوید یک آرایه‌ی جدید و بزرگ‌تر بسازید و تمام عناصر قبلی را به آن کپی کنید که عملیاتی بسیار کند است. کالکشن‌ها این مشکل را با مدیریت خودکار حافظه و اندازه‌ی خود حل می‌کنند.

فضای نام System.Collections.Generic

مدرن‌ترین و پرکاربردترین انواع کالکشن در .NET در فضای نام System.Collections.Generic قرار دارند. کلمه‌ی جنریک (Generic) به این معناست که این کالکشن‌ها به صورت امن از نظر نوع (type-safe) هستند. یعنی شما هنگام تعریف کالکشن، دقیقاً مشخص می‌کنید که چه نوع داده‌ای قرار است در آن ذخیره شود (مثلاً List<string> یا Dictionary<int, Product>). این کار از خطاهای زمان اجرا جلوگیری کرده و نیاز به تبدیل نوع (casting) را از بین می‌برد. در این درس با دو مورد از مهم‌ترین کالکشن‌های جنریک آشنا می‌شویم.

List<T>: آرایه‌ی پویا

کلاس List<T> (بخوانید "لیستِ تی") احتمالاً پرکاربردترین کالکشن در C# است. این کلاس یک لیست از اشیاء با قابلیت تغییر اندازه را نشان می‌دهد که می‌توان از طریق اندیس به عناصر آن دسترسی داشت، دقیقاً مانند یک آرایه. اما برخلاف آرایه، شما می‌توانید به راحتی به آن عنصر اضافه یا از آن حذف کنید.

برخی از متدها و پراپرتی‌های رایج List<T> عبارتند از:

  • Add(T item): یک عنصر به انتهای لیست اضافه می‌کند.
  • Remove(T item): اولین رخداد یک عنصر خاص را از لیست حذف می‌کند.
  • RemoveAt(int index): عنصری که در اندیس مشخص‌شده قرار دارد را حذف می‌کند.
  • Count: یک پراپرتی که تعداد عناصر موجود در لیست را برمی‌گرداند (معادل Length در آرایه‌ها).
  • Clear(): تمام عناصر را از لیست حذف می‌کند.
Copy Icon Program.cs
// Create a new list of strings.
var names = new List<string>();

// Add items to the list.
names.Add("Alice");
names.Add("Bob");
names.Add("Charlie");

// Access an item by index.
Console.WriteLine($"The first name is: {names[0]}");

// Check the number of items.
Console.WriteLine($"There are {names.Count} names in the list.");

// Remove an item.
names.Remove("Bob");

// Iterate through the list.
foreach (var name in names)
{
    Console.WriteLine(name);
}

List<T> در پشت صحنه از یک آرایه برای ذخیره‌سازی داده‌ها استفاده می‌کند. وقتی لیست پر می‌شود، به طور خودکار یک آرایه‌ی جدید و بزرگ‌تر ایجاد کرده و عناصر را به آن منتقل می‌کند. این فرآیند به صورت خودکار مدیریت می‌شود و شما نیازی به نگرانی در مورد آن ندارید.

Dictionary<TKey, TValue>: ذخیره‌سازی کلید-مقدار

گاهی اوقات دسترسی به داده‌ها بر اساس یک اندیس عددی (0, 1, 2, ...) کارآمد نیست. ما نیاز داریم تا یک مقدار را بر اساس یک کلید (Key) منحصر به فرد پیدا کنیم. برای مثال، پیدا کردن اطلاعات یک دانشجو بر اساس شماره دانشجویی او، یا اطلاعات یک محصول بر اساس کد آن. برای این سناریوها از Dictionary<TKey, TValue> استفاده می‌کنیم.

یک دیکشنری، مجموعه‌ای از جفت‌های کلید-مقدار (Key-Value Pair) است. هر کلید در دیکشنری باید منحصر به فرد باشد. جستجو بر اساس کلید در یک دیکشنری بسیار سریع و بهینه است.

Copy Icon Program.cs
// Create a dictionary to store student IDs and names.
var studentNames = new Dictionary<int, string>();

// Add items to the dictionary.
studentNames.Add(101, "Alice");
studentNames.Add(102, "Bob");

// A simpler way to add or update items is using the indexer.
studentNames[103] = "Charlie";

// Retrieve a value by its key.
string student102 = studentNames[102];
Console.WriteLine($"Student 102 is: {student102}");

// Iterate through the dictionary. Each item is a KeyValuePair.
foreach (KeyValuePair<int, string> student in studentNames)
{
    Console.WriteLine($"ID: {student.Key}, Name: {student.Value}");
}

تلاش برای افزودن یک کلید که از قبل در دیکشنری وجود دارد، منجر به یک استثناء می‌شود. اگر می‌خواهید قبل از افزودن، از عدم وجود یک کلید مطمئن شوید، می‌توانید از متد ContainsKey(TKey key) استفاده کنید.

نگاهی کوتاه به دیگر کالکشن‌های مفید

فریم‌ورک .NET کالکشن‌های مفید دیگری نیز دارد که بسته به نیاز می‌توانید از آن‌ها استفاده کنید:

  • Queue: یک کالکشن از نوع FIFO (First-In, First-Out) یا "اولین ورودی، اولین خروجی". مانند یک صف نانوایی عمل می‌کند. از متد Enqueue برای اضافه کردن به انتهای صف و از Dequeue برای برداشتن از ابتدای صف استفاده می‌شود.
  • Stack: یک کالکشن از نوع LIFO (Last-In, First-Out) یا "آخرین ورودی، اولین خروجی". مانند یک دسته بشقاب عمل می‌کند. از متد Push برای اضافه کردن به بالای پشته و از Pop برای برداشتن از بالای پشته استفاده می‌شود.
  • HashSet: مجموعه‌ای از عناصر منحصر به فرد و بدون ترتیب مشخص. این کالکشن برای بررسی سریع وجود یا عدم وجود یک عنصر بسیار بهینه است.