📖 سری آموزشی کتاب C# 12 in a Nutshell
آمادهاید برای یک شیرجه عمیق به قلب تپنده برنامهنویسی شیءگرا؟ امروز میخوایم تمام ابزارهای #C برای کنترل دقیق وراثت و چندریختی رو کالبدشکافی کنیم.
برای اینکه به کلاسهای فرزند اجازه بدیم رفتار کلاس پدر رو تخصصیسازی کنن، از این دو کلمه کلیدی استفاده میکنیم:
با virtual، کلاس پایه یک پیادهسازی پیشفرض ارائه میده ولی به فرزندان اجازه override کردن (بازنویسی) اون رفتار رو میده.
با abstract، کلاس پایه هیچ پیادهسازیای ارائه نمیده و فرزندان رو مجبور به override کردن و پیادهسازی اون عضو میکنه. کلاسی که عضو abstract داره، خودش هم باید abstract باشه و نمیشه ازش نمونه (new) ساخت.
گاهی وقتا یه عضو در کلاس فرزند، هماسم عضوی در کلاس پدر میشه. به این کار میگن مخفی کردن (Hiding). کلمه کلیدی new فقط برای اینه که به کامپایلر بگیم "من میدونم دارم چیکار میکنم، این کارم عمدیه!" و جلوی Warning رو بگیریم.
این مهمترین بخش ماجراست. تفاوت این دو، در رفتار چندریختی (Polymorphic) مشخص میشه:
• override:
رفتار واقعی چندریختی رو پیاده میکنه. مهم نیست متغیر شما از چه نوعی باشه (پدر یا فرزند)، همیشه متدِ نوعِ واقعی آبجکت صدا زده میشه.
• new:
چندریختی رو میشکنه! متدی که صدا زده میشه، بستگی به نوع متغیر در زمان کامپایل داره، نه نوع واقعی آبجکت.
این مثال رو ببینید تا کامل متوجه بشید:
حالا اگه بخواید جلوی override شدن بیشتر رو در زنجیره وراثت بگیرید، از sealed استفاده میکنید.
• sealed روی متد:
میتونید یه متد override شده رو sealed کنید تا کلاسهای فرزند بعدی دیگه نتونن اون رو تغییر بدن.
• sealed روی کلاس:
یا میتونید کل یک کلاس رو sealed کنید تا دیگه هیچ کلاسی نتونه ازش ارثبری کنه.
تسلط بر این کلمات کلیدی، شما رو از یه کاربر ساده وراثت، به یک معمار واقعی کلاسها تبدیل میکنه.
🏛 راهنمای کامل وراثت پیشرفته در #C: از virtual تا sealed
آمادهاید برای یک شیرجه عمیق به قلب تپنده برنامهنویسی شیءگرا؟ امروز میخوایم تمام ابزارهای #C برای کنترل دقیق وراثت و چندریختی رو کالبدشکافی کنیم.
1️⃣ تعریف قرارداد برای فرزندان: virtual و abstract
برای اینکه به کلاسهای فرزند اجازه بدیم رفتار کلاس پدر رو تخصصیسازی کنن، از این دو کلمه کلیدی استفاده میکنیم:
virtual (قرارداد اختیاری): ✍️
با virtual، کلاس پایه یک پیادهسازی پیشفرض ارائه میده ولی به فرزندان اجازه override کردن (بازنویسی) اون رفتار رو میده.
public class Asset
{
public string Name;
public virtual decimal Liability => 0;
}
public class House : Asset
{
public decimal Mortgage;
public override decimal Liability => Mortgage;
}
abstract (قرارداد اجباری): 🏗
با abstract، کلاس پایه هیچ پیادهسازیای ارائه نمیده و فرزندان رو مجبور به override کردن و پیادهسازی اون عضو میکنه. کلاسی که عضو abstract داره، خودش هم باید abstract باشه و نمیشه ازش نمونه (new) ساخت.
public abstract class Asset
{
public abstract decimal NetValue { get; }
}
public class Stock : Asset
{
public long SharesOwned;
public decimal CurrentPrice;
public override decimal NetValue => CurrentPrice * SharesOwned;
}
2️⃣ مخفی کردن عضو با new (Hiding)
گاهی وقتا یه عضو در کلاس فرزند، هماسم عضوی در کلاس پدر میشه. به این کار میگن مخفی کردن (Hiding). کلمه کلیدی new فقط برای اینه که به کامپایلر بگیم "من میدونم دارم چیکار میکنم، این کارم عمدیه!" و جلوی Warning رو بگیریم.
3️⃣ دوئل بزرگ: override در برابر new ⚔️
این مهمترین بخش ماجراست. تفاوت این دو، در رفتار چندریختی (Polymorphic) مشخص میشه:
• override:
رفتار واقعی چندریختی رو پیاده میکنه. مهم نیست متغیر شما از چه نوعی باشه (پدر یا فرزند)، همیشه متدِ نوعِ واقعی آبجکت صدا زده میشه.
• new:
چندریختی رو میشکنه! متدی که صدا زده میشه، بستگی به نوع متغیر در زمان کامپایل داره، نه نوع واقعی آبجکت.
این مثال رو ببینید تا کامل متوجه بشید:
public class BaseClass
{
public virtual void Foo() { Console.WriteLine("BaseClass.Foo"); }
}
public class Overrider : BaseClass
{
public override void Foo() { Console.WriteLine("Overrider.Foo"); }
}
public class Hider : BaseClass
{
public new void Foo() { Console.WriteLine("Hider.Foo"); }
}
// --- نتایج ---
Overrider over = new Overrider();
BaseClass b1 = over;
over.Foo(); // خروجی: Overrider.Foo
b1.Foo(); // خروجی: Overrider.Foo <- (چون override شده، رفتار واقعی حفظ میشه)
Hider h = new Hider();
BaseClass b2 = h;
h.Foo(); // خروجی: Hider.Foo
b2.Foo(); // خروجی: BaseClass.Foo <- (چون new شده، متد کلاس پایه اجرا میشه!)
4️⃣ قفل کردن وراثت با sealed 🔒
حالا اگه بخواید جلوی override شدن بیشتر رو در زنجیره وراثت بگیرید، از sealed استفاده میکنید.
• sealed روی متد:
میتونید یه متد override شده رو sealed کنید تا کلاسهای فرزند بعدی دیگه نتونن اون رو تغییر بدن.
• sealed روی کلاس:
یا میتونید کل یک کلاس رو sealed کنید تا دیگه هیچ کلاسی نتونه ازش ارثبری کنه.
🤔 حرف حساب
تسلط بر این کلمات کلیدی، شما رو از یه کاربر ساده وراثت، به یک معمار واقعی کلاسها تبدیل میکنه.
🔖 هشتگها:
#OOP #Inheritance #Override #Sealed #Polymorphism
📖 سری آموزشی کتاب C# 12 in a Nutshell
وقتی کلاسهای فرزند میخوان آبجکت بسازن، چه اتفاقی پشت صحنه میفته؟ امروز به عمیقترین قوانین ساخت و ساز در سلسلهمراتب وراثت شیرجه میزنیم.
base
به شما اجازه میده از داخل کلاس فرزند، به اعضای کلاس پدر دسترسی داشته باشید. دو کاربرد اصلی داره:
صدا زدن عضو بازنویسی شده:
اگه متدی رو override کردید، با base میتونید به پیادهسازی اصلی در کلاس پدر دسترسی پیدا کنید.
صدا زدن سازنده پدر:
با سینتکس : base(...) صراحتاً سازنده مورد نظرتون در کلاس پدر رو صدا میزنید.
• قانون اول: سازنده کلاس پدر همیشه قبل از سازنده کلاس فرزند اجرا میشه.
• قانون دوم (تله مهم): اگه شما : base(...) رو ننویسید، #C به صورت خودکار سعی میکنه سازنده بدون پارامتر پدر رو صدا بزنه. اگه کلاس پدر سازنده بدون پارامتر نداشته باشه، خطای زمان کامپایل میگیرید!
جایگزین مدرن ✨
گاهی وقتا زنجیره سازندهها خیلی طولانی میشه. از #C 11 به بعد، میتونید پراپرتیها یا فیلدهایی رو با کلمه کلیدی required مشخص کنید. این یعنی هر کسی که از کلاس شما آبجکت میسازه، مجبوره که این اعضا رو با استفاده از Object Initializer مقداردهی کنه.
💡نکته: برای اینکه به یک سازنده اجازه بدید این قانون رو دور بزنه، میتونید از اتریبیوت
وقتی یه آبجکت فرزند ساخته میشه، ترتیب اجرای دقیق عملیات به این صورته:
فاز اول (از فرزند به پدر):
•فیلدهای کلاس فرزند مقداردهی اولیه میشن.
• آرگومانهای پاس داده شده به base(...) ارزیابی میشن.
• این روند تا بالاترین کلاس در سلسلهمراتب ادامه پیدا میکنه.
فاز دوم (از پدر به فرزند):
• حالا بدنهی سازندهها، از کلاس پدر شروع شده و به سمت کلاس فرزند اجرا میشن.
مثال کتاب برای درک بهتر این ترتیب:
🤔 حرف حساب و تجربه شما
درک این جزئیات، شما رو به یک متخصص واقعی در طراحی کلاسهای شیءگرا تبدیل میکنه.
🏛 کالبدشکافی وراثت در #C: سازندهها، required و ترتیب اجرا
وقتی کلاسهای فرزند میخوان آبجکت بسازن، چه اتفاقی پشت صحنه میفته؟ امروز به عمیقترین قوانین ساخت و ساز در سلسلهمراتب وراثت شیرجه میزنیم.
1️⃣ کلیدواژه base: صحبت با کلاس پدر
base
به شما اجازه میده از داخل کلاس فرزند، به اعضای کلاس پدر دسترسی داشته باشید. دو کاربرد اصلی داره:
صدا زدن عضو بازنویسی شده:
اگه متدی رو override کردید، با base میتونید به پیادهسازی اصلی در کلاس پدر دسترسی پیدا کنید.
public override decimal Liability => base.Liability + Mortgage;
صدا زدن سازنده پدر:
با سینتکس : base(...) صراحتاً سازنده مورد نظرتون در کلاس پدر رو صدا میزنید.
public class Subclass : Baseclass
{
public Subclass(int x) : base(x) { }
}
2️⃣ قوانین سازندهها در وراثت ⚖️
• قانون اول: سازنده کلاس پدر همیشه قبل از سازنده کلاس فرزند اجرا میشه.
• قانون دوم (تله مهم): اگه شما : base(...) رو ننویسید، #C به صورت خودکار سعی میکنه سازنده بدون پارامتر پدر رو صدا بزنه. اگه کلاس پدر سازنده بدون پارامتر نداشته باشه، خطای زمان کامپایل میگیرید!
3️⃣ required members (از C# 11):
جایگزین مدرن ✨
گاهی وقتا زنجیره سازندهها خیلی طولانی میشه. از #C 11 به بعد، میتونید پراپرتیها یا فیلدهایی رو با کلمه کلیدی required مشخص کنید. این یعنی هر کسی که از کلاس شما آبجکت میسازه، مجبوره که این اعضا رو با استفاده از Object Initializer مقداردهی کنه.
public class Asset
{
public required string Name;
}
// ✅ درسته
Asset a1 = new Asset { Name = "House" };
// ❌ خطای زمان کامپایل! چون Name مقداردهی نشده
Asset a2 = new Asset();
💡نکته: برای اینکه به یک سازنده اجازه بدید این قانون رو دور بزنه، میتونید از اتریبیوت
[SetsRequiredMembers] استفاده کنید.4️⃣ ترتیب دقیق ساخت (Deep Dive) 🔬
وقتی یه آبجکت فرزند ساخته میشه، ترتیب اجرای دقیق عملیات به این صورته:
فاز اول (از فرزند به پدر):
•فیلدهای کلاس فرزند مقداردهی اولیه میشن.
• آرگومانهای پاس داده شده به base(...) ارزیابی میشن.
• این روند تا بالاترین کلاس در سلسلهمراتب ادامه پیدا میکنه.
فاز دوم (از پدر به فرزند):
• حالا بدنهی سازندهها، از کلاس پدر شروع شده و به سمت کلاس فرزند اجرا میشن.
مثال کتاب برای درک بهتر این ترتیب:
public class B
{
int x = 1; // مرحله ۳: این اجرا میشه
public B(int x)
{
// مرحله ۴: بدنه سازنده پدر اجرا میشه
}
}
public class D : B
{
int y = 1; // مرحله ۱: این اول از همه اجرا میشه
public D(int x)
: base(x + 1) // مرحله ۲: آرگومان base ارزیابی میشه
{
// مرحله ۵: بدنه سازنده فرزند در آخر اجرا میشه
}
}
🤔 حرف حساب و تجربه شما
درک این جزئیات، شما رو به یک متخصص واقعی در طراحی کلاسهای شیءگرا تبدیل میکنه.
🔖 هشتگها:
#OOP #Inheritance #BestPractices #Constructor
📖 سری آموزشی کتاب C# 12 in a Nutshell
تو پست قبلی، وراثت و اینترفیسها رو جداگونه بررسی کردیم. اما وقتی این دو تا با هم ترکیب میشن، دنیایی از نکات ظریف و الگوهای پیشرفته به وجود میاد.
امروز میخوایم یاد بگیریم چطور یه عضو اینترفیس رو در سلسلهمراتب وراثت به درستی override کنیم.
به صورت پیشفرض، وقتی یه عضو اینترفیس رو پیادهسازی میکنید، اون sealed (مهر و موم شده) هست. برای اینکه به کلاسهای فرزند اجازه override کردنش رو بدید، باید اون رو صراحتاً virtual مشخص کنید. این کار به شما اجازه میده از قدرت کامل چندریختی (Polymorphism) استفاده کنید.
حالا فرض کنید کلاس پدر، متد رو virtual نکرده. یه راه برای "override" کردنش، بازپیادهسازی اینترفیس در کلاس فرزنده. این کار، پیادهسازی رو فقط وقتی که آبجکت از طریق خود اینترفیس صدا زده بشه، "هایجک" میکنه.
تلهی بزرگ: ☠️ اگه پیادهسازی در کلاس پدر به صورت ضمنی (public) باشه، این الگو باعث رفتار متناقض و خطرناک میشه!
بازپیادهسازی معمولاً یه راه حل ضعیف و نشانهی طراحی بده. دو الگوی خیلی بهتر برای طراحی کلاسهای توسعهپذیر وجود داره:
الگوی اول: اگه پیادهسازی ضمنیه، همیشه virtual ـش کنید (همون روش شماره ۱).
الگوی دوم (برای پیادهسازی صریح): پیادهسازی صریح (explicit) اینترفیس رو به یک متد protected virtual وصل کنید. این الگو، قدرت کامل رو به کلاسهای فرزند میده تا رفتار رو به صورت امن override کنن.
این الگوها، تفاوت بین یه کتابخونه قابل اعتماد و یه کتابخونه شکننده رو رقم میزنن. طراحی برای توسعهپذیری، نشانه یک معمار نرمافزار حرفهایه.
🧩 وراثت و اینترفیسها در #C: بازپیادهسازی و الگوهای حرفهای
تو پست قبلی، وراثت و اینترفیسها رو جداگونه بررسی کردیم. اما وقتی این دو تا با هم ترکیب میشن، دنیایی از نکات ظریف و الگوهای پیشرفته به وجود میاد.
امروز میخوایم یاد بگیریم چطور یه عضو اینترفیس رو در سلسلهمراتب وراثت به درستی override کنیم.
1️⃣ روش استاندارد: virtual و override
به صورت پیشفرض، وقتی یه عضو اینترفیس رو پیادهسازی میکنید، اون sealed (مهر و موم شده) هست. برای اینکه به کلاسهای فرزند اجازه override کردنش رو بدید، باید اون رو صراحتاً virtual مشخص کنید. این کار به شما اجازه میده از قدرت کامل چندریختی (Polymorphism) استفاده کنید.
public interface IUndoable { void Undo(); }
public class TextBox : IUndoable
{
public virtual void Undo() => Console.WriteLine("TextBox.Undo");
}
public class RichTextBox : TextBox
{
public override void Undo() => Console.WriteLine("RichTextBox.Undo");
}
// --- نتایج ---
RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo
((IUndoable)r).Undo(); // RichTextBox.Undo
((TextBox)r).Undo(); // RichTextBox.Undo (رفتار یکپارچه و درست)2️⃣ تکنیک خطرناک: بازپیادهسازی
(Re-implementation) ⚠️
حالا فرض کنید کلاس پدر، متد رو virtual نکرده. یه راه برای "override" کردنش، بازپیادهسازی اینترفیس در کلاس فرزنده. این کار، پیادهسازی رو فقط وقتی که آبجکت از طریق خود اینترفیس صدا زده بشه، "هایجک" میکنه.
تلهی بزرگ: ☠️ اگه پیادهسازی در کلاس پدر به صورت ضمنی (public) باشه، این الگو باعث رفتار متناقض و خطرناک میشه!
public class TextBox : IUndoable
{
// این متد virtual نیست!
public void Undo() => Console.WriteLine("TextBox.Undo");
}
public class RichTextBox : TextBox, IUndoable // بازپیادهسازی اینترفیس
{
// اینجا new هم میتونستیم بذاریم
public void Undo() => Console.WriteLine("RichTextBox.Undo");
}
// --- نتایج متناقض و خطرناک ---
RichTextBox r = new RichTextBox();
r.Undo(); // RichTextBox.Undo
((IUndoable)r).Undo(); // RichTextBox.Undo (هایجک شد)
((TextBox)r).Undo(); // TextBox.Undo (فاجعه! رفتار چندریختی شکست)
3️⃣ الگوهای حرفهای (جایگزینهای بهتر) ✅
بازپیادهسازی معمولاً یه راه حل ضعیف و نشانهی طراحی بده. دو الگوی خیلی بهتر برای طراحی کلاسهای توسعهپذیر وجود داره:
الگوی اول: اگه پیادهسازی ضمنیه، همیشه virtual ـش کنید (همون روش شماره ۱).
الگوی دوم (برای پیادهسازی صریح): پیادهسازی صریح (explicit) اینترفیس رو به یک متد protected virtual وصل کنید. این الگو، قدرت کامل رو به کلاسهای فرزند میده تا رفتار رو به صورت امن override کنن.
public class TextBox : IUndoable
{
// پیادهسازی صریح، کار را به یک متد مجازی و محافظتشده میسپارد
void IUndoable.Undo() => Undo();
protected virtual void Undo() => Console.WriteLine("TextBox.Undo");
}
public class RichTextBox : TextBox
{
protected override void Undo() => Console.WriteLine("RichTextBox.Undo");
}
🤔 حرف حساب و تجربه شما
این الگوها، تفاوت بین یه کتابخونه قابل اعتماد و یه کتابخونه شکننده رو رقم میزنن. طراحی برای توسعهپذیری، نشانه یک معمار نرمافزار حرفهایه.
🔖 هشتگها:
#CSharp #DotNet #OOP #Interface #Inheritance