در پست قبل در مورد Invariant ها صحبت کردم و چندین مثال برای Built-in types زدم و در این پست می خوام در مورد Covariance و Contravariant صحبت کنم پس بریم شروع کنیم:

فرض کنیم سه مدل زیر وجود داره ( تو این مثال کلاس حیوانات رو داریم و فرزند اون پستاندار هست و فرزند پستاندار گربه!) :

class Animal { }
class Mammal : Animal { } 
class Cat : Mammal { }

 

و در کنار اون یه اینترفیس جنریک به حالت زیر داریم( که T اون یک Type رو میگیره و دو امضاء داره و میتونه اون Type رو به عنوان پارامتر قبول و یا به بیرون پاس بده) :

interface IAnimal<T>
{
T Get(); 
void Set(T t); 
}

خوب بریم با توجه به تعاریف پست قبل ببنیم چی رو میشه به چی اختصاص داد فقط نکته ای که آخر پست قبل گفته بودم گوشه ذهنتون باشه(نوع تخصیص و نوع اختصاص داده شده باید یکسان باشه):

IAnimal<Animal> invariantAnimal1 = (IAnimal<Animal>)null;
IAnimal<Animal> invariantAnimal2 = (IAnimal<Mammal>)null; // compilation error
IAnimal<Animal> invariantAnimal3 = (IAnimal<Cat>)null; // compilation error

IAnimal<Mammal> invariantMammal1 = (IAnimal<Animal>)null; // compilation error
IAnimal<Mammal> invariantMammal2 = (IAnimal<Mammal>)null;
IAnimal<Mammal> invariantMammal3 = (IAnimal<Cat>)null; // compilation error

IAnimal<Cat> invariantCat1 = (IAnimal<Animal>)null; // compilation error
IAnimal<Cat> invariantCat2 = (IAnimal<Mammal>)null; // compilation error
IAnimal<Cat> invariantCat3 = (IAnimal<Cat>)null;

همانطور که  در مثال بالا مشاهده می کنید تنهادر صورتی که نوع تخصیص و نوع اختصاص داده یکسان باشه به خطا نمی خوریم یعنی یا دو طرف باید Animal یا Mammal یا Cat باشه مثل حالتی که در پست قبل برای string و Object صادق بود! خوب تا اینجا که شد همون مباحث پست قبل حالا بیایم چی کار کنیم؟

بیاین فرض کنیم :

  1. اگر این نوع تبدیل به راحتی و بدون خطا انجام می شد
  2. و با فرض اینکه ما یک کلاس دیگه به اسم Crocodile داشته باشیم که از Animal ارث ببره

آیا باید Crocodile به Cat یا Mammal تبدیل بشه؟ آیا به اصلاح type-safe هستیم؟ قطعا جواب خیر هست!!!!

برای همین مورد هست که زبان های امروزی به سمت type-safe رفتن و به راحتی اجازه تبدیل های این موردی رو نمیدن! خوب پس ما چی کار کنیم که بتونیم در حالت امن تبدیل های مجاز رو انجام بدیم؟ جواب فیچرهایی هست که زبان های امروزی ارائه میدن و دات نت نیز دارای این ویژگی هاست که در ادامه اون موارد رو توضیح میدم!

بریم ابتدا خطای More Derived به Less Derived رو حل کنیم یعنی تمامی مواردی که فرزند به پدر تخصیص داده میشه مثل Mammal  به Animal یا Cat به Mammal.

در دات نت وقتی می خوایم از More Derived به Less Derived بریم می بایست از کلمه out استفاده کنیم. در زمان استفاده از این کلمه سی شارپ به ما میگه من یه قانون دارم و اون اینه که تبدیل فرزند به پدر رو برای خروجی ها(output) انجام میدم نه به عنوان پارامتر ورودی!!! یعنی اگر دو متد دارید مثل اینترفیسی که تعریف کردیم تنها امضاء خروجی مورد قبول هست به این شکل:

interface IAnimal<out T>
{
    T Get(); 
    //void Set(T t); 
}

با تغییر کد بالا وضعیت مواردی که اختصاص دادیم به شکل زیر میشه:

IAnimal<Animal> invariantAnimal1 = (IAnimal<Animal>)null;
IAnimal<Animal> invariantAnimal2 = (IAnimal<Mammal>)null; 
IAnimal<Animal> invariantAnimal3 = (IAnimal<Cat>)null; 
IAnimal<Mammal> invariantMammal1 = (IAnimal<Animal>)null; // compilation error
IAnimal<Mammal> invariantMammal2 = (IAnimal<Mammal>)null;
IAnimal<Mammal> invariantMammal3 = (IAnimal<Cat>)null; 

IAnimal<Cat> invariantCat1 = (IAnimal<Animal>)null; // compilation error
IAnimal<Cat> invariantCat2 = (IAnimal<Mammal>)null; // compilation error
IAnimal<Cat> invariantCat3 = (IAnimal<Cat>)null;

و خطای تمامی More Derived به Less Derived رفع میشه! تبریک میگم شما Covariance رو پیاده کردید که کل حرفش همین بود! سوئیچ از فرزند به پدر با یکسری قوانین ریز و گوگولی که در آخر لینک اون رو میدم برای مطالعه!

خوب بریم سمت خطاهای Less Derived به More Derived مثل Animal به Mammal.

دات نت برای این حالت نیز یه فیچر داره و به شما باید از کلمه in استفاده کنید و در زمان استفاده از این کلمه تنها مواردی که به عنوان پارامتر ورودی (input) هستن مورد قبول واقع میشن (برعکس covariance)!

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

interface IAnimal<in T>
{
    //T Get(); 
    void Set(T t); 
}

در این خطای حالت خطای Less Derived به More Derived رفع میشه و

IAnimal<Animal> invariantAnimal1 = (IAnimal<Animal>)null;
IAnimal<Animal> invariantAnimal2 = (IAnimal<Mammal>)null; // compilation error
IAnimal<Animal> invariantAnimal3 = (IAnimal<Cat>)null; // compilation error

IAnimal<Mammal> invariantMammal1 = (IAnimal<Animal>)null; 
IAnimal<Mammal> invariantMammal2 = (IAnimal<Mammal>)null;
IAnimal<Mammal> invariantMammal3 = (IAnimal<Cat>)null; // compilation error

IAnimal<Cat> invariantCat1 = (IAnimal<Animal>)null; 
IAnimal<Cat> invariantCat2 = (IAnimal<Mammal>)null; 
IAnimal<Cat> invariantCat3 = (IAnimal<Cat>)null;

تبریک میگم شما contravariance رو پیاده کردید البته این سری با قوانین مگولی که باز در آخر لینک اون رو میدم برای مطالعه!

دیدید سخت نبود و الکی بزرگش کردید؟؟!!

در آخر پیشنهاد می کنم این لینک رو مطالعه کنید تا از قوانین این دو موضوع و موارد مربوطه بیشتر اطلاع پیدا کنید!

 

اگر مطالب بالا به دردتون خورد و دوست داشتید خوشحال میشم من رو به یه قهوه مهمون کنید!!!

دسته بندی شده در: