Przeciążanie operatorów w języku C#

Przeciążanie operatorów - definiowanie operatorów dla własnych typów.


Język C# pozwala zdefiniować funkcjonalność operatorów dla własnych typów, czyli np. stworzonych przez nas obiektów. Dzięki takim zabiegom zamiast pisać i wywoływać za każdym razem metodę odejmowania możemy nadpisać operator, czyli utworzyć metodę, która coś zwraca, przyjmuje parametry oraz posiada w deklaracji słowo kluczowe "operator" wraz z symbolem operatora, który nadpisujemy a więc w naszym wypadku będzie to "operator-". Każdy operator ma określoną liczbę argumentów, dla operatora "-" musza to być 2 argumenty.

Składnie definicji przeciążonego operatora możemy podzielić na 4 typy:

  • public static zwracany_typ operator op_unarny(typ_arg argument),
    
  • public static zwracany_typ operator op_binarny(typ_arg1 argument1, typ_arg2 argument2),
    
  • public static implicit operator typ_wyj(typ_wej argument),
    
  • public static explicit operator typ_wej(typ_wyj argument)
    
gdzie:
  • zwracany_typ - typ zwracany przez operator (wymagane),
  • op_unarny - operator unarny dostępny do przeciążania: +, -, !, ~, ++, --, true, false (wymagane),
  • op_binarny - operator binarny dostępny do przeciążania: +, -, *, /, %, <<, >>, <, >, <=, >=, ==, !=, &, |, ^ (wymagane),
  • typ_arg - typy argumentów wejściowych (wymagane),
  • argument - nazwa argumentu wejściowego (wymagane),
  • typ_wyj - typ wyjściowy operatora konwersji (wymagane),
  • typ_wej - typ wejściowy operatora konwersji (wymagane).

Operatory których nie wolno przeciążać:

  • logiczne warunkowe: &&, ||,
  • konwersji: () - służą do tego słowa kluczowe explicit i implicit,
  • indeksacji: [] - stosujemy w tym celu indeksatory,
  • przypisań: +=, -=, *=, %=, <<=, >>=, |=, ^=, &=, /=,
  • pozostałe: =, ., new, is, sizeof, typeof, ?:.

Przeciążanie operatorów relacji

Operatory relacji czyli operatory takie jak: "==" i "!=", "<" i ">", "<=" i ">=" muszą być przeciążane parami, nie można przeciążyć tylko jednego z pary operatorów.

Equals() i GetHashCode()

Przeciążając operatory "==" i "!=" powinniśmy również nadpisać metody Equals() i GetHashCode(), które są dziedziczone z klasy System.Object. Metoda Equals() działa identycznie jak operator "==" natomiast metoda GetHashCode() jest to wartość liczbowa, służąca do identyfikacji obiektu podczas testowania równości. Jeśli wynik metody Equals() daje wartość true to metoda GetHashCode() musi zwracać te same wartości dla obydwu obiektów, jeśli natomiast metoda Equals() zwraca wartość false to metoda GetHashCode() może zwracać takie same lub różne wartości dla porównywanych obiektów. Metoda GetHashCode() musi zwracać te samą wartość tak długo, jak długo zmiany obiektu nie wpływają na zmianę wyniku metody Equals().

Przeciążanie operatorów konwersji

Można przeciążać operator konwersji w postaci jawnej (explicit) oraz w postaci niejawnej (implicit). Aby przeciążyć operator niejawny używamy słowa kluczowego implicit, natomiast aby przeciążyć operator jawny, używamy słowa kluczowego explicit w definicji operatora.

Przykładowy kod - konwersja jawna:

class Obwod
{
    public int _bok1;
    public int _bok2;
    public double _bok3;
    public Obwod(int bok1, int bok2, double bok3)
    {
        _bok1 = bok1;
        _bok2 = bok2;
        _bok3 = bok3;
    }
    public static explicit operator double(Obwod f)
    {
        return f._bok1 * f._bok2 * f._bok3;
    }
}
class Wyswietl
{
    public static void Main()
    {
        Obwod fig1 = new Obwod(3, 4, 4.34);
        Obwod fig2 = new Obwod(4, 5, 4.34);
        Obwod fig3 = new Obwod(5, 6, 4.34);
        double obwod = (double)fig1;
        Console.WriteLine("Obwód wynosi: " + obwod);
    }
}

Przykładowy kod - konwersja niejawna:

class Obwod
{
    public int _bok1;
    public int _bok2;
    public double _bok3;

    public Obwod(int bok1, int bok2, double bok3)
    {
        _bok1 = bok1;
        _bok2 = bok2;
        _bok3 = bok3;
    }

    public static implicit operator double(Obwod f)
    {
        return f._bok1 * f._bok2 * f._bok3;
    }
}

class Wyswietl
{
    public static void Main()
    {
        Obwod fig1 = new Obwod(3, 4, 4.34);
        Obwod fig2 = new Obwod(4, 5, 4.34);
        Obwod fig3 = new Obwod(5, 6, 4.34);

        double obwod = fig1;

        Console.WriteLine("Obwód wynosi: " + obwod);
    }
}
Jak łatwo zauważyć jedyna różnica pomiędzy tymi dwoma rozwiązaniami sprowadza się do tego, że przy korzystaniu z konwersji jawnej musimy wykonać rzutowanie na typ double.

Przeciążanie operatorów logicznych

Operatory logiczne są rozwijane za pomocą: &, |, true, false dlatego nie ma możliwości rozwinięcia operatorów logicznych wprost. Aby rozwinąć wyrażenie z operatorem && lub || musimy przeciążyć operatory &, |, true oraz false.

Rozwinięcie wygląda następująco:

  • dla operatora: && jest to T.false(x) ? x : T.&(x,y),
  • dla operatora: || jest to T.true(x) ? x : T.|(x,y).
gdzie: T jest to typ zmiennych x i y.

Przykładowy kod:

class Obwod
{
    public int _bok1;
    public int _bok2;

    public Obwod(int bok1, int bok2)
    {
        _bok1 = bok1;
        _bok2 = bok2;
    }

    public static Obwod operator &(Obwod ob1, Obwod ob2)
    {
        return new Obwod(ob1._bok1 & ob2._bok1, ob1._bok2 & ob2._bok2 );
    }

    public static Obwod operator |(Obwod ob1, Obwod ob2)
    {
        return new Obwod(ob1._bok1 | ob2._bok1, ob1._bok2 | ob2._bok2);
    }

    public static bool operator true(Obwod ob1)
    {
        return ob1._bok1 != 0;
    }

    public static bool operator false(Obwod ob1)
    {
        return ob1._bok1 == 0;
    }

}

class Wyświetl
{
    public static void Main()
    {
        Obwod fig1 = new Obwod(3, 4);
        Obwod fig2 = new Obwod(4, 5);
        Obwod fig3 = new Obwod(5, 6);

        if(fig1 && fig2)
        Console.WriteLine("Długości boków różne od zera");

        if (fig1 || fig3)
        Console.WriteLine("Długości boków fig1 lub fig2 różne od zera");
    }
}
Dzięki przeciążeniu operatorów możemy użyć operatora && lub || dla obiektu typu Obwod. Bez przeciążenia nie było by możliwości zastosowania tych operatorów do działań na obiektach z klasy Obwod.

Przeciążanie operatorów arytmetycznych

W języku C# możemy przeciążać następujące operatory arytmetyczne: +, -, /, *. Jedynym ograniczeniem, jakie występuje w przypadku przeciążania operatorów arytmetycznych jest to, że przynajmniej jeden argument definiowanego operatora musi być obiektem klasy, dla której został przeciążony.

Przykładowy kod:

class Obwod
{
    public int _bok1;
    public int _bok2;
    public double _bok3;

    public Obwod(int bok1, int bok2, double bok3)
    {
        _bok1 = bok1;
        _bok2 = bok2;
        _bok3 = bok3;
    }

    public static Obwod operator +(Obwod ob1, Obwod ob2)
    {
        return new Obwod(ob1._bok1 + ob2._bok1, ob1._bok2 + ob2._bok2, ob1._bok3 + ob2._bok3);
    }

    public static Obwod operator -(Obwod ob1, int liczba)
    {
        return new Obwod(ob1._bok1 - liczba, ob1._bok2 - liczba, ob1._bok3 - liczba);
    }

}

class Wyswietl
{
    public static void Main()
    {
        Obwod fig1 = new Obwod(3, 4, 4.34);
        Obwod fig2 = new Obwod(4, 5, 4.34);
        Obwod fig3 = new Obwod(5, 6, 4.34);

        Obwod obwod = fig1 + fig2 - 2;

        Console.WriteLine("Obwód wynosi: " + obwod);
    }
}
W powyższym przykładzie mamy przeciążony operator "+" i "-". W przypadku operatora dodawania dodajemy do siebie długości boków natomiast w przypadku operatora odejmowania odejmujemy od każdego boku określoną liczbę, w naszym przypadku jest to 2.

Więcej na temat przeciążania operatorów:

http://msdn.microsoft.com/pl-pl/library/8edha89s.aspx
Komentarze facebook (polub nasz profil na FB aby je zobaczyć):