کاربرد کلمه کلیدی BASE در سی شارپ (#C)
مقدمه: چیستی وراثت و اهمیت base
کلمه کلیدی base یک ارجاع خاص است که از داخل یک کلاس مشتق شده، به نمونه (Instance) کلاس پایه اشاره میکند. وظیفه اصلی آن فراهم کردن دسترسی به اعضای کلاس پایه است، به ویژه در دو حالت حیاتی:
- ۱. فراخوانی متد سازنده (Constructor) کلاس پایه: این مهمترین و پرتکرارترین کاربرد base است که هنگام نمونهسازی کلاس مشتق شده، اطمینان حاصل میکند که بخش پایه شیء به درستی مقداردهی اولیه شده است.
- 2. دسترسی به اعضای بازنویسی شده (Overridden Members): زمانی که یک متد در کلاس مشتق شده، متدی همنام را از کلاس پایه بازنویسی (override) میکند، میتوان با استفاده از base به پیادهسازی اصلی آن متد در کلاس پایه دسترسی پیدا کرد.
کاربرد base در متدهای سازنده (Constructors)
در سی شارپ، هنگامی که یک شیء از کلاس مشتق شده ایجاد میشود، متد سازنده کلاس پایه همیشه قبل از متد سازنده کلاس مشتق شده اجرا میشود. این تضمین میکند که تمامی فیلدها و منطقهای مقداردهی اولیه که مربوط به ساختار کلی شیء هستند (که در کلاس پایه تعریف شدهاند)، پیش از منطقهای تخصصیتر کلاس فرزند، انجام شوند.
استفاده درست: فراخوانی صریح سازنده با پارامتر
اگر کلاس پایه دارای یک یا چند سازنده پارامتردار باشد و فاقد سازنده بدون پارامتر صریح باشد، کلاس مشتق شده باید به طور صریح یکی از سازندههای کلاس پایه را فراخوانی کند. این کار با استفاده از سینتکس زیر در تعریف سازنده کلاس مشتق شده انجام میشود:
// کلاس پایه
public class Person
{
public string Name { get; set; }
// سازنده پایه با پارامتر
public Person(string name)
{
Name = name;
Console.WriteLine($"Person constructor called for: {Name}");
}
}
// کلاس مشتق شده
public class Employee : Person
{
public int EmployeeId { get; set; }
// استفاده درست: فراخوانی صریح سازنده پایه با استفاده از : base(name)
public Employee(string name, int id) : base(name)
{
EmployeeId = id;
Console.WriteLine($"Employee constructor called for ID: {EmployeeId}");
}
}
در این مثال:
-
Employee، مقدار name را از ورودی خود دریافت کرده و بلافاصله آن را با استفاده از : base(name) به سازنده Person ارسال میکند.
-
این تضمین میکند که خاصیت Name (که در کلاس پایه است) به درستی مقداردهی اولیه شود.
عدم استفاده درست (خطا): فراموش کردن فراخوانی سازنده پارامتردار
اگر کلاس پایه فقط یک سازنده پارامتردار داشته باشد و شما در کلاس مشتق شده، سازنده پایه را به صورت صریح فراخوانی نکنید، کامپایلر سی شارپ بهطور پیشفرض تلاش میکند تا سازنده بدون پارامتر کلاس پایه را فراخوانی کند. از آنجایی که سازنده بدون پارامتر در Person وجود ندارد، با خطای کامپایل مواجه خواهیم شد.
// کلاس پایه (مانند مثال قبل، فقط سازنده پارامتردار دارد)
public class Person
{
public Person(string name) { /* ... */ }
}
// کلاس مشتق شده با استفاده نادرست
public class Employee : Person
{
// ❌ خطای کامپایل:
// 'Person' does not contain a constructor that takes 0 arguments
public Employee(string name, int id)
{
// ... (تلاش کامپایلر برای فراخوانی ضمنی base() شکست میخورد)
}
}
چند نکته در مورد مثال فوق:
1) در کد بالا خطا پیش می آید چون بین name و Id مشخص نکردیم که کدام باید متد سازنده ی کلاس پایه را پر کند؟
مشکل اصلی این نیست که کامپایلر بین name و id گیج شده و نمیداند کدام را انتخاب کند (کامپایلر اصلا حدس نمیزند).
مشکل اصلی این است که: کامپایلر به صورت پیشفرض همیشه دنبال سازنده بدون پارامتر میگردد، مگر اینکه شما خلافش را بگویید.
بیایید دقیقتر بررسی کنیم چه اتفاقی "پشت پرده" میافتد:
وقتی شما کد را به صورت زیر مینویسید:
public Employee(string name, int id)
{
// ...
}
کامپایلر سیشارپ آن را به صورت زیر تفسیر و بازنویسی میکند (چون شما base را ننوشتید، خودش یک base() خالی اضافه میکند)
// آنچه کامپایلر میبیند:
public Employee(string name, int id) : base() // <--- این را خودش اضافه میکند
{
// ...
}
حالا چرا خطا میدهد؟
-
کامپایلر سعی میکند base() یعنی سازنده بدون پارامتر کلاس Person را صدا بزند.
-
به سراغ کلاس Person میرود.
-
میبیند که در کلاس Person شما فقط یک سازنده با پارامتر (Person(string name)) تعریف کردهاید.
-
طبق قوانین سیشارپ، وقتی شما دستی یک سازنده میسازید، سازنده پیشفرض (خالی) حذف میشود.
-
نتیجه: کامپایلر میگوید: "من دنبال سازنده Person() گشتم اما وجود نداشت" و خطا میدهد.
پس درستش این است:
شما باید به کامپایلر اجبار کنید که از سازنده پیشفرض (که وجود ندارد) استفاده نکند و دقیقاً بگوید چه چیزی را بفرستد:
// حالا به کامپایلر میگوییم: "لطفاً base() خالی را صدا نزن!
// به جایش base(name) را صدا بزن و این مقدار name را به آن بده."
public Employee(string name, int id) : base(name)
{
this.Id = id;
}
خلاصه: حق با شماست که "مشخص نکردیم کدام باید برود"، اما نکته کلیدی این است که اگر مشخص نکنیم، کامپایلر هیچکدام را انتخاب نمیکند و سعی میکند هیچی (خالی) بفرستد، که چون کلاس پدر (Person) انتظار "نام" دارد، با در بسته مواجه میشود.
2) اگر کد زیر را مینوشتیم چه؟ باز هم کامپایلر گیچ میشد و یا این بار خودش name را تشخیص میداد؟
// کلاس مشتق شده با استفاده نادرستpublic class Employee : Person
{
// 'Person' does not contain a constructor that takes 0 arguments
public Employee(string name)
{}
}
بله، دقیقاً! باز هم خطا میدهد و باز هم دلیلش همان است.
در سیشارپ، کامپایلر اصلا به نام پارامترها (اینکه هر دو name هستند) یا نوع پارامترها (اینکه هر دو string هستند) نگاه نمیکند تا "حدس بزند" که باید آنها را به هم وصل کند.
حالت ضمنی: فراخوانی خودکار سازنده بدون پارامتر - فراخوانی اختیاری BASE
اگر کلاس پایه دارای یک سازنده بدون پارامتر باشد (چه آن را صریحاً تعریف کنید و چه به کامپایلر اجازه دهید سازنده پیشفرض را ایجاد کند)، فراخوانی base اختیاری است و به صورت خودکار انجام میشود.
// کلاس پایه با سازنده بدون پارامتر
public class Shape
{
public Shape()
{
Console.WriteLine("Shape created.");
}
}
// کلاس مشتق شده - فراخوانی base() اختیاری است و به صورت ضمنی رخ میدهد
public class Circle : Shape
{
public Circle()
{
// در اینجا، base() به صورت خودکار قبل از اجرای بدنه این سازنده فراخوانی میشود.
Console.WriteLine("Circle created.");
}
// یا به صورت صریح هم میتوان نوشت:
// public Circle() : base() { /* ... */ }
}
۳. کاربرد base در دسترسی به اعضای کلاس پایه
دومین کاربرد مهم base زمانی است که یک عضو (معمولاً یک متد) در کلاس مشتق شده، عضو همنامی در کلاس پایه را یا بازنویسی کرده است override یا مخفی کرده است new.
استفاده درست: فراخوانی پیادهسازی پایه در متدهای بازنویسی شده
در سی شارپ، از کلمات کلیدی virtual و override برای پیادهسازی چندریختی (Polymorphism) استفاده میشود. هنگامی که یک متد در کلاس مشتق شده بازنویسی میشود، اغلب لازم است که منطق کلاس پایه را نیز اجرا کرده و تنها منطق جدید و تخصصی کلاس مشتق شده را به آن اضافه کنیم.
// کلاس پایه
public class BaseLogger
{
public virtual void Log(string message)
{
Console.Write($"[{DateTime.Now.ToShortTimeString()}] LOG: ");
}
}
// کلاس مشتق شده
public class ConsoleLogger : BaseLogger
{
public override void Log(string message)
{
// استفاده درست: اجرای منطق کلاس پایه (زمان و سرآیند "LOG: ")
base.Log(message);
// افزودن منطق تخصصی کلاس مشتق شده
Console.WriteLine(message);
}
}
// خروجی: [6:49 PM] LOG: System initialized.
عدم استفاده درست (ناقص): حذف فراخوانی base در متدهای override
عدم فراخوانی base.\text{MethodName() در یک متد بازنویسی شده، منجر به خطای کامپایل نمیشود، اما منطق کلاس پایه را کاملاً نادیده میگیرد. این موضوع میتواند به از دست رفتن رفتارهای حیاتی یا مقداردهیهای ضروری که توسط کلاس پایه طراحی شدهاند، منجر شود (بهویژه در چارچوبهای بزرگتر و کتابخانهها).
public class ConsoleLogger : BaseLogger
{
public override void Log(string message)
{
// ❌ استفاده نادرست: منطق کلاس پایه نادیده گرفته شد.
// زمان و سرآیند "LOG: " چاپ نخواهد شد.
Console.WriteLine(message);
}
}
// خروجی: System initialized. (فاقد اطلاعات زمان و LOG:)
در این حالت، کلاس مشتق شده، به جای گسترش رفتار کلاس پایه، آن را به طور کامل جایگزین میکند. اگر هدف جایگزینی کامل نباشد، این عدم استفاده، نادرست تلقی میشود.
دسترسی به اعضای مخفی شده (Shadowing/Hiding)
اگر یک متد یا ویژگی در کلاس مشتق شده، با استفاده از کلمه کلیدی new، عضوی همنام در کلاس پایه را مخفی hide کند، میتوان با استفاده از base به عضو مخفی شده دسترسی پیدا کرد.
public class Parent
{
public void Display() { Console.WriteLine("Parent Display"); }
}
public class Child : Parent
{
// مخفی کردن متد Display در کلاس پایه (استفاده از new الزامی است)
public new void Display()
{
Console.WriteLine("Child Display");
}
public void ShowAll()
{
Display(); // فراخوانی Child.Display
base.Display(); // فراخوانی Parent.Display (با استفاده از base)
}
}
مثال جامع و مقایسهای: سازنده و متد
برای روشنتر شدن تفاوتها، مثالی جامع از یک سیستم مدیریت حیوانات ارائه میشود:
public class Animal
{
public string Species { get; private set; }
// 1. سازنده پایه با پارامتر
public Animal(string species)
{
Species = species;
Console.WriteLine($"[BASE CONSTRUCTOR] Animal species set to: {Species}");
}
// 2. متد مجازی (قابل بازنویسی)
public virtual void MakeSound()
{
Console.WriteLine("Animal makes a generic sound.");
}
}
public class Dog : Animal
{
public string Breed { get; set; }
// 3. سازنده مشتق شده - استفاده درست از base
// ارسال پارامتر 'Dog' به سازنده پایه، برای مقداردهی Species
public Dog(string breed) : base("Dog")
{
Breed = breed;
Console.WriteLine($"[DERIVED CONSTRUCTOR] Dog breed set to: {Breed}");
}
// 4. بازنویسی متد پایه - استفاده درست از base
public override void MakeSound()
{
// فراخوانی پیادهسازی کلاس پایه
base.MakeSound();
// افزودن رفتار تخصصی
Console.WriteLine("Dog says: Woof Woof!");
}
public void DisplayBaseSoundOnly()
{
// دسترسی مستقیم به متد بازنویسی شده کلاس پایه
base.MakeSound();
}
}
پیادهسازی و خروجی:
Dog myDog = new Dog("Labrador");
// خروجی سازنده:
// [BASE CONSTRUCTOR] Animal species set to: Dog
// [DERIVED CONSTRUCTOR] Dog breed set to: Labrador
myDog.MakeSound();
// خروجی متد (استفاده درست از base):
// Animal makes a generic sound.
// Dog says: Woof Woof!
myDog.DisplayBaseSoundOnly();
// خروجی:
// Animal makes a generic sound.
خلاصه و نتیجهگیری
کلمه کلیدی base یکی از ستونهای اساسی پیادهسازی مفهوم وراثت و چندریختی در سی شارپ است.
| کاربرد base | هدف اصلی | مکان استفاده | پیامد عدم استفاده درست |
| در سازنده | فراخوانی سازنده کلاس پایه (بخصوص پارامتردار) | تعریف سازنده کلاس مشتق شده ($\mathbf{: base(...)}$) | خطای کامپایل در صورت عدم وجود سازنده بدون پارامتر در کلاس پایه |
| در متد $\mathbf{override}$ | اجرای منطق کلاس پایه و سپس گسترش آن | داخل بدنه متد بازنویسی شده | نادیده گرفتن منطق کلاس پایه و حذف رفتار ضروری (اشکال منطقی) |
| در دسترسی به عضو | دسترسی به عضو مخفی یا بازنویسی شده | داخل متدهای کلاس مشتق شده | عدم توانایی در استفاده از پیادهسازی اصلی کلاس پایه |
استفاده صحیح از base تضمین میکند که:
-
مقداردهی اولیه شیء در طول سلسله مراتب وراثت به درستی و به ترتیب انجام شود.
-
اصل چندریختی (Polymorphism) رعایت شده و بتوانیم به طور همزمان از پیادهسازی اصلی و تخصصی یک متد استفاده کنیم.
برنامهنویسان حرفهای سی شارپ با درک کامل تفاوتهای فراخوانی ضمنی و صریح base، کدهای انعطافپذیر، قابل نگهداری و عاری از خطاهای زمان اجرا را در ساختارهای شیءگرا ایجاد میکنند.
0 نظر
هنوز نظری برای این مقاله ثبت نشده است.