FER / ZPR /  Ergonomija
Stringovi Metode
Za napredne
Working with I/O je vodič u kojem se opisuju najčešći primjeri korištenja klasa iz System.IO imenika. U njemu su sadržani tipovi koji omogućuju sinkrono i asinkrono čitanje i pisanje u podatkovne tokove i datoteke, te još neke pomoćne klase pri radu sa datotečnim sustavom.

» Pročitaj članak

Klase

Klasa je najmoćniji podatkovni oblik u C#-u. Poput struktura, klasa definira podatke i ponašanje. Programeri nakon toga mogu stvoriti objekte koji su instance tih klasa. Za razliku od struktura, klase podržavaju nasljeđivanje što je fundamentalni dio objektno-orijentiranog programiranja.

Klase se definiraju pomoću class ključne riječi, kao što je prikazanu u primjeru:

public class Customer
{
    //Fields, properties, methods and events go here...
}
Prije class ključne riječi se nalazi riječ public koja označava da svatko može stvoriti objekt iz ove klase. Ime se nalazi nakon class riječi. Ostatak definicije se nalazi unutar vitičastih zagrada gdje se obično nalaze definicije podataka i ponašanja objekta. Polja (fields), svojstva, metode, događaji i drugi dijelovi klase se zovu članovi klase.

Objekt se stvara pomoću ključne riječi new nakon koje trebamo navesti ime klase po kojoj će taj objekt biti baziran:
Customer object1 = new Customer();
Jednom kad je instanca klase stvorena, referenca na taj objekt je vraćena programeru. U ovom slučaju, object1 je referenca na objekt baziran na Customer klasi. Ta referenca pokazuje na novi objekt, ali ne sadrži i same podatke. Štoviše, referenca se može stvoriti bez da se stvori objekt:
Customer object2;
Stvaranje reference bez da stvorimo i objekt može dovesti do pogreške ako pokušamo pristupiti objektu pomoću te reference. Ako smo već stvorili referencu možemo zadati da pokazuje na neki novi objekt ili na neki već postojeći.
Customer object3 = new Customer();
Customer object4 = object3;
U prethodnom slučaju obje reference pokazuju na isti objekt, pa ako promijenimo nešto u objektu pristupajući mu preko prve reference, promjene će biti vidljive ako nakon toga pristupimo i preko druge reference.

Polimorfizam

Klase mogu nasljeđivati druge klase. To možemo napraviti tako što stavimo dvotočku nakon imena klase prilikom njene deklaracije, i dodamo ime klase koju želimo naslijediti:
public class A
{
    public A() { }
}

public class B : A
{
    public B() { }
}
Nova, izvedena klasa dobiva sve ne-privatne podatke i ponašanje bazne klase, čemu doda još i podatke i ponašanje koje sama definira. Nova klasa efektivno ima dva tipa: tip bazne klase i tip nove klase. U prethodnom primjeru to znači da je klasa B efektivno i B i A tipa. Kad pristupamo B objektu, možemo ga koristiti kao da je to A objekt (npr. koristi ga kao parametar neke metode koja zahtijeva objekt klase A). Obrnuto ne vrijedi. Svojstvo da objekt može predstavljati više od jednog tipa podataka zove se polimorfizam.

Kroz nasljeđivanje, klasa se može koristiti kao više od jednog tipa. Može se koristiti kao svoj tip, svoj bazni tip ili kao tip sučelja ako je implementirano koje sučelje. U C#-u svaki tip je polimorfan jer je svaki tip izveden iz Object tipa.

Što da radimo ako ne želimo samo proširiti baznu klasu pomoću nasljeđivanja, nego i promijeniti njezine već definirane podatke i ponašanje? Imamo dvije opcije: možemo zamijeniti članove iz bazne klase sa novim članom ili možemo prospojiti (override) virtualni bazni član.

Zamjena člana bazne klase sa novim izvedenim članom zahtijeva new ključnu riječ. Ako je bazna klasa definirala metodu, polje ili svojstvo, new ključna riječ se stavlja prije povratnog tipa člana koji želimo zamijeniti u izvedenoj klasi:

public class BaseClass
{
    public void DoWork() { }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}
Kad je korištena new ključna riječ, članovi izvedene klase se pozivaju umjesto članova bazne klase. Članovi bazne klase se zovu skriveni članovi. Skriveni članovi se još uvijek mogu pozvati ako izvedenu klasu castamo kao instancu bazne klase:
DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.
Da bi instanca izvedene klase u potpunosti preuzela članove bazne klase, u baznoj klasi članovi trebaju biti deklarirani kao virtualni. To radimo tako što dodamo virtual ključnu riječ ispred tipa povratne vrijednosti člana. Izvedena klasa onda ima opciju da iskoristi override ključnu riječ umjesto new da bi zamijenila implementaciju sa svojom:
public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}
Polja ne mogu biti virtualni, samo metode, svojstva, događaji i indekseri. Kad izvedena klasa premosti virtualnog člana, taj član se zove čak i ako se instanci nove klase pristupa kao da je starog, baznog tipa:
DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Also calls the new method.
Virtualni član ostane virtualni bez obzira koliko puta je naslijeđena bazna klasa. Ako klasa A deklarira virtualnog člana, klasa B naslijedi klasu A i klasa C naslijedi klasu B, klasa C je naslijedila virtualnog člana i ima opciju da ga premosti, bez obzira da li je klasa B premostila tog člana u svojoj definiciji:
public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}
public class C : B
{
    public override void DoWork() { }
}
Izvedena klasa može zaustaviti virtualno nasljeđivanje ako deklarira premoštenog člana kao sealed:
public class C : B
{
    public sealed override void DoWork() { }
}
Sada metoda DoWork više nije virtualna ni jednoj klasi koja se izvede iz klase C. Ona je još uvijek virtualna za instance klase C, čak i ako se castaju kao tip B ili A. Sealed metode se mogu zamijeniti u izvedenim klasama pomoću new ključne riječi:
public class D : C
{
    public new void DoWork() { }
}
U ovom slučaju, ako se metoda DoWork pozove na varijabli tipa D, poziva se nova metoda. Ako se varijable tipa A, B ili C koriste za pristup objektu tipa D i poziv metodi DoWork, prate se pravila virtualnog nasljeđivanja, te se poziv prosljeđuje do implementacije u klasi C.

Apstraktne i sealed klase

Ključna riječ abstract omogućuje nam da stvorimo klase i članove klase čije je svrha da definiraju svojstva koja moraju implementirati izvedene, ne-apstraktne klase.
public abstract class A
{
    // Class members here.
}
Apstraktne klase se ne mogu instancirati. Njihova svrha je da pruže zajedničku definiciju bazne klase koju nasljeđuje više klasa. Apstraktne klase mogu definirati i apstraktne metode:
public abstract class A
{
    public abstract void DoWork(int i);
}
Apstraktne metode nemaju implementaciju pa imaju samo točku-zarez umjesto normalne definicije u vitičastim zagradama. Izvedene klase apstraktne klase moraju implementirati sve apstraktne metode. Kad apstraktna klasa nasljeđuje virtualnu metodu iz bazne klase, ona može prospojiti virtualnu metodu sa apstraktnom metodom:
public class D
{
    public virtual void DoWork(int i)
    {
        // Original implementation.
    }
}

public abstract class E : D
{
    public abstract override void DoWork(int i);
}

public class F : E
{
    public override void DoWork(int i)
    {
        // New implementation.
    }
}
Ako je virtualna metoda deklarirana kao apstraktna, ona je još uvijek virtualna za klasu koja naslijedi apstraktnu klasu. Izvedena klasa ne može pristupiti implementaciji metode. U prethodnom primjeru DoWork klase F ne može pozvati DoWork klase D. Na taj način, pomoću apstraktne klase smo natjerali izvedenu klasu da stvori novu implementaciju za virtualnu metodu.

Ako ne želimo da se klasa može naslijediti definiramo je kao sealed. Obzirom da se sealed klase ne mogu koristiti kao bazne klase, neki run-time optimizatori mogu ubrzati poziv sealed članova. Primjer sealed klase:

public sealed class D
{
    // Class members here.
}

Sučelja

Sučelje se definira pomoću interface ključne riječi. Npr:
interface IComparable
{
    int CompareTo(object obj);
}
Sučelja opisuju skupinu povezanih ponašanja koji mogu pripadati bilo kojoj klasi ili strukturi. Sastoje se od metoda, svojstava, događaja i indeksera. Ne može sadržavati polja. Sučelja su automatski public.

Klase i strukture koje nasljeđuju sučelja slični su klasama koje nasljeđuju baznu klasu ili strukturu, sa dvije iznimke:

  • Klasa ili struktura može naslijediti više od jednog sučelja
  • Kad klasa ili struktura naslijedi sučelje, ona nasljeđuje definicije članova, ali ne i implementacije, npr:
public class Minivan : Car, IComparable
{
    public int CompareTo(object obj)
    {
        //implementation of CompareTo
        return 0;  //if the Minivans are equal
    }
}
Da bi implementirali člana sučelja, taj član mora biti public, non-static i imati isto ime i potpis kao član sučelja. Pod potpisom se misli na tipove parametara i povratne vrijednosti. Svojstva i indekseri mogu dodavati i ekstra načine pristupa. Ako npr. sučelje definira get dio svojstva, klasa koja implementira sučelje može definirati i get i set dio svojstva.

Sučelja mogu naslijediti druga sučelja Moguće je da klasa naslijedi neko sučelje više puta, kroz baznu klasu i sučelje koje je sam definirao. U tom slučaju, klasa implementira sučelje samo jednom ako je deklarirano kao dio nove klase. Ako naslijeđeno sučelje nije deklarirano kao dio nove klase, njegova implementacija se uzima iz bazne klase.

Eksplicitna implementacija sučelja

Ako klasa implementira dva sučelja koja sadrže članove sa istim potpisom, onda će implementacija tog člana u klasi prouzročiti da ova sučelja koriste tu istu implementaciju.
interface IControl
{
    void Paint();
}
interface ISurface
{
    void Paint();
}
class SampleClass : IControl, ISurface
{
    // Both ISurface.Paint and IControl.Paint call this method.
    public void Paint()
    {
    }
}
Ako dva člana sučelja ne bi trebali izvršavati istu funkciju, onda ovo može dovesti do krive implementacije za jednog ili oba člana. Moguće je implementirati člana sučelja eksplicitno tako da se član klase poziva samo preko sučelja. Ovo je izvedeno imenovanjem člana klase sa imenom sučelja i točkom:
public class SampleClass : IControl, ISurface
{
    void IControl.Paint()
    {
        System.Console.WriteLine("IControl.Paint");
    }
    void ISurface.Paint()
    {
        System.Console.WriteLine("ISurface.Paint");
    }
}
Član klase IControl.Paint je dostupan kroz sučelje IControl i ISurface.Paint je dostupan kroz sučelje ISurface. Obje implementacije metode su odvojene i ni jedna nije dostupna direktno preko klase :
SampleClass obj = new SampleClass();
//obj.Paint();  // Compiler error.

IControl c = (IControl)obj;
c.Paint();  // Calls IControl.Paint on SampleClass.

ISurface s = (ISurface)obj;
s.Paint(); // Calls ISurface.Paint on SampleClass.
Eksplicitna implementacija se koristi i za rješavanje slučaja kad dva sučelja deklariraju različite članove sa istim imenom poput svojstva i metode:
interface ILeft
{
    int P { get;}
}
interface IRight
{
    int P();
}
Da bi implementirali oba sučelje klasa mora koristiti eksplicitnu implementaciju za svojstvo P ili za metodu P, ili za oboje, kako bi se izbjegla pogreška pri kompilaciji. Npr:
class Middle : ILeft, IRight
{
    public int P() { return 0; }
    int ILeft.P { get { return 0; } }
}

Članovi klase

Klase i strukture imaju članove koji predstavljaju njihove podatke i ponašanje. Vrste članova:
  • Polja
  • Svojstva
  • Metode
  • Događaji
  • Operatori
  • Indekseri
  • Konstruktori
  • Destruktori
  • Ugniježđeni tipovi

Polja

Polja omogućuju klasama i strukturama da enkapsuliraju podatke. Radi jednostavnosti, u sljedećim primjerima su polja deklarirana kao public, iako se to ne preporučuje u praksi. Polja bi trebala biti private, a pristup bi trebao biti indirektan, kroz svojstva, indeksere ili metode.

Polja sadrže podatke koji su potrebni klasi da obavi svoju zadaću. Tako na primjer, klasa koja predstavlja kalendar bi mogla imati tri integer polja: jedan za dan, drugi za mjesec i treći za godinu:

public class CalendarDate
{
    public int month;
    public int day;
    public int year;
}
Pristup pojedinom polju u objektu se ostvaruje imenovanjem objekta, zatim točka, pa ime polja:
CalendarDate birthday = new CalendarDate();
birthday.month = 7;
Polju možemo zadati inicijalnu vrijednost koristeći operator pridruživanja prilikom deklaracije. Ako želimo automatski dodijeliti vrijednost 7 polju za mjesec, to ćemo napravit ovako:
public class CalendarDateWithInitialization
{
    public int month = 7;
    //...
}
Polja se inicijaliziraju prije nego što se pozove konstruktor za objekt, pa tako, ako u konstruktoru zadamo neku drugu vrijednost polju, inicijalna vrijednost će se izgubiti.

Konstante

Klase i strukture mogu deklarirati konstante kao članove. Konstante su vrijednosti koje su poznate pri kompilaciji i ne mijenjaju se. (Ako želite stvoriti konstantnu vrijednost koja će se inicijalizirati u run-time, koristite readonly ključnu riječ). Konstante su deklarirane kao polja, koristeći const ključnu riječ prije tipa polja. Konstante moraju biti inicijalizirane pri deklaraciji:
class Calendar1
{
    public const int months = 12;
}
U ovom primjeru, polje sa mjesecom će uvijek biti 12 i ne može biti promijenjen, čak ni unutar klase. Konstante moraju biti integralnog tipa, pobrojani tip ili referenca na null.

Moguće je i deklarirati više konstanti odjednom:

class Calendar2
{
    const int months = 12, weeks = 52, days = 365;
}
Izraz koji se koristi za inicijalizaciju konstante može sadržavati neku drugu konstantu, sve dok nema kružnih referenci, npr:
class Calendar3
{
    const int months = 12;
    const int weeks = 52;
    const int days = 365;

    const double daysPerWeek = days / weeks;
    const double daysPerMonth = days / months;
}

Ugniježđeni tip

Tip definiran unutar klase ili strukture se zove ugniježđeni tip.
class Container
{
    class Nested
    {
        Nested() { }
    }
}
Unutarnji tip može pristupiti vanjskom tipu ako mu se konstruktoru preda referenca na vanjski tip:
public class Container
{
    public class Nested
    {
        private Container m_parent;

        public Nested()
        {
        }
        public Nested(Container parent)
        {
            m_parent = parent;
        }
    }
}

na vrh

Stringovi Metode

 
Copyright © 2006, Mario Bošnjak