مقدمه

پس از یک معرفی کلی از فریمورک .NET و ویژگی‌های کلیدی آن در درس قبل، اکنون قصد داریم به جزئیات مربوط به معماری .NET بپردازیم و با اجزای تشکیل‌دهنده‌ی این فریمورک و نقش آنها آشنا شویم. این اجزا عبارتند از: CTS، CLS، Runtime و BCL.

نقش CTS در .NET

CTS یا Common Type System یک استاندارد تدوین‌شده توسط مایکروسافت است که مستندات آن همه‌ی نوع‌های داده‌ای ممکن و همه‌ی ساختارهای برنامه‌نویسی که توسط .NET Runtime پشتیبانی می‌شوند را توصیف کرده و نحوه‌ی ارتباط این موجودیت‌ها با یکدیگر و جزئیات مربوط به نمایش آنها در فرمت .NET metadata را مشخص می‌کند.

سیستم نوع یا Type System مفهومی است که در همه‌ی زبان‌های برنامه‌نویسی وجود دارد و مختص C# و .NET نیست اما ویژگی منحصر به فرد سیستم نوع .NET این است که بین همه‌ی زبان‌های .NET مشترک است و واژه‌ی Common (به معنای مشترک) به همین موضوع اشاره دارد.

نوع یا type در دنیای .NET یک اصطلاح عمومی برای ارجاع به کلاس (class)، اینترفیس (interface)، ساختار (structure)، نماینده (delegate) و شمارشی (enumerate) است. این‌ها نوع‌های پنچ‌گانه‌ی تعریف‌شده در CTS هستند که زبان‌های .NET می‌توانند از آنها برای ساخت برنامه‌های خود استفاده کنند. اگر شما قبلاً برنامه‌هایی را با یکی از زبان‌های .NET ایجاد کرده باشید، احتماًلاً با همه یا بعضی از این نوع‌ها آشنا هستید. به عنوان مثال، در یک برنامه ممکن است یک کلاس تعریف کرده باشید که یک یا چند اینترفیس را پیاده‌سازی کند و یا متدی ایجاد کرده باشید که یک نوع شمارشی یا enumeration را به عنوان پارامتر ورودی دریافت کرده و یک نوع ساختار یا structure را به عنوان خروجی برگرداند.

اما در CTS علاوه بر تعریف این نوع‌ها، موارد دیگری مثل نحوه‌ی استفاده از این نوع‌ها نیز آورده شده است. هرچند معمولاً نیازی نیست تا برنامه‌نویسان درگیر جزئیات مربوط به CTS شوند و فقط سازندگان ابزارهایی مانند کامپایلرها با جزئیات CTS سر و کار دارند، اما برای همه‌ی برنامه‌نویسان مفید خواهد بود که با نحوه‌ی کار CTS و نوع‌های پنج‌گانه‌ی آن که در بالا نام برده شد، آشنا باشند. در ادامه اشاره‌ای مختصر به نوع‌های تعریف شده در CTS داریم.

نوع کلاس (Class)

همه‌ی زبان‌های .NET از نوع کلاس پشتیبانی می‌کنند. کلاس، واحد اصلی در برنامه‌نویسی شی‌گرا (OOP) به حساب می‌آید. یک کلاس می‌تواند شامل اعضایی مانند متد (method)، سازنده (constructor)، پراپرتی (property) و رویداد (event) باشد که در آینده با این اعضا آشنا خواهیم شد. در C# برای ایجاد یک کلاس از کلمه کلیدی class مانند زیر استفاده می‌شود:

// A  C#  class type with 1 method.
class Calc
{
    public int Add(int addend1, int addend2)
    {
        return addend1 + addend2;
    }
}

در این مثال، کلاسی با نام Calc تعریف شده که شامل یک متد با نام Add() است. خط اول که با دو کاراکتر اسلش (//) شروع شده، کامنت است و توسط کامپایلر نادیده گرفته می‌شود.

نوع اینترفیس (interface)

یک اینترفیس چیزی نیست جز یک مجموعه از اعضای انتزاعی (abstract) که می‌توانند دارای یک پیاده‌سازی پیش‌فرض باشند. یک اینترفیس با این هدف تعریف می‌شود که یک یا چند کلاس (یا ساختار) آن را پیاده‌سازی کنند که در این صورت کلاس یا ساختارهای مورد نظر می‌توانند پیاده‌سازی پیش‌فرض اعضای اینترفیس (در صورت وجود) را بپذیرند و یا اینکه پیاده‌سازی خودشان را ارائه دهند. بنا بر قرارداد، نام اینترفیس‌ها با حرف I شروع می‌شود.

public interface IDraw
{
    void Draw();
}

در این مثال، یک اینترفیس با نام IDraw تعریف شده که تنها عضو آن یک متد با نام Draw() است.

نوع ساختار (Structure)

یک ساختار را می‌توان معادل یک کلاس در نظر گرفت. البته باید در نظر داشت که یک ساختار در رفتارهایی مانند چگونگی ذخیره‌شدن و حذف از حافظه با یک کلاس تفاوت دارد و در واقع، یک ورژن لایت از کلاس محسوب می‌شود. ساختارها معمولاً برای مدل‌سازی داده‌های ریاضی و هندسی به کار می‌روند و در C# با استفاده از کلمه کلیدی struct ساخته می‌شوند.

struct Point
{
    public int xPos, yPos;
    public Point(int x, int y)
    { xPos = x; yPos = y; }
    public void PrintPosition()
    {
        Console.WriteLine("({0}, {1})", xPos, yPos);
    }
}

در این مثال، ساختاری با نام Point تعریف شده که شامل دو فیلد، یک سازنده و یک متد است.

نوع شمارشی (Enumeration)

یکی دیگر از نوع‌های موجود در CTS نوع شمارشی است. یک شمارشی مجموعه‌ای از چند مقدار عددی است که نام با معنایی به آنها نسبت داده می‌شود و می‌تواند به افزایش خوانایی برنامه کمک کند. به عنوان یک مثال، فرض کنید یک بازی ویدیویی ساخته‌اید که به بازیکن اجازه می‌دهد هر یک از شخصیت‌های جادوگر، مبارز و سارق را انتخاب کند. در این وضع می‌توانید با استفاده از کلمه کلیدی enum یک نوع شمارشی را به صورت زیر تعریف کنید.

enum CharacterTypeEnum
{
    Wizard = 100,
    Fighter = 200,
    Thief = 300
}

نوع نماینده (Delegate)

اگر با زبان‌های C یا C++ کار کرده باشید، باید با مفهوم اشاره‌گر تابع (function pointer) آشنا باشید. یک نوع نماینده یا delegate در C# مثل یک pointer در C++ است با این تفاوت که در C# نماینده‌ها از یک نوع پایه با نام System.MulticastDelegate مشتق می‌شوند درحالی که یک pointer در C++ تنها یک اشاره‌گر به محلی از حافظه است. در C# یک نماینده را می‌توان با استفاده از کلمه کلیدی delegate ایجاد کرد.

delegate int BinaryOp(int x, int y);

این مثال شامل تعریف یک نماینده با نام BinaryOp است که می‌تواند به هر متدی که دو پارامتر ورودی از نوع int دریافت کرده و یک خروجی از نوع int برمی‌گرداند، اشاره کند.

اعضای یک نوع

اکنون که با نوع‌های موجود در CTS آشنا شدید، باید بدانید که هر نوع می‌تواند تعدادی عضو داشته باشد. به بیان رسمی، یک عضو واژه‌ای است که به یکی از اعضای مجموعه‌ی زیر اطلاق می‌شود:

{constructor, nested type, operator, method, property, indexer, field, constant, event, finalizer}

هر عضو می‌تواند دارای یکی از سطوح دسترسی public، private یا protected باشد. بعضی از اعضا می‌توانند به صورت abstract یا virtual تعریف شوند. همچنین بعضی از اعضا می‌توانند static باشند. در فصول آینده با اعضای نوع‌ها و ویژگی‌های ذکر شده آشنا خواهید شد.

نوع‌های داده‌ای پایه

آخرین مطلبی که در مورد CTS بیان می‌کنیم، مربوط به نوع‌های داده‌ای پایه یا Primitive Data Types است. در CTS چند نوع داده‌ای پایه تعریف شده که هر زبان .NET از یک کلمه کلیدی برای ارجاع به هر یک از این نوع‌ها استفاده می‌کند. به عنوان مثال، یکی از این نوع‌های پایه System.Int32 است که در C# با نام int و در VB با نام Integer شناخته می‌شود. این نوع‌های داده‌ای پایه در جدول زیر به همراه نام مستعارشان در زبان‌های C# و VB آورده شده‌اند.

نوع داده CTS کلمه کلیدی C# کلمه کلیدی VB
System.Byte byte Byte
System.SByte sbyte SByte
System.int16 short Short
System.Int32 int Integer
System.Int64 long Long
System.UInt16 ushort UShort
System.UInt32 uint UInt
System.UInt64 ulong ULong
System.Single float Single
System.Double double Double
System.Object object Object
System.Char char Char
System.String string String
System.Decimal decimal Decimal
System.Boolean bool Boolean

بنابراین، کلمه کلیدی int در C# و Integer در VB هر دو نام‌های مستعاری هستند برای نوع System.Int32 که در CTS تعریف شده است. البته در هر دو زبان می‌توان به جای نام مستعار از نام اصلی یک نوع نیز استفاده کرد اما قطعاً استفاده از نام مستعاری مانند int نسبت به System.Int32 راحت‌تر و سرراست‌تر است.

نقش CLS در .NET

مؤلفه‌ی بعدی Common Language Specification یا CLS نام دارد که یک زیرمجموعه از نوع‌های داده‌ای و ساختارهای برنامه‌نویسی تعریف‌شده در CTS است که توسط همه‌ی زبان‌های .NET پشتیبانی می‌شوند. بنابراین، اگر نوع‌هایی ایجاد کنیم که تنها از ویژکی‌های سازگار با CLS یا CLS-Compliant features استفاده کنند، همه‌ی زبان‌های .NET می‌توانند از این نوع‌ها استفاده کنند. برعکس، اگر از یک نوع یا ساختار برنامه‌نویسی خارج از CLS استفاده کنیم، تضمینی وجود ندارد که هر زبان .NET بتواند کد ما را درک کند.

زبان‌های برنامه‌نویسی مختلف، مفاهیم برنامه‌نویسی را به شکل‌های متفاوتی پیاده‌سازی می‌کنند. به عنوان مثال، برای چسباندن دو رشته‌ی متنی به یکدیگر در C# از عملگر + استفاده می‌شود اما در VB عملگر & برای این کار در نظر گرفته شده است.

علاوه بر این، هر زبان ویژگی‌هایی دارد که منحصر به همان زبان است و در زبان‌های دیگر پشتیبانی نمی‌شود. با در نظر گرفتن این موارد، ایده‌آل آن است که یک مجموعه از ویژگی‌های مشترک بین همه‌ی زبان‌های .NET وجود داشته باشد و CLS دقیقاً همین مجموعه است. در واقع، CLS قراردادی است که رعایت آن باعث می‌شود که برنامه‌ی تولید شده برای همه‌ی زبان‌های .NET قابل استفاده باشد. به عبارت دیگر، اگر بخواهید برنامه‌ای که توسط یک زبان .NET می‌نویسید، توسط سایر زبان‌های .NET نیز قابل بسط و ویرایش باشد، باید قوانین CLS را رعایت کنید. به عنوان مثال، کد زیر سازگار با CLS نیست زیرا از نوع داده ای ulong برای پارامترهای متد Add() استفاده کرده در حالی که در CLS استفاده از این نوع داده مجاز نیست.

class Calc
{
    public ulong Add(ulong addend1, ulong addend2)
    {
        return addend1 + addend2;
    }
}

نقش لایه‌ی Runtime در .NET

مؤلفه‌ی Runtime شامل همه‌ی نوع‌های پایه (basic types) و یک مجموعه از پیاده‌سازی‌های مینیمالی است که به یک پلتفرم و یک معماری خاص (x86 ،x64 ،ARM) وابسته هستند. مدیریت برنامه و فراهم کردن سرویس‌هایی برای تامین امنیت و مدیریت حافظه در برنامه، مهمترین وظایف .NET Runtime محسوب می‌شوند.

.NET Runtime شباهت زیادی به Java Runtime دارد. برنامه‌ای که به زبان جاوا نوشته شده، برای اجرای صحیح به JVM یا Java Virtual Machine نیاز دارد. تا قبل از سال 2016 برای اینکه برنامه‌های .NET روی یک سیستم اجرا شده و به درستی کار کنند، به نصب فریمورک .NET که در آن زمان یک فریمورک یکپارچه بود، نیاز داشتند. اما از سال 2016 و با معرفی .NET Core که یک فریمورک ماژولار و مستقل از پلتفرم است، نیازی به نصب فریمورک روی ماشین هدف نیست. چون یک برنامه‌ی .NET Core بخش‌های مورد نیاز خود از Runtime را با خود به سیستم هدف حمل می‌کند. ساختار ماژولار و مستقل از پلتفرم فریمورک .NET پس از ادغام نسخه‌ی کلاسیک .NET و نسخه‌ی .NET Core حفظ شده است.

نقش BCL در .NET

فریمورک .NET یک مجموعه از کتابخانه کلاس‌های پایه یا Base Class Libraries (BCL) ارائه می‌کند که در دسترس همه‌ی زبان‌های برنامه‌نویسی .NET قرار دارد. BCL مجموعه‌ای است از هزاران قابلیت از پیش ساخته شده که کار برنامه‌نویسی را ساده‌تر می‌کنند. BCL نوع‌های متعددی را تعریف می‌کند که برنامه‌نویسان می‌توانند از آنها برای ساخت انواع مختلف اپلیکیشن‌ها استفاده کنند. به عنوان مثال، از ASP.NET برای ساخت وبسایت‌ها و سرویس‌های REST و WPF برای ساخت اپلیکیشن‌های گرافیکی دسکتاپ استفاده می‌شود. علاوه بر اینها، BCL امکاناتی برای تعامل با سیستم فایل و پوشه در کامپیوتر، ارتباط با پایگاه‌های داده و غیره فراهم می‌کند.