Személyes eszközök
Keresés

 

A InfoWiki wikiből



Tartalomjegyzék

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 <név>' 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
A lap eredeti címe: „http://wiki.ektf.hu/wiki/Mp3/ea15
Nézetek
nincs sb_18.227.161.132 cikk