مقدمه

در اکوسیستم .NET، یک کلاس ویژه و بنیادی وجود دارد که به عنوان ریشه‌ی تمام انواع داده عمل می‌کند: کلاس System.Object. در زبان C#، کلمه‌ی کلیدی object یک نام مستعار برای این کلاس است. هر نوع داده‌ای که شما در C# تعریف یا استفاده می‌کنید، چه از نوع مقداری و چه از نوع ارجاعی، به صورت مستقیم یا غیرمستقیم از کلاس object ارث‌بری می‌کند. این ویژگی، یکپارچگی شگفت‌انگیزی در سیستم نوع .NET ایجاد کرده و تضمین می‌کند که تمام اشیاء، صرف‌نظر از نوعشان، مجموعه‌ای از رفتارهای پایه‌ای مشترک را به ارث می‌برند. در این درس، به بررسی این کلاس بنیادی و متدهای مهم آن می‌پردازیم.

وراثت ضمنی از System.Object

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

// This class definition...
public class Person { }

// ...is treated by the compiler as this:
public class Person : System.Object { }

این وراثت ضمنی به این معناست که هر متغیر از هر نوعی می‌تواند به یک متغیر از نوع object اختصاص داده شود. این قابلیت، پایه‌ی اصلی چندریختی در بالاترین سطح ممکن است.

Copy Icon Program.cs
object a = 10; // An integer can be treated as an object.
object b = "Hello"; // A string can be treated as an object.
object c = new Person(); // A custom class instance can be treated as an object.

این یکپارچگی به ما اجازه می‌دهد تا متدها یا کالکشن‌هایی بسازیم که بتوانند با هر نوع داده‌ای در .NET کار کنند. اما مهم‌تر از آن، این وراثت تضمین می‌کند که هر شیء در C# دارای چند متد بنیادی است که از کلاس object به ارث برده است.

متدهای عمومی System.Object

کلاس object چندین متد عمومی دارد، اما چهار مورد از آن‌ها اهمیت ویژه‌ای دارند زیرا اغلب در کلاس‌های مشتق‌شده بازنویسی (override) می‌شوند تا رفتار پیش‌فرض آن‌ها متناسب با نوع جدید تغییر کند. این چهار متد عبارتند از:

  • ToString()
  • Equals(object obj)
  • GetHashCode()
  • GetType()

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

متد ToString()

وظیفه‌ی متد ToString() بازگرداندن یک نمایش رشته‌ای و قابل خواندن از شیء فعلی است. پیاده‌سازی پیش‌فرض این متد در کلاس object، تنها نام کامل نوع (شامل فضای نام) را برمی‌گرداند که معمولاً اطلاعات مفیدی به ما نمی‌دهد. بنابراین، یکی از اولین کارهایی که هنگام تعریف یک کلاس جدید انجام می‌دهیم، بازنویسی این متد است.

Copy Icon Program.cs
public class Book
{
    public string Title { get; set; }
    public string Author { get; set; }

    // Overriding the ToString method to provide a meaningful representation.
    public override string ToString()
    {
        return $"'{Title}' by {Author}";
    }
}

Book myBook = new Book { Title = "1984", Author = "George Orwell" };
Console.WriteLine(myBook); // Console.WriteLine automatically calls ToString().

خروجی این کد به جای نام کلاس، یک رشته‌ی معنادار خواهد بود:

'1984' by George Orwell

بازنویسی ToString برای دیباگ کردن و لاگ‌گیری بسیار مفید است.

متد Equals(object obj)

این متد برای بررسی برابری دو شیء به کار می‌رود. پیاده‌سازی پیش‌فرض آن در System.Object برای انواع ارجاعی، برابری ارجاع (Reference Equality) را بررسی می‌کند؛ یعنی فقط زمانی true برمی‌گرداند که دو متغیر به یک شیء واحد در حافظه اشاره کنند. این رفتار معمولاً آن چیزی نیست که ما می‌خواهیم. ما اغلب می‌خواهیم برابری مقدار (Value Equality) را بررسی کنیم، یعنی دو شیء متفاوت را بر اساس مقادیر فیلدهایشان با هم مقایسه کنیم.

متد GetHashCode()

این متد یک عدد صحیح (هش‌کد) را برمی‌گرداند که نماینده‌ی وضعیت شیء است. این هش‌کد توسط ساختارهای داده‌ی مبتنی بر هش مانند Dictionary و HashSet برای جستجو و ذخیره‌سازی بهینه استفاده می‌شود. یک قانون بسیار مهم وجود دارد: اگر دو شیء بر اساس متد Equals شما برابر هستند، آن‌ها باید حتماً GetHashCode یکسانی نیز داشته باشند. بنابراین، هرگاه Equals را بازنویسی می‌کنید، باید GetHashCode را نیز بازنویسی کنید.

Copy Icon Program.cs
public class Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public override bool Equals(object obj)
    {
        // Check if the other object is a Point and its coordinates match.
        if (obj is Point p)
        {
            return this.X == p.X && this.Y == p.Y;
        }
        return false;
    }

    public override int GetHashCode()
    {
        // Combine the hash codes of the properties that determine equality.
        return HashCode.Combine(X, Y);
    }
}

در این مثال، ما Equals را طوری بازنویسی کرده‌ایم که دو شیء Point را بر اساس مختصات X و Y آن‌ها مقایسه کند. سپس GetHashCode را نیز متناسب با آن بازنویسی کرده‌ایم تا از همان پراپرتی‌ها برای تولید هش‌کد استفاده کند. کلاس HashCode یک ابزار کمکی مدرن در .NET برای ترکیب آسان هش‌کدهاست.
نکته: به یاد بیاورید که نوع record که در درس‌های قبل معرفی شد، به طور خودکار این دو متد را به صورت بهینه برای ما پیاده‌سازی می‌کند.

متد GetType()

آخرین متد مهم، GetType است. این متد مجازی نیست و نمی‌توان آن را بازنویسی کرد. وظیفه‌ی آن بازگرداندن یک شیء از نوع System.Type است که حاوی تمام اطلاعات فراداده‌ای (metadata) در مورد نوع واقعی شیء در زمان اجراست. این متد در سناریوهای پیشرفته‌تری مانند انعکاس (Reflection) کاربرد دارد که به ما اجازه می‌دهد تا در زمان اجرا، ساختار یک نوع را بررسی و با آن تعامل کنیم.

Copy Icon Program.cs
Point p = new Point();
Type pointType = p.GetType();
Console.WriteLine($"The type is: {pointType.FullName}"); // Output: The type is: YourNamespace.Point