اذهب إلى المحتوى

التجميعات (Assemblies)

تُصرَّف (compile) الأصناف التي تُعرِّفها، مَصحُوبة بتوابعها وخواصها وملفات البايتكود (bytecode) الخاصة بها، وتُحزَّم بداخل تجميعة (Assembly) تكون في صورة ملف يتضمن شيفرة مُصرَّفة جزئيًا بامتداد ‎.dll او ‎.exe. هذه التجميعات (Assemblies) هي المُكوِّن الأساسي لأي برنامج يتم تشغيله من خلال بيئة التنفيذ المشتركة (CLR).

تُعدّ التجميعات ذاتية التَوْثيق، فهي لا تَحتوِي على اﻷنواع وتوابعها وملفات اللغة الوسيطة (IL code) الخاصة بها فقط، بل أيضًا تَضُمّ البيانات الوَصفيّة (metadata) الضرورية للفَحْص والاستهلاك خلال زمني التَصرِّيف (compile time) والتشغيل (runtime).

تَملُك كل تجميعة (Assembly) اسم يَصِف هويتها المتفردة توصيفًا كاملًا:

// Will print: "mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"

تُعدّ أسماء التجميعات التي تحتوي على PublicKeyToken أسماء صارمة (strong).

لِمَنح تجميعة اسمًا صارمًا (strong-naming)، لابّد أن يكون لديك زوجًا من المفاتيح، أحدهما عام (public key) والآخر سري (private key). يُوزَّع المفتاح العام مع التجميعة أما المفتاح السري فيُستخدَم لانشاء بصمة (signature) تُضاف لبيان التجميعة (Assembly manifest)، والذي يحتوي على أسماء جميع ملفات التجميعة وقيمها المقطّعة (hashes)، كما تُصبِح قيمة PublicKeyToken جزءًا من اسمها.

التجميعات التي تَملُك نفس الاسم الصارم هي بالضرورة مُتطابقة، ومِنْ ثَمَّ يُمكِن الاعتماد على ذلك لتَجنُب تضارب أسماء التجميعات (assembly conflicts) وكذلك للإصدارة (versioning).

تنشئة تجميعة (assembly) ديناميكيًا

يُوفِّر إطار عمل ‎.NET عددًا من الأصناف والتوابع بفضاء الاسم System.Reflection.Emit، والتي يُمكِن اِستخدَامِها لإنشاء تجميعة (assembly) بشكل ديناميكي. عامةً، تَضمّ أي تجميعة (assembly) وَحدة (module) واحدة أو أكثر، كلًا منها قد يتَضمَن صنف واحد أو أكثر.

مثلًا، يَتوفَّر التابع ModuleBuilder.DefineType الذي يُمكِن اِستخدَامه لإضافة نوع جديد، ويُعيد قيمة من النوع TypeBuilder. يُوفِّر هذا النوع بدوره العديد من التوابع لإضافة أعضاء (members) بالنوع المُنشَئ. فمثلًا، يُستخدَم التابع TypeBuilder.DefineField لإضافة حَقْل، بينما يُستخدَم التابع TypeBuilder.DefineProperty لإضافة خاصية. يَتوفَّر أيضًا التابعين TypeBuilder.DefineMethod و TypeBuilder.DefineConstructor لإضافة التوابع وبواني الكائن على الترتيب.

في المثال التالي، نَستعرِض طريقة تَنشئة تجميعة تَضُمّ وَحدة (module) وَاحدة تَشتمِل على تَعرِيف لنوع واحد يَحمِل الاسم MyDynamicType. يتكون هذا النوع من:

  • حَقْل وحيد يُسمَى m_number من النوع العددي int.
  • خاصية مُناظِرة لهذا الحقل تَحمِل الاسم Number لها ضَابِط (setter) وجَالِب (getter).
  • بانيين للكائن (constructor) أحدهما بدون مُعامِلات والآخر يَستقبِل مُعامِل وحيد لتهيئة قيمة الحَقْل المبدئية.
  • التابع MyMethod والذي يَستقبِل مُعامِل من النوع العددي int، ويُعيد حاصل ضرب قيمة المُعامِل في قيمة الحَقْل m_number.

وبالتالي، يَكُون النوع المُراد إنشائه كالتالي:

public class MyDynamicType
{
    private int m_number;

    public MyDynamicType() : this(42) {}

    public MyDynamicType(int initNumber)
    {
        m_number = initNumber;
    }

    public int Number
    {
        get { return m_number; }
        set { m_number = value; }
    }

    public int MyMethod(int multiplier)
    {
        return m_number * multiplier;
    }
}

سنحتاج أولًا لاستدعاء التابعين DefineDynamicAssembly و DefineDynamicModule لتنشئة كائنين من النوع AssemblyBuilder و ModuleBuilder على الترتيب، يُمثل هذين التابعين كُلًا من التجميعة (assembly) والوَحدة (module) المُراد إنشائها، كالتالي:

AssemblyName aName = new AssemblyName("DynamicAssemblyExample");
AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
    aName,
    AssemblyBuilderAccess.RunAndSave
);

// عادةً ما يكون اسم الوحدة هو نفسه اسم ملف التجميع عند تنشئة ملف تجميع من وحدة واحدة 
ModuleBuilder mb =
    ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");

للإعلان عن النوع MyDynamicType داخل الوَحدة المُنشَأة، نَستخدِم الشيفرة التالية:

TypeBuilder tb = mb.DefineType("MyDynamicType", TypeAttributes.Public);

لإضافة الحَقْل m_number بالنوع الجديد، نَستخدِم الشيفرة التالية:

FieldBuilder fbNumber = tb.DefineField(
    "m_number",
    typeof(int),
    FieldAttributes.Private);

لإضافة الخاصية Number المُناظِرة للحَقْل، نَستخدِم الشيفرة التالية:

PropertyBuilder pbNumber = tb.DefineProperty(
    "Number", // اسم الخاصية
    PropertyAttributes.None,
    typeof(int), // نوع الخاصية
    new Type[0]);

لإضافة ضَابِط (setter) للخاصية المُنشَئة للتو، نَستخدِم الشيفرة التالية:

MethodBuilder mbSetNumber = tb.DefineMethod(
    "set_Number",
    // لعدم السماح باستدعاء الضابط لأنه تابع من نوع خاص
    MethodAttributes.PrivateScope | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName,
    typeof(void), // لا يُعيد الضابط قيمة
    // يستقبل الضابط قيمة من النوع العددي
    new[] { typeof(int) }); 

// ‫سنستخدم مولد الشيفرة الوسيطة IL generator‎ للحصول على متن التابع
il = mbSetNumber.GetILGenerator();

// ‫لابد من تحميل this‎ لأنه المُعامِل الأول لجميع التوابع
il.Emit(OpCodes.Ldarg_0); 
il.Emit(OpCodes.Ldarg_1); // حمل المعامل الثاني الذي يمثل القيمة المراد إسنادها للحقل
il.Emit(OpCodes.Stfld, fbNumber); // خزن القيمة الجديدة المحملة للتو بالحقل
il.Emit(OpCodes.Ret); // عُد

// وأخيرًا، اربط التابع بضابط الخاصية
pbNumber.SetSetMethod(mbSetNumber);

عادةً ما يَكُون اسم الضَابِط هو set_Property.

بالمثل، لإضافة جَالِب (getter) لنفس الخاصية، نَستخدِم الشيفرة التالية:

MethodBuilder mbGetNumber = tb.DefineMethod(
    "get_Number",
    MethodAttributes.PrivateScope | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName,
    typeof(int),
    new Type[0]);

il = mbGetNumber.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); 
il.Emit(OpCodes.Ldfld, fbNumber); // حمل قيمة الحقل
il.Emit(OpCodes.Ret); // أعد القيمة المحملة

// وأخيرًا، اربط التابع بجالب الخاصية
pbNumber.SetGetMethod(mbGetNumber);

لإضافة بواني الكائن بالنوع الجديد، نَضيِف الشيفرة التالية:

ConstructorBuilder intConstructor = tb.DefineConstructor(
    MethodAttributes.Public, 
    CallingConventions.Standard | CallingConventions.HasThis,
    new[] { typeof(int) });

il = intConstructor.GetILGenerator();
// ‫حمل this‎
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0])); // اِستدعي باني الأب
// ‫حمل this‎
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldarg_1); // حمل قيمة المعامل الثاني الذي يمثل القيمة الممررة لباني الكائن
il.Emit(OpCodes.Stfld, fbNumber); // خزن القيمة المحملة بالحقل
il.Emit(OpCodes.Ret);

var parameterlessConstructor = tb.DefineConstructor(
    MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new
    Type[0]);
il = parameterlessConstructor.GetILGenerator();
il.Emit(OpCodes.Ldarg_0); 
il.Emit(OpCodes.Ldc_I4_S, (byte)42); // حمل القيمة 42
// ‫استدعي this(42)‎
il.Emit(OpCodes.Call, intConstructor); 
il.Emit(OpCodes.Ret);

لاحِظ أنه لابُدّ للبواني من استدعاء باني الصنف الأساسي أو بَانِي آخر بنفس الصنف.

لإضافة التابع MyMethod، نَستخدِم الشيفرة التالية:

MethodBuilder mbMyMethod = tb.DefineMethod(
    "MyMethod",
    MethodAttributes.Public,
    typeof(int),
    new[] { typeof(int) });

ILGenerator il = mbMyMethod.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Ldfld, fbNumber); // حمل قيمة الحقل
il.Emit(OpCodes.Ldarg_1); // حمل قيمة المعامل الممرر
il.Emit(OpCodes.Mul); // احسب حاصل ضرب قيمة الحقل بقيمة المعامل
il.Emit(OpCodes.Ret); // عُد

وأخيرًا نقوم بالتَنشئِة الفِعلّية للنوع الجديد عن طريق التابع CreateType:

Type ourType = tb.CreateType();

الشيفرة بالكامل:

using System;
using System.Reflection;
using System.Reflection.Emit;
class DemoAssemblyBuilder
{
    public static void Main()
    {
        AssemblyName aName = new AssemblyName("DynamicAssemblyExample");
        AssemblyBuilder ab =
            AppDomain.CurrentDomain.DefineDynamicAssembly(
            aName,
            AssemblyBuilderAccess.RunAndSave
        );

        ModuleBuilder mb =
            ab.DefineDynamicModule(aName.Name, aName.Name + ".dll");

        TypeBuilder tb = mb.DefineType(
            "MyDynamicType",
            TypeAttributes.Public);

        FieldBuilder fbNumber = tb.DefineField(
            "m_number",
            typeof(int),
            FieldAttributes.Private);

        MethodBuilder mbMyMethod = tb.DefineMethod(
            "MyMethod",
            MethodAttributes.Public,
            typeof(int),
            new[] { typeof(int) });
        ILGenerator il = mbMyMethod.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); 
        il.Emit(OpCodes.Ldfld, fbNumber);
        il.Emit(OpCodes.Ldarg_1); 
        il.Emit(OpCodes.Mul); 
        il.Emit(OpCodes.Ret); 

        PropertyBuilder pbNumber = tb.DefineProperty(
            "Number", 
            PropertyAttributes.None,
            typeof(int),
            new Type[0]); 

        MethodBuilder mbSetNumber = tb.DefineMethod(
            "set_Number", 
            MethodAttributes.PrivateScope | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName,
            typeof(void), 
            new[] { typeof(int) }); 
        il = mbSetNumber.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); 
        il.Emit(OpCodes.Ldarg_1); 
        il.Emit(OpCodes.Stfld, fbNumber); 
        il.Emit(OpCodes.Ret); 
        pbNumber.SetSetMethod(mbSetNumber);

        MethodBuilder mbGetNumber = tb.DefineMethod(
            "get_Number",
            MethodAttributes.PrivateScope | MethodAttributes.HideBySig | MethodAttributes.Public | MethodAttributes.SpecialName,
            typeof(int),
            new Type[0]);
        il = mbGetNumber.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); 
        il.Emit(OpCodes.Ldfld, fbNumber); 
        il.Emit(OpCodes.Ret); 
        pbNumber.SetGetMethod(mbGetNumber);

        ConstructorBuilder intConstructor = tb.DefineConstructor(
            MethodAttributes.Public, 
            CallingConventions.Standard | CallingConventions.HasThis,
            new[] { typeof(int) });
        il = intConstructor.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); 
        il.Emit(OpCodes.Call, typeof(object).GetConstructor(new Type[0])); 
        il.Emit(OpCodes.Ldarg_0); 
        il.Emit(OpCodes.Ldarg_1); 
        il.Emit(OpCodes.Stfld, fbNumber); 
        il.Emit(OpCodes.Ret);

        var parameterlessConstructor = tb.DefineConstructor(
            MethodAttributes.Public, CallingConventions.Standard | CallingConventions.HasThis, new
            Type[0]);
        il = parameterlessConstructor.GetILGenerator();
        il.Emit(OpCodes.Ldarg_0); 
        il.Emit(OpCodes.Ldc_I4_S, (byte)42); 
        il.Emit(OpCodes.Call, intConstructor); 
        il.Emit(OpCodes.Ret);

        Type ourType = tb.CreateType();

        object ourInstance = Activator.CreateInstance(ourType);
        Console.WriteLine(ourType.GetProperty("Number")
                          .GetValue(ourInstance));

        ab.Save(@"DynamicAssemblyExample.dll");

        var myDynamicType = tb.CreateType();
        var myDynamicTypeInstance = Activator.CreateInstance(myDynamicType);

        Console.WriteLine(myDynamicTypeInstance.GetType()); 

        var numberField = myDynamicType.GetField("m_number", BindingFlags.NonPublic | BindingFlags.Instance);
        numberField.SetValue (myDynamicTypeInstance, 10);

        Console.WriteLine(numberField.GetValue(myDynamicTypeInstance)); 
    }
}

الانعكاس (Reflection)

يُوفِّر الانعكاس العديد من الأصناف -منها الصنف Assembly- والذي يَعمَل كمُغلِّف للتجميعة (assemblies). يُمكِن لبعض هذه الأصناف تَوفِير معلومات عن التجميعات المُحمَّلة من خلال البيانات الوَصفيّة (metadata) ببيان تلك التجميعات (Assembly manifest). يُمكِن استخدام بعض الأصناف الأُخرى لتَحمِيل ديناميكي للتجميعات، بل ولإنشاء أنواع جديدة واستدعائها ديناميكيا أثناء وقت التشغيل.

جَلْب بيانات عن تجميعة باستخدام الانعكاس

اِستخدِم الشيفرة التالية لجلب كائن Assembly الخاص بصنف معين:

using System.Reflection;

Assembly assembly = this.GetType().Assembly;

اِستخدِم الشيفرة التالية لجَلْب كائن Assembly الخاص بالشيفرة قيد التنفيذ:

Assembly assembly = Assembly.GetExecutingAssembly();

يُوفِّر الصنف Assembly التابع GetTypes المُستخدَم لجَلْب قائمة بجميع الأصناف المُعرَّفة ضِمْن التجميعة:

foreach (var type in assembly.GetTypes())
{
    Console.WriteLine(type.FullName);
}

موازنة كائنين باستخدام الانعكاس

في المثال التالي، يُستخَدم الانعكاس لموازنة كائنين. بالتحديد، يُستخدم التابع GetType لجلْب قيمة من الصنف Type تُحدد نوع الكائن، والتي بدورها تُستخدَم لجلْب قائمة بحقول الكائن باستدعاء التابع GetFields من خلالها، ثم يتم موازنة قيم تلك الحقول مع نظيراتها بالكائن الآخر.

public class Equatable
{
    public string field1;


    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;

        var type = obj.GetType();
        if (GetType() != type)
            return false;

        var fields = type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        foreach (var field in fields)
            if (field.GetValue(this) != field.GetValue(obj))
                return false;

        return true;
    }


    public override int GetHashCode()
    {
        var accumulator = 0;
        var fields = GetType().GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
        foreach (var field in fields)
            accumulator = unchecked ((accumulator * 937) ^ field.GetValue(this).GetHashCode());

        return accumulator;
    }
}

لاحظ أنه بغرض التبسيط، فإن المثال باﻷعلى يُجري موازنة معتمدة على الحقول فقط (يتجاهل الحقول الساكنة [static fields]، والخاصيات [properties]).

جَلْب سِمة تعداد (enum) باستخدام الانعكاس (وتخزينها بصورة مؤقتة caching)

تُعدّ السمات (attributes) مفيدة للإشارة إلى بعض البيانات الوَصفيّة (metadata) بالتعدادات (enums).

يُستخدَم عادة التابع GetCustomAttributes لجَلْب قيم تلك السمات والذي قد يكون بطيئًا، لذلك من المهم الاستعانة بالذاكرة المخبئية لتخزين تلك القيم (caching)، كالتالي:

private static Dictionary<object, object> attributeCache = new Dictionary<object, object>();


public static T GetAttribute<T, V>(this V value) where T : Attribute
    where V : struct
    {
        object temp;

        // حاول جلب قيمة السمة من الذاكرة المخبئية أولًا
        if (attributeCache.TryGetValue(value, out temp))
        {
            return (T) temp;
        }
        else
        {
            // اجلب النوع
            Type type = value.GetType();
            FieldInfo fieldInfo = type.GetField(value.ToString());

            // اجلب سمات هذا النوع
            T[] attribs = (T[])fieldInfo.GetCustomAttributes(typeof(T), false);

            // أعد أول سمة تجدها
            var result = attribs.Length > 0 ? attribs[0] : null;

            // خزن النتيجة بالذاكرة المخبئية
            attributeCache.Add(value, result);
            return result;
        }
    }

ضَبْط خواص الكائنات باستخدام الانعكاس

بفرض أن لدينا الصنف التالي Classy الذي يَملك الخاصية Propertua:

public class Classy
{
    public string Propertua {get; set;}
}

لضبْط الخاصية Propertua الموجودة بكائن من النوع Classy باستخدام الانعكاس، يمكن استخدام التابع SetValue:

var typeOfClassy = typeof (Classy);
var classy = new Classy();
var prop = typeOfClassy.GetProperty("Propertua");
prop.SetValue(classy, "Value");

تنشئة كائن من النوع T باستخدام الانعكاس

باستخدام باني الكائنات الافتراضي (default constructor):

T variable = Activator.CreateInstance(typeof(T));

باستخدام بَانِي ذات معاملات غير محدَّدة النوع (parameterized constructor):

T variable = Activator.CreateInstance(typeof(T), arg1, arg2);

الإطار المُدار القابل للتوسيع (MEF)

الإطار المُدار القَابِل للتوسيع Managed Extensibility Framework - MEF هو مكتبة لإنشاء برامج صغيرة الحجم وقابلة للتوسيع.

عادةً ما تُسجَّل التَبَعيّات (dependencies) داخِل ملفات إعداد بالشيفرة المصدرية (hardcoding). يَترتَب على ذلك أنه لا يُصبِح بالإمكان تَغيير تلك التَبَعيّات إلا عن طريق تعديل الشيفرة وإعادة تَجمِيعها. على العكس من ذلك، يَسمَح الإطار المُدار القابل للتوسيع MEF باكتشاف التَبَعيّات أثناء زمن التشغيل (runtime) ضِمْنِيًّا، واستخدامها دون إعداد مُسبَق.

يَسمَح MEF لعِدة مُكَوِّنات (components) بالتواصل معًا بانسيابية وسهولة. يَستخدِم كل مُكَوِّن سِمات مُعينة (attributes) للإعلان عن تَبَعيّاته وقُدراته، أي ما يَحتاج إلى اِستيراده (imports) وما يقوم بتَصديره (exports)-إن وُجِدَ- على الترتيب. يُعلَّن عن كُلًا من الاستيرادات والتصديرات بصورة مُواصَفَة اِصطلاحيّة (contract). يَنبغي لمُصَدِّر ومُستورِد مُعينين الإعلان عن نفس المُواصَفَة الاصطلاحيّة لعَدِّهما نَظيرين. لاحظ أنه لمّا كانت كل هذه المَعلومات مُتوفِّرة بالبيانات الوصفية (metadata) للمُكَوِّن، أَصبَح مِن المُمكن اكتشافها أثناء زمن التشغيل (runtime).

يُزوِّد مُحرِك MEF المُكَوِّنات باستيراداتها (imports) المُعلَّن عنها اعتمادًا على حَاوِي التركيب (composition container) الذي يَضُمّ كتالوجات (catalogs) تَشتمِل على معلومات عن جميع المُكَوِّنات المُصدَّرة والمُتاحة للتركيب.

تصدير صنف (Exporting)

يُمكن لأي مُكَوِّن استخدام السمة ExportAttribute للاعلان عن تَصدير (export). في المثال التالي، صُدِّرَ النوع UserProvider كمُحقِّق للمُواصَفَة الاصطلاحيّة IUserProvider:

using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel.Composition;
namespace Demo
{
    [Export(typeof(IUserProvider))]
    public sealed class UserProvider : IUserProvider
    {
        public ReadOnlyCollection<User> GetAllUsers()
        {
            return new List<User>
            {
                new User(0, "admin"),
                new User(1, "Dennis"),
                new User(2, "Samantha"),
            }.AsReadOnly();
        }
    }
}

في المثال بالأعلى، يُمكن تَعرِيف الصنف UserProvider بأيّ مكان؛ فالمهم هو تَزْوِيد الكتالوج (ComposablePartCatalogs) -الذي يُنشئه البرنامج- بطريقة يَستطيع مِن خلالها اكتشاف هذا الصنف.

استيراد صنف (Importing)

يُمكن لأي مُكَوِّن استخدَام السمة ImportAttribute للاعلان عن استيراد (import) أو تَبَعيّة. انظر المثال التالي:

using System;
using System.ComponentModel.Composition;
namespace Demo
{
    public sealed class UserWriter
    {
        [Import(typeof(IUserProvider))]
        private IUserProvider userProvider;
        public void PrintAllUsers()
        {
            foreach (User user in this.userProvider.GetAllUsers())
            {
                Console.WriteLine(user);
            }
        }
    }
}

في المثال بالأعلى، يُعلِّن الصنف UserWriter عن استيراد لصنف يُحقِّق المُواصَفَة الاصطلاحيّة IUserProvider كقيمة للحَقْل userProvider. لاحِظ أنه ليس مُهمًا أين تقوم بتَعرِيف الصنف المُناظِر؛ فالمهم هو تَزْوِيد الكتالوج (ComposablePartCatalogs) -الذي يُنشئه البرنامج- بطريقة يستطيع من خلالها اكتشاف هذا الصنف.

الرَبطْ (مثال بسيط)

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;

namespace Demo
{
    public static class Program
    {
        public static void Main()
        {
            using (var catalog = new ApplicationCatalog())
            using (var exportProvider = new CatalogExportProvider(catalog))
            using (var container = new CompositionContainer(exportProvider))
            {
                exportProvider.SourceProvider = container;
                UserWriter writer = new UserWriter();
                // at this point, writer's userProvider field is null
                container.ComposeParts(writer);
                // now, it should be non-null (or an exception will be thrown).
                writer.PrintAllUsers();
            }
        }
    }
}

في المثال بالأعلى، تم تَزْوِيد حَاوِي التركيب (composition container) بكتالوج من النوع ApplicationCatalog، والذي يَعتمِد -في بَحْثه عن التَصديرات المُناظِرة- على ملفات التجميعات بامتداد ‎.exe و DLL الموجودة بمجلد البرنامج. لذلك طالما تَوَفَّرت السِمة [Export(typeof(IUserProvider))‎] باحدى ملفات التجميعات بالمجلد، ستَنجَح عملية استيراد المُواصَفَة الاصطلاحيّة IUserProvider المُعلَّن عنها بداخل الصنف UserWriter.

تَتوفَّر أنواع أُخرى من الكتالوجات مثل DirectoryCatalog، والتي يُمكِن استخدَامِها كبديل أو كإضافة للنوع ApplicationCatalog لتَوسِيع دائرة البَحْث عن تَصدِيرات مُناظِرة للاستيرادات المطلوبة.

ترجمة -وبتصرف- للفصول Reflection - Managed Extensibility Framework - System.Reflection.Emit namespace من كتاب ‎.NET Framework Notes for Professionals


تفاعل الأعضاء

أفضل التعليقات

لا توجد أية تعليقات بعد



انضم إلى النقاش

يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.

زائر
أضف تعليق

×   لقد أضفت محتوى بخط أو تنسيق مختلف.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   جرى استعادة المحتوى السابق..   امسح المحرر

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • أضف...