fel
le

Névterek

Egy nagyobb projektben rengeteg objektumosztályt használunk fel. Az objektumosztályok nevei egyedi kell legyen, hiszen egyúttal típusnévként is szerepelnek. Nem könnyű ennyi objektumosztálynak egyedi, de mégis a funkciójára utaló nevet találni.

Azt is figyelembe kell vennünk, hogy maga a Base Class Library is több mint 10.000 objektumosztályt tartalmaz, melyek neveivel szintén nem szabad összeütköznie a saját fejlesztésű osztályainknak.

Még ezen felül is fennáll annak a veszélye, hogy a projekt elkészítése során igénybe veszünk egy külsős cég által fejlesztett (és tesztelt, támogatott) objektumosztály-gyűjteményt, amely valamely részterületet fed le.

Ennyi szereplős project esetén nagyon könnyen előadódhat már névütközés. Arról nem is beszélve, hogy ennyi objektum-osztály nevét megjegyezni sem könnyű, amikor keresünk egy osztályt, és nem emlékszünk pontosan a nevére, sokáig tart esetleg megtalálni azt a név-katalógusban.

Szükség van az objektum-osztályok csoportosítására. Ekkor az egy feladatkört ellátó osztályokat csoportokba tudjuk szervezni, így könnyebben meg tudjuk találni a keresett osztályt. A csoportokat is csoportokba tudjuk szervezni, így hierarchikus szervezést tudunk előidézni.

Például a felhasználói felületet megvalósító objektum-osztályokat tovább tudjuk csoportosítani grafikus, web-es, és mobil eszközökön használható csoportokra. A grafikus csoportot is tovább bonthatjuk akár nyomógombokra, szöveg beviteli elemekre, listákra, stb. Így az egyes csoportok már aránylag kevés osztályt tartalmaznak, megtalálásuk könnyű lesz.

Másrészt a csoportosítás segíti a névütközések elkerülését. Csak arra kell ügyelnünk, hogy a csoportok belsejében ne legyenek azonos nevű objektum-osztályok, különböző csoportok között előfordulhat, hiszen ez esetben a csoport nevével kiegészített osztálynév már különböző!

Névterek létrehozása

Javasolt tehát az objektum-osztályokat csoportosítani. Ennek eszköze a névterek, melyek gyakorlatilag ebben az esetben a csoportokat jelentik:

namespace Grafika
{
  class Szakasz
  {
   ..
  }

  class Kor
  {
   ..
  }

  class Teglalap
  {
   ..
  }
}
 

A névteret a namespace kulcsszóval hozhatjuk létre, és a belsejében objektum-osztályokat helyezhetünk el. Ekkor az objektum-osztályok azonosítója kiegészül a névtér nevével:

Grafika.Szakasz sz = new Grafika.Szakasz();
sz.Kirajzol();
 

A fenti kód a Grafika névtérbe tartozó Szakasz osztályt példányosítja, majd használja.

Névterek egymásba ágyazása

Volt arról szó, hogy szerencsés ötletnek tűnik a csoportok (névterek) egymásba ágyazása, hogy al-csoportokat is ki lehessen alakítani. Ez tetszőleges mélységben elvégezhető:

namespace Grafika
{
  namespace 2DElemek
  {
    class Szakasz
    {
       ..
    }

    class Kor
    {
       ..
    }

    class Teglalap
    {
       ..
    }
 }
}
 

A Szakasz objektumosztálynak a fenti esetben a teljes, minősített neve nem más, mint Grafika.2DElemek.Szakasz.

A fenti példa más formában is megvalósítható:

namespace Grafika.2DElemek
{
  class Szakasz
  {
   ..
  }

    class Kor
    {
       ..
    }

    class Teglalap
    {
       ..
    }
}
 

Ekkor a névtér neve eleve alcsoportot is tartalmaz. A két megvalósítás ekvivalens, vagyis mindkét esetben a Szakasz osztály teljes neve Grafika.2DElemek.Szakasz lesz.

Névterek bővíthetősége

Amikor a forráskódot írjuk, az osztályainkat valamely névtérbe célszerű helyezni. Szerencsés, ha a névtér neve segít az osztályunkat valamely szempont szerinti csoportosításba helyezni. A névteret a namespace formában azonosítjuk, és saját blokk-jelek { ... } közé kell elhelyezni a tartalmát, a benne lévő objektum-osztályokat.

A névteret bármikor "bezárhatjuk", új névteret kezdhetünk, majd a régebbit újra "megnyithatjuk", és folytathatjuk az osztályok elhelyezését benne. A fordítóprogram a névtereink tartalmát a halmazelméleti unió művelethet hasonló módon egyesíti, függetlenül attól, hányszor nyitottuk meg és zártuk be a névterünket:

namespace Grafika
{
    class Szakasz  { .. }
    class Kor   {  .. }
}  

namespace Monitorok
{
    class Felbontas { ... }
    class Szinmelyseg { ... }
}

namespace Grafika
{
    class Teglalap { ... }
    class EllipszisCikkely { ... }
}
 

A fenti példában a Grafika névteret kétszer is megnyitjuk, mivel a közben ideiglenesen a Monitorok névtérbe is készítettünk néhány osztályt.

Tipikus esete a névterek bővítésének, amikor több file-ban (több forráskód) tároljuk a programot. Ekkor minden forráskód maga azonosítja, nyitja meg a saját névterét, és nyilvánvalóan a lezáró blokkjelet is tartalmazza. De több forráskód is foglalkozhat ugyanazzal a névtérrel, ekkor a fordítóprogram a több forráskódból összeolvassa, és felépíti a névtér teljes tartalmát. Ezt könnyen beláthatjuk, ha a forráskódunk valamely pontján, mondjuk a Main() függvényben példányosítani szeretnénk, és leírjuk a szóban forgó névtér nevét, majd a pontot, akkor a felbukkanó listában nem csak az adott forráskódban szereplő adott névtérben deklarált osztályok nevei fognak szerepelni, hanem a többi forráskódban, ezen névtérbe tartozó osztályok nevei is.

A rendszer névtér

A Base Class Library összes osztálya már valamilyen névtér-hierarchiába van szervezve. A BCL-t fejlesztő Microsoft minden osztályát a System, vagy annak valamely al-névterébe helyezett el. Ezen névtér nincs fenntartva a BCL-nek, vagy a Microsoft-nak, mi is fejleszthetünk olyan osztályokat, melyek a System névtérbe kerülnek, de ez nem javasolt. A BCL további verziói ugyanis nyilvánvalóan további osztályokat és al-névtereket tartalmazhatnak majdan, és így előfordulhat később név-ütközés.

Javasolt, hogy a saját osztályainkat ne a System névtérbe fejlesszük, így magával a BCL-el már biztosan elkerültük a névütközést. Javasolt helyette a saját cégünk nevét használni legkülső szintű névtér-névnek, és azon belül dolgozni. Remélhetőleg nem lesz a világon másik ugyanolyan szoftverfejlesztéssel foglalkozó cég, amelyet ugyanúgy hívnak mint bennünket, így egymással is nagy valószínűséggel elkerüljük a névütközéseket:

namespace PotyalesoKft
{
  namespace Web
  {
   class AdatbekeroUrlap { .. }
   class SajatNyomogomb { .. }
  }
  class CegesLogo { .. }
}
 

Alapértelmezett névtér

Mindazonáltal lehetőségünk van valamely objektum-osztályt névtéren kívül is elhelyezni. Egyszerűen nem írunk köré namespace-t, hanem a forráskódban egyenesen egy class-t fejlesztünk. Ezen osztály nem része semmilyen névtérnek, ekkor azt mondhatjuk, hogy az "alapértelmezett névtér" (default namespace), vagy más néven "globális névtér" (global namespace) részét képezi.

Az ilyen esetek erősen kerülendők, mert ha mindenki (vagy ha többen) is az alapértelmezett névtérbe fejlesztenek, akkor a névütközéseket nem lehet kezelni. Ugyanis továbbra is fennáll az, hogy a névterek belsejében nem lehet két azonos nevű osztály (az alapértelmezett névtérben sem), valamint az is, hogy a fordítóprogram a névtereket akár több forráskód-file alapján is, de összegzi. Vagyis ha két forráskódunk van, mindkettőben szerepel az alapértelmezett névtérben mondjuk egy lista nevű osztály, és ezt a két forráskódot egyetlen projektbe helyezzük, akkor a fordítóprogram névütközést fog jelezni.

Ezt a névütközést csak úgy tudjuk feloldalni, ha valamelyik osztályt másképp nevezzük el, vagy névtérbe helyezzük. Ugyanakkor az eddig elkészült kódjainkat, akik valamely listát használták, majdnem biztosan módosítanunk kell, hogy az átnevezést érvényre juttassuk. Ez mindíg rizikós művelet, könnyű benne hibázni! Ezért az alapértelmezett névtérbe való osztályok elhelyezését nem javasoljuk!

// forraskod1.cs
class lista
{
...
}

// forraskod2.cs
class lista
{
...
}

// forraskod3.cs
lista l = new lista(); // ez melyik lista lesz?? hiba!!
 

A C# 2.0 fordítóprogram már hibaüzenettel jelez, ha valamely osztályt lát az alapértelmezett névtérben, kéri, hogy tegyük azt valamely jól definiált névtér belsejébe inkább. Helyesen teszi ezt!

A using használata

Nyilvánvalóan fárasztó a névterek nevét minden esetben begépelni. Nézzük csak az alábbi példákat:

public static void Feltolt(System.Random rnd, System.Collections.ArrayList l)
{
    for(System.Int32 i=0;i<l.Count;i++)
    {
       l.Add( rnd.Next(0,100);
    }
}

public static void Main()
{
    System.Random rnd = new System.Random();
    System.Collections.ArrayList lista = new System.Collections.ArrayList();
    Feltolt(rnd, lista);
}
 

Az objektumosztályok teljes nevét ki kell írni példányosításkor kétszer is, egyrészt a példány típusának deklarálásakor, másrészt a konstruktor megnevezésekor. Eljárások, függvények paraméterezése esetén a formális paraméterlistában is az osztályok teljes (minősített) nevét kell kiírni.

A C# két formában segíti a kód olvashatóságának növelését, és a gépelés felgyorsítását:

  • alapvető típusok nevére alias létrehozásával. Ilyenek a System.Int32 -> int, a System.Boolean -> bool. Ezeket annyira megszoktuk, hogy kevés programozó van azzal tisztában, hogy az int az nem létező típusnév a C#-ban, csak egy aliasneve a System.Int32-nek.
  • a using kulcsszó segítségével.

A using kulcsszót a forráskódok elején kell megadni. A using után egy névtér nevét írhatjuk le. Ekkor ebben a forráskódban ezen névteret mintegy megnyitjuk, használatba vesszük. Ha valamely osztályra hivatkoznánk a using mellett feltüntetett névtérből, akkor nem kell annak nevét kiírni:

using System;

public static void Feltolt(Random rnd, System.Collections.ArrayList l)
{
    for(Int32 i=0;i<l.Count;i++)
    {
       l.Add( rnd.Next(0,100);
    }
}

public static void Main()
{
    Random rnd = new Random();
    System.Collections.ArrayList lista = new System.Collections.ArrayList();
    Feltolt(rnd, lista);
}
 

Vegyük észre, hogy a Random elől eltüntek a System. megnevezések. A fordító amikor a Random azonosítót talája a fenti forráskódban, a using miatt felismeri, hogy az a System.Random azonosítót takarja.

Sajnos, a System.Collections nevét nem írhatjuk Collections formában, mivel ez a mechanizmus csak osztályok nevének leírását könnyíti meg. Ha az ArrayList osztályt is rövidebb formában kívánjuk írni, még egy using-ot kell írnunk a forráskódba:

using System;
using System.Collections;

public static void Feltolt(Random rnd, ArrayList l)
{
    for(Int32 i=0;i<l.Count;i++)
    {
       l.Add( rnd.Next(0,100);
    }
}

public static void Main()
{
    Random rnd = new Random();
    ArrayList lista = new ArrayList();
    Feltolt(rnd, lista);
}
 

Ilyen using-ot elvileg tetszőleges számban írhatunk a forráskódunk elejére. Ezzel csak a lehetőséget kapjuk meg, hogy a felnyitott névterekben lévő osztályok nevét ne kelljen teljes minősített névvel azonosítani. Ettől ezt természetesen továbbra is megtehetjük:

using System;
using System.Collections;

public static void Main()
{
    Random rnd = new Random();
    System.Collections.ArrayList lista = new ArrayList();
    Feltolt(rnd, lista);
}
 

A fenti példában a lista példányosításkor kiírtuk az ArrayList típusnevet annak teljes nevével (névtér-névvel együtt). A jobb oldalt a new után nem azért írhattuk a konstruktor nevét le ilyen röviden, mert előtte a típusnévnél már megadtuk a névtér nevét is: a kettőnek semmi köze nincs egymáshoz. A new ArrayList() azért lehet ilyen rövid, mert a forráskód elején szerepel a using System.Collections!

Problémák a using használata során

A using használata nem teljesen problémamentes. Többször voltam tanúja annak, hogy a programozók új forráskód kezdetekor egy régebbi forráskód elejéről a using blokkot copy-paste módon átmásolják, majd az új forráskód igényei szerint azt további using sorokkal bővítik.

Ezen mód felesleges using-okat fog eredményezni az új forráskódban. Ez bár nem jelentősen, de lassítja a fordítás menetét, illetve a fordítóprogram több memóriát fog lekötni, hiszen a felnyitott névterekben lévő azonosítókat ki kell gyűjtenie egy listába, hogy a forráskódban szereplő osztályneveket hozzá tudja rendelni annak teljes nevéhez, vagyis azonosítani tudja azokat.

Másrészt ha két olyan névteret is felnyitunk, melyeknek a belsejében azonos osztálynevek fordulnak elő, úgy gyakorlatilag azonnali névütközést generálunk. Ilyen névterek például a System.Timers, és a System.Threading névterek. Mindkettőben szerepel ugyanis egy Timer nevű objektum-osztály (más-más céllal és feladattal természetesen). Amennyiben ilyen típusú példányt szeretnénk létrehozni, a fordító jelzi is a hibát:

using System.Timers;
using System.Threading;

public static void Main()
{
    // HIBA: __Timer__ is an ambiguous reference between
    // __System.Timers.Timer__ and __System.Threading.Timer__  
    Timer t = new Timer();
}
 

Ezt a problémát természetesen kezelhetjük a teljes név megadásával:

using System.Timers;
using System.Threading;

public static void Main()
{
    // ez igy rendben van
    System.Timers.Timer t = new System.Timers.Timer();
}
 

Persze a fenti esetben mindkét névtér felnyitása most azt okozta, hogy ebben a forráskódban a Timer típus minden egyes előfordulása esetén ki kell írni annak teljes, minősített nevét a névütközés feloldása miatt. A többi típusnév, amely nem fordul elő több using-olt névtérben is, természetesen problémamentesen használható.

A using használata alias név létrehozására

A using egy érdekes használata alias névtér-nevek létrehozására. Ekkor a using-ot nem névtér-felnyitásra használjuk, helyette egy névtér (általában hosszú, bonyolult) nevéhez egy rövid, egyszerű alias nevet hozunk létre:

using System.Windows.Forms = win;

public static void Main()
{
    win.MessageBox.Show("Hello!");
}
 

Ekkor a System.Windows.Forms névtérben szereplő MessageBox osztály Show osztályszintű metódusát hívtuk meg megfelelő paraméterekkel.

Az alias név csak az adott forráskódban van érvényben. A using ilyen használata a szóban forgó névteret (System.Windows.Forms) nem nyitja fel, tehát a benne szereplő osztályok nevei elé vagy ki kell írni a névtér nevét, vagy az alias nevét.

A névtér tartalma

A névtérbe csak olyan konstrukciókat helyezhetünk el, amelyek a C# nyelvben mint típusok jelentkeznek:

  • osztályokat (class, struct, abstract class, sealed class, ...)
  • felsorolásokat (enum)
  • interface-eket (interface)
  • callback függvények típusleírását (delegate)

A névterek nem tartalmazhatnak direkt módon:

  • eljárásokat, függvényeket (ez az egységbezárás OOP alapelvvel lenne ellentmondásban, mely kimondja, hogy a kódot és az adatokat egységbe, osztályokba kell elhelyezni)
  • konstansokat, mezőket, változókat (hasonlóan, egységbezárás alapelvvel lenne ellentétes)
  • utasításokat, kódot (ezeket ugyanis eljárásokba, függvények belsejébe kell elhelyezni)
Hernyák Zoltán
2013-03-17 18:47:18