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.
- Przeszukuje bieżącą i wszystkie zaimportowane przestrzenie nazw dla danej klasy.
- Jeśli znajdzie pasującą nazwę klasy, to zostanie przy niej.
- 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.