نظرسنجی
بیشترین استفاده شما از اینترنت در چه زمینه ای است؟






 
خروجی RSS

Covariance و Contravariance در C# 4.0


25 مرداد 1390

واریانس یکی از قابلیت هایی است که در C# 4.0 به این زبان اضافه شد که خوب شاید تا به حال به عدم پشتیبانی از آن در نسخه های قبلی C# هم برخورد نکرده باشید. به طور مثال کد زیر را در نظر بگیرید:

IList<string> strings = new List<string>();
IList<object> objects = strings;

در نگاه اول کد بالا صحیح به نطر میاد چون کلاس string از کلاس object  مشتق شده و می توان شی کلاس مشتق شده را به کلاس پایه نسبت داد اما کامپایلر از خط دوم کد خطا می گیرد.

دلیل خطا تقریبا واضح است زیرا اگر انتساب بالا صحیح بود، در ادامه امکان نوشتم همچین کدی را هم داشتیم:
objects.Add(10);

و بنابراین type safe بودن کلاس List را نقض کردیم.

در C# 4.0 قابلیتی به نام واریانس مطرح شد که اینگونه انتساب ها را ممکن می کند. واریانس به صورت کلی در مورد دو کلاس پایه و مشتق شده و رابطه ی آن ها نسبت به یکدیگر در Generic ها صحبت می کند که به دو صورت Covariance و Contravariance شناخته می شود. البته ناگفته نماند که واریانس صرفا در مورد اینترفیس ها و delegate های generic مطرح است و نه در مورد کلاس ها، حتی کلاس های generic!

Covariance:

Covariance انتساب هایی از type ئه کلاس مشتق شده به اینترفیسی از type ئه کلاس پایه را مشخص می کند، به این معنی که اگر کلاس string از کلاس   object مشتق شده، بنابراین ایترفیسی از نوع string نیز باید بتواند به یک ایترفیس از نوع object نسبت داده شود. اگر این مفهوم را توسعه بدیم به اینترفیس معروف IEnumerable<T> می رسیم، به این معنی که اگر IEnumerable<string> توانایی شمارش نوع string را دارد، باید بتواند نوع پایه ی خود یعنی object را نیز شمارش کند.

برای مشخص کردن covariant بودن نوع T، در تعریف اینترفیس باید از کلمه ی کلیدی out استفاده کنیم. همانند IEnumerable که در نسخه ی چهارم C# تعریف جدیدی به شکل زیر دارد:

public interface IEnumerable<out T> : IEnumerable
{
    IEnumerator<T> GetEnumerator();
}
public interface IEnumerator<out T> : IEnumerator
{
    bool MoveNext();
    T Current {get; }
}

out در تعریف اینترفیس مشخص می کند که نوع T یک نوع خروجی است، یعنی در هیچ یک از متد ها یا خصوصیات این اینترفیس این نوع به صورت پارامتر ورودی در نظر گرفته نشده است و فقط می تواند به صورت return value مورد استفاده قرار گیرد. شاید در ابتدا یه مقدار درک این مفهوم سخت باشه، اما با چند بار تمرین مسلما بدیهی خواهد شد.

با این اوصاف حال مجاز به نوشتن کد زیر هستیم:

IEnumerable<object> objList = new List<string>();

 در واقع خروجی بودن نوع T در IEnumerable تضمین می کند که در زمان پیمایش List<string> هر شی کلاس string تنها می تواند return شود و با return شدن آن فقط یک انتساب به آبجکت کلاس پایه ی خود یعنی object را دارد که امری بدیهی هست. مثلا در حلقه ی foreach:

foreach(object o in objList)
{
    ...
}

اما فرض کنید که نوع T در متدی به صورت پارامتر ورودی نیز مورد استفاده قرار می گرفت، که در این صورت تبدیلی از کلاس object به کلاس string داشتیم که غیر ممکن بود.


Contravariance:

Contravariance نقطه ی مقابل Covariance است به این معنی که اینترفیسی از نوع کلاس پایه می تواند به اینترفیسی از کلاس مشتق شده نسبت داده شود، اگر و تنها اگر نوع T تنها از نوع ورودی باشد. برای مشخص کردن نوع ورودی نیز از کلمه ی کلیدی in استفاده می کنیم. در این مورد اینترفیس IComparer نیز در نسخه ی چهارم تعریف جدیدی به شکل زیر پیدا کرده:

public interface IComparer<in T>
{
    public int Compare(T left, T right);
}

با این مفهوم یک IComparer<object> قابل تبدیل به یک IComparer<string> است، در واقع اگر یک Comparer بتواند هر دو نوع object ای را مقایسه کند، بنابراین باید دو نوع string را نیز بتواند مقایسه کند.

در ادامه یک مثال نسبتا عملی تر را پیاده سازی می کنیم:

namespace irpcn
{
    public class Employee
    {
        public Employee(string name, int id)
        {
            this.Name = name;
            this.ID = id;
        }

        public string Name { get; set; }
        public int ID { get; set; }
    }

    public class Manager : Employee
    {
        public Manager(string name, int id) : base(name, id) { }
    }

    public class BaseComparer<T> : IComparer<T> where T : Employee
    {
        public int Compare(T x, T y)
        {
            return (x.ID < y.ID ? -1 : 1);
        } 
    }

    class Program
    {
        static void Main(string[] args)
        {
            List<Manager> managerList = new List<Manager>();
            managerList.Add(new Manager("reza", 1));
            managerList.Add(new Manager("saeed", 3));
            managerList.Add(new Manager("ali", 2));

            //Runs as per Contravariance Rule.
            BaseComparer<Employee> objComparer = new BaseComparer<Employee>();
            managerList.Sort(objComparer);
           
            managerList.ForEach(e => Console.WriteLine(e.ID + " " + e.Name));
        }
    }
}

در کلاس BaseComparer اینترفیس IComparer پیاده سازی شده  و سپس از آن برای Sort کردن لیستی از نوع Manager استفاده می کنیم، دقت داشته باشید که شی objComparer از نوع Employee  می باشد که اگر اینترفیس IComparer در نوع T کواریانت نبود، با خطا مواجه می شدیم.

همانطور که در ابتدا عرض کردم، واریانس در مورد generic delegate ها نیز صدق می کند. با توضیحات داده شده فکر می کنم تنها دیدن یک مثال ساده در این مورد کفایت می کند و نیازی به توضیحات بیشتر نیست:

namespace irpcn
{
    class Animal { }
    class Cat : Animal { }

    delegate T Func1<out T>();
    delegate void Action1<in T>(T a);

    class Program
    {
        public static Cat CreateCatInstance()
        {
            return new Cat();
        }

        public static void DisplayCatInstance(Animal animal)
        {
            Console.WriteLine(animal);
        }

        static void Main(string[] args)
        {
            // Covariance
            Func1<Cat> cat = new Func1<Cat>(CreateCatInstance);
            Cat objCat = cat();
            Console.WriteLine("Co:" + objCat);

            Func1<Animal> animal = cat;
            Animal objAnimal = animal();
            Console.WriteLine("Co:" + objAnimal);

            // Contravariance
            Action1<Animal> act1 = new Action1<Animal>(DisplayCatInstance);
            act1(new Animal());
            Action1<Cat> cat1 = act1;
            cat1(new Cat());
        }
    }

}

پ.ن: در دو خط کد اول مقاله از اینترفیس IList برای رساندن مفهوم وارایانس استفاده کردم، این مورد صرفا جهت مثال بود و این اینترفیس به صورت IList<T> تعریف شده است که فاقد modifier های in یا out می باشد، در واقع اصطلاحا گفته می شود نوع T در این اینترفیس invariant است و بنابر این تبدیل یک IList<string> به IList<object>حتی در C# 4.0 نیز ممکن نمی باشد.

تعداد آراء: 22 رای
یادداشت ها
سینا
1390/06/04 - 01:18:00
سلام. عزیز دلم. داداش عماد گلم. چطوری عزیزم. خوبی؟ چه خبر؟ سلامتی؟ آقا خییییلی دلم برات تنگ شده بود. راستی طاعات و عبادات قبول التماس دعا داداشم. تا بعد...
عماد:
سلااااااام سینای عزیز، ممنون بد نیستم، تو خوبی؟ خوش میگذره؟ شرمنده یه مقدار دیر جواب میدم، مشغلست و هزار دردسر. نماز و روزه های تو هم قبول، ایشالله همیشه سلام و موفق باشی :33 :08
yusefi
1390/06/09 - 11:30:00
با سلام خدمت شما وب سایت بانک اطلاعات صنعت ایران با پیج رنک 5 آماده تبادل لینک با شما می باشد . لطفا لینک ما را با عنوان بانک صنعت ایران و آدرس http://www.dadset.com در وب سایتتان قراردهید و به ما اطلاع دهید تا لینک شما را در وب سایتمان قرار دهیم.
مسعود
1390/06/28 - 01:30:00
سلام ايا با ما تبادل لينك ميكنيد؟ ما را با نام سورس برنامه نويسي لينك كنيد؟ http://mspsoft.com بعد بگيد كه منم لينك كنم.با تشكر
فناوری اطلاعات آبی
1390/09/15 - 13:52:16
مرسی بسیار عالی و کامل بود
یادداشت خود را ثبت کنید



کد امنیتی:    =
loader
در حال ارسال - چند لحظه صبر کنید...