Systém typů jazyka C#

C# je jazyk silného typu. Každá proměnná a konstanta má typ, stejně jako každý výraz, který je vyhodnocen na hodnotu. Každá deklarace metody určuje název, počet parametrů a typ a druh (hodnota, odkaz nebo výstup) pro každý vstupní parametr a pro návratovou hodnotu. Knihovna tříd .NET definuje sadu předdefinovaných číselných typů a složitějších typů, které reprezentují širokou škálu logických konstrukcí, jako je například systém souborů, síťová připojení, kolekce a pole objektů a data. Typický program v jazyce C# používá typy z knihovny tříd a uživatelsky definovaných typů, které modelují koncepty specifické pro problémovou doménu programu.

Informace uložené v typu můžou zahrnovat následující položky:

  • Prostor úložiště, který proměnná typu vyžaduje.
  • Maximální a minimální hodnoty, které může představovat.
  • Členové (metody, pole, události atd.), které obsahuje.
  • Základní typ, ze kterého dědí.
  • Rozhraní, které implementuje.
  • Druhy operací, které jsou povoleny.

Kompilátor používá informace o typu k zajištění, že všechny operace, které jsou provedeny ve vašem kódu, jsou typově bezpečné. Například pokud deklarujete proměnnou typu int , kompilátor vám umožní použít proměnnou v operaci sčítání a odčítání. 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 jazyka C a C++ si všimněte, že v jazyce C# bool nelze převést na int .

Kompilátor vloží informace o typu do spustitelného souboru jako metadata. Modul CLR (Common Language Runtime) tato metadata používá za běhu k další zaručení bezpečnosti typů při přidělování a opětovném získání paměti.

Určení typů v deklaracích proměnných

Když deklarujete proměnnou nebo konstantu v programu, musíte buď zadat její typ, nebo použít klíčové slovo , aby kompilátor var odvodit typ. Následující příklad ukazuje některé deklarace proměnných, které používají jak předdefinované číselné typy, tak komplexní typy definované uživatelem:

// 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ých hodnot jsou určeny v deklaraci metody. Následující podpis ukazuje metodu, která vyžaduje jako int vstupní argument a vrací řetězec:

public string GetName(int ID)
{
    if (ID < names.Length)
        return names[ID];
    else
        return String.Empty;
}
private string[] names = { "Spencer", "Sally", "Doug" };

Po deklaraci proměnné ji nelze znovu deklarovat s novým typem a nelze přiřadit hodnotu, která není kompatibilní s jejím deklarovaným typem. Nemůžete například deklarovat a pak int mu přiřadit logickou hodnotu true . Hodnoty lze však převést na jiné typy, například když jsou přiřazeny k novým proměnným nebo předány jako argumenty metody. Převod typu, který nezpůsobí 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 najdete v tématu Přetypování a převody typů.

Předdefinované typy

Jazyk C# poskytuje standardní sadu předdefinovaných typů, které představují celá čísla, hodnoty s plovoucí desetinnou čárkou, logické výrazy, textové znaky, desetinné hodnoty a další typy dat. K dispozici jsou také integrované string typy object a . Tyto typy můžete použít v jakémkoli programu v jazyce C#. Úplný seznam předdefinových typů najdete v tématu Předdefinové typy.

Vlastní typy

Pomocí struct konstruktorů , class , , a můžete vytvořit vlastní interface enum record typy. Samotná knihovna tříd .NET je kolekce vlastních typů, které můžete použít ve svých 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řípadě, že explicitně přidáte odkaz na projekt do sestavení, ve kterém jsou definovány. Poté, co má kompilátor odkaz na sestavení, můžete deklarovat proměnné (a konstanty) typů deklarovaných v tomto sestavení ve zdrojovém kódu. Další informace naleznete v tématu Knihovna tříd .NET.

Obecný systém typů

Je důležité porozumět dvěma základním bodům o systému typů v .NET:

  • Podporuje princip dědičnosti. Typy mohou být odvozeny od jiných typů, které se nazývají 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 může být odvozen z jiného typu. v takovém případě odvozený typ dědí členy obou základních typů v rámci své Hierarchie dědičnosti. Všechny typy, včetně předdefinovaných číselných typů System.Int32 (klíčové slovo c#: int ), jsou odvozeny nakonec z jednoho základního typu, který je System.Object (klíčové slovo c#: object ). Tato hierarchie sjednoceného typu se nazývá CTS ( Common Type System ). Další informace o dědičnosti v jazyce C# naleznete v tématu Dědičnost.
  • Každý typ v CTS je definován buď jako typ hodnoty , nebo jako typ odkazu. Tyto typy zahrnují všechny vlastní typy v knihovně tříd .NET a také vlastní uživatelsky definované typy. Typy, které definujete pomocí struct klíčového slova, jsou typy hodnot; všechny předdefinované číselné typy structs . Typy, které definujete pomocí class record klíčového slova nebo, jsou odkazové typy. Typy odkazů a typy hodnot mají odlišná pravidla kompilace a jiné chování za běhu.

Následující ilustrace znázorňuje vztah mezi typy hodnot a typy odkazů v CTS.

Snímek obrazovky zobrazující typy hodnot CTS a odkazové typy

Poznámka

Můžete vidět, že nejčastěji používané typy jsou v System oboru názvů uspořádány. 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 základní konstrukce společného systému typů v rozhraní .NET. C# 9 přidá 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ří k sobě jako logická jednotka. Data a chování jsou členy třídy, struktury nebo záznamu a zahrnují její metody, vlastnosti, události atd., jak je uvedeno dále v tomto článku.

Deklarace třídy, struktury nebo záznamu se je podobná podrobného plánu, 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 p proměnnou typu Person , je objekt nebo instance p Person . Lze vytvořit více instancí stejného typu a každá instance může mít ve svých vlastnostech a Person polích různé hodnoty.

Třída nebo záznam je odkazový typ. Když je objekt typu vytvořen, proměnná, ke které je objekt přiřazen, obsahuje pouze odkaz na paměť. Když 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 proměnná, ke které je struktura přiřazena, obsahuje skutečná 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 neovlivní druhou kopii.

Obecně se třídy používají k modelování složitějšího chování nebo dat, která mají být upravena po vytvoření objektu třídy. Struktury jsou vhodné pro malé datové struktury, které obsahují primárně data, která se po vytvoření struktury nemá upravovat. Typy záznamů jsou pro větší datové struktury, které obsahují hlavně data, která nejsou určena pro úpravu po vytvoření objektu.

Typy hodnot

Typy hodnot jsou odvozeny z System.ValueType , která je odvozena z System.Object . Typy, které jsou odvozeny z System.ValueType mají zvláštní chování v modulu CLR. Proměnné typu hodnoty přímo obsahují jejich hodnoty, což znamená, že je paměť přidělena vloženému v jakémkoli kontextu, kdy je proměnná deklarována. Neexistuje žádné samostatné přidělení haldy nebo režie uvolňování paměti pro proměnné typu hodnoty.

Existují dvě kategorie typů hodnot: 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 se jedná o jednoduché neagregované typy:

byte num = 0xA;
int i = 5;
char c = 'Z';

Typy hodnot jsou zapečetěné, což znamená, že nemůžete odvozovat typ z libovolného typu hodnoty, například System.Int32 . Nelze definovat strukturu, která by dědila z jakékoli uživatelsky 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í zabalí strukturu uvnitř objektu typu reference na spravované haldě. K operacím zabalení dojde, když předáte typ hodnoty metodě, která přijímá typ System.Object rozhraní nebo libovolný typ rozhraní jako vstupní parametr. Další informace naleznete v tématu zabalení a rozbalení.

Klíčové slovo struct můžete použít k vytvoření vlastních typů 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 struktury. Další informace o typech hodnot naleznete v tématu typy hodnot.

Druhá kategorie typů hodnot je enum . Výčet definuje sadu pojmenovaných celočíselných konstant. Například výčet v knihovně tříd .NET obsahuje sadu pojmenovaných konstantních celých čísel, která určují, System.IO.FileMode jak se má soubor otevřít. Je definovaný tak, 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 ale pro člověka, který čte zdrojový kód, mnohem smysluplnější, a proto je lepší místo konstantních literálových čísel použít výčty. 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, platí také pro výčty. Další informace o výčtech najdete v tématu Typy výčtů.

Odkazové typy

Typ, který je definován jako class , , , pole nebo je record delegate interface odkazový typ. Když za běhu deklarujete proměnnou typu odkazu, proměnná obsahuje hodnotu , dokud explicitně nevytváříte objekt pomocí operátoru nebo jej přiřazujete objekt, který byl vytvořen jinde pomocí , jak je znázorněno v null následujícím příkladu: new new

MyClass mc = new MyClass();
MyClass mc2 = mc;

Rozhraní musí být inicializováno společně s objektem třídy, který jej implementuje. Pokud MyClass implementuje , vytvoříte instanci , jak je IMyInterface IMyInterface znázorněno v následujícím příkladu:

IMyInterface iface = new MyClass();

Při vytvoření objektu je paměť přidělena na spravované haldě a proměnná obsahuje pouze odkaz na umístění objektu. Typy na spravované haldě vyžadují režii jak při jejich přidělení, tak při jejich uvolnění funkcí automatické správy paměti modulu CLR, která se označuje jako uvolňování paměti. Uvolňování paměti je ale také vysoce optimalizované a ve většině scénářů nevytváří problém s výkonem. Další informace o uvolňování paměti najdete v tématu Automatická správa paměti.

Všechna pole jsou odkazové typy, i když jejich prvky jsou typy hodnot. Pole implicitně odvozují z třídy , ale deklarujete je a používáte se zjednodušenou syntaxí, která je poskytována jazykem C#, jak je znázorněno System.Array 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;

Odkazové 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 sealed, a jiné třídy mohou dědit z vaší třídy a přepsat vaše 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 naleznete v tématu Dědičnost.

Typy hodnot literálů

V jazyce C# obdrží hodnoty literálů typ z kompilátoru. Můžete určit, jak se má číselný literál zadat připojením písmene ke konci čísla. Například chcete-li určit, že má 4.56 být hodnota považována za float , přidejte "f" nebo "f" za číslo: 4.56f . Pokud není připojeno žádné písmeno, kompilátor odvodí typ literálu. Další informace o tom, které typy lze zadat pomocí přípon písmen, naleznete v části integrální číselné typy a číselné typy s plovoucí desetinnoučárkou.

Vzhledem k tomu, že jsou zadány literály a všechny typy jsou odvozeny z System.Object , můžete napsat 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 s jedním nebo více parametry typu , které slouží jako zástupný text pro skutečný typ ( konkrétní typ), který klientský kód poskytne při vytváření instance typu. Tyto typy se nazývají Obecné typy. Například typ .NET System.Collections.Generic.List<T> má jeden parametr typu, který je podle konvence dán názvem T . Při vytváření instance typu zadáte typ objektů, které bude seznam 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 znovu použít stejnou třídu pro uchování libovolného typu elementu, aniž by bylo nutné převést každý prvek na Object. Třídy obecné kolekce se nazývají silně typové kolekce , 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 stringList objektu 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 slova můžete implicitně zadat místní proměnnou (ale ne členy var 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ě typované místní proměnné.

Vytvoření pojmenovaného typu pro jednoduché sady souvisejících hodnot, které nechcete ukládat nebo předávat mimo hranice metody, může být nepohodlné. Pro tento účel můžete vytvořit anonymní typy. Další informace najdete v tématu Anonymní typy.

Běžné hodnotové typy nemají hodnotu null . Typy hodnot s možnou hodnotou null ale můžete vytvořit tak, že za ? typ připojíte . Například je int? int typ, který může mít také hodnotu null . Typy hodnot s možnou hodnotou null jsou instance obecného typu struktury System.Nullable<T> . Typy hodnot s možnou hodnotou null jsou zvláště užitečné, když předáváte data do a z databází, ve kterých můžou být číselné hodnoty null . Další informace najdete v tématu Typy hodnot s povolenou hodnotou Null.

Typ kompilace a typ běhu

Proměnná může mít různé typy kompilace a běhu. Typ kompilace je deklarovaný nebo odvozený typ proměnné ve zdrojovém kódu. Typ běhu je typ instance, na kterou tato proměnná odkazuje. 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 doby 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 modulu runtime string . Typ doby kompilace je object na prvním řádku a IEnumerable<char> druhý.

Pokud jsou tyto dva typy odlišné pro proměnnou, je důležité pochopit, kdy se typ při kompilaci a typ běhu aplikuje. Typ při kompilaci určuje všechny akce prováděné kompilátorem. Tyto akce kompilátoru zahrnují řešení volání metody, rozlišení přetížení a dostupná implicitní a explicitní přetypování. Typ běhu určuje všechny akce, které jsou vyřešeny v době běhu. Tyto akce za běhu zahrnují odesílání volání virtuální metody, vyhodnocování is a výrazů a switch jiné rozhraní API pro testování typu. Chcete-li lépe pochopit, jak váš kód komunikuje s typy, zjistěte, která akce se vztahuje na daný typ.

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#.