Konwersja Typów w C#

Wprowadzenie do konwersji typów

Konwersja typów to proces przekształcania wartości z jednego typu danych w inny. Jest to fundamentalna operacja w programowaniu, która pozwala nam:

  • Łączyć dane różnych typów w obliczeniach
  • Przetwarzać dane wejściowe od użytkownika (zwykle string → inne typy)
  • Dostosowywać dane do wymagań różnych metod i operacji
  • Bezpiecznie przekazywać dane między komponentami aplikacji

Dlaczego potrzebujemy konwersji?

// Bez konwersji - błąd kompilacji!
string wiek = "25";
int rokUrodzenia = 2024 - wiek;  // ❌ Błąd! Nie można odejmować string od int

// Z konwersją - działa poprawnie
string wiekTekst = "25";
int wiekLiczba = int.Parse(wiekTekst);  // Konwersja string → int
int rokUrodzenia = 2024 - wiekLiczba;   // ✅ OK!
Console.WriteLine($"Rok urodzenia: {rokUrodzenia}");

Rodzaje konwersji typów

1. Konwersja niejawna (Implicit Conversion)

Odbywa się automatycznie przez kompilator, gdy:

  • Nie ma ryzyka utraty danych
  • Typ docelowy może pomieścić wszystkie wartości typu źródłowego
  • Konwersja jest zawsze bezpieczna

Hierarchia konwersji niejawnych dla typów liczbowych:

byte → short → int → long → float → double
  ↘     ↘       ↘     ↘       ↘
   char → ushort → uint → ulong
   
decimal (osobna ścieżka od byte, short, int, long, char, ushort, uint)

Przykłady konwersji niejawnych:

// Liczby całkowite - poszerzanie zakresu
byte malaByte = 100;
int większyInt = malaByte;        // byte → int (automatycznie)
long dużyLong = większyInt;       // int → long (automatycznie)

// Liczby zmiennoprzecinkowe
float liczbaFloat = 3.14f;
double liczbaDouble = liczbaFloat; // float → double (automatycznie)

// Mieszane konwersje
int liczbaInt = 42;
float liczbaFloat2 = liczbaInt;   // int → float (automatycznie)
double liczbaDouble2 = liczbaInt; // int → double (automatycznie)

// Wyświetlenie wyników
Console.WriteLine($"byte {malaByte} → int {większyInt}");
Console.WriteLine($"int {liczbaInt} → double {liczbaDouble2}");

Uwagi o konwersjach niejawnych:

// Uwaga: int → float może spowodować utratę precyzji (ale nie zakresu)
int dużaLiczba = 16777217;  // Więcej niż 24 bity precyzji float
float jakofloat = dużaLiczba;
int z_powrotem = (int)jakofloat;

Console.WriteLine($"Oryginalna: {dużaLiczba}");
Console.WriteLine($"Jako float: {jakofloat}");
Console.WriteLine($"Z powrotem: {z_powrotem}");
// Może pokazać różne wartości przez ograniczoną precyzję float!

2. Konwersja jawna (Explicit Conversion) – Casting

Wymaga ręcznego rzutowania, gdy:

  • Istnieje ryzyko utraty danych
  • Kompilator nie może zagwarantować bezpieczeństwa
  • Konwertujemy z większego typu na mniejszy

Składnia casting:

typDocelowy zmienna = (typDocelowy)źródło;

Przykłady konwersji jawnych:

// Zawężanie zakresu liczbowego
double liczbaDouble = 123.456;
int liczbaInt = (int)liczbaDouble;    // Utrata części ułamkowej!
float liczbaFloat = (float)liczbaDouble; // Możliwa utrata precyzji

Console.WriteLine($"double: {liczbaDouble}");
Console.WriteLine($"int: {liczbaInt}");        // 123 (bez części ułamkowej)
Console.WriteLine($"float: {liczbaFloat}");

// Zawężanie między typami całkowitymi
long dużaLong = 3000000000L;  // Przekracza zakres int
int mniejszyInt = (int)dużaLong;  // Może spowodować overflow!

Console.WriteLine($"long: {dużaLong}");
Console.WriteLine($"int: {mniejszyInt}");  // Może być negatywny przez overflow

// Bezpieczne sprawdzanie granic przed casting
long wartość = 2500000000L;
if (wartość >= int.MinValue && wartość <= int.MaxValue)
{
    int bezpiecznyInt = (int)wartość;
    Console.WriteLine($"Bezpieczna konwersja: {bezpiecznyInt}");
}
else
{
    Console.WriteLine($"Wartość {wartość} przekracza zakres int!");
}

Konwersje między typami liczbowymi – szczegóły

Tabela konwersji z potencjalnymi problemami:

Od → DoNiejawnaJawnaPotencjalne problemy
byteintBrak
intbyteOverflow jeśli > 255
intfloatUtrata precyzji dla dużych liczb
floatintUtrata części ułamkowej
doubledecimalMożliwy overflow
decimaldoubleUtrata precyzji

Praktyczne przykłady z różnymi typami:

// Praca z różnymi typami liczbowymi
Console.WriteLine("=== KONWERSJE MIĘDZY TYPAMI LICZBOWYMI ===");

// byte, short, int, long
byte małaWartość = 50;
short średniaWartość = małaWartość;  // Niejawne: byte → short
int dużaWartość = średniaWartość;    // Niejawne: short → int
long największaWartość = dużaWartość; // Niejawne: int → long

Console.WriteLine($"byte(50) → short({średniaWartość}) → int({dużaWartość}) → long({największaWartość})");

// Zawężanie - wymaga jawnego casting
byte z_powrotem_byte = (byte)średniaWartość;  // short → byte
Console.WriteLine($"Z powrotem do byte: {z_powrotem_byte}");

// Typy zmiennoprzecinkowe
float precyzjaJedna = 3.14159f;
double precyzjaDwa = precyzjaJedna;          // Niejawne: float → double
decimal precyzjaFinansowa = (decimal)precyzjaDwa; // Jawne: double → decimal

Console.WriteLine($"float: {precyzjaJedna}");
Console.WriteLine($"double: {precyzjaDwa}");
Console.WriteLine($"decimal: {precyzjaFinansowa}");

// Problemy z precyzją
float dużaLiczbaFloat = 16777216f + 1f;  // float ma ~7 cyfr precyzji
Console.WriteLine($"16777216 + 1 jako float: {dużaLiczbaFloat}"); // Może wyświetlić 16777216!

Konwersje string ↔ inne typy

Konwersja typów → string (zawsze bezpieczna)

Każdy typ ma metodę .ToString():

// Podstawowe konwersje na string
int liczbaInt = 42;
double liczbaDouble = 3.14159;
bool wartośćLogiczna = true;
char znak = 'A';
DateTime data = DateTime.Now;

Console.WriteLine("=== KONWERSJE NA STRING ===");
Console.WriteLine($"int → string: '{liczbaInt.ToString()}'");
Console.WriteLine($"double → string: '{liczbaDouble.ToString()}'");
Console.WriteLine($"bool → string: '{wartośćLogiczna.ToString()}'");
Console.WriteLine($"char → string: '{znak.ToString()}'");
Console.WriteLine($"DateTime → string: '{data.ToString()}'");

// Formatowanie podczas konwersji
Console.WriteLine("\n=== FORMATOWANIE ===");
Console.WriteLine($"double z 2 miejscami: {liczbaDouble.ToString("F2")}");
Console.WriteLine($"int jako waluta: {liczbaInt.ToString("C")}");
Console.WriteLine($"data w formacie: {data.ToString("yyyy-MM-dd HH:mm")}");

// Użycie interpolacji stringów (najwygodniejsze)
Console.WriteLine($"\nInterpolacja: Liczba {liczbaInt} i data {data:yyyy-MM-dd}");

Konwersja string → inne typy

1. Metody Parse() – rzucają wyjątek przy błędzie

Console.WriteLine("=== METODY PARSE ===");

try 
{
    // Poprawne konwersje
    string tekst1 = "123";
    string tekst2 = "45.67";
    string tekst3 = "true";
    string tekst4 = "2024-12-25";
    
    int liczbaZ1 = int.Parse(tekst1);
    double liczbaZ2 = double.Parse(tekst2);
    bool wartośćZ3 = bool.Parse(tekst3);
    DateTime dataZ4 = DateTime.Parse(tekst4);
    
    Console.WriteLine($"'{tekst1}' → int: {liczbaZ1}");
    Console.WriteLine($"'{tekst2}' → double: {liczbaZ2}");
    Console.WriteLine($"'{tekst3}' → bool: {wartośćZ3}");
    Console.WriteLine($"'{tekst4}' → DateTime: {dataZ4}");
}
catch (FormatException ex)
{
    Console.WriteLine($"Błąd formatowania: {ex.Message}");
}
catch (OverflowException ex)
{
    Console.WriteLine($"Wartość poza zakresem: {ex.Message}");
}

// Przykład błędu
try 
{
    string niepoprawnyTekst = "abc123";
    int liczba = int.Parse(niepoprawnyTekst);  // Rzuci FormatException
}
catch (FormatException)
{
    Console.WriteLine("Nie można przekonwertować 'abc123' na int!");
}

Metody TryParse() – bezpieczne, zwracają bool

Console.WriteLine("\n=== METODY TRYPARSE (BEZPIECZNE) ===");

// Prawidłowe konwersje
string[] testoweTeksty = { "123", "45.67", "abc", "true", "999999999999999999999" };

foreach (string tekst in testoweTeksty)
{
    // TryParse dla int
    if (int.TryParse(tekst, out int wynikInt))
    {
        Console.WriteLine($"✅ '{tekst}' → int: {wynikInt}");
    }
    else
    {
        Console.WriteLine($"❌ '{tekst}' nie można przekonwertować na int");
    }
    
    // TryParse dla double
    if (double.TryParse(tekst, out double wynikDouble))
    {
        Console.WriteLine($"✅ '{tekst}' → double: {wynikDouble}");
    }
    else
    {
        Console.WriteLine($"❌ '{tekst}' nie można przekonwertować na double");
    }
}

// Praktyczny przykład - pobieranie danych od użytkownika
Console.Write("\nPodaj swój wiek: ");
string inputWiek = Console.ReadLine();

if (int.TryParse(inputWiek, out int wiek) && wiek >= 0 && wiek <= 150)
{
    Console.WriteLine($"Twój wiek: {wiek} lat");
    
    if (wiek >= 18)
        Console.WriteLine("Jesteś pełnoletni");
    else
        Console.WriteLine($"Do pełnoletności pozostało: {18 - wiek} lat");
}
else
{
    Console.WriteLine("Podano nieprawidłowy wiek!");
}

Porównanie metod konwersji string → int:

MetodaObsługa nullWyjątkiWydajnośćUżycie
int.Parse()❌ Rzuca wyjątekFormatException, OverflowExceptionNajszybszaGdy pewni jesteśmy, że string jest poprawny
int.TryParse()❌ Zwraca falseNie rzucaSzybkaBezpieczna konwersja, zawsze używaj
Convert.ToInt32()✅ Zwraca 0FormatException, OverflowExceptionWolniejszaKonwersje między różnymi typami

Konwersje char ↔ int (kody ASCII/Unicode)

Console.WriteLine("=== KONWERSJE CHAR ↔ INT ===");

// char → int (kod ASCII/Unicode)
char litera = 'A';
int kodLitery = litera;  // Niejawna konwersja: char → int
Console.WriteLine($"'{litera}' ma kod: {kodLitery}");

char cyfra = '5';
int kodCyfry = cyfra;
int wartośćCyfry = cyfra - '0';  // Konwersja znaku cyfry na jej wartość liczbową
Console.WriteLine($"'{cyfra}' ma kod: {kodCyfry}, wartość: {wartośćCyfry}");

// int → char (z kodu na znak)
int kod = 66;
char znakZKodu = (char)kod;  // Jawna konwersja: int → char
Console.WriteLine($"Kod {kod} to znak: '{znakZKodu}'");

// Praktyczne przykłady
Console.WriteLine("\nAlphabet z kodów:");
for (int i = 65; i <= 90; i++)  // Kody A-Z
{
    Console.Write((char)i + " ");
}

Console.WriteLine("\n\nCyfry z kodów:");
for (int i = 48; i <= 57; i++)  // Kody 0-9
{
    char cyfrZnk = (char)i;
    int cyfrWart = i - 48;  // lub i - '0'
    Console.WriteLine($"Kod {i} → '{cyfrZnk}' → wartość {cyfrWart}");
}

Konwersje bool ↔ inne typy

Console.WriteLine("=== KONWERSJE BOOL ===");

// bool → string (zawsze działa)
bool prawda = true;
bool fałsz = false;

Console.WriteLine($"true → string: '{prawda.ToString()}'");
Console.WriteLine($"false → string: '{fałsz.ToString()}'");

// string → bool
string[] testoweBool = { "true", "false", "True", "FALSE", "1", "0", "yes", "no" };

foreach (string tekst in testoweBool)
{
    if (bool.TryParse(tekst, out bool wynik))
    {
        Console.WriteLine($"✅ '{tekst}' → bool: {wynik}");
    }
    else
    {
        Console.WriteLine($"❌ '{tekst}' nie można przekonwertować na bool");
    }
}

// bool → int (nie ma bezpośredniej konwersji, trzeba użyć Convert)
bool stan = true;
int stanJakoInt = Convert.ToInt32(stan);  // true → 1, false → 0
Console.WriteLine($"bool {stan} → int: {stanJakoInt}");

// int → bool (Convert: 0 = false, wszystko inne = true)
int[] liczby = { 0, 1, -1, 42, 999 };
foreach (int liczba in liczby)
{
    bool jakoBool = Convert.ToBoolean(liczba);
    Console.WriteLine($"int {liczba} → bool: {jakoBool}");
}

Konwersje z formatowaniem kulturowym

// Konwersje uwzględniające ustawienia regionalne
double liczba = 1234.56;

// Formatowanie według różnych kultur
var kulturaPL = new System.Globalization.CultureInfo("pl-PL");
var kulturaUS = new System.Globalization.CultureInfo("en-US");

Console.WriteLine("=== FORMATOWANIE KULTUROWE ===");
Console.WriteLine($"Polska: {liczba.ToString("C", kulturaPL)}");    // 1 234,56 zł
Console.WriteLine($"USA: {liczba.ToString("C", kulturaUS)}");       // $1,234.56

// Parsowanie z uwzględnieniem kultury
string polskaLiczba = "1 234,56";
string amerykańskaLiczba = "1,234.56";

if (double.TryParse(polskaLiczba, NumberStyles.Number, kulturaPL, out double wynikPL))
    Console.WriteLine($"Polska liczba: {wynikPL}");

if (double.TryParse(amerykańskaLiczba, NumberStyles.Number, kulturaUS, out double wynikUS))
    Console.WriteLine($"Amerykańska liczba: {wynikUS}");

Obsługa błędów przy konwersji – najlepsze praktyki

1. Zawsze używaj TryParse dla danych od użytkownika

// ❌ Źle - Parse może rzucić wyjątek
public int PobierzWiekŹle()
{
    Console.Write("Podaj wiek: ");
    return int.Parse(Console.ReadLine());  // Może się crash'ować!
}

// ✅ Dobrze - TryParse z walidacją
public int PobierzWiekDobrze()
{
    while (true)
    {
        Console.Write("Podaj wiek (0-150): ");
        string input = Console.ReadLine();
        
        if (int.TryParse(input, out int wiek) && wiek >= 0 && wiek <= 150)
        {
            return wiek;
        }
        
        Console.WriteLine("❌ Nieprawidłowy wiek! Spróbuj ponownie.");
    }
}

Ćwiczenia praktyczne

Ćwiczenie 1: Kalkulator z konwersjami

class KalkulatorZKonwersjami
{
    static void Main()
    {
        Console.WriteLine("🧮 Kalkulator z automatycznymi konwersjami typów");
        
        while (true)
        {
            Console.WriteLine("\nPodaj dwie wartości i operację:");
            
            // Pobierz pierwszą wartość
            Console.Write("Pierwsza wartość: ");
            if (!TryGetNumber(Console.ReadLine(), out double liczba1))
            {
                Console.WriteLine("❌ Nieprawidłowa pierwsza wartość!");
                continue;
            }
            
            // Pobierz operację
            Console.Write("Operacja (+, -, *, /, %, ^, exit): ");
            string operacja = Console.ReadLine();
            
            if (operacja?.ToLower() == "exit")
                break;
                
            // Pobierz drugą wartość (jeśli potrzebna)
            double liczba2 = 0;
            if (operacja != "sqrt")
            {
                Console.Write("Druga wartość: ");
                if (!TryGetNumber(Console.ReadLine(), out liczba2))
                {
                    Console.WriteLine("❌ Nieprawidłowa druga wartość!");
                    continue;
                }
            }
            
            // Wykonaj operację i wyświetl wyniki w różnych formatach
            double wynik = WykonajOperację(liczba1, liczba2, operacja);
            if (!double.IsNaN(wynik))
            {
                WyświetlWyniki(liczba1, liczba2, operacja, wynik);
            }
        }
    }
    
    static bool TryGetNumber(string input, out double number)
    {
        // Spróbuj różnych typów konwersji
        if (double.TryParse(input, out number))
            return true;
            
        // Może to ułamek?
        if (input.Contains("/"))
        {
            string[] części = input.Split('/');
            if (części.Length == 2 && 
                double.TryParse(części[0], out double licznik) &&
                double.TryParse(części[1], out double mianownik) &&
                mianownik != 0)
            {
                number = licznik / mianownik;
                return true;
            }
        }
        
        number = 0;
        return false;
    }
    
    static double WykonajOperację(double a, double b, string op)
    {
        return op switch
        {
            "+" => a + b,
            "-" => a - b,
            "*" => a * b,
            "/" => b != 0 ? a / b : double.NaN,
            "%" => b != 0 ? a % b : double.NaN,
            "^" => Math.Pow(a, b),
            "sqrt" => a >= 0 ? Math.Sqrt(a) : double.NaN,
            _ => double.NaN
        };
    }
    
    static void WyświetlWyniki(double a, double b, string op, double wynik)
    {
        Console.WriteLine($"\n✅ Wynik: {a} {op} {b} = {wynik}");
        
        Console.WriteLine("📊 Konwersje wyniku:");
        
        // int (jeśli da się bez straty)
        if (wynik == Math.Floor(wynik) && wynik >= int.MinValue && wynik <= int.MaxValue)
        {
            Console.WriteLine($"   int: {(int)wynik}");
        }
        
        // float
        Console.WriteLine($"   float: {(float)wynik}");
        
        // decimal (jeśli w zakresie)
        if (wynik >= (double)decimal.MinValue && wynik <= (double)decimal.MaxValue)
        {
            Console.WriteLine($"   decimal: {(decimal)wynik}");
        }
        
        // string z różnym formatowaniem
        Console.WriteLine($"   string (2 miejsca): \"{wynik:F2}\"");
        Console.WriteLine($"   string (naukowy): \"{wynik:E2}\"");
        Console.WriteLine($"   string (procent): \"{wynik:P1}\"");
        
        // Reprezentacja binarna (dla liczb całkowitych)
        if (wynik == Math.Floor(wynik) && wynik >= 0 && wynik <= long.MaxValue)
        {
            Console.WriteLine($"   binarny: {Convert.ToString((long)wynik, 2)}");
            Console.WriteLine($"   hex: 0x{(long)wynik:X}");
        }
    }
}