csharp-da-kovariant-kontravariant-ve-invariantliq

C#-da Kovariant kontravariant və invariantlıq

Bu məqalədə C#-ın 4.0 versiyasında əlavə edilmiş yeniliklərdən biri olan kovariant, kontravariant və invariantlıqdan danışacağıq. Məqaləmizdə aşağıdakı sualları cavablandırmağa çalışacam:

1)Kovariantlıq(kovariasiya) nədir və nəyə görə var?

2)Kontravariantlıq(kontravariasiya) nədir və nəyə görə var?

3)İnvariantlıq(invariasiya)

PS: Müəllif bu üçlünü bundan sonra kovariant-kontravariant-invariant olaraq qeyd edir.

               Bəzi vaxtlar işlədiyim yerlərdə intervyu götürən tərəf olaraq iştirak edən zaman verdiyim suallardan biri “C#-da Kovariant,kontravariant və invariantlıq haqqında nə deyə bilərsiniz?” olub. Həsrətlə cavabını gözləsəm də , çox hallarda sualıma cavab ala bilməmişəm...

               Kovariantlıq və kontravariantlıq ümumiləşmiş(generic) deleqat və ümumiləşmiş interfeyslər yaradan zaman isitifadə olunur. Məqsəd yaradacağımız ümumiləşdirilmiş tipə törəmə ierarxiyası üzrə müəyyən məhdudiyyət qoymaqdır.

1)Kovariantlıq(kovariasiya) nədir və nəyə görə var?

               Məsələnin incəliklərinə enmədən əvvəl aşağıdakı sadə misala baxaq.

 

static void Main(string[] args)
       {
           object[] array = new string[3];
           array[0] = "some text";
           array[1] = "second text";
           
       }

Çoxumuzun bildiyi kimi yuxarıdakı kod nümunəsi tamamilə işləkdir. Törəmə ierarxiyası üzrə object bütün tiplərin ana tipi olaraq çıxış edir. Belə olan halda , string massivini ana klas massivi səviyyəsinə qaldıra (object[] ) bilirik. Burada Child(bala) tiplərin parent(ana) tip səviyyəsinə qaldırıb inkapsulyasiya olunması prosesi kovariantlıq adlanır. Bütün massivlər kovariantdır. Kovariant proseslərdə təyinetmə əməliyyatının sol tərəfində ana tip, sağ tərəfində isə bala tip olaraq iştirak edir. Lakin kovariantlıq heç də həmişə tövsiyə olunan behavior(rəftar) forması deyil. Aşağıdakı kodu qiymətləndirək:

  public class User
   {
       public string Name { get; set; }
       public byte Age { get; set; }
       public string Email { get; set; }
   }
 
   class Program
   {
       static void Main(string[] args)
       {
           //kovariantlıq hesabına ana tip səviyyəsinə qalxırıq
           object[] array = new User[3];
           array[0] = new User() { Age = 45, Email = "test@gmail.com", Name = "Test" };
 
          // kovariantlıq heç də həmişə yaxşı deyilmiş.....
          //burada exception qalxır..
           array[1] = "Хуяк-хуяк, и booom....";
           
       }
   }

 

 

Bir çox interfeyslər .NET platformasında kovariant formada özünü göstərir. Əgər interfeys və ya deleqatin ümumiləşdirilmiş tip bölməsində out açar sözü görürsünüzsə, bu həmin interfeys və ya deleqatın kovariant olduğunu göstərir.Misalçün Object Browser vasitəsilə System.Collections.Generic adsahəsinə baxaq.

Gördüyümüz kimi, çoxumuza tanış olan IEnumerable və IEnumerator generic interfeysləri də kovariantdır!!

 

Kovariantlıq praktikada:

 Ödəmə terminalının proyektləşdirilməsi üçün bir interfeysimiz var. Bu interfeys müxtəlif növ şəxs tipləri üzərində əməliyyat aparmağa imkan verir.

 

public interface ITerminal< T >
   {
       void Pay(decimal amount,T user);
       void Check(T user);
   }

Yuxarıda yazılan interfeysdə ümumiləşmiş tip qarşısında heç bir (burada in/out ) məhdudiyyəti göstərən açar söz yoxdursa ozaman bu interfeys invariant adlanır. Yuxarıda İTerminal interfeysi də invariantdır. Bu haqda az sonra danışacam.Amma hələlik kovarianlığa yönələk. Gəlin interfeysi kovariant edək. Bir interfeysi kovariant etmək üçün sadəcə out sözünü yazmağımız kifayətdir.

 

Bu interfeysi mütləq kovariant yaratmaq lazımdırsa ozaman :

 

  //kovariant anında metod arqumentləri ümumiləşmiş tip qəbul edə bilmir!!
   //kovariantlıq yalnızca generic tipdə nəticə qaytarmağa imkan verir!!
   public interface ITerminal< out T >
   {
       void Pay(decimal amount,User user);
       T Check(User user);
   }

 

Təbii ki , interfeysin belə dəyişməsi real praktika deyil. Ümumiyyətlə, bu absurddur. Sintaktik vahidə görə biznes məsələnin həllini dəyişmək olmaz. Amma nəisə, göstərmək istədiyimiz bu deyil…Kovariantlığı emulyasiya etmək üçün aşağıdakı formada interfeysimizi dəyişirik və davam edirik…

 

public interface ITerminal< out T >
   { 
       void Pay(decimal amount,User user);
       T Check(User user);
   }
 
 
   internal class PaymentTerminal< T > : ITerminal< T > where T:User,new()
   {
       public T Check(User user)
       {
           throw new NotImplementedException();
       }
 
       public void Pay(decimal amount, User user)
       {
           throw new NotImplementedException();
       }
   }
 
 
   public class User
   {
       public string Name { get; set; }
       public byte Age { get; set; }
       public string Email { get; set; }
   }
 
   public class Worker: User
   {
       public string WorkBookNumber { get; set; }
       public decimal Salary { get; set; }
 
   }
 
   class Program
   {
       static void Main(string[] args)
       {
           //kovariantliq hesabına bu cevrilmə uğurludur!!!
           ITerminal< User > paymentTerminal = new PaymentTerminal< Worker >();
           
       }
   }
 
Lakin bir məqamı nəzərə almaq lazımdırki , klas özü kovariantlığı seçə bilmir ona görə də bax belə bir kod error verir.
 
static void Main(string[] args)
       {
           //kovariantliq hesabına bu cevrilmə uğurludur!!!
           ITerminal< User > paymentTerminal = new PaymentTerminal< Worker >();
 
           //ammaki, klas özü generic olaraq kovariantlığı seçə bilmir və....
           //bu kod error verir...
           PaymentTerminal< User > paymentTerminal = new PaymentTerminal< Worker >();
       }
 

Səbəb odur ki, kovariant-kontravariant məsələlər interfeys və deleqat səviyyəsinə baş verən prosesdir, biz isə birbaşa klas səviyyəsinə əməliyyat aparmağa çalışırıq.

 

Kovariantlıq bizə nə qazandırır? Niyə istifadə etməliyəm sullarına gəlincə isə, birmənalı deyə bilərəm ki, bu şeylər biraz təhlükəlidir. Sadəcə istifadə xatirinə istifadə etmək gələcək arxitektur həll yollarında ciddi problem ola bilər. Ona görə istifadə etmədən əvvəl düşünmək lazımdır ki , həqiqətən buna ehtiyac varmı.

  Kovariantlıq yekun olaraq , out açar sözü ilə göstərilir və kovariant interfeys arqument olaraq generic tipi istifadə edə bilməsə də nəticə olaraq həmin tipi geri qaytara bilir.Çünki geri qayıdan tip təyinetmə tərəfinin solunda iştirak edir və sağ tərəfdəki dəyəri təyin edir. Kovariantlıq bala tipin ana tipə qaldırılması hallarında , törəmə ierarxiyasına tabe olunması halında  istifadə olunur.

Microsoftun IEnumerable,IEnumerator,IQueryable,IGrouping kimi interfeysləri kovariantdir.

 

2)Kontravariantlıq(kontravariasiya) nədir və nəyə görə var?

               Kontravariantlıq kovariantlığın tam əksinə olaraq bala tipin ana tipə yox, ana tipin bala tipə çevrilə bilməsi prosesidir...

Kontravariant tiplər in açar sözü vasitəsilə yaradılır və kovariantlığın əksinə olaraq geriyə nəticə qaytarmır, arqument olaraq generic tipi ötürür.


Microsoftun IComparable, IObserver,IComparer,IEqualityComparer generic interfeysləri, Action generic deleqatını kontravariantlığa misal göstərə bilərik.

Kod nümunəmizə düzəliş edək:

//kontravariantlıq anında metod arqumentləri ümumiləşmiş tip qəbul edir!!
   //Amma həmin tipdə geriyə nəticə qaytara bilmir....
   public interface ITerminal< in T >
   {
       void Pay(decimal amount,T user);
       User Check(T user);
   }
 
static void Main(string[] args)
       {
           
           //kontravarinatlıq zamanı cevrilmə uğursuzdur!!!
           ITerminal< User > paymentTerminal = new PaymentTerminal();
 
           //ana tipin balaya çevrilməsi isə uğurludur!!!
           ITerminal< Worker > otherPaymentTerminal = new PaymentTerminal< User >();
 
           //ammaki, klas özü generic olaraq kontravariantlığı seçə bilmir və....
           //bu kod da error verir...
           PaymentTerminal< Worker > _paymentTerminal = new PaymentTerminal< User >();
       }
 
İComparer generic interfeysi nümunəsində kontravariantlığın istifadəsinə baxaq:
static void Main(string[] args)
       {
           // Create an array of worker objects.     
           Worker[] arrayOfUsers = new Worker[4]
           {
              new Worker{ Age=34,Email="test1@gmail.com", Name="Test1" },
              new Worker{ Age=24,Email="test2@gmail.com", Name="Test2" },
              new Worker{ Age=44,Email="test3@gmail.com", Name="Test3" },
              new Worker{ Age=55,Email="test4@gmail.com", Name="Test4" },
           };
 
           //user UserComparer to Compare Workers ....
           Array.Sort(arrayOfUsers, new UserComparer());
          
 
       }
 
 
//workerleri muqayisə üçün UserComparer istifadə edəcəyik..
   internal class UserComparer : IComparer< User >
   {
       public int Compare(User x, User y)
       {
           throw new NotImplementedException();
       }
   }
 

Gördüyümüz kimi, massiv Worker tipində olsa da Comparer-in Ana klas tipindəki müqayisə realizasiyasını istifadə etdik.

 

 

3)İnvariantlıq(invariasiya)

Əksər istifadə etdiyimiz interfeys və deleqatlar invariantdır. Əgər ümumiləşmiş tip in/out açar sözləri ilə göstərilmirsə ozaman o invariantdır. Praktikada məhz ən çox tip ümumiləşmələr istifadə edirik.

İnvariant tiplərdə elanolunma hansı tipdədirsə davamı da həmin tipdə göstərilməlidir.

 

static void Main(string[] args)
       {
           //List kovariant deyil, kod işləməyəcək!
           List users1 = new List< Worker >();
 
           //List kontravariant deyil, bu kod da işləməyək!
           List< Worker > users2 = new List< User >();
 
           //List invariantdir,ona görə təyinetmənin sol və sağındakı tiplər eyni olmalıdı
           List< User > users3 = new List< User >();
 
       }


Tural

Tural Süleymani

Süleymani Tural Microsoft-un MCSD statuslu mütəxəssisidir, 2008-ci ildən bu yana proqramlaşdırma üzrə tədris aparır

Müəllifin bu dildə ən son postları

Bu yazıları da bəyənə bilərsiniz