مقدمه

در درس گذشته، با دایرکتیوها و صفت‌های سطح بالای CIL که ساختار کلی یک اسمبلی و انواع داده‌ی درون آن را تعریف می‌کنند، آشنا شدیم. دیدیم که چگونه دایرکتیو .class یک نوع جدید را معرفی می‌کند و .assembly اطلاعات کلی اسمبلی را مشخص می‌نماید. اکنون زمان آن رسیده که به درون این ساختارها نفوذ کرده و ببینیم که اعضای مختلف یک نوع (type members) - یعنی فیلدها، متدها، سازنده‌ها و پراپرتی‌ها - چگونه در سطح زبان میانی مشترک تعریف و توصیف می‌شوند.

هر عضوی که ما در C# تعریف می‌کنیم، یک نمایش متناظر و دقیق در CIL دارد. کامپایلر C# وظیفه دارد تا ساختارهای سطح بالای زبان را به این تعاریف سطح پایین‌تر ترجمه کند. درک این فرآیند ترجمه به ما کمک می‌کند تا بفهمیم ویژگی‌هایی مانند پراپرتی‌ها یا سازنده‌ها که در C# به نظر یکپارچه می‌آیند، در واقع چگونه از ترکیب چندین جزء در CIL ساخته شده‌اند.

تعریف فیلدها با دایرکتیو .field

فیلدها (Fields)، که متغیرهای عضو یک کلاس یا ساختار هستند، با استفاده از دایرکتیو .field در CIL تعریف می‌شوند. این دایرکتیو نه تنها نام و نوع فیلد را مشخص می‌کند، بلکه ویژگی‌های دیگری مانند سطح دسترسی و استاتیک بودن را نیز تعیین می‌نماید.

سینتکس کلی به این صورت است: .field [attributes] type fieldName
بیایید یک کلاس C# با چند فیلد متفاوت را در نظر بگیریم و ببینیم کامپایلر چگونه آن‌ها را به CIL ترجمه می‌کند.

Copy Icon C# Code
public class Vehicle
{
    private int _horsePower;
    public static readonly int NumberOfWheels = 4;
}

کد CIL تولید شده برای این فیلدها به شکل زیر خواهد بود:

Copy Icon CIL Code
.class public auto ansi beforefieldinit Vehicle extends [System.Runtime]System.Object
{
  .field private int32 _horsePower
  .field public static literal int32 NumberOfWheels = int32(4)
  
  // ... constructor and other methods ...
}

بیایید این کد را تحلیل کنیم:

  • .field private int32 _horsePower: این خط یک فیلد نمونه (instance) به نام _horsePower تعریف می‌کند. صفت private سطح دسترسی آن را مشخص می‌کند و int32 نوع داده‌ی آن است که معادل int در C# می‌باشد.
  • .field public static literal int32 NumberOfWheels...: این خط یک فیلد استاتیک و عمومی را تعریف می‌کند. کلمه‌ی کلیدی static نشان می‌دهد که این فیلد به خود کلاس Vehicle تعلق دارد، نه به یک نمونه‌ی خاص. اما نکته‌ی جالب، کلمه‌ی کلیدی literal است. کامپایلر C#، فیلدهای const و فیلدهای static readonly که با یک مقدار ثابت در زمان کامپایل مقداردهی شده‌اند را به عنوان یک "لیترال" در فراداده ثبت می‌کند. این به CLR اجازه می‌دهد تا این مقدار را بهینه کرده و مستقیماً در کد جایگزین کند. اگر `NumberOfWheels` در سازنده‌ی استاتیک مقداردهی می‌شد، به جای literal از صفت initonly (معادل readonly) استفاده می‌شد.

تعریف متدها با دایرکتیو .method

همانطور که در درس‌های قبل دیدیم، متدها با دایرکتیو .method تعریف می‌شوند. این دایرکتیو امضای کامل متد را مشخص می‌کند.

صفت‌های متداول متدها

علاوه بر سطح دسترسی (public, private, family که معادل protected است) و ماهیت (static, virtual, abstract)، دو صفت ویژه وجود دارند که اغلب در CIL تولید شده توسط کامپایلر دیده می‌شوند:

  • specialname: این صفت نشان می‌دهد که نام این متد برای کامپایلرها یا ابزارها معنای خاصی دارد. این صفت معمولاً برای متدهایی که مستقیماً در کد C# قابل فراخوانی نیستند، مانند سازنده‌ها و اکسسورهای پراپرتی‌ها، به کار می‌رود.
  • rtspecialname: این صفت که همیشه با specialname همراه است، نشان می‌دهد که خود CLR نیز با این متد به صورت ویژه‌ای رفتار می‌کند. این صفت تقریباً همیشه برای سازنده‌ها (.ctor) به کار می‌رود.

نمایش سازنده‌ها و پراپرتی‌ها

سازنده‌ها و پراپرتی‌ها در C#، مفاهیم سطح بالایی هستند که در CIL به ترکیبی از متدها و دایرکتیوهای دیگر ترجمه می‌شوند.

سازنده‌ها به عنوان .ctor

هر سازنده‌ای که در C# می‌نویسید، به یک متد با نام ثابت .ctor (مخفف constructor) در CIL ترجمه می‌شود. این متدها همیشه با صفت‌های specialname و rtspecialname علامت‌گذاری می‌شوند.

Copy Icon CIL for a Constructor
.method public hidebysig specialname rtspecialname 
        instance void .ctor() cil managed
{
  // ... constructor body ...
}

پراپرتی‌ها: ترکیبی از .property و متدهای get/set

یک پراپرتی در C#، یک مفهوم انتزاعی است. کامپایلر آن را به سه بخش در CIL ترجمه می‌کند:

  1. یک فیلد پشتیبان (backing field) خصوصی (اگر پراپرتی خودکار باشد).
  2. دو متد اکسسور: یکی برای get (با پیشوند get_) و یکی برای set (با پیشوند set_). این متدها با صفت specialname علامت‌گذاری می‌شوند.
  3. یک دایرکتیو .property که این دو متد را به یک نام پراپرتی واحد متصل می‌کند.

بیایید پراپرتی public string Name { get; set; } را در نظر بگیریم. کد CIL آن (به صورت ساده‌شده) به شکل زیر خواهد بود:

Copy Icon CIL for a Property
// 1. The backing field (name is generated by compiler)
.field private string '<Name>k__BackingField'

// 2. The property definition linking the accessors
.property instance string Name()
{
  .get instance string MyNamespace.MyClass::get_Name()
  .set instance void MyNamespace.MyClass::set_Name(string)
}

// 3. The 'get' accessor method
.method public hidebysig specialname instance string 
        get_Name() cil managed
{
  // CIL to load and return the backing field
} 

// 4. The 'set' accessor method
.method public hidebysig specialname instance void 
        set_Name(string 'value') cil managed
{
  // CIL to store the 'value' parameter into the backing field
}

همانطور که می‌بینید، یک پراپرتی ساده در C# به یک ساختار نسبتاً پیچیده در CIL تبدیل می‌شود. این دایرکتیو .property است که به ابزارهایی مانند ویژوال استودیو و دیباگر می‌گوید که این دو متد در واقع اکسسورهای یک پراپرتی واحد هستند و باید به صورت یکپارچه نمایش داده شوند.