3. Třídy

Implementace tříd a jejich členů v jazyce C# se příliš neliší od implementace v jazyce Java. Třída může obsahovat tyto členy:

  • položky (field) – členské proměnné, udržují stav objektu;

  • metody – jde o funkce, které implementují služby objektem poskytované. Každá metoda má návratovou hodnotu, pokud nic nevrací, je označena klíčovým slovem void. V jazyce C# lze přetěžovat metody. Přetížené metody se musí lišit v počtu parametrů, v typu parametrů nebo v obojím. Výjimečná metoda je statická metoda s názvem Main. Právě pomocí této metody je projekt spouštěn. Je-li v projektu definováno více metod Main, je nutné při kompilaci zadat jako parametr jméno jedné třídy z těchto tříd. Metoda Main této třídy je pak spuštěna. Každá třída může obsahovat maximálně jednu metodu Main;

  • vlastnost (property) – je také označována za chytrou položku. Navenek vypadají jako položky, ale umí kontrolovat přístup k jednotlivým datům;

  • indexer – u některých tříd je výhodné definovat operátor []. Indexer je speciální metoda, která umožňuje aby se daný objekt choval jako pole;

  • operátory – v jazyce C# máme možnost definovat množinu operátorů sloužících pro manipulaci s jejími objekty;

  • událost (event) – jejím účelem je upozorňovat na změny, které nastaly např. v položkách tříd.

U jednotlivých členů třídy můžeme použít modifikátory přístupu. Modifikátor je nutné aplikovat na každého člena zvlášť. Jeho uvedení není povinné, implicitní hodnota je private. Možné modifikátory jsou:

  • public – člen označený tímto modifikátorem je dostupné bez omezení;

  • private – člen je přístupny pouze členům stejné třídy;

  • protected – přistupovat k takovému členu můžeme uvnitř vlastní třídy a ve všech třídách, pro které je třída základem;

  • internal – člen je přístupný všem v rámci jedné assembly.

Dalším možným modifikátorem je modifikátor static, pomocí něj lze deklarovat, že je daný člen třídy statický. Uvnitř nestatických metod třídy můžeme použít klíčové slovo this, to reprezentuje referenci objektu na sebe sama.

[ukázka kódu]
class Point
{
  short x; //implicitně private
  private short y; //explicitně jsme uvedli, že jde o privátní položku

  public short GetX()
  {
    return x;
  }
  public short GetY()
  {
    return y;
  }
}

3.1. Metody a parametry

Parametry jsou obvykle předávány do metody hodnotou. Funkce získá kopii skutečných parametrů a jakékoliv modifikace uvnitř těla metody se nepromítnou zpět. V jazyce C# máme dvě řešení.

  • Předání parametru odkazem. Metoda si nevytváří vlastní kopii, nýbrž přímo modifikuje proměnnou, která ji byla předána. Takovéto parametry označíme klíčovým slovem ref. Předávané parametry musí být inicializovány.

  • Definovat parametry jako výstupní. Takové parametry označíme klíčovým slovem out. Parametry pak přenášejí své hodnoty směrem ven z metody. Hlavním rozdílem oproti předávání parametrů odkazem je, že předané proměnné nemusí být inicializovány před voláním metody. Uvnitř těla funkce je brán parametr jako neinicalizovaný.

[ukázka kódu]
class Test
{
  public void Swap(ref int a, ref int b){
    int tmp;
    tmp=a;
    a=b;
    b=tmp;
  }

  public void GetXY(Point somePoint,out int x, out int y){
    x=somePoint.GetX();
    y=somePoint.GetY();
  }
  public static void Main() 
  {
    int a=5,b=3;
    Swap(ref a,ref b);
    GetXY(new Point(1,2),out a,out b);
  }
}

3.2. Metoda s proměnným počtem parametrů

Chceme-li definovat metodu s proměnným počtem parametrů, využijeme klíčové slovo params. To musíme uvést před posledním parametrem. Typ tohoto parametru musí být pole.

[ukázka kódu]
public void someMethod(params object[] p); //deklarace metody s proměnným počtem parametrů

3.3. Konstanty

Chceme-li realizovat konstanty v jazyce C#, máme dvě možnosti. Definovat konstantu pomocí klíčového slova const . Žádná metoda pak nesmí takovou to hodnotu modifikovat. Jediné vhodné místo pro její inicializaci je definice. Druhou možností je implementovat položku jako read-only. Takovou to položku definujeme pomocí klíčového slova readonly. Rozdíl proti konstantě je, že takovouto položku můžeme inicializovat v konstruktoru. Konstanty jsou překladačem jazyka C# chápány jako statické položky.

3.4. Konstruktory a destruktory

Konstruktor nevrací žádnou hodnotu (ani void). Každá třída má definovaný implicitně konstruktor bez parametrů a s prázdným tělem. Každý objekt by pak měl být vytvořen pomocí operátoru new. Jiným typem konstruktoru je tzv. statický konstruktor. Pomocí něj lze inicializovat statické položky. Jiné položky pomocí tohoto typu konstruktoru inicializovat nelze. Tento konstruktor nesmí mít žádné parametry a je vyvolán ještě před vytvořením prvního objektu. Pro statické konstruktory dále platí, že jsou spouštěny v náhodném pořadí. Nelze se tedy v jednom spoléhat na druhý.

[ukázka kódu]
class Point
{
  static short dimension;
  short x=0
  private short y=0;
  
  public Point()
  {
  }
  public Point(short nx, short ny)
  {
    x=nx;
    y=ny;
  }
  static Point()
  { //statický konstruktor
    dimension=2;
  }
  static void Main()
  {
    Point a = new Point(); 
    Point b = new Point(1,2);
  }
}

Chceme-li zavolat konstruktor stejné třídy, můžeme to udělat pomocí klíčového slova this. Syntaxi demonstruje následující příklad.

[ukázka kódu]
public Point(Point p):this (p.GetX(), p.GetY())
{
}

Destruktor má název začínající tildou (~) následovaný jménem třídy. Od konstruktoru se liší tím, že nesmí mít žádné formální parametry a nemůže být přetížen. Pokud to není nutné, nemusí být definován.

3.5. Vlastnosti

Vlastnosti se navenek chovají jako veřejné položky třídy, avšak interně jsou tvořeny množinou přístupových metod, které realizují zápis a čtení vlastností.

[ukázka kódu]
class Point
{
  private short x, y;
  public short X
  {
    get
    {
      return x;
    }
    set
    {
      x = value;
    }
  }
  public short Y
  {
    get
    {
      return y;
    }
    set
    {
      y = value;
    }
  }
  public static void Main()
  {
    Point p = new Point();
    p.X = 1;
    p.Y = 2;
  }
}

Metody set a get jsou spouštěny při operaci čtení a zápisu do vlastností. Vynecháním jedné z metod dostaneme read only respektive write only vlastnost. Předávaná hodnota do metody set je v jejím těle reprezentována klíčovým slovem value. V představeném příkladě jsou vlastnosti interně realizovány pomocí privátních položek. To ale nemusí být pravidlem. Vlastnosti mohou například zapisovat a číst přímo z nějakého portu, nebo realizovat komunikaci prostřednictvím sítě.

3.5.1. Odlišná přístupová práva pro get a set

Vlastnosti (properties) musely mít ve verzi jazyka 1.0 stejnou úroveň přístupových práv pro akcesor get i set. Když jsme tedy chtěli, aby byla vlastnost z vnějšku jen pro čtení, nesměla mít operaci set definovanou vůbec. Od verze 2.0 je možno přiřadit každému akcesoru jinou úroveň oprávnění.

[ukázka kódu]
public string Name {
   get {
      return name;
   }
   protected set {
      name = value;
   }
}

V ukázkovém kódu deklarujeme vlastnost jako veřejnou public), potom však uvedením modifikátoru private zamezujeme veřejný přístup k akcesoru set.

Při deklaraci odlišných přístupových práv pro get/set musíme dodržet některá omezující pravidla. Vnitřní změna práv může být uvedena jen u jedné z operací get/set a musí být vždy více restriktivní (v praxi tedy bude většinou využita pro set). Při předefinování vlastnosti předka (pomocí override) musíme vlastnost deklarovat vždy s identickými oprávněními.

Narozdíl od C++, implementace rozhraní se v C# nepovažuje za dědění. Pokud je vlastnost součástí rozhraní, které implementujeme, můžeme u akcesoru, který není součástí implementovaného rozhraní, modifikátor oprávnění použít. Viz následující příklad.

[ukázka kódu]
public interface MyInterface {
   int MyProperty {
      get;
   }
}

public class MyClass : MyInterface {
   public int MyProperty {
      get {...}
      protected set {...}
   }
}

V ukázce: Akcesor set nepatří do rozhraní MyInterface, ve třídě MyClass tedy může být deklarován (i na vlastnosti MyProperty, která sama o sobě je součástí rozhraní MyInterface) s omezením přístupu na private.

3.6. Indexer

Přidáním této členské položky třídě lze pracovat s třídou jako s polem a tedy indexovat ji. Obecná syntaxe indexeru používá klíčové slovo this s hranatými závorkami. Rozšiřme předcházející příklad o definici třídy reprezentující pole bodů. Tato třída bude obsahovat indexer. Pole bude mít omezený rozsah. Pokud se pokusíme vložit prvek mimo tento rozsah nestane se nic. Pokud čteme bod mimo tento rozsah vrátí objekt bod se souřadnicemi 0,0.

[ukázka kódu]
class Point 
{
  public int x,y;
  public Point(int x,int y)
  {
    this.x=x;this.y=y;
  }
}
class SetOfPoints
{
  Point[] points;
  public SetOfPoints(int size)
  {
    points=new Point[size];
  }
  public Point this[int index]
  {
    set
    {
      if (index<points.Length) points[index]=value;
    }
    get
    {
      if (index<=points.Length) return points[index];
      else return new Point(0,0);
    }
  }
}
class RunApp
{
  public static void Main()
  {
    SetOfPoints setOfPoints=new SetOfPoints(2);
    setOfPoints[3]=new Point(1,1); //pokud by indexer nefungoval, skončí výjimkou 
                                   //IndexOutOfRangeException
    Point p=setOfPoints[0]; //bude null...
  }
}

3.7. Dědičnost a polymorfismus

Jazyk C# definuje pouze jednoduchou dědičnost. Dědičnost v definici třídy vyjádříme dvojtečkou uvedenou za jménem třídy a názvem základní třídy. Pro metody odvozené třídy pak platí:

  • chceme-li předefinovat veřejnou metodu třídy kterou dědíme, musíme použít klíčové slovo new. V tomto případě záleží na typu reference, jaká metoda se zavolá;

  • chceme-li realizovat polymorfismus, použijeme virtuální metody. Postup je následující: metodu základní třídy označíme jako virtuální (klíčové slovo virtual) a metodu v odvozené třídě označíme klíčovým slovem override (má se chovat polymorfně).

[ukázka kódu]
class A
{
  public void SomeMethod()
  {
  }
  public virtual void AnotherMethod()
  {
  }
}
class B : A
{
  public new void SomeMethod()
  { 
    //původní metoda je překryta
  }
  public override void AnotherMethod()
  { 
  }
}
class Run
{
  static void Main()
  {
    A a=new B();
    a.SomeMethod(); //zpustí původní metodu třídy A
    a.AnotherMethod(); //zpustí metodu třídy B
  }
}

3.8. Konstruktory v odvozených třídách

Chceme-li volat konstruktor základní třídy v odvozené třídě, můžeme k tomu využít klíčové slovo base. Syntaxi demonstruje následující příklad:

[ukázka kódu]
public SomeName(…):base(…){ … }

3.9. Další modifikátory

  • Modifikátor sealed - označíme-li tímto modifikátorem třídu, nelze ji již dále rozšiřovat. Případný řetězec dědičností touto třídou končí. Aplikujeme-li tento modifikátor na metodu nebo vlastnost, určíme že takovýto člen třídy nemůže být předefinován. Metoda, kterou chceme označit jako sealed misí být virtuální a musí být předefinovaná! Modifikátor sealed v takovém případě musí být kombinován s modifikátorem override.

  • Modifikátor abstrakt - tento modifikátor lze opět aplikovat na metody a třídy. Třída označená tímto modifikátorem je implicitně virtuální. Abstraktní třídy a třídy s abstraktními členy nelze instanciovat. Na abstraktní metody nelze aplikovat modifikátory sealed a private.

3.10. Statická třída

Statické třídy jsou třídy, u nichž neexistují členské metody ani členská data. Doplňují existující návrhový vzor Sealed class rozšířením o privátní konstruktor a statické členy. Statické třídy musí být odvozeny od třídy object.

Pokud třídu deklarujeme klíčovým slovem static, překladač si vynutí dodržení pravidel:

  • zabrání založení instancí a dědičnosti

  • zabrání použití členských metod a dat

3.11. Cvičení

[k procvičení]

Napište program, který vytvoří strukturu Point. Ta bude mít dvě veřejné proměnné x, y typu int reprezentující souřadnice bodu, konstruktor Point, který naplní tyto proměnné a metodu int MeasureDistance(int x2, int y2), která vrátí vzdálenost bodu struktury od bodu zadaného v argumentu funkce souřadnicemi x2, y2.

[řešení]

[k procvičení]

Předchozí program přepište tak, že vytvoříte jmenný prostor Graphics a v něm místo struktury vytvoříte třídu Point. Tato třída bude implementovat rozhraní IDistance obsahující metodu double MeasureDistance(Point p) pro zjištění vzdálenosti aktuálního bodu od bodu předaného jako argument funkce MeasureDistance().

[řešení]