Reflection و Metadata در C#؛ Type، TypeInfo و Generic Types

فصل ۱۸: Reflection و Metadata؛ Type، TypeInfo، Activator و نوع‌های Generic

فصل ۱۸: Reflection و Metadata؛ Type، TypeInfo، Activator و نوع‌های Generic

تصویر پرندهٔ آغاز فصل ۱۸ در منبع
نشان تصویری آغاز فصل ۱۸ در منبع

فصل ۱۸: بازتاب و فراداده (Reflection and Metadata)

همان‌طور که در فصل ۱۷ دیدیم، یک برنامهٔ C# به یک اسمبلی (Assembly) کامپایل می‌شود که شامل فراداده (Metadata)، کد کامپایل‌شده و منابع است. بازرسی فراداده و کد کامپایل‌شده در زمان اجرا، بازتاب (Reflection) نامیده می‌شود.

کد کامپایل‌شده در یک اسمبلی تقریباً تمام محتوای کد منبع اصلی را در خود دارد. بخشی از اطلاعات، مانند نام متغیرهای محلی، توضیحات (Comments) و دستورهای پیش‌پردازنده (Preprocessor Directives)، از دست می‌رود. با این حال، Reflection تقریباً به هر چیز دیگری دسترسی دارد، تا جایی که حتی نوشتن یک دی‌کامپایلر (Decompiler) را ممکن می‌کند.

بسیاری از سرویس‌های موجود در .NET که از طریق C# در دسترس‌اند—مانند اتصال پویا (Dynamic Binding)، سریال‌سازی (Serialization) و اتصال داده (Data Binding)—به وجود فراداده وابسته‌اند. برنامه‌های خود شما نیز می‌توانند از این فراداده بهره ببرند و حتی با استفاده از صفت‌های سفارشی (Custom Attributes) اطلاعات تازه‌ای به آن بیفزایند. فضای نام System.Reflection میزبان API مربوط به Reflection است. همچنین در زمان اجرا می‌توان از طریق کلاس‌های فضای نام System.Reflection.Emit، فرادادهٔ جدید و دستورهای اجرایی در زبان میانی (Intermediate Language یا IL) را به‌صورت پویا ایجاد کرد.

مثال‌های این فصل فرض می‌کنند که علاوه بر فضای نام System، فضاهای نام System.Reflection و System.Reflection.Emit را نیز import کرده‌اید.

بازتاب و فعال‌سازی نوع‌ها (Reflecting and Activating Types)

در این بخش بررسی می‌کنیم چگونه یک Type را به‌دست آوریم، فرادادهٔ آن را بررسی کنیم و از آن برای نمونه‌سازی پویا از یک شیء استفاده کنیم.

به‌دست‌آوردن یک Type

یک نمونه از System.Type فرادادهٔ یک نوع را نمایش می‌دهد. از آنجا که Type بسیار پرکاربرد است، به‌جای فضای نام System.Reflection در فضای نام System قرار دارد.

می‌توانید با فراخوانی GetType روی هر شیء یا با عملگر typeof در C# یک نمونه از System.Type به‌دست آورید:

Type t1 = DateTime.Now.GetType();           // Type obtained at runtime
Type t2 = typeof (DateTime);                // Type obtained at compile time

می‌توانید از typeof برای به‌دست‌آوردن نوع‌های آرایه‌ای و نوع‌های Generic نیز استفاده کنید:

Type t3 = typeof (DateTime[]);          // 1-d Array type
Type t4 = typeof (DateTime[,]);         // 2-d Array type
Type t5 = typeof (Dictionary<int,int>); // Closed generic type
Type t6 = typeof (Dictionary<,>);       // Unbound generic type

همچنین می‌توانید یک Type را بر اساس نام آن بازیابی کنید. اگر به Assembly آن ارجاع دارید، Assembly.GetType را فراخوانی کنید؛ این موضوع را در بخش «بازتاب اسمبلی‌ها» در صفحهٔ ۸۲۷ بیشتر توضیح می‌دهیم:

Type t = Assembly.GetExecutingAssembly().GetType ("Demos.TestProgram");

اگر شیء Assembly ندارید، می‌توانید نوع را از طریق نام واجد شرایط اسمبلی (Assembly-Qualified Name)—یعنی نام کامل نوع که پس از آن نام کامل یا تاحدی‌واجدشرایط اسمبلی آمده است—به‌دست آورید. اسمبلی به‌طور ضمنی همانند حالتی بارگذاری می‌شود که Assembly.Load(string) را فراخوانی کرده باشید:

Type t = Type.GetType ("System.Int32, System.Private.CoreLib");

پس از آنکه یک شیء System.Type دارید، می‌توانید از propertyهای آن برای دسترسی به نام نوع، اسمبلی، نوع پایه، سطح دسترسی و موارد دیگر استفاده کنید:

Type stringType = typeof (string);
string name     = stringType.Name;                 // String
Type baseType   = stringType.BaseType;             // typeof(Object)
Assembly assem = stringType.Assembly;              // System.Private.CoreLib
bool isPublic   = stringType.IsPublic;             // true

یک نمونهٔ System.Type پنجره‌ای رو به تمام فرادادهٔ آن نوع—و اسمبلی‌ای که نوع در آن تعریف شده است—فراهم می‌کند.

TypeInfo

اگر .NET Core 1.x (یا یک پروفایل قدیمی‌تر Windows Store) را هدف قرار دهید، متوجه می‌شوید بیشتر اعضای Type وجود ندارند. این اعضای مفقود در کلاسی به نام TypeInfo عرضه می‌شوند که با فراخوانی GetTypeInfo آن را به‌دست می‌آورید. بنابراین برای اجرای مثال قبلی به این شکل عمل می‌کنید:

Type stringType = typeof(string);
string name = stringType.Name;
Type baseType = stringType.GetTypeInfo().BaseType;
Assembly assem = stringType.GetTypeInfo().Assembly;
bool isPublic = stringType.GetTypeInfo().IsPublic;

TypeInfo در .NET Core 2 و 3 و .NET 5+ (و نیز .NET Framework 4.5+ و همهٔ نسخه‌های .NET Standard) وجود دارد؛ بنابراین کد بالا تقریباً در همه‌جا کار می‌کند. TypeInfo همچنین propertyها و متدهای بیشتری برای Reflection روی اعضا دارد.

به‌دست‌آوردن نوع‌های آرایه‌ای

همان‌طور که دیدیم، typeof و GetType با نوع‌های آرایه‌ای کار می‌کنند. همچنین می‌توانید با فراخوانی MakeArrayType روی نوع عنصر، نوع آرایه را به‌دست آورید:

Type simpleArrayType = typeof (int).MakeArrayType();
Console.WriteLine (simpleArrayType == typeof (int[]));          // True

برای ساخت نوع آرایهٔ چندبعدی، یک آرگومان عدد صحیح به MakeArrayType بدهید:

Type cubeType = typeof (int).MakeArrayType (3);           // cube shaped
Console.WriteLine (cubeType == typeof (int[,,]));         // True

GetElementType عمل معکوس را انجام می‌دهد و نوع عنصر یک نوع آرایه‌ای را بازیابی می‌کند:

Type e = typeof (int[]).GetElementType();      // e == typeof (int)

GetArrayRank تعداد ابعاد یک آرایهٔ مستطیلی را برمی‌گرداند:

int rank = typeof (int[,,]).GetArrayRank();    // 3

به‌دست‌آوردن نوع‌های تو‌در‌تو

برای بازیابی نوع‌های تو‌در‌تو (Nested Types)، روی نوع دربرگیرنده GetNestedTypes را فراخوانی کنید:

foreach (Type t in typeof (System.Environment).GetNestedTypes())
  Console.WriteLine (t.FullName);

OUTPUT: System.Environment+SpecialFolder

یا:

foreach (TypeInfo t in typeof (System.Environment).GetTypeInfo()
                                                  .DeclaredNestedTypes)
  Debug.WriteLine (t.FullName);

تنها نکته دربارهٔ نوع‌های تو‌در‌تو این است که CLR آن‌ها را دارای سطح‌های دسترسی ویژهٔ «تو‌در‌تو» در نظر می‌گیرد:

Type t = typeof (System.Environment.SpecialFolder);
Console.WriteLine (t.IsPublic);                     // False
Console.WriteLine (t.IsNestedPublic);               // True

نام نوع‌ها (Type Names)

یک نوع propertyهای Namespace، Name و FullName دارد. در بیشتر موارد، FullName ترکیبی از دو مورد اول است:

Type t = typeof (System.Text.StringBuilder);

Console.WriteLine (t.Namespace);            // System.Text
Console.WriteLine (t.Name);                 // StringBuilder
Console.WriteLine (t.FullName);             // System.Text.StringBuilder

این قاعده دو استثنا دارد: نوع‌های تو‌در‌تو و نوع‌های Generic بسته (Closed Generic Types).

نام نوع‌های تو‌در‌تو

در نوع‌های تو‌در‌تو، نوع دربرگیرنده فقط در FullName ظاهر می‌شود:

Type t = typeof (System.Environment.SpecialFolder);

Console.WriteLine (t.Namespace);            // System
Console.WriteLine (t.Name);                 // SpecialFolder
Console.WriteLine (t.FullName);             // System.Environment+SpecialFolder

نماد + نوع دربرگیرنده را از یک فضای نام تو‌در‌تو متمایز می‌کند.

نام نوع‌های Generic

نام نوع‌های Generic با نماد backtick یعنی ` و سپس تعداد پارامترهای نوع پسوند می‌گیرد. اگر نوع Generic نامقید (Unbound) باشد، این قاعده هم بر Name و هم بر FullName اعمال می‌شود:

Type t = typeof (Dictionary<,>); // Unbound
Console.WriteLine (t.Name);      // Dictionary`2
Console.WriteLine (t.FullName); // System.Collections.Generic.Dictionary`2

اما اگر نوع Generic بسته باشد، فقط FullName دنباله‌ای طولانی دریافت می‌کند. نام کامل واجد شرایط اسمبلیِ هر پارامتر نوع فهرست می‌شود:

Console.WriteLine (typeof (Dictionary<int,string>).FullName);

// OUTPUT:
System.Collections.Generic.Dictionary`2[[System.Int32,
System.Private.CoreLib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=7cec85d7bea7798e],[System.String, System.Private.CoreLib,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]]

این کار تضمین می‌کند AssemblyQualifiedName—که ترکیبی از نام کامل نوع و نام اسمبلی است—اطلاعات کافی برای شناسایی کامل خود نوع Generic و پارامترهای نوع آن داشته باشد.

نام نوع‌های آرایه و اشاره‌گر

آرایه‌ها همان پسوندی را نمایش می‌دهند که در یک عبارت typeof استفاده می‌کنید:

Console.WriteLine (typeof ( int[] ).Name);      // Int32[]
Console.WriteLine (typeof ( int[,] ).Name);     // Int32[,]
Console.WriteLine (typeof ( int[,] ).FullName); // System.Int32[,]

نوع‌های اشاره‌گر (Pointer Types) نیز مشابه‌اند:

Console.WriteLine (typeof (byte*).Name);        // Byte*

نام نوع پارامترهای ref و out

یک Type که پارامتر ref یا out را توصیف می‌کند، پسوند & دارد:

public void RefMethod (ref int p)
{
  Type t = MethodInfo.GetCurrentMethod().GetParameters()[0].ParameterType;
  Console.WriteLine (t.Name);     // Int32&
}

بعدتر در بخش «بازتاب و فراخوانی اعضا» در صفحهٔ ۸۱۳ بیشتر دربارهٔ این موضوع صحبت می‌کنیم.

نوع‌های پایه و Interfaceها (Base Types and Interfaces)

Type یک property به نام BaseType ارائه می‌کند:

Type base1 = typeof (System.String).BaseType;
Type base2 = typeof (System.IO.FileStream).BaseType;

Console.WriteLine (base1.Name);       // Object
Console.WriteLine (base2.Name);       // Stream

متد GetInterfaces رابط‌هایی را برمی‌گرداند که یک نوع پیاده‌سازی می‌کند:

foreach (Type iType in typeof (Guid).GetInterfaces())
  Console.WriteLine (iType.Name);

IFormattable
IComparable
IComparable`1
IEquatable`1

(متد GetInterfaceMap یک struct برمی‌گرداند که نشان می‌دهد هر عضو یک interface چگونه در یک class یا struct پیاده‌سازی شده است؛ کاربرد این قابلیت پیشرفته را در بخش «فراخوانی اعضای Static Virtual/Abstract در Interface» در صفحهٔ ۸۲۶ نشان می‌دهیم.)

Reflection سه معادل پویا برای عملگر ایستای is در C# فراهم می‌کند:

IsInstanceOfType
یک نوع و یک نمونه را می‌پذیرد.
IsAssignableFrom و—از .NET 5—IsAssignableTo
دو نوع را می‌پذیرند.

مثالی از مورد اول:

object obj = Guid.NewGuid();
Type target = typeof (IFormattable);

bool isTrue   = obj is IFormattable;                // Static C# operator
bool alsoTrue = target.IsInstanceOfType (obj);      // Dynamic equivalent

IsAssignableFrom انعطاف بیشتری دارد:

Type target = typeof (IComparable), source = typeof (string);
Console.WriteLine (target.IsAssignableFrom (source));         // True

متد IsSubclassOf بر همان اصل IsAssignableFrom کار می‌کند، با این تفاوت که interfaceها را کنار می‌گذارد.

نمونه‌سازی نوع‌ها (Instantiating Types)

برای نمونه‌سازی پویا از یک شیء بر اساس نوع آن دو راه وجود دارد:

  • فراخوانی متد static یعنی Activator.CreateInstance
  • فراخوانی Invoke روی یک شیء ConstructorInfo که از GetConstructor روی یک Type به‌دست آمده است—برای سناریوهای پیشرفته.

Activator.CreateInstance یک Type و آرگومان‌های اختیاری می‌پذیرد و آن‌ها را به سازنده پاس می‌دهد:

int i = (int) Activator.CreateInstance (typeof (int));

DateTime dt = (DateTime) Activator.CreateInstance (typeof (DateTime),
                                                   2000, 1, 1);

CreateInstance گزینه‌های دیگری نیز در اختیار می‌گذارد؛ مثلاً مشخص‌کردن اسمبلی‌ای که نوع باید از آن بارگذاری شود یا تعیین اینکه اتصال به سازندهٔ nonpublic مجاز باشد یا نه. اگر Runtime نتواند سازندهٔ مناسبی پیدا کند، MissingMethodException رخ می‌دهد.

فراخوانی Invoke روی ConstructorInfo زمانی لازم است که مقدار آرگومان‌های شما نتوانند بین سازنده‌های Overload‌شده ابهام‌زدایی کنند. فرض کنید کلاس X دو سازنده دارد: یکی با پارامتر string و دیگری با پارامتر StringBuilder. اگر null را به Activator.CreateInstance بدهید، مقصد مبهم می‌شود. در این حالت باید از ConstructorInfo استفاده کنید:

// Fetch the constructor that accepts a single parameter of type string:
ConstructorInfo ci = typeof (X).GetConstructor (new[] { typeof (string) });
// Construct the object using that overload, passing in null:
object foo = ci.Invoke (new object[] { null });

یا اگر .NET Core 1 یا یک پروفایل قدیمی Windows Store را هدف گرفته‌اید:

ConstructorInfo ci = typeof (X).GetTypeInfo().DeclaredConstructors
  .FirstOrDefault (c =>
     c.GetParameters().Length == 1 &&
     c.GetParameters()[0].ParameterType == typeof (string));

برای به‌دست‌آوردن یک سازندهٔ nonpublic باید BindingFlags را مشخص کنید؛ بخش «دسترسی به اعضای Nonpublic» در صفحهٔ ۸۲۲، در ادامهٔ بخش «بازتاب و فراخوانی اعضا» در صفحهٔ ۸۱۳ را ببینید.

برای نمونه‌سازی پویا از آرایه‌ها فقط بر مبنای نوع عنصر، ابتدا MakeArrayType را فراخوانی کنید. همچنین می‌توانید نوع‌های Generic را نمونه‌سازی کنید؛ این موضوع در بخش بعد توضیح داده می‌شود.

برای نمونه‌سازی پویا از یک delegate، Delegate.CreateDelegate را فراخوانی کنید. مثال زیر هم یک delegate نمونه (instance delegate) و هم یک delegate ایستا (static delegate) را نمونه‌سازی می‌کند:

class Program
{
  delegate int IntFunc (int x);

  static int Square (int x) => x * x;             // Static method
  int        Cube   (int x) => x * x * x;         // Instance method

  static void Main()
  {
    Delegate staticD = Delegate.CreateDelegate
      (typeof (IntFunc), typeof (Program), "Square");

    Delegate instanceD = Delegate.CreateDelegate
      (typeof (IntFunc), new Program(), "Cube");

    Console.WriteLine (staticD.DynamicInvoke (3));            // 9
    Console.WriteLine (instanceD.DynamicInvoke (3));          // 27
  }
}

می‌توانید شیء Delegate برگردانده‌شده را مانند مثال بالا با DynamicInvoke فراخوانی کنید یا آن را به delegate تایپ‌دار cast کنید:

IntFunc f = (IntFunc) staticD;
Console.WriteLine (f(3));              // 9 (but much faster!)

به‌جای نام متد می‌توانید یک MethodInfo به CreateDelegate بدهید. اندکی بعد در بخش «بازتاب و فراخوانی اعضا» در صفحهٔ ۸۱۳، MethodInfo را توضیح می‌دهیم و علت cast کردن یک delegate که به‌صورت پویا ساخته شده است به نوع delegate ایستا را نیز بیان می‌کنیم.

نوع‌های Generic

یک Type می‌تواند نوع Generic بسته یا نامقید را نمایش دهد. همانند زمان کامپایل، نوع Generic بسته قابل نمونه‌سازی است، اما نوع نامقید نیست:

Type closed = typeof (List<int>);
List<int> list = (List<int>) Activator.CreateInstance (closed);            // OK

Type unbound   = typeof (List<>);
object anError = Activator.CreateInstance (unbound);             // Runtime error

متد MakeGenericType یک نوع نامقید را به نوع Generic بسته تبدیل می‌کند. کافی است آرگومان‌های نوع موردنظر را به آن بدهید:

Type unbound = typeof (List<>);
Type closed = unbound.MakeGenericType (typeof (int));

متد GetGenericTypeDefinition کار معکوس را انجام می‌دهد:

Type unbound2 = closed.GetGenericTypeDefinition();          // unbound == unbound2

propertyِ IsGenericType اگر یک Type Generic باشد true برمی‌گرداند و IsGenericTypeDefinition اگر نوع Generic نامقید باشد true می‌دهد. مثال زیر بررسی می‌کند که یک نوع، value type تهی‌پذیر (Nullable) هست یا نه:

Type nullable = typeof (bool?);
Console.WriteLine (
  nullable.IsGenericType &&
  nullable.GetGenericTypeDefinition() == typeof (Nullable<>));             // True

GetGenericArguments آرگومان‌های نوعِ نوع‌های Generic بسته را برمی‌گرداند:

Console.WriteLine (closed.GetGenericArguments()[0]);              // System.Int32
Console.WriteLine (nullable.GetGenericArguments()[0]);            // System.Boolean

برای نوع‌های Generic نامقید، GetGenericArguments شبه‌نوع‌هایی (Pseudotypes) را برمی‌گرداند که نمایندهٔ placeholderهای مشخص‌شده در تعریف نوع Generic هستند:

Console.WriteLine (unbound.GetGenericArguments()[0]);               // T
منبع: C# 12 in a Nutshell, The Definitive Reference — Chapter 18: Reflection and Metadata — صفحات 805–812 کتاب.

امتیاز کاربران به این مقاله

☆☆☆☆☆

0 نفر امتیاز داده اند. میانگین: 0.0 از 5

 

0 نظر

نظر محترم شما در مورد مقاله های وب سایت برنامه نویسی و پایگاه داده

نظرات محترم شما در خدمات رسانی بهتر ما را یاری می نمایند. لطفا اگر مایل بودید یک نظر ما را مهمان فرمائید. آدرس ایمیل و وب سایت شما نمایش داده نخواهد شد.

0 / 500

اطلاعات تماس

  • آدرس:اصفهان-خیابان ام کلثوم غربی - بعد خیابان تخم چی - بیست متر بعد از پیتزا ننه شب - کوچه تعمیر گاه سمار زغالی - پلاک 354 - درب مشکی - طبقه هفتم
  • آدرس ایمیل:najafzade@gmail.com
  • وب سایت:http://www.a00b.com/
  • تلفن ثابت:(+98)9131253620
  • تلفن همراه:09131253620