using a névtéren belül vagy kívül?

Többször előjött már a kérdés, hogy miért számíthat, ha a using-ot a névtéren belülre tesszük, mint ahogy azt a StyleCop szereti. Eddig egy-két erőltetett példát leszámítva soha nem találtam magyarázatot, még Microsoftos blogokon vagy az MSDN-en sem. Két nappal ezelőtt azonban egy véletlennek köszönhetően sikerül megvilágosodni.

Hosszú névterek

Az történt, hogy az alábbi képernyőn látható névtér-konvencióhoz hasonló programon dolgoztam:

A hosszú névtereket valaki szeretni, valaki nem, valahol használják, valahol nem, ez most teljesen lényegtelen. A lényeg, hogy a using-ok szerkesztése közben egy Ctrl-c-t félrenyomva Ctrl-x lett, és a jó egérpozíciónak köszönhetően ekkor elémtárult az alábbi képen látható jelenet:

Miért kell csodálkozni? A Visual Studio szépen kiírta a teljes névteret a Validators-hoz, pedig a Company.UsefulProduct.UsefulService.Handlers névtér alatt vagyunk, ami nem foglalja magába az Validators névteret. “Eggyel kiljebb kell menni”, hogy meglegyen a Validators. Annak, hogy a Visual Studio, illetve a C# compiler ilyen ügyes, az a következménye, hogy nem kell teliszemetelni a using blokkot:

Persze ez csak akkor működik, ha a StyleCop által preferált módon használjuk a using-ot. Egyébként a trükk nem működik:

Miért van ez így?

Fully Qualified Name

A működéshez két dolgot kell megérteni. Egyrészt, nézzük meg, mit jelent a “fully qualified name” (C# specifikáció 10.8.2). A precizitás igénye nélkül, egy példán könnyű ezt érzékeltetni:

namespace Company
{
  namespace UsefulProduct
  {
    class UsefulClass
    {
    } // class UsefulClass
  } // namespace UsefulProduct
} // namespace Company

Ebben az esetben a UsefulClass fully qualified neve Company.UsefulProduct.UsefulClass, tehát a típusdeklarációtól kifele haladva, egy pontot a név elé toldva a befoglaló típus vagy névtér nevét hozzá kell venni az eredeti névhez. Az “eredeti név” (vagy a nem teljesen feloldott név) egyébként hivatalosan az “Unqualified name”.

Miért érdekes a Fully Qualified Name?

Mert itt csak egy mechanikus névösszerakós játékról van szó. A névterek a C# által mesterségesen keltett kreálmányok, IL és metaadat szinten csak Fully Qualified Name-ek vannak (és mivel csak ezek vannak, ott mezei módon full name-nek hívják). Annak, hogy egy mechanikus névgeneráló eljárásról van szó, több következménye van.

Az egyik, hogy ugyanazt a nevet több módon is elkészíthetjük:

namespace Company
{
  namespace UsefulProduct
  {
    class UsefulClass
    {
    } // class UsefulClass
  } // namespace UsefulProduct
} // namespace Company

ugyanaz mint:

namespace Company.UsefulProduct
{
  class UsefulClass
  {
  } // class UsefulClass
} // namespace Company.UsefulProduct

De ez cserélgethetőség a mi esetünkben pont fordítva érdekes, a fenti screenshot-okon validátor osztály így látható:

namespace Company.UsefulProduct.UsefulService.Validators
{
    class UsefulValidator
    {
        //...
    } // class UsefulValidator
} // namespace Company.UsefulProduct.UsefulService.Validators  

Ami pedig ezzel egyenértékű:

namespace Company
{
  namespace UsefulProduct
  {
    namespace UsefulService
    {
      namespace Validators
      {
        class UsefulValidator
        {
            //...
        } // class UsefulValidator
      } // namespace Validators
    } // namespace UsefulService
  } // namespace UsefulProduct
} // namespace Company

Látszik tehát, hogy sok névterünk van akkor is, ha azt egy sorba sűrítjük. Másképpen fogalmazva, a C# fordító akkor is kreál egy névteret, ha pöttyöt lát, és akkor is, ha a namespace kulcsszót.

Ugyanakkor a programokban lehetőség van “Unqualified name”-eket használni, amihez viszont a C# specifikáció definiál egy – elég bonyolult – névfeldoldási algoritmust, amivel meghatározza a Fully Qualified Name-et – az előbb látott névtereken keresztül.

A névfeloldás

Az algoritmus a C# specifikáció 10.8 pontjában van leírva két oldal terjedelemben és rekurzív módon. Ennek megfelelően egy órányi nézegetés után sem sikerült teljesen átlátni. A számunkra érdekes részt azonban sikerült kibogozni. Egy konkrét példát nézve ez világos lesz:

namespace Company.UsefulProduct.UsefulService.Handlers
{
    using System;

    using Interfaces;
    using Models;
    using Validators;

...

A “using Interfaces” feloldásánál a fordító először a Company.UsefulProduct.UsefulService.Handlers.Interfaces névtér létezését vizsgálja, ez volt eddig a számomra ismert működés. Ezután viszont megnézni a Company.UsefulProduct.UsefulService.Interfaces létezését. Itt az esetünkben sikeres is lesz, de ha nem lenne, következne a Company.UsefulProduct.Interfaces, majd a Company.Interfaces majd global::Interfaces. A névtereken tehát kifele megy. Ennek volt köszönhető, hogy a screenshot-on látható példa esetében nem zavarta meg a Handlers névtér, egyszerűen kiljebb lépett a sikertelen próba után.

Ennél nagyobb trükköt is tud a fordító, ha esetleg ezt írnánk le:

namespace Company.UsefulProduct.UsefulService.Handlers
{
    using System;

    using UsefulProduct.UsefulService.Interfaces;
    using UsefulProduct.UsefulService.Models;
    using UsefulProduct.UsefulService.Validators;

... 

Ebben az esetben a fordító először a UsefulProduct névtér-névvel játssza el a fenti folyamatot, ami során megtalálja a Company.UsefulProduct névteret a következő sikertelen próbálkozások után:

Company.UsefulProduct.UsefulService.Handlers.UsefulProduct
Company.UsefulProduct.UsefulService.UsefulProduct
Company.UsefulProduct.UsefulProduct

Ezután a UsefulService névteret keresi az előbbi eredményhez (Company.UsefulProduct) csatolva, tehát Company.UsefulProduct.UsefulService-nek kell létezni. Végül pedig feloldja a Company.UsefulProduct.UsefulService.Interfaces névteret, és az ebben definiált típusok elérhetővé válnak.

Konklúzió

Látható, hogy a using-ok elhelyezése a namespace-en belül nem csak hóbort, hanem hasznos lehetőséget rejt magában. Érdekes, hogy számomra ez csak fél évtizedes C# tanulás után, egy valószínűtlen véletlennek köszönhetően derült ki.

  1. Leave a comment

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: