مقدمه

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

پیمایش کالکشن‌ها با IEnumerable

اینترفیس System.Collections.IEnumerable سنگ بنای پیمایش در .NET است. هر نوعی که این اینترفیس را پیاده‌سازی کند، به کامپایلر اعلام می‌کند که "من یک دنباله از عناصر هستم و می‌توان روی من با یک حلقه‌ی foreach حرکت کرد".

این اینترفیس تنها یک متد به نام GetEnumerator دارد که یک شیء از نوع IEnumerator برمی‌گرداند. این شیء IEnumerator است که منطق واقعی حرکت در کالکشن (با متد MoveNext) و دسترسی به عنصر فعلی (با پراپرتی Current) را مدیریت می‌کند. خوشبختانه، برای تمام کالکشن‌های استاندارد .NET مانند آرایه‌ها و List<T> این اینترفیس قبلاً پیاده‌سازی شده است.

Copy Icon Program.cs
// A List implements IEnumerable (which inherits from IEnumerable).
var names = new List<string> { "Alice", "Bob", "Charlie" };

// Because it implements IEnumerable, we can use it in a foreach loop.
foreach (var name in names)
{
    Console.WriteLine(name);
}

// Arrays also implement IEnumerable.
int[] numbers = { 1, 2, 3 };
foreach (var num in numbers)
{
    // ...
}

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

مرتب‌سازی اشیاء با IComparable

فرض کنید لیستی از اشیاء سفارشی خودتان (مثلاً لیستی از دانشجویان) دارید و می‌خواهید آن را با استفاده از متد List<T>.Sort() مرتب کنید. لیست از کجا باید بداند که دانشجویان را بر اساس نام، نمره یا شماره دانشجویی مرتب کند؟ اینجاست که اینترفیس System.IComparable وارد عمل می‌شود. این اینترفیس به یک نوع اجازه می‌دهد تا ترتیب مرتب‌سازی طبیعی و پیش‌فرض خود را تعریف کند.

IComparable تنها یک متد به نام CompareTo(object obj) دارد. این متد شیء فعلی را با شیء دیگری مقایسه کرده و یک عدد صحیح برمی‌گرداند:

  • مقدار منفی: نمونه‌ی فعلی قبل از شیء دیگر قرار می‌گیرد.
  • صفر: دو نمونه از نظر ترتیب مرتب‌سازی برابرند.
  • مقدار مثبت: نمونه‌ی فعلی بعد از شیء دیگر قرار می‌گیرد.
Copy Icon Program.cs
public class Student : IComparable
{
    public string Name { get; set; }
    public double GPA { get; set; }

    // Implementing the IComparable interface
    public int CompareTo(object obj)
    {
        if (obj is Student otherStudent)
        {
            // We want to sort by GPA, descending.
            // So we compare otherStudent.GPA to this.GPA.
            return otherStudent.GPA.CompareTo(this.GPA);
        }
        throw new ArgumentException("Object is not a Student");
    }
}

// --- Usage ---
var students = new List<Student>
{
    new Student { Name = "Alice", GPA = 3.8 },
    new Student { Name = "Bob", GPA = 3.9 },
    new Student { Name = "Charlie", GPA = 3.5 }
};

students.Sort(); // This works because Student implements IComparable.

foreach (var s in students)
{
    Console.WriteLine($"{s.Name}: {s.GPA}");
}

با پیاده‌سازی IComparable، ما به متد Sort آموزش دادیم که چگونه اشیاء Student را با هم مقایسه کند. اکنون لیست به درستی بر اساس معدل (به صورت نزولی) مرتب می‌شود.

مدیریت منابع با IDisposable

زباله‌روب (Garbage Collector) در .NET به طور خودکار حافظه‌ی مدیریت‌شده (managed memory) را مدیریت می‌کند. اما برخی از اشیاء با منابعی خارج از کنترل .NET کار می‌کنند، مانند فایل‌ها، اتصالات پایگاه داده، یا سوکت‌های شبکه. این منابع که منابع مدیریت‌نشده (unmanaged resources) نام دارند، باید به طور صریح آزاد شوند تا از نشت منابع جلوگیری شود.

اینترفیس System.IDisposable یک قرارداد استاندارد برای این منظور فراهم می‌کند. این اینترفیس تنها یک متد به نام Dispose() دارد. هر کلاسی که این اینترفیس را پیاده‌سازی می‌کند، اعلام می‌دارد که "من منابعی دارم که باید به صورت دستی پاکسازی شوند و این کار در متد Dispose من انجام می‌شود."

بلوک using: بهترین روش کار با IDisposable

بهترین و ایمن‌ترین روش برای کار با اشیاء IDisposable، استفاده از بلوک using است. این بلوک تضمین می‌کند که متد Dispose شیء همیشه و به طور خودکار فراخوانی می‌شود، حتی اگر در حین کار با آن خطایی رخ دهد.

Copy Icon Program.cs
// A using statement ensures Dispose() is called.
using (System.IO.StreamReader reader = new System.IO.StreamReader("my_file.txt"))
{
    // Work with the file here.
    string content = reader.ReadToEnd();
    Console.WriteLine(content);
} // At this point, reader.Dispose() is automatically called by the compiler.

کامپایلر در پشت صحنه، بلوک using را به یک بلوک try-finally تبدیل می‌کند که در آن، فراخوانی Dispose در بخش finally قرار می‌گیرد. این کار تضمین می‌کند که فایل همیشه بسته می‌شود و منابع آن آزاد می‌گردند. استفاده از using یک اصل بسیار مهم در نوشتن کدهای قوی و پایدار در C# است.