۱۳۹۰/۱۲/۱۲

تعريف نوع جنريك به صورت متغير


در تهيه مثال Auto Mapping به كمك امكانات توكار NH 3.2 به اين مورد نياز پيدا كردم:
بتوان نوع متد جنريك را به صورت متغير تعريف كرد و اين نوع در زمان كامپايل برنامه مشخص نباشد. مثلا چيزي شبيه به اين مثال:

using System;

namespace GenericsSample
{
    class TestGenerics
    {
        public static void Print<T>(T data)
        {
            Console.WriteLine("Print<T>");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var type = typeof(Nullable<int>);
            TestGenerics.Print<type>(1);
        }
    }
}

اين نوع فراخواني متد Print در دات نت به صورت پيش فرض غيرمجاز است و نوع جنريك را نمي‌توان به صورت متغير معرفي كرد.
كه البته اين هم راه حل دارد و به كمك Reflection قابل حل است:

using System;

namespace GenericsSample
{
    class TestGenerics
    {
        public static void Print<T>(T data)
        {
            Console.WriteLine("Print<T>");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var nullableIntType = typeof(Nullable<>).MakeGenericType(typeof(int));
            var method = typeof(TestGenerics).GetMethod("Print");
            var genericMethod = method.MakeGenericMethod(new[] { nullableIntType });
            genericMethod.Invoke(null, new object[] { 1 });
        }
    }
}

دو متد MakeGenericType و MakeGenericMethod براي ساخت پوياي نوع‌هاي جنريك و همچنين ارسال آن‌ها به متدهاي جنريك در دات نت وجود دارند كه مثالي از نحوه استفاده از آن‌ها را در بالا ملاحظه مي‌كنيد.

مثال دوم:
اگر كلاس TestGenerics نسخه غيرجنريك متد Print را هم داشت، ‌چطور؟ مثلا:

class TestGenerics
{
    public static void Print<T>(T data)
    {
         Console.WriteLine("Print<T>");
    }

    public static void Print(object data)
    {
         Console.WriteLine("Print");
    }
}

اينبار اگر برنامه فوق را اجرا كنيم، پيغام Ambiguous match found را حين فراخواني GetMoethod دريافت خواهيم كرد؛ چون دو متد با يك نام در كلاس ياد شده وجود دارند. براي حل اين مشكل بايد به نحو زير عمل كرد:

using System;
using System.Linq;

namespace GenericsSample
{
    class TestGenerics
    {
        public static void Print<T>(T data)
        {
            Console.WriteLine("Print<T>");
        }

        public static void Print(object data)
        {
            Console.WriteLine("Print");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var nullableIntType = typeof(Nullable<>).MakeGenericType(typeof(int));
            var method = typeof(TestGenerics).GetMethods()
                         .First(x => x.Name == "Print" && (x.GetParameters()[0]).ParameterType.IsGenericParameter);
            var genericMethod = method.MakeGenericMethod(new[] { nullableIntType });
            genericMethod.Invoke(null, new object[] { 1 });
        }
    }
}

GetMethods تمام متدها را بازگشت داده و سپس بر اساس متاديتاي متدها، ‌مي‌توان تشخيص داد كه كدام يك جنريك است.