CodeGym /Kurslar /C# SELF /Irsalma və override zamanı səhvlər

Irsalma və override zamanı səhvlər

C# SELF
Səviyyə, Dərs
Mövcuddur

1. Giriş

Yeni başlayan biri irsalma sözünü eşidəndə elə bilər ki, hər şey çox asandır: hansısa mövcud class-ı götürüb bir az genişləndirirsən və ya dəyişirsən – vəssalam! Əslində isə xeyli nüans var. İrsalma zamanı səhvlər müxtəlif cür özünü göstərə bilər: səhv signaturalar, unudulmuş açar sözlər, pis iyerarxiya dizaynı – bunların hamısı çətin bug-lara gətirib çıxarır.

Bəzən səhvlər dərhal görünür: kod compile olmur. Bəzən isə bug-lar yalnız runtime-da özünü göstərir, məsələn, sənin yeni SuperMegaLogger tam başqa bir şey yazır və ya ümumiyyətlə heç nə yazmır. Kədər, məyusluq, uzun debugging saatları – tanış hissdir?

Gəl, birlikdə irsalma və metodların override olunması ilə bağlı ən çox rast gəlinən səhvlərə baxaq və kodumuzu "sağaldaraq" davam edək.

2. Unudulmuş virtual: niyə metodu override etmək olmur?

Problem

C#-da override (override) ancaq o metodlara icazə verilir ki, onlar base class-da virtual və ya abstract və ya özləri override kimi (irsalma zəncirində) elan olunub. Əgər metodda bu söz yoxdursa, derived class-da override yazmağa cəhd etsən, compile error alacaqsan.


class Animal
{
    public void Speak()
    {
        Console.WriteLine("Heyvan nəsə deyir.");
    }
}

class Cat : Animal
{
    // Compile error! Base class-da metod virtual, abstract və ya override deyil.
    public override void Speak()
    {
        Console.WriteLine("Miyau!");
    }
}

Səhv mesajı təxminən belə olacaq: "'Cat.Speak()': cannot override inherited member 'Animal.Speak()' because it is not marked virtual, abstract, or override".

Bu tələyə necə düşməmək olar?

Metodları override etmək üçün, onları base class-da virtual kimi elan et. Əgər class-larını genişləndirmək üçün dizayn edirsənsə, hansı metodların override üçün faydalı ola biləcəyini əvvəlcədən düşün.


class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("Heyvan səs çıxarır.");
    }
}

class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("Miyau!");
    }
}

Bunu niyə etmək lazımdır?
Metodu virtual kimi elan edəndə, sənin varislərinə onun davranışını dəyişməyə icazə verirsən və compiler sənin dostun olur – o, "səhv" metodu təsadüfən override etməyə imkan verməyəcək.

3. override və new açar sözləri ilə səhv

Bəzən əks problem yaranır: derived class müəllifi metodu "override" etmək istəyir, amma base class-da o virtual deyil. Burada compiler override istifadə etməyə icazə vermir, amma new istifadə etməyə icazə verir. Amma bu artıq tam başqa davranışdır!


class Dog : Animal
{
    // Bu override deyil, base class metodunun hiding-dir.
    public new void Speak()
    {
        Console.WriteLine("Hav!");
    }
}

Əgər bu metodu Dog tipli referens ilə çağırsan – hər şey qaydasındadır:


Dog dog = new Dog();
dog.Speak(); // "Hav!"

Amma əgər obyektə base tipli referens ilə müraciət etsən, base metod çağırılacaq:


Animal dog2 = new Dog();
dog2.Speak(); // "Heyvan səs çıxarır."

İzah:
new açar sözü metodu override etmir, sadəcə parent metodunu gizlədir. Buna "hiding" deyilir. Bu yanaşma çaşdırıcı ola bilər, çünki polimorf davranış gözlədiyin kimi işləmir.

new və override ilə tələlərdən necə qaçmaq olar?

Əgər klassik polimorf override istəyirsənsə – virtual/override istifadə et. Əgər tamamilə yeni funksiya əlavə edirsənsə (və ya bilərəkdən base metodun davranışını gizlətmək istəyirsənsə, diqqətli ol!), onda new istifadə et.

Vəziyyət Açar sözü Polimorfizm işləyir? Base tip ilə çağırışda davranış
Davranışı dəyişmək override Bəli Derived class metodu çağırılır
Metodu gizlətmək/dəyişmək new Xeyr Base class metodu çağırılır

4. Metod signaturalarının uyğun gəlməməsi

Yeni başlayanlar, bəzən də təcrübəli dev-lər, derived metodda parametr və ya return type dəyişəndə səhv edirlər. Məsələn, base metod public virtual void Print(string message) kimi elan olunubsa, derived class-da public override void Print(object message) yazsan, bu artıq override deyil, yeni metoddur.


class Printer
{
    public virtual void Print(string msg)
    {
        Console.WriteLine("Base printer: " + msg);
    }
}

class SmartPrinter : Printer
{
    // Compile error! Signatura base metodla uyğun gəlmir.
    public override void Print(object msg)
    {
        Console.WriteLine("Smart printer: " + msg);
    }
}

Hint:
Metodun adı, return type-ı, parametrləri (tip, sayı və sırası) tam uyğun olmalıdır.

Əgər təsadüfən parametr tipini dəyişsən və ya adında typo olsa – compiler xəbərdarlıq edəcək.

5. Access modifier-ların uyğun olmaması

Başqa bir tələ də access modifier-lardır. Derived metod base-dən daha qapalı ola bilməz. Pis kod nümunəsi:


public class Vehicle
{
    public virtual void StartEngine() { /* ... */ }
}

public class Car : Vehicle
{
    // Səhv! 'private' modifier-i base metodun 'public'-undan daha sərtdir.
    private override void StartEngine() { /* ... */ }
}

Nə etməli?
Derived metodun access modifier-i base metodla eyni və ya daha açıq olmalıdır. Əksər hallarda public və ya protected istifadə olunur.

6. Unudulmuş/artıq abstract metod

Əgər base class-da metod abstract kimi elan olunubsa, derived class-da onun override-u mütləq olmalıdır, yoxsa class da abstract olur (və instance yarada bilməzsən).


abstract class Shape
{
    public abstract double Area();
}

class Circle : Shape
{
    // Səhv! Abstract metod Area() implement olunmayıb
}

Həll:
Bu metodu implement et:


class Circle : Shape
{
    public override double Area()
    {
        return 3.14 * 2 * 2; // Təxmini...
    }
}

7. Base implementasiyanı çağırmaq: base.Method()

Bəzən metodu tam əvəz etmək yox, ona nəsə əlavə etmək lazımdır. Bu halda çoxları unudur (və ya bilmir) ki, derived metodda base class implementasiyasını base açar sözü ilə çağırmaq olar.


class Logger
{
    public virtual void Log(string msg)
    {
        Console.WriteLine("Base log: " + msg);
    }
}

class FancyLogger : Logger
{
    public override void Log(string msg)
    {
        // "Fişka" əlavə edə və base metodu çağıra bilərsən:
        Console.WriteLine("[FANCY] " + msg);
        base.Log(msg);
    }
}

İzah:
Əgər base.Log(msg) çağırmasan, base class-da yazılmış məntiq tam itəcək.

8. Base konstruktorun çağırılması (base)

Əgər base class-ın konstruktoru mütləq parametrlər tələb edirsə, derived class mütləq uyğun konstruktoru base açar sözü ilə çağırmalıdır.


class Engine
{
    public Engine(int cylinders)
    {
        Console.WriteLine("Engine silindrlərlə: " + cylinders);
    }
}

class RaceEngine : Engine
{
    // Compile error! Engine-də default konstruktor yoxdur.
    public RaceEngine() { }
}

// Düzgün variant:
class RaceEngine2 : Engine
{
    public RaceEngine2() : base(8) // Base konstruktoru açıq çağırırıq
    {
        Console.WriteLine("Yarış motoru hazırdır!");
    }
}

9. Metodların sealed kimi işarələnməsinin unudulması

Bəzən derived class-larda metodun daha override olunmasını qadağan etmək lazımdır – bunun üçün C#-da sealed açar sözü var, onu override ilə birgə yazırsan. Əgər istifadə etməsən, kimsə sənin metodunu override edib, gözlədiyin məntiqi poza bilər.


class Hero
{
    public virtual void Attack() => Console.WriteLine("Qəhrəman hücum edir!");
}
class Warrior : Hero
{
    public sealed override void Attack() => Console.WriteLine("Döyüşçü zərbə vurur!");
}
class Mutant : Warrior
{
    // Səhv! Attack metodu yuxarıda sealed-dir.
    // public override void Attack() { ... }
}

İzah:
Beləliklə, implementasiyanı bu səviyyədə "bağlayırsan" və aşağıdakı class-lar onu dəyişə bilməyəcək.

10. Override əvəzinə artıq və ya səhv overload-lar

Bəzən dev-lər overloading (metodun eyni adı, fərqli signaturası) və overriding (override) anlayışlarını qarışdırırlar. Overloading – eyni ad, fərqli signatura (məsələn, parametr sayı fərqli), amma bunun polimorfizmə aidiyyatı yoxdur.


class Animal
{
    public virtual void Eat()
    {
        Console.WriteLine("Heyvan yeyir.");
    }
}

class Panda : Animal
{
    // Bu override deyil! Sadəcə yeni overload metoddur.
    public void Eat(string what)
    {
        Console.WriteLine("Panda yeyir: " + what);
    }
}

...

Animal a = new Panda();
a.Eat(); // Əgər metod override olunubsa – derived class implementasiyası çağırılacaq. Yoxdursa – base istifadə olunur
// a.Eat("bambuk"); // Compile error: Animal-da belə metod yoxdur

Polimorf davranış üçün məhz override etmək lazımdır, sadəcə overload yox.

11. Formal və qeyri-formal dizayn səhvləri

Class-lar pis dizayn olunanda, irsalma siyahıları dolaşıq olur:

  • daxili "karusel" override-lar, base. düzgün istifadə olunmur,
  • modifier-ların ardıcıllığı pozulur,
  • çox dərin və ya qeyri-aydın iyerarxiyalar,
  • metodların məntiqi irsalma səviyyələri arasında "yayılıb",
  • virtual/abstract metodlara şərh yazılmır,
  • gözlənilməz yan effektlər (məsələn, base class konstruktorunda virtual metod çağırılır, amma derived hələ tam inicializasiya olunmayıb).

Məsləhət:
Əgər qarışıq düşdüyünü hiss edirsənsə – tənbəllik etmə, kağızda (əsl oldschool!) iyerarxiyanı çək və hansı metodun harada olduğunu, kimdə override olunduğunu, nəyi çağırdığını və harada base. çağırılmalı olduğunu qeyd et.
Real layihələrdə sənədlərdə də hansı metodun override olunmasından nə gözlənildiyini qeyd etmək çox vacibdir.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
Metodun `new` açarı ilə gizlədilməsi xətası
Metodun `new` açarı ilə gizlədilməsi xətası
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION