уторак, 23. фебруар 2016.

Object klasa u C# programskom jeziku


Već vam je poznato da klasu možete da posmatrate i kao šablon objekta, dok za objekat možete da kažete da je objekat instanca klase. Međutim, šta je Object klasa? U mnogim programskim jezicima, tako i u C# programskom jeziku, za sve klase postoji koreni tip iz koga se izvode svi ostali objekti u hijerarhiji. Tako su sve .NET klase u krajnjoj liniji izvedene ili preciznije rečeno nasleđene iz klase Object koja se nalazi u imenskom prostoru System (System.Object). Ukoliko u C# programskom jeziku ne naznačite da je neka klasa izvedena iz druge klase, kompajler automatski pretpostavlja da je navedena klasa izvedena iz klase Object. Što u praksi znači da vi automatski imate pristup velikom broju javnih i zaštićenih metoda članova koji su već definisani za klasu Object. Ove metode su vam takođe dostupne i u svim drugim klasama koje definišete.

 
( Metode Object klase u C# programskom jeziku )

Isto tako kada koristite u vašim deklaracijama tip object, on definitivno predstavlja krajnji roditeljski tip iz kog se dalje izvode svi izvorni i korisnički definisani tipovi. Ključna reč ili tip object je samo alias za System.Object klasu. To je najključnija stvar u C# programskom jeziku po kojoj se C# programski jezik razlikuje od C++ programskog jezika. Pre nego što pređemo na praktičan primer kako se koristi klasa Object, pogledajte spisak metoda koji su definisani u klasi Object.
  • ToString() – Vraća objekat u obliku string.
  • GetHashCode() – Koristi se prilikom implementiranja rečnika (heš tabela).
  • Equals(Object) – Poredi jednakost primerka objekta.
  • Equals(Object, Object) – Poredi jednakost dva primerka objekta.
  • ReferenceEquals(Object, Object) – Proverava da li se dve reference odnose na isti objekat.
  • GetType() – Vraća detalje tipa objekta.
  • MemberwiseClone() – Pravi površnu kopiju objekta.
  • Finalize() – Ovo je .NET verzija destruktora.
Da bi ste koristili navedene metode u vašim klasama, većinu njih treba da prekoračite sa ključnom rečju override i napišete vašu implementaciju. U praksi bi na primer svaka klasa trebala da ima svoju implementaciju metode ToString() inače svako ko koristi vašu klasu može pozvati metodu ToString() jer vaša klasa je izvedena iz Object klase; ali ako niste implementirali ovu metodu, nećete dobiti rezultat koji očekujete.

Kako u praktičnom primeru da koristimo navedene metode? 
 
Napravićemo jednu klasu Point koja treba pored prikaza pozicije, tj. koordinata X i Y da nam omogući i pravilnu upotrebu metoda klase Object. Pored javnih članova tipa integer, napravićemo i jedan konstruktor radi lakšeg unosa koordinata X i Y.

public int X, Y;
     
public Point(int X, int Y)
{
this.X = X;
       this.Y = Y;

}

Prva metoda koju ćemo implementirati je metoda ToString(). Prekoračenjem navedene metode omogućićemo prikaz koordinata X i Y, na primer prikazaćemo kordinate u malim u malim zagradama. Inače treba da znate da prekoračenje metode ToString() je najčešće prekoračenje koje ćete implementirati u vašim projektima i na njega će te se najviše naviknuti.

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

Nemojte da vas zbuni skraćeno pisanje navedene metode koji je omogućeno u verziji C# 6.0. Ono je isto kao da ste napisali:

public override String ToString()
{
return String.Format("({0}, {1})", X, Y);

}

Prekoračenjem i implementiranjem metode ToString() vi ste omogućili programerima koji koriste vašu klasu Point sledeći prikaz:

Point p1 = new Point(3, 5);
WriteLine("Point 1: " + p1.ToString()); // Point 1: (3, 5)

Da niste prekoračili i implementirali metodu ToString(), vaš rezultat bi bio ovakav:

Point p1 = new Point(3, 5);
WriteLine("Point 1: " + p1.ToString()); // Point 1: ClassObject.Point

Pretpostavimo da hoćete da imate i metodu Copy() koja će vam olakšati kopiranje vrednosti jednog objekta u drugi. Za tu svrhu možemo implicitno iskoristiti metodu MemberwiseClone() koja površinski klonira zadati objekat i navedenu metodu ne morate ni da prekoračite, niti da implementirate. Tako na vrlo jednostavan način dobijate i korisnu metodu za kopiranje u vašoj klasi.

public Point Copy() => (Point)this.MemberwiseClone();     

Druga korisna metoda koju ćete koristiti je metoda Equals(). Kao što smo naveli ova metoda poredi jednakost. Iako možete pomisliti da je metoda Equals() identična operatoru referentne jednakosti == ; jednostavno nije. Najbitnija razlika je u tome da Equals() je virtualna metoda dok je referentni operator jednakosti == statičan. Pogledajte jednostavan mali izdvojeni primer u razlici poređenja.

Object A = 5, B = 5;
WriteLine(A == B); // False
WriteLine(A.Equals(B)); // True

U ovom slučaju A nije isto što i B zato što su promenjive spakovane u objekat i nalaze se na različitim memorijskim lokacijama. Međutim ako promenimo tip promenjivi na primer u integer. Rešenje je onda definitivno drugačije.

int A = 5, B = 5;
WriteLine(A == B); // True
WriteLine(A.Equals(B)); // True

Međutim u praksi ćete češće koristiti operator referentne jednakosti ==. Zato je poželjno Equals() nadjačati i implementirati sa navedenim operatorom.

public override bool Equals(object obj)
{
if (obj.GetType() != this.GetType()) return false;

       Point P = (Point)obj;
       return (this.X == P.X) && (this.Y == P.Y);

}

Ovako implementiranja metoda će definitivno raditi odlično posao. Još jedna od korisni metoda klase Object je i metoda GetHashCode() koja generiše heš kod objekta pogodan za upotrebu na primer sa rečnicima. Međutim nije svejedno kako ćete implementirati navedenu metodu. Ukoliko ne implementirate navedenu metodu definitivno ćete imati isti heš kod za različite objekte. Najjednostavnija implementacija metode GetHashCode() je:

public override int GetHashCode() => X ^ Y;

Pogledajte rezultate:

Point p1 = new Point(3, 5);
Point p2 = new Point(5, 3);

WriteLine("GetHashCode 1 method for Point 1: " + p1.GetHashCode()); // 6
WriteLine("GetHashCode 1 method for Point 2: " + p2.GetHashCode()); // 6

Na navedeni način, objekti i p1 i p2 imaju istu vrednost heš koda 6 iako se razlikuju. Najveća mana metode GetHashCode() je što lako može dodeliti i drugim objektima isti heš kod. Da bi smo ovo izbegli poželjno je koristiti Tuple klasu sa kojom možete praviti Tuple objekte. Napravićemo novu metodu GetHashCode() i nazvaćemo je GetHashCode2().

public int GetHashCode2() => Tuple.Create(X, Y).GetHashCode();  

Sad se objekti p1 i p2 definitivno imaju različit heš kod.

WriteLine("GetHashCode 2 method for Point 1: " + p1.GetHashCode2()); // 102
WriteLine("GetHashCode 2 method for Point 2: " + p2.GetHashCode2()); // 166

Međutim, postoji i treći način koji se itekako koristi; jeste da konvertovanjem i pomeranjem bitova implementirate GetHashCode() metodu. Pogledajte kod GetHashCode3() metode.

public int GetHashCode3() => ShiftAndWrap(X.GetHashCode(), 2) ^ Y.GetHashCode();
      
public int ShiftAndWrap(int value, int positions)
{
positions = positions & 0x1F;

       uint number = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
       uint wrapped = number >> (32 - positions);

    return BitConverter.ToInt32(BitConverter.GetBytes((number << positions) | wrapped), 0);

}

Sad možete biti sigurni u različitost heš koda različiti objekata:

WriteLine("GetHashCode 3 method for Point 1: " + p1.GetHashCode3()); // 9
WriteLine("GetHashCode 3 method for Point 2: " + p2.GetHashCode3()); // 23

Što se tiče ostali metoda klase Object, nećete imati potrebu za njihovom implementacijom osim u specijalnim slučajevima. Pogledajte kod celog programa.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ObjectClass
{
    class Point
    {
        public int X, Y;
     
        public Point(int X, int Y)
        {
            this.X = X;
            this.Y = Y;

        }

        public override bool Equals(object obj)
        {
            // If this and obj do not refer to the same type, then they are not equal.
            if (obj.GetType() != this.GetType()) return false;

            // Return true if X and Y fields match.
            Point P = (Point)obj;
            return (this.X == P.X) && (this.Y == P.Y);
        }

       
        // Return the XOR of the X and Y fields.
        public override int GetHashCode() => X ^ Y;
                 
        public int GetHashCode2() => Tuple.Create(X, Y).GetHashCode();
       
        public int GetHashCode3() => ShiftAndWrap(X.GetHashCode(), 2) ^ Y.GetHashCode();
      
        public int ShiftAndWrap(int value, int positions)
        {
            positions = positions & 0x1F;

            // Save the existing bit pattern, but interpret it as an unsigned integer.
            uint number = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
            // Preserve the bits to be discarded.
            uint wrapped = number >> (32 - positions);
            // Shift and wrap the discarded bits.
            return BitConverter.ToInt32(BitConverter.GetBytes((number << positions) | wrapped), 0);
        }

        // Return a copy of this point object by making a simple field copy.
        public Point Copy() => (Point)this.MemberwiseClone();
              
        // Return the point's value as a string.
        public override String ToString() => $"({X}, {Y})";

      
    }
}

using System;
using static System.Console;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ObjectClass
{
    class Program
    {
        static void Main(string[] args)
        {
            // Call override ToString() from the class           
            Point p1 = new Point(3, 5);
            WriteLine("Point 1: " + p1.ToString());

            Point p2 = new Point(5, 3);
            WriteLine("Point 2: " + p2.ToString());

            WriteLine(new String('-', 20));

            Point p3 = p1.Copy();
            WriteLine("Point 3 as copy of the Point 1: " + p3.ToString());

            WriteLine(new String('-', 20));

            WriteLine("Is Point 3 type of the Point: " + (p3.GetType() == typeof(Point)));

            WriteLine("Is Point 1 eguals Point 3: " + p1.Equals(p3));

            WriteLine("Is Point 3 reference equals Point 1: " + Point.ReferenceEquals(p3, p1));

            WriteLine(new String('-', 20));

            WriteLine("GetHashCode 1 method for Point 1: " + p1.GetHashCode()); // 6
            WriteLine("GetHashCode 1 method for Point 2: " + p2.GetHashCode()); // 6

            WriteLine();

            WriteLine("GetHashCode 2 method for Point 1: " + p1.GetHashCode2()); // 102
            WriteLine("GetHashCode 2 method for Point 2: " + p2.GetHashCode2()); // 166

            WriteLine();

            WriteLine("GetHashCode 3 method for Point 1: " + p1.GetHashCode3()); // 9
            WriteLine("GetHashCode 3 method for Point 2: " + p2.GetHashCode3()); // 23

            ReadKey();

        }
    }
}

Kada pokrenete navedeni program dobićete sledeće rezultate:

Point 1: (3, 5)
Point 2: (5, 3)
--------------------
Point 3 as copy of the Point 1: (3, 5)
--------------------
Is Point 3 type of the Point: True
Is Point 1 eguals Point 3: True
Is Point 3 reference equals Point 1: False
--------------------
GetHashCode 1 method for Point 1: 6
GetHashCode 1 method for Point 2: 6

GetHashCode 2 method for Point 1: 102
GetHashCode 2 method for Point 2: 166

GetHashCode 3 method for Point 1: 9
GetHashCode 3 method for Point 2: 23





Kako izgleda prethodni program možete pogledati i na video-u:



( C# 6.0 Tutorial - Fundamentals - 41. Object Class )