Earn More by Sharing What You Love
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!

Tim Corey
38m 51s
Generyki w C# stały się integralną częścią języka od momentu ich wprowadzenia, oferując liczne korzyści, nawet jeśli ich działanie nie jest w pełni zrozumiałe przez wszystkich deweloperów. W swoim filmie "How To Create Generics in C#, Including New Features" Tim Corey wyjaśnia, dlaczego generyki są ważne, jak je tworzyć oraz demonstruje ich praktyczne zastosowania.
Ten artykuł oferuje kompleksowy przewodnik po generykach w C#, zawierający cenne informacje z filmu Tima Coreya na ten temat. Omówiono tutaj podstawy generyków, w tym bezpieczeństwo typów, korzyści wydajnościowe oraz praktyczne zastosowania. Artykuł obejmuje również tworzenie metod generycznych, klas, interfejsów oraz stosowanie ograniczeń na generyki. Dodatkowo podkreśla rzeczywiste przypadki użycia oraz znaczenie stosowania generyków w celu pisania efektywnego, bezpiecznego pod względem typów kodu.
Generyki w C# oferują potężny sposób na tworzenie elastycznego, wielokrotnego zastosowania i bezpiecznego pod względem typów kodu, pozwalając deweloperom definiować klasy, metody, interfejsy i kolekcje działające z dowolnym typem danych. Korzystając z klasy generycznej lub metody generycznej, deweloperzy mogą zdefiniować parametr typu generycznego (np. T), który może reprezentować dowolny typ danych. Eliminuje to koniecznosc duplikowania kodu i zwieksza mozliwosc ponownego wykorzystania kodu, zapewniajac jednoczesnie bezpieczenstwo typow w czasie kompilacji. Klasy kolekcji ogolnych, takie jak List i Dictionary<TKey, TValue>, pozwalaja na wydajne zarzadzanie roznego rodzaju danymi, podczas gdy interfejsy ogolne i delegaty ogolne umozliwiaja tworzenie niestandardowych typow ogolnych, ktore moga pracowac z wieloma parametrami typow. Wykorzystując generyki, deweloperzy mogą maksymalizować wydajność kodu i minimalizować wady klas niegenerycznych. Ta elastyczność umożliwia tworzenie bardziej wielokrotnego zastosowania kodu bez poświęcenia wydajności czy bezpieczeństwa typów.
Tim (0:00) wprowadza temat, podkreślając powszechne użycie generyków w C#. Jego celem jest wyjaśnienie, dlaczego generyki są niezbędne oraz jak je tworzyć i efektywnie używać.
Tim zaczyna od stworzenia nowej aplikacji konsolowej o nazwie "GenericsDemoApp", aby skupić się wyłącznie na demonstrowaniu generyków bez rozpraszania uwagi przez interfejs użytkownika. Ustawia projekt korzystając z .NET 8 i Visual Studio 2022.
Tim na (2:22) zaczyna od przykladu uzycia klasy List, aby zilustrowac pojecie generycznosci. Generyki pozwalają określać typ elementów, które kolekcja może zawierać, zapewniając bezpieczeństwo typów i unikając błędów w czasie wykonania.
List<int> numbers = new List<int> { 1, 2, 3 };
List<string> strings = new List<string> { "Tim", "Corey", "Sue" };Dim numbers As New List(Of Integer) From {1, 2, 3}
Dim strings As New List(Of String) From {"Tim", "Corey", "Sue"}List zapewnia, ze tylko liczby calkowite moga byc dodawane do listy liczb, a tylko lancuchy znakow moga byc dodawane do listy lancuchow.
Tim podkreśla znaczenie bezpieczeństwa typów zapewnianego przez generyki. Kompiator sprawdza typy już na etapie projektowania, zapobiegając niezgodności typów i zapewniając bezpieczne wykonanie kodu. Generyki również eliminują potrzebę stosowania boxingu i unboxingu, co prowadzi do bardziej efektywnego kodu.
Aby zademonstrować nieskuteczność stosowania kolekcji niegenerycznych, Tim tworzy Listę i pokazuje, jak może ona przechowywać różne typy obiektów.
List<object> objects = new List<object> { "Tim", 4, 3.6m };Dim objects As New List(Of Object) From {"Tim", 4, 3.6D}Wyjaśnia, że stosowanie kolekcji niegenerycznych może prowadzić do niezgodności typów i nieskuteczności z powodu boxingu i unboxingu.
Tim (6:15) przeprowadza porównanie wydajności między korzystaniem z List<object> i List<int>, aby dodać milion elementów. Używa stopera, aby zmierzyć czas wykonania każdej operacji.
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 1; i <= 1_000_000; i++)
{
objects.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of object elapsed time: {stopwatch.ElapsedMilliseconds} ms");
stopwatch.Restart();
for (int i = 1; i <= 1_000_000; i++)
{
numbers.Add(i);
}
stopwatch.Stop();
Console.WriteLine($"List of int elapsed time: {stopwatch.ElapsedMilliseconds} ms");Dim stopwatch As New Stopwatch()
stopwatch.Start()
For i As Integer = 1 To 1_000_000
objects.Add(i)
Next
stopwatch.Stop()
Console.WriteLine($"List of object elapsed time: {stopwatch.ElapsedMilliseconds} ms")
stopwatch.Restart()
For i As Integer = 1 To 1_000_000
numbers.Add(i)
Next
stopwatch.Stop()
Console.WriteLine($"List of int elapsed time: {stopwatch.ElapsedMilliseconds} ms")Wyniki pokazują, że dodawanie liczb całkowitych do List<int> jest znacznie szybsze niż do List<object> z powodu braku boxingu i unboxingu.

Tim (10:14) demonstruje, jak stworzyć metodę generyczną o nazwie TypeChecker. Ta metoda sprawdza typ podanej wartości i wypisuje go, pokazując elastyczność i moc generyków.
public static void TypeChecker<t>(T value)
{
Console.WriteLine($"Type: {typeof(T)}, Value: {value}");
}
Metoda TypeChecker używa operatora typeof do określenia typu parametru generycznego T i wypisuje zarówno typ, jak i wartość.
Tim prezentuje przykłady wywołania metody TypeChecker z różnymi typami argumentów.
TypeChecker(1); // Type: System.Int32, Value: 1
TypeChecker("Tim"); // Type: System.String, Value: Tim
TypeChecker(1.1); // Type: System.Double, Value: 1.1TypeChecker(1) ' Type: System.Int32, Value: 1
TypeChecker("Tim") ' Type: System.String, Value: Tim
TypeChecker(1.1) ' Type: System.Double, Value: 1.1Przekazując różne typy do metody TypeChecker, Tim pokazuje, jak generyki mogą bezproblemowo obsługiwać różne typy danych.
Tim (16:25) przechodzi do stworzenia klasy generycznej o nazwie BetterList. Ta klasa enkapsuluje listę określonego typu i zapewnia dodatkową funkcjonalność.
public class BetterList<t>
{
private List<t> data = new List<t>();
public void AddToList(T value)
{
data.Add(value);
Console.WriteLine($"{value} has been added to the list");
}
}
Klasa BetterList zawiera prywatna liste List oraz metode AddToList, ktora dodaje wartosc do listy i wyswietla wiadomosc wskazujaca na dodanie.
Tim przedstawia przykłady korzystania z klasy BetterList z różnymi typami.
BetterList<int> betterNumbers = new BetterList<int>();
betterNumbers.AddToList(5);
BetterList<PersonRecord> people = new BetterList<PersonRecord>();
people.AddToList(new PersonRecord("Tim", "Corey"));Dim betterNumbers As New BetterList(Of Integer)()
betterNumbers.AddToList(5)
Dim people As New BetterList(Of PersonRecord)()
people.AddToList(New PersonRecord("Tim", "Corey"))W tych przykładach, BetterList<int> zarządza listą liczb całkowitych, podczas gdy BetterList<PersonRecord> zarządza listą obiektów PersonRecord. Każde dodanie do listy powoduje wyświetlenie komunikatu konsolowego wskazującego dodaną wartość.
Tim (21:48) wprowadza pomysł generycznego interfejsu o nazwie IImportance. Ten interfejs definiuje metodę do określenia, która z dwóch wartości jest ważniejsza.
public interface IImportance<t>
{
T MostImportant(T a, T b);
}
Tim pokazuje, jak zaimplementować ten interfejs dla różnych typów. Zaczyna od implementacji dla liczb całkowitych.
public class EvaluateImportance : IImportance<int>
{
public int MostImportant(int a, int b)
{
return a > b ? a : b;
}
}Public Class EvaluateImportance
Implements IImportance(Of Integer)
Public Function MostImportant(a As Integer, b As Integer) As Integer Implements IImportance(Of Integer).MostImportant
Return If(a > b, a, b)
End Function
End ClassNastępnie implementuje interfejs dla napisów, używając długości napisów do określenia ważności.
public class EvaluateStringImportance : IImportance<string>
{
public string MostImportant(string a, string b)
{
return a.Length > b.Length ? a : b;
}
}Public Class EvaluateStringImportance
Implements IImportance(Of String)
Public Function MostImportant(a As String, b As String) As String Implements IImportance(Of String).MostImportant
Return If(a.Length > b.Length, a, b)
End Function
End ClassTe implementacje pokazują, jak ten sam interfejs może być zastosowany do różnych typów z określoną logiką dla każdego typu.
Tim (25:21) wyjaśnia, jak stosować ograniczenia na generyki, zapewniając, że spełniają one określone warunki. Na przykład, typ generyczny może być ograniczony do posiadania konstruktóra bezparametrowego lub implementacji określonego interfejsu.
public class SampleClass<t> where T : new()
{
// Class implementation
}
public class SampleClassWithInterface<t> where T : IImportance<t>
{
// Class implementation
}
Te ograniczenia pomagają zapewnić, że typ generyczny spełnia niezbędne kryteria, zapobiegając błędom w czasie wykonania i zwiększając bezpieczeństwo typów.
Tim omawia, jak Microsoft używa interfejsu INumber do ograniczania operacji numerycznych. Pozwala to na operacje arytmetyczne takie jak dodawanie i odejmowanie na typach generycznych.
public class MathOperations<t> where T : INumber<t>
{
public T Add(T x, T y)
{
return x + y;
}
}
Ograniczajac typ ogolny T do INumber, zapewnia sie, ze typ obsluguje operacje numeryczne.
Tim (33:55) rozszerza klasę MathOperations, aby zademonstrować, jak generyki mogą być używane z różnymi typami numerycznymi, takimi jak double i decimal.
Tim pokazuje, jak tworzyć instancje MathOperations dla liczb całkowitych i double:
MathOperations<int> intMath = new MathOperations<int>();
Console.WriteLine(intMath.Add(1, 4)); // Outputs: 5
MathOperations<double> doubleMath = new MathOperations<double>();
Console.WriteLine(doubleMath.Add(1.5, 4.3)); // Outputs: 5.8Dim intMath As New MathOperations(Of Integer)()
Console.WriteLine(intMath.Add(1, 4)) ' Outputs: 5
Dim doubleMath As New MathOperations(Of Double)()
Console.WriteLine(doubleMath.Add(1.5, 4.3)) ' Outputs: 5.8To demonstruje elastyczność generyków, pozwalając różnym typom numerycznym być obsługiwanym bezproblemowo w tej samej klasie.
Tim podkreśla znaczenie bezpieczeństwa typów, pokazując, że nie można mieszać różnych typów numerycznych. Na przykład, próba dodania double do liczby całkowitej spowoduje błąd w czasie kompilacji.
// This will result in a compile-time error
// Console.WriteLine(intMath.Add(1.5, 4));' This will result in a compile-time error
' Console.WriteLine(intMath.Add(1.5, 4))Tim wyjaśnia korzyści płynące z korzystania z generyków, by unikać kosztów związanych z konwersją typów. Na przykład, konwersja liczb całkowitych na double dla operacji matematycznych i z powrotem na liczby całkowite może być kosztowna. Korzystanie z generyków pozwala na bezpośrednie operacje na natywnych typach, zachowując wydajność i precyzję.
Tim zaleca ostrożność przy korzystaniu z generyków, sugerując, aby deweloperzy używali ich odpowiednio i unikali nadmiernego stosowania. Podkreśla korzyści z generyków, takie jak bezpieczeństwo typów, redukcja boxingu i unboxingu, sprawdzanie w czasie kompilacji i zwiększona czytelność kodu.
Rowniez zwraca uwage, ze generycznosc jest powszechna w kolekcjach takich jak List i Dictionary<TKey, TValue>, jak rowniez w frameworkach do logowania, ktore moga obslugiwac rozne typy bez potrzeby znajomosci szczegolow z gory.
Szczegółowe omówienie zaawansowanych generyków w C# przez Tima Coreya dostarcza cennych spostrzeżeń na temat ich praktycznych zastosowań i korzyści. Jeśli chcesz pogłębić swoje rozumienie generyków i zobaczyć rzeczywiste przykłady w użyciu, koniecznie obejrzyj szczegółowy film Tima Coreya na temat Generyków w C#. Jego jasne wyjaśnienia i praktyczne demonstracje pomogą ci w pełni zrozumieć koncepcje i skutecznie je zastosować w swoich własnych projektach.
Do you create content for developers working with .NET, C#, Java, Python, or Node.js? Turn your expertise into extra income!
Join our newsletter, you’ll get exclusive access on article updates. We value your privacy