• C# i .NET
  • typeof w C# - różnice, generyki i refleksja

typeof w C# - różnice, generyki i refleksja

Przemysław Kwiatkowski 2 czerwca 2026
Błąd kompilacji CS0012: typ 'DbContext' zdefiniowany w nieodwołanym zestawie. Brak odwołania do EntityFramework.

Spis treści

Masz obiekt typu object i chcesz wiedzieć, z jaką klasą naprawdę pracujesz? A może budujesz mechanizm oparty na refleksji, generykach lub rejestracji zależności? Operator typeof w C# zwraca obiekt System.Type, ale jego działanie łatwo pomylić z GetType() albo operatorem is. Poniżej pokazuję różnice, praktyczne przykłady, obsługę typów generycznych oraz pułapki, które często pojawiają się w kodzie .NET.

Najważniejsze informacje o typeof w C#

  • typeof zwraca obiekt System.Type reprezentujący wskazany typ.
  • Operator przyjmuje nazwę typu lub parametr generyczny, a nie dowolne wyrażenie.
  • GetType() sprawdza rzeczywisty typ obiektu w czasie działania programu.
  • is bada zgodność typu, także w przypadku dziedziczenia.
  • W generykach można użyć typeof(T), a także odwołać się do otwartych typów generycznych.

Co dokładnie zwraca operator typeof

Najprostszy przykład wygląda tak:

Type typLiczby = typeof(int);

Console.WriteLine(typLiczby.Name);       // Int32
Console.WriteLine(typLiczby.FullName);   // System.Int32

Wynikiem nie jest wartość int, lecz obiekt klasy System.Type. Ten obiekt przechowuje informacje o typie, między innymi jego nazwę, przestrzeń nazw, właściwości, metody, konstruktory oraz informacje o dziedziczeniu. To właśnie dlatego typeof jest często pierwszym krokiem do refleksji.

Operator działa zarówno dla typów wbudowanych, jak i własnych klas:

Type typTekstu = typeof(string);
Type typDaty = typeof(DateTime);
Type typUzytkownika = typeof(Uzytkownik);

public class Uzytkownik
{
    public string Email { get; set; } = string.Empty;
}

Nie tworzę tutaj żadnego obiektu. Wyrażenie typeof(Uzytkownik) nie wywołuje konstruktora i nie alokuje instancji klasy. Otrzymuję jedynie opis typu, który mogę później wykorzystać na przykład do odczytania właściwości albo znalezienia odpowiedniego konstruktora.

Typ można też porównywać bezpośrednio:

if (typLiczby == typeof(int))
{
    Console.WriteLine("To jest int.");
}

Takie porównanie jest jednoznaczne i odporne na zmianę nazwy typu. Zwykle jest lepszym wyborem niż sprawdzanie tekstu zwróconego przez Name, ponieważ nazwa może być niepełna albo niejednoznaczna.

typeof, GetType i is rozwiązują różne problemy

Najczęstsze nieporozumienie polega na traktowaniu tych konstrukcji jako zamienników. Nie są nimi. typeof opisuje typ znany w kodzie, GetType() odczytuje typ konkretnego obiektu, a is sprawdza, czy obiekt jest zgodny z określonym typem.

Konstrukcja Co sprawdza Przykład
typeof Typ podany bezpośrednio w kodzie typeof(Klient)
GetType() Rzeczywisty typ konkretnej instancji obiekt.GetType()
is Zgodność obiektu z typem, także przez dziedziczenie obiekt is Klient

Różnicę dobrze widać na przykładzie dziedziczenia:

class Zwierze
{
}

class Pies : Zwierze
{
}

object obiekt = new Pies();

Console.WriteLine(obiekt is Zwierze);              // True
Console.WriteLine(obiekt.GetType() == typeof(Zwierze)); // False
Console.WriteLine(obiekt.GetType() == typeof(Pies));    // True

Operator is zwraca true, ponieważ pies jest zwierzęciem. Porównanie GetType() == typeof(Zwierze) zwraca jednak false, bo rzeczywisty typ instancji to Pies. Ta różnica ma znaczenie w obsłudze polimorfizmu, czyli sytuacji, gdy zmienna bazowego typu przechowuje obiekt klasy pochodnej.

Jeżeli interesuje mnie dokładny typ obiektu, wybieram GetType(). Jeżeli chcę sprawdzić, czy obiekt może być używany jako określony typ, częściej właściwy będzie is:

if (obiekt is Zwierze zwierze)
{
    Console.WriteLine("Zwierzę: " + zwierze.GetType().Name);
}

Trzeba też pamiętać o wartości null. Wywołanie GetType() na pustej referencji kończy się wyjątkiem NullReferenceException, natomiast null is JakiśTyp zwraca false. Samo typeof(JakiśTyp) jest bezpieczne, bo nie operuje na instancji.

typeof w generykach i dla typów złożonych

Jednym z najlepszych zastosowań operatora jest kod generyczny. Parametr typu może być nieznany w momencie pisania metody, ale kompilator nadal pozwala użyć konstrukcji typeof(T):

static void WyswietlTyp(T wartosc)
{
    Console.WriteLine(typeof(T).FullName);
}

WyswietlTyp(42);        // System.Int32
WyswietlTyp("tekst");   // System.String

W tym przykładzie typeof(T) opisuje parametr typu użyty przy wywołaniu metody. To przydatne między innymi w generycznych loggerach, serializerach, fabrykach oraz rejestrach usług.

Można również odwołać się do zamkniętego typu generycznego, czyli takiego, któremu podano konkretne argumenty:

Type listaTekstow = typeof(List);
Type slownik = typeof(Dictionary);

Console.WriteLine(listaTekstow);
Console.WriteLine(slownik);

Istnieje także forma typu generycznego bez określonych argumentów:

Type definicjaListy = typeof(List<>);
Type definicjaSlownika = typeof(Dictionary<,>);

Zapis List<> oznacza typ generyczny z jednym parametrem, a Dictionary<,> typ z dwoma parametrami. Liczba przecinków musi odpowiadać liczbie parametrów pomniejszonej o jeden. Taka forma jest szczególnie przydatna przy analizowaniu generyków za pomocą refleksji.

Warto odróżnić definicję typu od typu skonstruowanego. typeof(List<>) opisuje ogólną definicję listy, natomiast typeof(List) opisuje konkretną listę przechowującą napisy. W kodzie refleksyjnym ta różnica często decyduje o tym, czy uda się poprawnie utworzyć instancję lub odczytać argumenty generyczne.

Praktyczne zastosowania w refleksji i .NET

Sam obiekt Type nie daje jeszcze gotowej funkcjonalności, ale otwiera dostęp do metadanych typu. Można sprawdzić jego właściwości, metody albo atrybuty:

Type typ = typeof(Uzytkownik);

foreach (var wlasciwosc in typ.GetProperties())
{
    Console.WriteLine($"{wlasciwosc.Name}: {wlasciwosc.PropertyType.Name}");
}

Tak działają między innymi proste mechanizmy mapowania, walidacji i serializacji. W praktyce używam Type jako klucza do decyzji podejmowanych dynamicznie, na przykład do wyboru właściwego konwertera dla konkretnej klasy.

Przykładowy rejestr obsługi typów może wyglądać tak:

var obslugi = new Dictionary
{
    [typeof(string)] = "Konwerter tekstu",
    [typeof(int)] = "Konwerter liczby",
    [typeof(DateTime)] = "Konwerter daty"
};

Type typDanych = typeof(int);

if (obslugi.TryGetValue(typDanych, out var nazwaObslugi))
{
    Console.WriteLine(nazwaObslugi);
}

To podejście jest czytelniejsze niż porównywanie nazw klas zapisanych jako tekst. Nie polecam jednak używania refleksji wszędzie, gdzie wystarczy zwykły kod statyczny. Refleksja daje elastyczność, ale zwiększa złożoność, utrudnia analizę przez kompilator i może być wolniejsza w bardzo często wykonywanych fragmentach.

Jeżeli typ jest wykorzystywany w pętli lub podczas obsługi wielu żądań, można przechować wynik w pamięci. Sam operator typeof jest tani, ale późniejsze operacje refleksyjne, takie jak wyszukiwanie właściwości czy tworzenie obiektów, mogą już uzasadniać cache.

Typowe błędy przy używaniu typeof

Przekazanie obiektu zamiast typu

Do operatora nie można przekazać dowolnego wyrażenia:

var tekst = "hello";

// Błąd kompilacji
// Type typ = typeof(tekst);

Jeśli chcę poznać typ zmiennej w czasie działania, używam tekst.GetType(). Jeśli typ jest znany z góry, zapisuję jego nazwę bezpośrednio, na przykład typeof(string).

Pomylenie typów nullable

Typ wartościowy z możliwością wartości null jest prawidłowym argumentem:

Type liczbaNullable = typeof(int?);

Console.WriteLine(liczbaNullable);
Console.WriteLine(Nullable.GetUnderlyingType(liczbaNullable));

Inaczej wygląda sytuacja z nullable reference types:

// Błąd kompilacji
// Type typ = typeof(string?);

Adnotacja string? pomaga kompilatorowi analizować możliwość wystąpienia wartości null, ale nie tworzy osobnego typu w metadanych środowiska uruchomieniowego. Dlatego w takim przypadku używa się typeof(string).

Przeczytaj również: Ile miejsca zajmuje int w C#? 4 bajty, zakres i wybór typu

Dynamic, var i typeof

Nie można użyć typeof(var) ani typeof(dynamic). var jest jedynie skrótem, który kompilator rozwiązuje na etapie kompilacji, a dynamic zmienia sposób wiązania wywołań, lecz nie reprezentuje osobnego typu CLR.

var liczba = 10;

// Poprawnie
Type typ = typeof(int);

// Niepoprawnie
// Type typ1 = typeof(var);
// Type typ2 = typeof(dynamic);

Gdy potrzebuję informacji o wartości przechowywanej w zmiennej typu dynamic, mogę użyć GetType(), o ile zmienna nie ma wartości null. To drobna różnica składniowa, ale często wyjaśnia błędy pojawiające się podczas pracy z kodem dynamicznym.

Jak wybrać właściwą konstrukcję w codziennym kodzie

Moja praktyczna reguła jest prosta. Gdy znam typ w kodzie, wybieram typeof. Gdy mam konkretną instancję i interesuje mnie jej rzeczywisty typ, używam GetType(). Gdy sprawdzam możliwość użycia obiektu jako typu bazowego lub interfejsu, korzystam z is.

  • typeof(Klient) stosuj do uzyskania obiektu Type dla znanego typu.
  • obiekt.GetType() stosuj do odczytu rzeczywistego typu instancji.
  • obiekt is IKlient stosuj do sprawdzenia zgodności z interfejsem lub klasą bazową.
  • typeof(T) stosuj w metodach i klasach generycznych.

Największy błąd nie polega na nieznajomości składni, lecz na użyciu poprawnej konstrukcji do niewłaściwego celu. typeof nie bada obiektu i nie zastępuje operatora is. Zwraca opis typu, który później można wykorzystać w refleksji, porównaniu lub logice generycznej.

Jeżeli zapamiętasz tę różnicę, większość przypadków stanie się oczywista: typ znany w kodzie oznacza typeof, typ konkretnej instancji oznacza GetType(), a sprawdzanie zgodności oznacza is. To niewielki fragment składni C#, ale fundament wielu mechanizmów działających wewnątrz .NET.

FAQ - Najczęstsze pytania

typeof opisuje typ podany bezpośrednio w kodzie, GetType() zwraca rzeczywisty typ konkretnej instancji, a is sprawdza zgodność obiektu z typem, także przez dziedziczenie. Dla obiektu klasy Pies przechowywanego jako Zwierze wyrażenie obiekt is Zwierze zwróci true, natomiast obiekt.GetType() == typeof(Zwierze) zwróci false.

Operator typeof przyjmuje nazwę typu albo parametr generyczny, a nie dowolne wyrażenie. Dla zmiennej tekst należy użyć tekst.GetType(), a gdy typ jest znany w kodzie, zapisać typeof(string). Wywołanie GetType() na wartości null spowoduje NullReferenceException, podczas gdy typeof nie operuje na instancji.

W metodzie generycznej typeof(T) zwraca obiekt Type odpowiadający parametrowi typu użytemu przy wywołaniu, na przykład System.Int32 lub System.String. Można też użyć typeof(List<string>) dla konkretnego typu generycznego oraz typeof(List<>) dla jego otwartej definicji. W przypadku Dictionary<,> przecinek oznacza dwa parametry typu.

Obiekt Type pozwala odczytywać metadane, takie jak właściwości, metody, konstruktory, atrybuty i informacje o dziedziczeniu. Może służyć między innymi do mapowania, walidacji, serializacji oraz wyboru konwertera w rejestrze Dictionary<Type, string>. Sam operator typeof jest tani, ale wyniki późniejszych operacji refleksyjnych, na przykład wyszukiwania właściwości, warto czasem przechowywać w pamięci.

Oceń artykuł

Ocena: 5.00 Liczba głosów: 1

Tagi

generyki
polimorfizm
typeof
refleksja
Autor Przemysław Kwiatkowski
Przemysław Kwiatkowski
Jestem Przemysław i od 15 lat zajmuję się programowaniem .NET, chmurą Azure oraz sztuczną inteligencją. Moja przygoda z tymi technologiami zaczęła się od fascynacji możliwościami, jakie dają, a z czasem przerodziła się w pasję do tworzenia rozwiązań, które realnie wpływają na pracę i życie ludzi. Na kursdotnet.pl staram się dzielić się swoją wiedzą w sposób przystępny, tłumacząc złożone zagadnienia i pomagając zrozumieć, jak te dynamicznie rozwijające się obszary IT mogą być wykorzystane w praktyce. Dokładam wszelkich starań, aby prezentowane przeze mnie materiały były rzetelne, aktualne i oparte na sprawdzonych źródłach, a także aby uporządkować wiedzę w sposób ułatwiający jej przyswojenie.

Udostępnij artykuł

Napisz komentarz