Diversity w polskim IT
Morgan Kenyon
Morgan KenyonSoftware Engineer @ Paycom

Przestrzenie nazw i aliasy w C#

Poznaj koncepty przestrzeni nazw i aliasowania typów w C# oraz sprawdź, jak z nich korzystać.
10.01.20206 min
Przestrzenie nazw i aliasy w C#

C# odwołuje się do wielu podstawowych typów dwojako. Np. String lub string, Boolean lub bool, Int32 lub int, oto cała lista. Czym one się różnią? Dlaczego C# zapewnia dwa sposoby odwoływania się do tej samej rzeczy? 

Ci od Javy od razu pomyślą, że jedno to typ prymitywny, a drugie to obiekt. Sam tak myślałem przez długi czas, ale nie o to tutaj chodzi. W rzeczywistości string nie jest nawet typem. To tylko alias dla System.String.

Ilekroć użyjesz "string", będzie to równoznaczne z wpisaniem System.String. To samo dotyczy wszystkich innych typów, które są aliasami.

Jakie zatem korzyści programista czerpie z aliasów? Dlaczego warto z nich korzystać? Jakie problemy mogą się pojawić, jeśli ich nie użyjesz?

Aby odpowiedzieć na te pytania, rzućmy okiem na kilka przykładów i omówmy przestrzenie nazw C#.

Przestrzenie nazw na przykładach

using System;

namespace SolarSystem
{
    public class Program
    {
        static void Main(string[] args)
        {
            string mission = "Blast Off!";
            Console.WriteLine(mission);
        }    
    }
}


Pierwszy przykład jest bardzo prosty. Wszyscy używamy łańcuchów znaków w ten sposób, wszystko się kompiluje, a łańcuch jest drukowany na konsoli. Nic prostszego.

using System;

namespace SolarSystem
{
    public class Program
    {
        static void Main(string[] args)
        {
            String mission = "To the moon!";
            Console.WriteLine(mission);
        }    
    }
}


Zmieniliśmy string na String, ale program działa dokładnie tak samo jak w przykładzie pierwszym.

using System;

namespace SolarSystem
{
    public class Program
    {
        static void Main(string[] args)
        {
            //Error: Cannot implicitly convert type 'string' to 'SolarSystem.String'
            String mission = "Get some rocks!";  
            Console.WriteLine(mission);
        }    
    }
    //new class
    public class String { }
}


Tutaj robi się ciekawie. Zdefiniowałem nową klasę o nazwie String w przestrzeni nazw SolarSystem i mój program nie kompiluje się. Wydaje się, że C# jest zdezorientowany wprowadzeniem nowego String.

using System;

namespace SolarSystem
{
    public class Program
    {
        static void Main(string[] args)
        {
            System.String mission = "Don't get lost";
            Console.WriteLine(mission);
        }
    }   
    public class String { }
}


Podałem pełną przestrzeń nazw dla prawdziwej klasy String i działa ona ponownie. Tak więc zamieszanie z SolarSystem.String musiało być powodem, dla którego przykład trzeci nie zadziałał.

using System;

namespace SolarSystem
{
    public class Program
    {
        static void Main(string[] args)
        {
            string mission = "Take some pictures";
            Console.WriteLine(mission);
        }    
    }
    public class String { }
}


Cofamy się z powrotem do string i znów działa. Ten alias sprawia, że C# nie pomyli go z klasą SolarSystem.String.

namespace SolarSystem
{
    //new namespace
    namespace Earth
    {
        //new import
        using System;
        public class Program
        {
            static void Main(string[] args)
            {
                //no error
                String mission = "Land safely!";
                Console.WriteLine(mission);
            }
        }
    }
    public class String { }
}


Dokonaliśmy tutaj wielu zmian. Dodaliśmy nową przestrzeń nazw, przenieśliśmy instrukcję using do naszej nowej przestrzeni nazw i usunęliśmy System z odwołania String. Teraz znowu działa! Mimo że klasa SolarSystem.String nadal istnieje, przeniesienie instrukcji using zmieniło sposób, w jaki C# wybrał klasę String, której będzie używać.

Powód, dla którego to działa, sprowadza się do sposobu, w jaki C# rozwiązuje nazwy klas, które omówimy po następnym przykładzie.

using System;
using RealString = System.String;

namespace SolarSystem
{
    public class Program
    {
        static void Main(string[] args)
        {
            RealString mission = "Welcome back!";
            Console.WriteLine(mission);
        }
    }
    public class String { }
}


Tutaj używamy tak zwanej dyrektywy "using" dotyczącej aliasów. Ponieważ mamy dwie klasy o nazwie String, tworzymy lokalny alias, aby uniknąć dwuznaczności między nimi.

Przyznam, że napisanie RealString zamiast SystemString nie oszczędza aż tak dużo czasu. Wydaje się to bardziej pomocne podczas odwoływania się do klas o długich przestrzeniach nazw. Jest to jednak kolejny sposób na uniknięcie niejednoznaczności nazw klas.

Podsumowując, nieużywanie aliasów może spowodować zepsucie się programu, zwłaszcza jeśli ktoś inny chce stworzyć nowy typ String lub Boolean.

Wprawdzie większość ludzi tego nie zrobi, ale problemy mogą się pojawić, jeśli zaczną używać String. Z tego powodu sugerowałbym używanie aliasów, aby uniknąć nieporozumień.

Zrozumienie przestrzeni nazw pomoże programistom uniknąć niektórych z tych problemów. Aby zrozumieć, co dzieje się w przykładach szóstym i siódmym, omówmy przestrzenie nazw C# oraz sposób, w jaki C# rozwiązuje odwołania do klas.

Przestrzenie nazw w C# 

Wszystko w C# istnieje w przestrzeni nazw, nawet jeśli jest to globalna przestrzeń nazw. Pomagają one organizować projekty, a ich nazwy zazwyczaj opisują rodzaj zawartego w nich kodu.

Jak myślisz, co znajduje się w przestrzeni nazw "System.Collections.Generic"? Kolekcje ogólne. A co znajduje się w przestrzeniach nazw "System.IO"? Kod pomagający radzić sobie z wejściem/wyjściem.

C# musi być w stanie zlokalizować klasy, aby skompilować i uruchomić Twój program. Możesz podać pełną przestrzeń nazw dla swojej klasy lub pozwolić, aby C # sam ją znalazł.

//Full namespace, no ambiguity
System.String example1 = "Test";

//C# must search to find concrete class
String example2 = "Test 2";


Tak stało się w przykładzie trzecim. Nie określiliśmy, w której przestrzeni nazw znajduje się nasz obiekt String, więc C# sam poszukał i znalazł ten, który popsuł program. 

Gdybyśmy zawsze chcieli unikać konfliktów na przestrzeni nazw, każde odwołanie do klasy zawierałoby całą hierarchię przestrzeni nazw, w której klasa jest zdefiniowana. W ten sposób naprawiliśmy nasz problem w przykładzie czwartym. Zawarliśmy pełną nazwę przestrzeni nazw w pierwotnej klasie String, ale pełne przestrzenie nazw są nieporęczne do pisania, więc są używane tylko wtedy, gdy Twój program się popsuje. W jaki zatem sposób C# próbuje znaleźć klasę, gdy przestrzenie nazw nie są używane?

Szukanie klas

C# stosuje prosty wzorzec wyszukiwania do zlokalizowania klasy.

  1. Przeszukuje bieżącą i wszystkie zaimportowane przestrzenie nazw dla danej klasy.
  2. Jeśli znajdzie pasującą nazwę klasy, to zostanie przy niej.
  3. Przejedzie do nadrzędnej przestrzeni nazw i zacznie od nowa w punkcie pierwszym.


Powtarza te kroki do czasu, gdy znajdzie pasującą klasę lub gdy zabraknie przestrzeni nazw.

Wiedząc o tym, powróćmy do niektórych z naszych poprzednich przykładów. Objaśnimy, w jaki sposób C # decyduje się rozwiązywać przestrzenie nazw dla klas.

Odświeżony przykład drugi

using System;

namespace SolarSystem
{
    public class Program
    {
        static void Main(string[] args)
        {
            String mission = "To the moon!";
            Console.WriteLine(mission);
        }    
    }
}


Kompilator musi zlokalizować miejsce zdefiniowania klasy String. Przegląda on przestrzeń nazw SolarSystem i niczego tam nie znajduje. Przechodzi do globalnej przestrzeni nazw, a przestrzeń nazw System, w której jest zdefiniowana klasa String, została zaimportowana. Kompilator wybiera właśnie tę klasę i wszystko działa poprawnie.

Odświeżony przykład trzeci

using System;

namespace SolarSystem
{
    public class Program
    {
        static void Main(string[] args)
        {
            //Error: Cannot implicitly convert type 'string' to 'SolarSystem.String'
            String mission = "Get some rocks!";  
            Console.WriteLine(mission);
        }    
    }
    //new class
    public class String { }
}


Kompilator musi zlokalizować klasę String. Przegląda przestrzeń nazw SolarSystem, znajduje klasę String i korzysta z niej. Niestety, ta klasa nie jest prawdziwym Stringiem, dlatego zgłaszany jest wyjątek.

Znalazł on SolarSystem.String, zanim dotarł do globalnej przestrzeni nazw, do której importowany jest System.String. Kompilator zatrzymuje się, gdy tylko coś znajdzie, nawet jeśli to nie jest to, czego szukaliśmy.

Odświeżony przykład czwarty

using System;

namespace SolarSystem
{
    public class Program
    {
        static void Main(string[] args)
        {
            System.String mission = "Don't get lost";
            Console.WriteLine(mission);
        }
    }   
    public class String { }
}


Tutaj dajemy pełną przestrzeń nazw dla klasy String, więc C# nie musi szukać klasy. Wszystko zatem działa zgodnie z naszymi oczekiwaniami.

Odświeżony przykład szósty

namespace SolarSystem
{
    //new namespace
    namespace Earth
    {
        //new import
        using System;
        public class Program
        {
            static void Main(string[] args)
            {
                //no error
                String mission = "Land safely!";
                Console.WriteLine(mission);
            }
        }
    }
    public class String { }
}


Kompilator musi zlokalizować klasę String. Przegląda on przestrzeń nazw SolarSystem.Earth, ale nie definiuje tam żadnej klasy String. Przestrzeń nazw System została jednak zaimportowana i to w niej znajduje się nasz String. Wszystko działa!

Podobnie jak w przykładzie czwartym, C# zatrzymuje się, gdy tylko znajdzie pasującą nazwę klasy. Ponieważ jednak zmieniliśmy miejsce użycia using, pozwoliliśmy C# zatrzymać wyszukiwanie przed znalezieniem SolarSystem.String.

Podsumowanie

Aliasowanie typów i przestrzenie nazw są czymś, czego używam na co dzień. Teraz i Wy znacie je lepiej i możecie popisywać się swoją wiedzą przed znajomymi. 

<p>Loading...</p>