1. Giriş
Təsəvvür elə, bizim sevimli DogShelter class-ımız var, hansı ki, itlərin kolleksiyasını saxlayır. Əvvəlki mühazirələrdə artıq ona indeksator əlavə edə bilirdik ki, itə sığınacaqda nömrəsinə görə çataq: Dog firstDog = myShelter[0];. Bu, əladır!
Amma bəs istifadəçimiz iti nömrə ilə yox, məsələn, adı ilə tapmaq istəsə? Yaxud cinsi ilə? Hətta bəlkə də bir neçə əlamətə görə? Əlbəttə, GetDogByName("Buddy") və ya GetDogByBreedAndAge("Labrador", 5) kimi metodlar əlavə edə bilərdik. Bu tam normal yanaşmadır.
Amma bəzən istəyirsən ki, giriş daha "array-vari" və intuitiv olsun. Elə yazasan: Dog buddy = myShelter["Buddy"]; və ya Dog oldLab = myShelter["Labrador", 8];.
Əgər DogShelter — bizim öz class-ımızdırsa, sadəcə yeni indeksatorlar əlavə edə bilərik. Amma bəs DogShelter — hansısa kənar kitabxanadandırsa və dəyişə bilmiriksə? Yaxud çox spesifik giriş üsulu əlavə etmək istəyirik ki, əsas class-ı "çirkləndirməsin"?
Bax, məhz burada səhnəyə extension indeksatorlar (Extension Indexers) çıxır!
2. "Kvadrat mötərizələr" çöldən
Yadındadır, keçən dərsdə DisplayName extension property-ni Dog üçün əlavə etmişdik? İndeksatorlarla da eyni cür işləyir!
Extension indeksator — bu, statik class-da müəyyən olunan statik indeksatordur və sənə obj[indeks] sintaksisini artıq mövcud olan tiplər üçün istifadə etməyə imkan verir, hətta əgər bu tiplərdə əvvəldən belə indeksator yoxdursa və ya başqa tip parametrli indeksator əlavə etmək istəyirsənsə.
Bu elə bil ki, soyuducu almısan, sonra isə fikirləşmisən ki, müəyyən yerinə vuranda oradan kola çıxacaq. Soyuducu eyni qalır, amma funksionallıq "çöldən" əlavə olunur!
Extension indeksatorun sintaksisi
public static class MyExtensionClass
{
extension(ObjectType nümunə)
{
public static ReturnType this[IndeksTipi indeks ]
{
get
{
// Oxumaq üçün məntiq, nümunə və indeks istifadə olunur
return ...;
}
set
{
// nümunə, indeks və 'value' açar sözündən istifadə
// 'value' - yeni dəyərdir
}
}
}
}
Diqqət et this TipGenişlənənObyekt nümunə-yə. Bu sintaksis tam olaraq extension method və property-lərdə gördüyümüz kimidir. nümunə — genişləndirdiyimiz obyektə accessor-larımızın (get və set) içində necə müraciət edəcəyimizdir.
3. Extension Indexer necə elan olunur (və beynin partlamasın)?
Sintaksis keçən dərsdə danışdığımız Extension Properties-ə bənzəyir, sadəcə indeks parametrləri ilə. Minimum nümunə belədir:
public static class DogShelterExtensions
{
extension(DogShelter shelter)
{
public static Dog this[string name]
{
get
{
foreach (var dog in shelter)
{
if (dog.Name == name)
return dog;
}
return null;
}
}
}
}
Tanış elementlər:
- this ilk parametrin qarşısında — Extension Members üçün tələbdir (genişlənən obyekt).
- Class adından sonra kvadrat mötərizədə istifadə olunacaq parametrlər gəlir.
Təcrübə: DogShelter-ə ada görə indeksator əlavə edirik
Gəlin tədris layihəmizi dəyişək. Təsəvvür elə ki, sığınacaqda hər it unikal ada malikdir:
DogShelter class-ı (kitabxana/kənar kod)
public class Dog
{
public string Name { get; set; }
public int Age { get; set; }
}
public class DogShelter : IEnumerable<Dog>
{
private List<Dog> dogs = new List<Dog>();
public void AddDog(Dog dog) => dogs.Add(dog);
// Köhnə indeksator nömrəyə görə
public Dog this[int index]
{
get => dogs[index];
set => dogs[index] = value;
}
public IEnumerator<Dog> GetEnumerator() => dogs.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
İstəyirik: shelter["Busya"]
Əvvəl — ancaq metodla:
// C# 14-dən əvvəl:
public static Dog? FindByName(this DogShelter shelter, string name) { ... }
İndi — Extension Indexer ilə:
public static class DogShelterExtensions
{
extension(DogShelter shelter)
{
public static Dog? this[string name]
{
get
{
foreach (var dog in shelter)
if (dog.Name == name)
return dog;
return null;
}
set
{
for (int i = 0; i < shelter.Count; i++)
{
if (shelter[i].Name == name)
{
shelter[i] = value!;
return;
}
}
throw new ArgumentException("It tapılmadı");
}
}
}
}
İndi əsas kodumuz daha gözəl görünür:
var shelter = new DogShelter();
shelter.AddDog(new Dog { Name = "Busya", Age = 3 });
shelter.AddDog(new Dog { Name = "Tuzik", Age = 5 });
// Extension-indeksator istifadə edirik!
Dog busya = shelter["Busya"]!;
Console.WriteLine(busya.Age);
shelter["Busya"] = new Dog { Name = "Busya", Age = 4 };
Vizualizasiya: nə baş verir?
| Əməliyyat | Əvvəl necə işləyirdi | Extension Indexer ilə |
|---|---|---|
| Ada görə axtarış | shelter.FindByName("X") | shelter["X"] |
| İtin adla dəyişdirilməsi | shelter.UpdateName("X", ..) | shelter["X"] = ... |
4. Extension Indexers-in incəlikləri və xüsusiyyətləri
Kompilyator və görünürlük sahəsi
- Extension Indexer mütləq public static class-da elan olunmalıdır (extension methods kimi).
- Lazımi using-i əlavə etməyi unutma. Unutsan — kompilyator heç nə demir, amma kod işləmir.
- Əgər əsas class-da artıq belə indeksator varsa — onu genişləndirmək olmur (imzalar fərqli olmalıdır).
set-accessor-un reallaşdırılması
Yalnız get elan edə bilərsən (onda indeksator yalnız oxumaq üçündür). Yaxud həm set əlavə edə bilərsən (yuxarıdakı nümunə) — onda həm oxuya, həm də yaza bilərsən.
Dəyərə və referansa görə ötürülmə
Extension Indexer obyektin nümunəsi ilə işləyir, hansı ki, sən ona extension əlavə edirsən (this ilk parametrin qarşısında). Əgər obyekt referans tipidirsə, onun vəziyyətini dəyişirsən.
Bir class-da bir neçə indeksator
Heç bir problem yoxdur — müxtəlif parametr dəstləri ilə bir neçə extension-indeksator elan edə bilərsən! Məsələn, yaşa görə axtarış: shelter[5] (köhnə), shelter["Busya"] (yeni), shelter[age: 3] (daha biri, istəsən).
Nümunə: DogShelter-ə iki indeksator əlavə edirik
public static class DogShelterExtensions
{
extension(DogShelter shelter)
{
// Ada görə
public static Dog? this[string name]
{
get => shelter.FirstOrDefault(d => d.Name == name);
set
{
for (int i = 0; i < shelter.Count; i++)
if (shelter[i].Name == name)
shelter[i] = value!;
}
}
// Yaşa görə — ilk uyğun iti qaytarır
public static Dog? this[int age]
{
get => shelter.FirstOrDefault(d => d.Age == age);
}
}
}
İndi belə yaza bilərsən:
var youngDog = shelter[1]; // Yaşa görə
var tony = shelter["Toni"]; // Ada görə
shelter["Tuzik"] = new Dog { Name = "Tuzik", Age = 9 };
Real ssenarilər
- Kənar kitabxanalar: Kənar class-a əlavə indeksasiya üsulları əlavə etmək istəyirsən, əsas kodu dəyişmədən. Məsələn, sifariş kolleksiyası ilə işləyirsən, onları nömrəyə, tarixə, statusa və s. görə tapırsan, əlavə wrapper metodlar yazmadan.
- "Adapter pattern": Köhnə kolleksiyanı "dumb" API ilə daha müasir, qısa və "C#-vari" API-yə çevirirsən, geriyə uyğunluğu pozmadan.
- Legacy kodun miqrasiyası: Artıq yazılmış tiplərə yeni imkanlar əlavə edirsən, mövcud kod və testlərə toxunmadan.
- Test üçün rahatlıq: Öz ehtiyacların üçün müvəqqəti indeksatorlar əlavə edə bilərsən (məsələn, test üçün unikal əlamətə görə axtarış), əsas class-ı çirkləndirmədən.
5. Extension Indexers ilə tipik səhvlər və tələlər
Əgər əsas class-da eyni imzalı indeksator varsa, extension-indeksator çağırılmayacaq — üstünlük əsas indeksatordadır.
Extension-indeksator da extension member-dir və lazımi using (namespace əlavə etmə) olmadan genişlənmə görünməyəcək.
Digər tez-tez rast gəlinən səhv — null qaytarmaq və istifadəçini xəbərdar etməməkdir. Kimsə səhvən mövcud olmayan elementə müraciət edərsə və extension-indeksator null qaytararsa, bu başqa kodda NullReferenceException-a səbəb ola bilər. Yaxşısı budur ki, implementasiyanı əvvəlcədən düşünəsən: istisna atmaq, xüsusi stub obyekt qaytarmaq və ya sadəcə null qaytarmaq.
Bir neçə extension-indeksatorun varsa, onların parametr tipi və sayı üzrə unikal olduğuna əmin ol. Eyni imzalı iki indeksator yaratmaq olmaz — kompilyator səhv verəcək.
Extension-indeksatorlar yalnız obyekt nümunələri ilə işləyir, statik tiplərlə yox.
GO TO FULL VERSION