مقدمه
در درس قبل، یاد گرفتیم که چگونه اینترفیسهای سفارشی خود را برای تعریف قراردادها ایجاد کنیم. با
این حال، قدرت واقعی اینترفیسها زمانی آشکار میشود که بدانیم فریمورک .NET خود بر
پایهی مجموعهای غنی از اینترفیسهای استاندارد ساخته شده است. با پیادهسازی این اینترفیسها در
انواع دادهی خود، ما به آنها اجازه میدهیم تا به طور یکپارچه با سایر بخشهای فریمورک تعامل
داشته باشند؛ برای مثال، بتوان آنها را در یک حلقهی foreach پیمایش کرد، به طور خودکار مرتبشان
کرد، یا منابعشان را به شکلی ایمن مدیریت نمود. در این درس، به بررسی برخی از مهمترین و
پرکاربردترین اینترفیسهای استاندارد .NET میپردازیم.
پیمایش کالکشنها با IEnumerable
اینترفیس System.Collections.IEnumerable سنگ بنای پیمایش در
.NET است. هر نوعی که این اینترفیس را پیادهسازی کند، به کامپایلر اعلام میکند که "من
یک دنباله از عناصر هستم و میتوان روی من با یک حلقهی foreach حرکت
کرد".
این اینترفیس تنها یک متد به نام GetEnumerator دارد که یک شیء از نوع IEnumerator برمیگرداند.
این شیء IEnumerator است که منطق واقعی حرکت در کالکشن (با متد MoveNext) و دسترسی به عنصر فعلی
(با پراپرتی Current) را مدیریت میکند. خوشبختانه، برای تمام کالکشنهای استاندارد
.NET مانند آرایهها و List<T> این اینترفیس قبلاً پیادهسازی شده است.
Program.cs
var names = new List<string> { "Alice", "Bob", "Charlie" };
foreach (var name in names)
{
Console.WriteLine(name);
}
int[] numbers = { 1, 2, 3 };
foreach (var num in numbers)
{
}
نکتهی کلیدی این است: هر زمان نوعی را دیدید که IEnumerable را پیادهسازی میکند، میدانید که
میتوانید با خیال راحت از foreach برای پیمایش آن استفاده کنید. در آینده یاد خواهیم گرفت که
چگونه کالکشنهای سفارشی خود را با پیادهسازی این اینترفیس بسازیم.
مرتبسازی اشیاء با IComparable
فرض کنید لیستی از اشیاء سفارشی خودتان (مثلاً لیستی از دانشجویان) دارید و میخواهید آن را با
استفاده از متد List<T>.Sort() مرتب کنید. لیست از کجا باید بداند که دانشجویان را بر اساس نام،
نمره یا شماره دانشجویی مرتب کند؟ اینجاست که اینترفیس System.IComparable وارد عمل میشود. این اینترفیس به یک نوع اجازه
میدهد تا ترتیب مرتبسازی طبیعی و پیشفرض خود را تعریف کند.
IComparable تنها یک متد به نام CompareTo(object obj) دارد. این متد شیء فعلی را با شیء دیگری
مقایسه کرده و یک عدد صحیح برمیگرداند:
- مقدار منفی: نمونهی فعلی قبل از شیء دیگر قرار میگیرد.
- صفر: دو نمونه از نظر ترتیب مرتبسازی برابرند.
- مقدار مثبت: نمونهی فعلی بعد از شیء دیگر قرار میگیرد.
Program.cs
public class Student : IComparable
{
public string Name { get; set; }
public double GPA { get; set; }
public int CompareTo(object obj)
{
if (obj is Student otherStudent)
{
return otherStudent.GPA.CompareTo(this.GPA);
}
throw new ArgumentException("Object is not a Student");
}
}
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();
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 شیء همیشه
و به طور خودکار فراخوانی میشود، حتی اگر در حین کار با آن خطایی رخ دهد.
Program.cs
using (System.IO.StreamReader reader = new System.IO.StreamReader("my_file.txt"))
{
string content = reader.ReadToEnd();
Console.WriteLine(content);
}
کامپایلر در پشت صحنه، بلوک using را به یک بلوک try-finally تبدیل میکند که در آن، فراخوانی
Dispose در بخش finally قرار میگیرد. این کار تضمین میکند که فایل همیشه بسته میشود و منابع
آن آزاد میگردند. استفاده از using یک اصل بسیار مهم در نوشتن کدهای قوی و پایدار در
C# است.