Systém typů jazyka C#
Jazyk C# je jazyk silného typu. Každá proměnná a konstanta má typ, stejně jako každý výraz, který se vyhodnotí jako hodnota. Každá deklarace metody určuje název, typ a druh (hodnotu, odkaz nebo výstup) pro každý vstupní parametr a návratovou hodnotu. Knihovna tříd .NET definuje předdefinované číselné typy a komplexní typy, které představují širokou škálu konstruktorů. Patří mezi ně systém souborů, síťová připojení, kolekce a pole objektů a kalendářní data. Typický program jazyka C# používá typy z knihovny tříd a uživatelem definovaných typů, které modelují koncepty specifické pro doménu problému programu.
Informace uložené v typu můžou obsahovat následující položky:
- Prostor úložiště, který vyžaduje proměnná typu.
- Maximální a minimální hodnoty, které může představovat.
- Členové (metody, pole, události atd.), které obsahuje.
- Základní typ, ze něhož dědí.
- Rozhraní, která implementuje.
- Typy operací, které jsou povoleny.
Kompilátor používá informace o typu, aby se zajistilo, že všechny operace prováděné v kódu jsou bezpečné. Pokud například deklarujete proměnnou typu int
, kompilátor umožňuje použít proměnnou při sčítání a odčítání operací. Pokud se pokusíte provést stejné operace s proměnnou typu bool
, kompilátor vygeneruje chybu, jak je znázorněno v následujícím příkladu:
int a = 5;
int b = a + 2; //OK
bool test = true;
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
Poznámka
Vývojáři C a C++, všimněte si, že v jazyce C# bool
není převoditelný na int
.
Kompilátor vloží informace o typu do spustitelného souboru jako metadata. Modul CLR (Common Language Runtime) používá tato metadata za běhu k dalšímu zajištění bezpečnosti typů při přidělení a uvolnění paměti.
Určení typů v deklarací proměnných
Když deklarujete proměnnou nebo konstantu v programu, musíte zadat jeho typ nebo použít var
klíčové slovo, aby kompilátor odvozoval typ. Následující příklad ukazuje některé deklarace proměnných, které používají předdefinované číselné typy i složité uživatelem definované typy:
// Declaration only:
float temperature;
string name;
MyClass myClass;
// Declaration with initializers (four examples):
char firstLetter = 'C';
var limit = 3;
int[] source = { 0, 1, 2, 3, 4, 5 };
var query = from item in source
where item <= limit
select item;
Typy parametrů metody a návratové hodnoty jsou zadány v deklaraci metody. Následující podpis ukazuje metodu, která vyžaduje int
jako vstupní argument a vrátí řetězec:
public string GetName(int ID)
{
if (ID < names.Length)
return names[ID];
else
return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };
Jakmile deklarujete proměnnou, nemůžete ji znovu předepsat novým typem a nemůžete přiřadit hodnotu, která není kompatibilní s deklarovaným typem. Nemůžete například deklarovat int
a pak mu přiřadit logickou hodnotu true
. Hodnoty lze ale převést na jiné typy, například když jsou přiřazené k novým proměnným nebo předány jako argumenty metody. Převod typu, který nezpůsobuje ztrátu dat, provádí kompilátor automaticky. Převod, který může způsobit ztrátu dat, vyžaduje přetypování ve zdrojovém kódu.
Další informace naleznete v tématu Přetypování a převody typů.
Předdefinované typy
Jazyk C# poskytuje standardní sadu předdefinovaných typů. Představují celá čísla, hodnoty s plovoucí desetinnou čárkou, logické výrazy, textové znaky, desetinné hodnoty a další typy dat. K dispozici jsou také předdefinované string
typy a object
typy. Tyto typy jsou k dispozici pro použití v libovolném programu jazyka C#. Úplný seznam předdefinovaných typů najdete v tématu Předdefinované typy.
Vlastní typy
Pomocí příkazu struct
, , class
interface
, enum
a record
konstruktorů můžete vytvořit vlastní typy. Samotná knihovna tříd .NET je kolekce vlastních typů, které můžete použít ve vlastních aplikacích. Ve výchozím nastavení jsou nejčastěji používané typy v knihovně tříd dostupné v libovolném programu jazyka C#. Ostatní budou k dispozici pouze v případech, kdy explicitně přidáte odkaz na projekt do sestavení, které je definuje. Jakmile kompilátor odkazuje na sestavení, můžete deklarovat proměnné (a konstanty) typů deklarovaných v daném sestavení ve zdrojovém kódu. Další informace naleznete v tématu Knihovna tříd .NET.
Běžný systém typů
Je důležité pochopit dva základní body o systému typů v .NET:
- Podporuje princip dědičnosti. Typy mohou být odvozeny od jiných typů, označovaných jako základní typy. Odvozený typ dědí (s určitými omezeními) metody, vlastnosti a další členy základního typu. Základní typ se pak může odvodit z jiného typu, v takovém případě odvozený typ dědí členy obou základních typů v hierarchii dědičnosti. Všechny typy, včetně předdefinovaných číselných typů, jako System.Int32 je (klíčové slovo jazyka C#:
int
), jsou odvozeny z jednoho základního typu, což je System.Object (klíčové slovo C#:object
). Tato sjednocená hierarchie typů se nazývá Common Type System (CTS). Další informace o dědičnosti v jazyce C# naleznete v tématu Dědičnost. - Každý typ v CTS je definován jako typ hodnoty nebo odkazový typ. Tyto typy zahrnují všechny vlastní typy v knihovně tříd .NET a také vlastní typy definované uživatelem. Typy, které definujete pomocí klíčového
struct
slova, jsou typy hodnot; všechny předdefinované číselné typy jsoustructs
. Typy, které definujete pomocí klíčovéhoclass
slova,record
jsou odkazové typy. Odkazové typy a typy hodnot mají různá pravidla kompilace a různé chování za běhu.
Následující obrázek znázorňuje vztah mezi typy hodnot a odkazovými typy v CTS.
Poznámka
Vidíte, že nejčastěji používané typy jsou všechny uspořádané v System oboru názvů. Obor názvů, ve kterém je typ obsažen, však nemá žádný vztah k tomu, zda se jedná o typ hodnoty nebo odkazový typ.
Třídy a struktury jsou dva ze základních konstruktorů systému běžných typů v .NET. C# 9 přidává záznamy, které jsou druhem třídy. Každá z nich je v podstatě datová struktura, která zapouzdřuje sadu dat a chování, které patří dohromady jako logická jednotka. Data a chování jsou členy třídy, struktury nebo záznamu. Členové zahrnují své metody, vlastnosti, události a tak dále, jak je uvedeno dále v tomto článku.
Deklarace třídy, struktury nebo záznamu je jako podrobný plán, který se používá k vytváření instancí nebo objektů za běhu. Pokud definujete třídu, strukturu nebo záznam s názvem Person
, Person
je název typu. Pokud deklarujete a inicializujete proměnnou p
typu Person
, říká se, p
že se jedná o objekt nebo instanci Person
. Lze vytvořit více instancí stejného Person
typu a každá instance může mít různé hodnoty ve vlastnostech a polích.
Třída je typ odkazu. Při vytvoření objektu typu obsahuje proměnná, ke které je objekt přiřazen, pouze odkaz na tuto paměť. Pokud je odkaz na objekt přiřazen k nové proměnné, nová proměnná odkazuje na původní objekt. Změny provedené prostřednictvím jedné proměnné se projeví v druhé proměnné, protože obě odkazují na stejná data.
Struktura je typ hodnoty. Při vytvoření struktury obsahuje proměnná, ke které je struktura přiřazena, vlastní data struktury. Když je struktura přiřazena k nové proměnné, zkopíruje se. Nová proměnná a původní proměnná proto obsahují dvě samostatné kopie stejných dat. Změny provedené v jedné kopii nemají vliv na druhou kopii.
Typy záznamů můžou být buď odkazové typy (record class
) nebo typy hodnot (record struct
).
Obecně platí, že třídy se používají k modelování složitějšího chování. Třídy obvykle ukládají data, která mají být změněna po vytvoření objektu třídy. Struktury jsou nejvhodnější pro malé datové struktury. Struktury obvykle ukládají data, která nemají být upravena po vytvoření struktury. Typy záznamů jsou datové struktury s dalšími syntetizovanými členy kompilátoru. Záznamy obvykle ukládají data, která nemají být změněna po vytvoření objektu.
Typy hodnot
Typy hodnot jsou odvozeny od System.ValueType, které jsou odvozeny od System.Object. Typy odvozené z System.ValueType modulu CLR mají zvláštní chování. Proměnné typu hodnoty přímo obsahují jejich hodnoty. Paměť pro strukturu je přidělena vložená v libovolném kontextu, který je proměnná deklarována. Pro proměnné typu hodnota neexistuje žádná samostatná režie přidělení haldy ani uvolňování paměti. Můžete deklarovat record struct
typy, které jsou typy hodnot, a zahrnout syntetizované členy pro záznamy.
Existují dvě kategorie hodnotových typů: struct
a enum
.
Předdefinované číselné typy jsou struktury a mají pole a metody, ke kterým máte přístup:
// constant field on type byte.
byte b = byte.MaxValue;
Ale deklarujete a přiřadíte jim hodnoty, jako by to byly jednoduché neagregační typy:
byte num = 0xA;
int i = 5;
char c = 'Z';
Typy hodnot jsou zapečetěny. Typ nelze odvodit z žádného typu hodnoty, například System.Int32. Nelze definovat strukturu, která má dědit z jakékoli uživatelem definované třídy nebo struktury, protože struktura může dědit pouze z System.ValueType. Struktura však může implementovat jedno nebo více rozhraní. Typ struktury můžete přetypovat na libovolný typ rozhraní, který implementuje. Toto přetypování způsobí, že operace zabalení struktury uvnitř objektu typu odkazu na spravované haldě. Operace boxování nastanou, když předáte typ hodnoty metodě, která přebírá System.Object nebo jakýkoli typ rozhraní jako vstupní parametr. Další informace najdete v tématu Boxing a Unboxing.
Pomocí klíčového slova struktury vytvoříte vlastní typy hodnot. Struktura se obvykle používá jako kontejner pro malou sadu souvisejících proměnných, jak je znázorněno v následujícím příkladu:
public struct Coords
{
public int x, y;
public Coords(int p1, int p2)
{
x = p1;
y = p2;
}
}
Další informace o strukturách naleznete v tématu Typy struktur. Další informace o typech hodnot naleznete v tématu Typy hodnot.
Druhá kategorie typů hodnot je enum
. Výčt definuje sadu pojmenovaných integrálních konstant. Například výčet v knihovně tříd .NET obsahuje sadu pojmenovaných celých čísel, které určují, System.IO.FileMode jak má být soubor otevřen. Definuje se, jak je znázorněno v následujícím příkladu:
public enum FileMode
{
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
}
Konstanta System.IO.FileMode.Create má hodnotu 2. Název je však mnohem smysluplnější pro lidi, kteří čtou zdrojový kód, a proto je lepší použít výčty místo konstantních literálových čísel. Další informace naleznete v tématu System.IO.FileMode.
Všechny výčty dědí z System.Enum, které dědí z System.ValueType. Všechna pravidla, která se vztahují na struktury, se vztahují také na výčty. Další informace o výčtech najdete v tématu Typy výčtů.
Odkazové typy
Typ, který je definován jako class
, , record
, delegate
pole nebo interface
je reference type
.
Při deklarování proměnné proměnné reference type
obsahuje hodnotu null
, dokud ji nepřiřadíte instanci tohoto typu nebo ji vytvoříte pomocí operátoru new
. Vytvoření a přiřazení třídy je znázorněno v následujícím příkladu:
MyClass myClass = new MyClass();
MyClass myClass2 = myClass;
Nelze interface
vytvořit instanci přímo pomocí operátoru new
. Místo toho vytvořte a přiřaďte instanci třídy, která implementuje rozhraní. Uvažujte následující příklad:
MyClass myClass = new MyClass();
// Declare and assign using an existing value.
IMyInterface myInterface = myClass;
// Or create and assign a value in a single statement.
IMyInterface myInterface2 = new MyClass();
Po vytvoření objektu se paměť přidělí na spravované haldě. Proměnná obsahuje pouze odkaz na umístění objektu. Typy spravované haldy vyžadují režii, když jsou přiděleny a kdy jsou uvolněné. Uvolňování paměti je funkce automatické správy paměti modulu CLR, která provádí relamaci. Uvolňování paměti je ale také vysoce optimalizované a ve většině scénářů nevytvoří problém s výkonem. Další informace o uvolňování paměti naleznete v tématu Automatická správa paměti.
Všechna pole jsou referenční typy, i když jsou jejich prvky typy hodnot. Pole implicitně odvozují z System.Array třídy. Deklarujete a používáte je se zjednodušenou syntaxí poskytovanou jazykem C#, jak je znázorněno v následujícím příkladu:
// Declare and initialize an array of integers.
int[] nums = { 1, 2, 3, 4, 5 };
// Access an instance property of System.Array.
int len = nums.Length;
Referenční typy plně podporují dědičnost. Při vytváření třídy můžete dědit z jakéhokoli jiného rozhraní nebo třídy, která není definována jako zapečetěná. Jiné třídy mohou dědit z vaší třídy a přepsat virtuální metody. Další informace o tom, jak vytvořit vlastní třídy, naleznete v tématu Třídy, struktury a záznamy. Další informace o dědičnosti a virtuálních metodách najdete v tématu Dědičnost.
Typy hodnot literálů
V jazyce C# obdrží hodnoty literálu typ z kompilátoru. Způsob psaní číselného literálu můžete určit tak, že na konec čísla připojíte písmeno. Chcete-li například určit, že hodnota 4.56
by měla být považována za float
, připojte za číslo "f" nebo "F": 4.56f
. Pokud není připojeno žádné písmeno, kompilátor odvozí typ literálu. Další informace o typech, které lze zadat pomocí přípon písmen, naleznete v tématu Celočíselné číselné typy a číselné typy s plovoucí desetinou čárkou.
Vzhledem k tomu, že literály jsou napsané a všechny typy se odvozují z nich System.Object, můžete psát a kompilovat kód, například následující kód:
string s = "The answer is " + 5.ToString();
// Outputs: "The answer is 5"
Console.WriteLine(s);
Type type = 12345.GetType();
// Outputs: "System.Int32"
Console.WriteLine(type);
Obecné typy
Typ lze deklarovat pomocí jednoho nebo více parametrů typu , které slouží jako zástupný symbol pro skutečný typ (konkrétní typ). Kód klienta poskytuje konkrétní typ, když vytvoří instanci typu. Tyto typy se nazývají obecné typy. Například typ System.Collections.Generic.List<T> .NET má jeden parametr typu, který má konvence název T
. Při vytváření instance typu zadáte typ objektů, které seznam bude obsahovat, například string
:
List<string> stringList = new List<string>();
stringList.Add("String example");
// compile time error adding a type other than a string:
stringList.Add(4);
Použití parametru typu umožňuje opakovaně použít stejnou třídu k uložení libovolného typu elementu, aniž by bylo nutné převést každý prvek na objekt. Obecné třídy kolekce se nazývají kolekce silného typu , protože kompilátor zná konkrétní typ prvků kolekce a může vyvolat chybu v době kompilace, pokud se například pokusíte přidat celé číslo k objektu stringList
v předchozím příkladu. Další informace najdete v tématu Obecné typy.
Implicitní typy, anonymní typy a typy hodnot s možnou hodnotou null
Pomocí klíčového var
slova můžete implicitně zadat místní proměnnou (ale ne členy třídy). Proměnná stále přijímá typ v době kompilace, ale typ je poskytován kompilátorem. Další informace najdete v tématu Implicitně zadané místní proměnné.
Je možné, že není vhodné vytvořit pojmenovaný typ pro jednoduché sady souvisejících hodnot, které nemáte v úmyslu ukládat nebo předávat vnější hranice metody. Pro tento účel můžete vytvořit anonymní typy . Další informace najdete v tématu Anonymní typy.
Běžné typy hodnot nemohou mít hodnotu null
. Typy hodnot s možnou hodnotou null ale můžete vytvořit tak ?
, že za tento typ připojíte. Jedná se například o typ, int?
který může mít také hodnotu null
.int
Typy hodnot s možnou hodnotou null jsou instance obecného typu System.Nullable<T>struktury . Typy hodnot s možnou hodnotou null jsou zvlášť užitečné, když předáváte data do databází a z databází, ve kterých mohou být null
číselné hodnoty . Další informace najdete v tématu Typy hodnot s možnou hodnotou Null.
Typ kompilace a typ běhu
Proměnná může mít různé typy času kompilace a běhu. Typ kompilátoru je deklarovaný nebo odvozený typ proměnné ve zdrojovém kódu. Typ běhu je typ instance odkazované danou proměnnou. Tyto dva typy jsou často stejné, jako v následujícím příkladu:
string message = "This is a string of characters";
V jiných případech se typ kompilace liší, jak je znázorněno v následujících dvou příkladech:
object anotherMessage = "This is another string of characters";
IEnumerable<char> someCharacters = "abcdefghijklmnopqrstuvwxyz";
V obou předchozích příkladech je typ běhu znakem string
. Typ kompilace je object
na prvním řádku a IEnumerable<char>
ve druhém.
Pokud jsou tyto dva typy pro proměnnou odlišné, je důležité pochopit, kdy se používá typ kompilátoru a typ běhu. Typ kompilace určuje všechny akce provedené kompilátorem. Tyto akce kompilátoru zahrnují překlad volání metody, překlad přetížení a dostupné implicitní a explicitní přetypování. Typ běhu určuje všechny akce, které jsou vyřešeny za běhu. Mezi tyto akce za běhu patří odesílání volání virtuálních metod, vyhodnocování is
a switch
výrazů a dalších rozhraní API pro testování typů. Abyste lépe pochopili, jak kód komunikuje s typy, rozpoznáte, která akce se vztahuje na jaký typ.
Související oddíly
Další informace najdete v následujících článcích:
specifikace jazyka C#
Další informace najdete v tématu Specifikace jazyka C#. Specifikace jazyka je úplným a rozhodujícím zdrojem pro syntaxi a použití jazyka C#.