CodeGym /Kurslar /C# SELF /C#-da operatorların overload olunması

C#-da operatorların overload olunması

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

1. Giriş

Yəqin ki, sən artıq + kimi operatorlardan ədədləri toplamaq və ya == ilə müqayisə etmək üçün istifadə etməyə öyrəşmisən. Bu operatorlar daxili data tipləri ilə əla işləyir. Bəs öz class-ını yaratsan, məsələn, Vector (vektor), və istəyirsən ki, iki vektoru toplamaq təbii görünsün, məsələn vector1 + vector2? Və ya iki Money (pul) obyektini müqayisə etmək moneyAmount1 == moneyAmount2 kimi işləsin? Məhz belə hallarda C#-da operatorların overload olunması mövcuddur.

Operator overload sənə imkan verir ki, standart operatorların (+, -, *, /, ==, !=, >, <, ++, -- və s.) sənin öz class və ya struct-ların üçün necə işləyəcəyini təyin edəsən. Bu, kodunu daha intuitiv, oxunaqlı və ifadəli edir, çünki artıq öz tiplərin üçün də tanış sintaksisdən istifadə edə bilərsən.

Operator overload-un əsasları

Operatoru overload etmək üçün class və ya struct-da static method elan edirsən, operator açar sözü ilə və sonra overload etmək istədiyin operatorun simvolu gəlir.

  • Static method: Operator overload method-u həmişə public static olmalıdır.
  • Method-un adı: Method-un adı operator açar sözü və sonra operatorun simvolu (məsələn, operator +, operator ==).
  • Parametrlər: Parametrlərin sayı operatorun tipindən asılıdır (unary və ya binary).
    • Unary operatorlar (məsələn, +, -, !, ++, --): bir parametr qəbul edir, həmin class/struct tipində olmalıdır.
    • Binary operatorlar (məsələn, +, -, *, /, ==, !=): iki parametr qəbul edir, ən azı biri həmin class/struct tipində olmalıdır.
  • Return tipi: Operatorun qaytardığı tip.

2. Binary operatorların overload olunması (Binary Operators)

Binary operatorlar iki operand qəbul edir. Ən məşhurları arifmetik operatorlardır (+, -, *, /) və müqayisə operatorları (==, !=, >, <, >=, <=).

Nümunə: Toplama operatorunun (+) overload olunması

public struct Point
{
    public int X { get; set; }
    public int Y { get; set; }

    public Point(int x, int y) => (X, Y) = (x, y);

    // + operatorunun overload olunması
    public static Point operator +(Point p1, Point p2)
    {
        return new Point(p1.X + p2.X, p1.Y + p2.Y);
    }

    public override string ToString() => $"({X}, {Y})";
}

// İstifadə:
Point point1 = new Point(1, 2);
Point point2 = new Point(3, 4);
Point sumPoint = point1 + point2; // Burada overload olunmuş + operatoru çağırılır

Console.WriteLine($"Nöqtələrin cəmi: {sumPoint}"); // Çıxış: Nöqtələrin cəmi: (4, 6)

Burada biz təyin etdik ki, iki Point obyektinin toplanması yeni Point obyektini qaytarmalıdır, koordinatları isə ilkin nöqtələrin uyğun koordinatlarının cəmi olmalıdır.

Nümunə: Vektoru skalyara (ədədə) vurmaq üçün operatorun (*) overload olunması

public struct Vector
{
    public double X { get; set; }
    public double Y { get; set; }

    public Vector(double x, double y) => (X, Y) = (x, y);

    // * operatorunun overload olunması (vektor * ədəd)
    public static Vector operator *(Vector vec, double scalar)
    {
        return new Vector(vec.X * scalar, vec.Y * scalar);
    }

    // * operatorunun overload olunması (ədəd * vektor) - simmetriya üçün
    public static Vector operator *(double scalar, Vector vec)
    {
        return new Vector(vec.X * scalar, vec.Y * scalar);
    }

    public override string ToString() => $"<{X}, {Y}>";
}

// İstifadə:
Vector vec1 = new Vector(2, 3);
Vector scaledVec1 = vec1 * 5;      // Vector * double çağırılır
Vector scaledVec2 = 5 * vec1;      // double * Vector çağırılır

Console.WriteLine($"Miqyaslanmış vektor 1: {scaledVec1}"); // Çıxış: Miqyaslanmış vektor 1: <10, 15>
Console.WriteLine($"Miqyaslanmış vektor 2: {scaledVec2}"); // Çıxış: Miqyaslanmış vektor 2: <10, 15>

Diqqət et ki, vurma operatorunu iki dəfə overload etdik ki, həm Vector * double, həm də double * Vector işləsin.

Nümunə: Müqayisə operatorlarının (== və !=) overload olunması

== və != operatorlarını overload edəndə vacib müqavilə var: əgər birini overload edirsənsə, mütləq digərini də overload etməlisən. Həmçinin, bu operatorların davranışı Equals() və GetHashCode() metodları ilə uyğun olmalıdır.

public class Money
{
    public decimal Amount { get; set; }
    public string Currency { get; set; }

    public Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    // Əgər == / != overload edirsənsə, mütləq Equals və GetHashCode-u override et
    public override bool Equals(object? obj)
    {
        if (obj is not Money other) return false;
        return Amount == other.Amount && Currency == other.Currency;
    }

    public override int GetHashCode() => HashCode.Combine(Amount, Currency);

    // == operatorunun overload olunması
    public static bool operator ==(Money? m1, Money? m2)
    {
        // Reference type-lar üçün null yoxlaması
        if (ReferenceEquals(m1, null)) return ReferenceEquals(m2, null);
        return m1.Equals(m2); // Bizim override etdiyimiz Equals-dan istifadə olunur
    }

    // != operatorunun overload olunması (== overload olunanda mütləq lazımdır)
    public static bool operator !=(Money? m1, Money? m2)
    {
        return !(m1 == m2);
    }
}

// İstifadə:
Money cash1 = new Money(100, "USD");
Money cash2 = new Money(100, "USD");
Money cash3 = new Money(50, "USD");
Money cash4 = new Money(100, "EUR");

Console.WriteLine($"cash1 == cash2: {cash1 == cash2}"); // True
Console.WriteLine($"cash1 == cash3: {cash1 == cash3}"); // False
Console.WriteLine($"cash1 == cash4: {cash1 == cash4}"); // False

Struct-lar üçün == və != operatorları default olaraq bitwise müqayisə edir, bu da adətən düzgündür. Amma class-lar (class) üçün default == referensləri müqayisə edir, ona görə overload etmək dəyər müqayisəsi üçün faydalıdır.

3. Unary operatorların overload olunması (Unary Operators)

Unary operatorlar bir operand ilə işləyir. Nümunələr: + (unary plus), - (unary minus), ! (logical NOT), ~ (bitwise NOT), ++ (increment), -- (decrement).

Nümunə: Unary minus operatorunun (-) overload olunması

public struct Vector3D
{
    public double X, Y, Z;

    public Vector3D(double x, double y, double z) => (X, Y, Z) = (x, y, z);

    // Unary - operatorunun overload olunması
    public static Vector3D operator -(Vector3D vec)
    {
        return new Vector3D(-vec.X, -vec.Y, -vec.Z);
    }

    public override string ToString() => $"<{X}, {Y}, {Z}>";
}

// İstifadə:
Vector3D originalVec = new Vector3D(1, -2, 3);
Vector3D invertedVec = -originalVec; // Overload olunmuş unary - operatoru çağırılır

Console.WriteLine($"Əsas vektor: {originalVec}"); // Çıxış: Əsas vektor: <1, -2, 3>
Console.WriteLine($"İnvert olunmuş vektor: {invertedVec}"); // Çıxış: İnvert olunmuş vektor: <-1, 2, -3>

Nümunə: İnkrement (++) və dekrement (--) operatorlarının overload olunması

Bu operatorlar operandı dəyişir və dəyişmiş dəyəri qaytarır.

public struct Counter
{
    public int Value { get; set; }

    public Counter(int value) => Value = value;

    // ++ operatorunun overload olunması
    public static Counter operator ++(Counter c)
    {
        // Vacibdir: Əgər struct dəyişməzdirsə, YENİ obyekt qaytarırıq,
        // yox əgər class-dırsa, mövcud olanı dəyişib qaytarırıq
        return new Counter(c.Value + 1);
    }

    // -- operatorunun overload olunması
    public static Counter operator --(Counter c)
    {
        return new Counter(c.Value - 1);
    }

    public override string ToString() => $"[Sayğac: {Value}]";
}

// İstifadə:
Counter myCounter = new Counter(5);
myCounter++; // Postfix increment
Console.WriteLine(myCounter); // Çıxış: [Sayğac: 6]

++myCounter; // Prefix increment
Console.WriteLine(myCounter); // Çıxış: [Sayğac: 7]

myCounter--;
Console.WriteLine(myCounter); // Çıxış: [Sayğac: 6]

Overload olunmuş ++ və -- operatorları həmişə prefix kimi işləyir (əvvəl dəyişir, sonra qaytarır). Compiler postfix üçün düzgün davranışı özü yaradır.

4. Tip çevirmə operatorlarının overload olunması

Sən öz tipinin obyektlərinin başqa tipə necə çevriləcəyini (explicit və ya implicit) və əksinə, təyin edə bilərsən.

  • implicit (gizli çevirmə): Çevirmə həmişə təhlükəsizdirsə və data itkisi olmursa istifadə olunur (məsələn, int dən long a).
  • explicit (açıq çevirmə): Çevirmə data itkisinə və ya səhvə səbəb ola bilərsə, açıq şəkildə göstərilməlidir ((Type)obj).

Nümunə: Score tipindən int tipinə gizli çevirmə

public struct Score
{
    public int Points { get; set; }
    public Score(int points) => Points = points;

    // Score-dan int-ə gizli çevirmə
    public static implicit operator int(Score s)
    {
        return s.Points;
    }
}

// İstifadə:
Score examScore = new Score(95);
int scoreValue = examScore; // Gizli çevirmə
Console.WriteLine($"Ballar: {scoreValue}"); // Çıxış: Ballar: 95

Nümunə: Celsius tipindən Fahrenheit tipinə açıq çevirmə

public struct Celsius
{
    public double Degrees { get; set; }
    public Celsius(double degrees) => Degrees = degrees;
}

public struct Fahrenheit
{
    public double Degrees { get; set; }
    public Fahrenheit(double degrees) => Degrees = degrees;

    // Fahrenheit-dən Celsius-a açıq çevirmə
    public static explicit operator Celsius(Fahrenheit f)
    {
        return new Celsius((f.Degrees - 32) * 5 / 9);
    }

    // Celsius-dan Fahrenheit-ə açıq çevirmə
    public static explicit operator Fahrenheit(Celsius c)
    {
        return new Fahrenheit(c.Degrees * 9 / 5 + 32);
    }
}

// İstifadə:
Celsius c = new Celsius(25);
Fahrenheit f = (Fahrenheit)c; // Açıq çevirmə
Console.WriteLine($"25°C = {f.Degrees}°F"); // Çıxış: 25°C = 77°F

Fahrenheit f2 = new Fahrenheit(212);
Celsius c2 = (Celsius)f2; // Açıq çevirmə
Console.WriteLine($"212°F = {c2.Degrees}°C"); // Çıxış: 212°F = 100°C

5. Məhdudiyyətlər və tövsiyələr

Bütün operatorlar overload oluna bilmir: &&, ||, ?., new, typeof, is, as, == (string üçün, çünki artıq string class-ında overload olunub və xüsusi davranışı var), () (method çağırışı), = (təyin etmə) və bəziləri overload oluna bilmir.

Simmetriya: Əgər binary operator overload edirsənsə, çox vaxt operandların əks ardıcıllığı üçün də overload etmək faydalıdır (məsələn, Vector * double nümunəsində olduğu kimi).

Müqavilələr: Həmişə müqavilələrə əməl et, xüsusilə müqayisə operatorları üçün (==, !=, >, <, >=, <=) və onların Equals() və GetHashCode() ilə əlaqəsinə. Müqaviləni pozsan, xüsusilə kolleksiyalarda, gözlənilməz və səhv davranışlar ola bilər.

Oxunaqlılıq və intuitivlik: Operatorları yalnız kodu daha intuitiv və oxunaqlı edirsə overload et. Əgər operatorun davranışı aydın deyilsə və ya çaşdırıcı ola bilərsə, daha yaxşısı açıq adla methodlardan istifadə et (məsələn, Add() əvəzinə +).

Dəyişən (Mutable) obyektlər: Dəyişən tiplər üçün operator overload edəndə diqqətli ol. Məsələn, Vector v1 = v2 + v3; default olaraq yeni obyekt yaradır, v2 dəyişmir. Əgər operator mövcud obyekti dəyişirsə, bu aydın olmaya bilər.

Struct-lar və Class-lar: Operator overload daha çox struct-lar üçün istifadə olunur, çünki onlar adətən dəyər tipli olur (məsələn, Point, ComplexNumber) və onlar üçün riyazi əməliyyatlar və ya dəyər müqayisəsi təbii görünür.

2
Tapşırıq
C# SELF, səviyyə, dərs
Bağlanıb
İkioperandlı toplama operatorunun overload edilməsi
İkioperandlı toplama operatorunun overload edilməsi
Şərhlər
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION