مقدمه

در درس گذشته با گرامر پایه‌ای زبان میانی مشترک (CIL) و دستورات مبتنی بر پشته‌ی آن آشنا شدیم. دیدیم که چگونه دستورات ساده‌ای مانند ldarg، add و ret برای اجرای منطق یک متد به کار می‌روند. اما یک اسمبلی .NET بسیار بیشتر از مجموعه‌ای از دستورالعمل‌های اجرایی است؛ این یک ساختار پیچیده با فراداده‌ی غنی است که انواع داده، اعضای آن‌ها، وابستگی‌ها و اطلاعات نسخه‌بندی را توصیف می‌کند. این ساختار و فراداده چگونه در CIL تعریف می‌شوند؟ پاسخ در دایرکتیوها (Directives) و صفت‌ها (Attributes) نهفته است.

دایرکتیوها که با یک نقطه شروع می‌شوند، به اسمبلر (ilasm.exe) و CLR دستور می‌دهند که چگونه ساختار اسمبلی را بسازند. آن‌ها معادل کلمات کلیدی ساختاری در C# مانند class، namespace و public هستند. صفت‌ها در CIL نیز، مانند صفت‌ها در C#، فراداده‌ی اضافی را به عناصر مختلف متصل می‌کنند. در این درس به تفصیل این دو مفهوم را بررسی می‌کنیم.

دایرکتیوهای CIL: تعریف ساختار

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

دایرکتیوهای سطح اسمبلی

این دایرکتیوها اطلاعات کلی در مورد کل اسمبلی را تعریف می‌کنند و معمولاً در ابتدای فایل .il قرار می‌گیرند.

  • .assembly extern <Name>: این دایرکتیو یک ارجاع به یک اسمبلی خارجی را تعریف می‌کند. هر اسمبلی .NET حداقل به کتابخانه‌ی هسته‌ی .NET (که در نسخه‌های قدیمی‌تر mscorlib و در نسخه‌های مدرن System.Runtime نامیده می‌شود) نیاز دارد. این دایرکتیو به CLR می‌گوید که انواع داده‌ی پایه‌ای مانند System.Object یا System.Int32 در کجا قرار دارند.
  • .assembly <Name>: نام اسمبلی فعلی را مشخص می‌کند. این دایرکتیو همچنین می‌تواند شامل دایرکتیوهای تودرتو برای تعیین نسخه (.ver) یا کلید عمومی (.publickeytoken) باشد.
  • .module <FileName>: نام فایل فیزیکی ماژول را مشخص می‌کند (معمولاً همان نام فایل .dll یا .exe).
Copy Icon Assembly Directives Example
.assembly extern System.Runtime { }
.assembly MyAwesomeLibrary
{
  .ver 1:0:0:0
}
.module MyAwesomeLibrary.dll

کد بالا یک اسمبلی به نام MyAwesomeLibrary با نسخه‌ی 1.0.0.0 را تعریف می‌کند که به اسمبلی System.Runtime وابستگی دارد.

دایرکتیوهای سطح نوع (Type-Level)

مهم‌ترین دایرکتیو در این سطح، .class است که برای تعریف یک کلاس، اینترفیس یا ساختار به کار می‌رود. این دایرکتیو می‌تواند با چندین صفت همراه شود تا ویژگی‌های نوع را مشخص کند:

  • سطح دسترسی: public یا private (که در CIL به آن assembly گفته می‌شود).
  • وراثت: کلمه‌ی کلیدی extends برای مشخص کردن کلاس پایه به کار می‌رود. کلمه‌ی کلیدی implements برای پیاده‌سازی اینترفیس‌ها استفاده می‌شود.
  • دیگر صفت‌ها: abstract (برای کلاس‌های انتزاعی)، sealed (برای کلاس‌های مهر و موم شده)، auto (برای چیدمان خودکار فیلدها توسط CLR) و ansi (برای نحوه‌ی مارشال کردن رشته‌ها).
Copy Icon Class Definition in CIL
.class public auto ansi sealed beforefieldinit MySealedClass
       extends [System.Runtime]System.Object
       implements [MyContracts]MyContracts.ISomeInterface

کد بالا یک کلاس public sealed به نام MySealedClass را تعریف می‌کند که از System.Object ارث‌بری کرده و اینترفیس ISomeInterface را پیاده‌سازی می‌کند.

صفت‌های CIL

همانطور که در C# از صفت‌ها برای افزودن فراداده‌ی اضافی استفاده می‌کنیم، در CIL نیز می‌توانیم این کار را انجام دهیم. صفت‌ها در CIL با استفاده از دایرکتیو .custom تعریف می‌شوند.

فرض کنید در C# صفت [Serializable] را به یک کلاس اعمال کرده‌ایم:

Copy Icon C# Code
[Serializable]
public class Person { }

کامپایلر این کد را به CIL زیر ترجمه می‌کند:

Copy Icon CIL Code
.class public auto ansi serializable beforefieldinit Person
       extends [System.Runtime]System.Object
{
  .custom instance void [System.Runtime.Serialization]System.SerializableAttribute::.ctor() = ( 01 00 00 00 )
  // ... class members ...
}

در اینجا، دایرکتیو .custom به کامپایلر می‌گوید که یک نمونه از کلاس System.SerializableAttribute را ایجاد کرده و آن را به کلاس Person متصل کند. بخش ::.ctor() به سازنده‌ی پیش‌فرض آن صفت اشاره دارد. این مکانیزم به ما اجازه می‌دهد تا هر نوع فراداده‌ی سفارشی را به عناصر کد خود در سطح پایین متصل کنیم.