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"}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 };List<object> objects = new List<object> { "Tim", 4, 3.6m };Dim objects As New List(Of Object) From {"Tim", 4, 3.6D}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");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")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}");
}public static void TypeChecker<t>(T value)
{
Console.WriteLine($"Type: {typeof(T)}, Value: {value}");
}Public Shared Sub TypeChecker(Of T)(value As T)
Console.WriteLine($"Type: {GetType(T)}, Value: {value}")
End SubPublic Shared Sub TypeChecker(Of T)(value As T)
Console.WriteLine($"Type: {GetType(T)}, Value: {value}")
End SubMetoda 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.1TypeChecker(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");
}
}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");
}
}Public Class BetterList(Of t)
Private data As New List(Of t)()
Public Sub AddToList(value As t)
data.Add(value)
Console.WriteLine($"{value} has been added to the list")
End Sub
End ClassPublic Class BetterList(Of t)
Private data As New List(Of t)()
Public Sub AddToList(value As t)
data.Add(value)
Console.WriteLine($"{value} has been added to the list")
End Sub
End ClassKlasa 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"));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"))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);
}public interface IImportance<t>
{
T MostImportant(T a, T b);
}Public Interface IImportance(Of T)
Function MostImportant(a As T, b As T) As T
End InterfacePublic Interface IImportance(Of T)
Function MostImportant(a As T, b As T) As T
End InterfaceTim 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 : 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 ClassPublic 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 : 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 ClassPublic 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
}public class SampleClass<t> where T : new()
{
// Class implementation
}
public class SampleClassWithInterface<t> where T : IImportance<t>
{
// Class implementation
}Public Class SampleClass(Of T As {New})
' Class implementation
End Class
Public Class SampleClassWithInterface(Of T As IImportance(Of T))
' Class implementation
End ClassPublic Class SampleClass(Of T As {New})
' Class implementation
End Class
Public Class SampleClassWithInterface(Of T As IImportance(Of T))
' Class implementation
End ClassTe 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;
}
}public class MathOperations<t> where T : INumber<t>
{
public T Add(T x, T y)
{
return x + y;
}
}Public Class MathOperations(Of T As {INumber(Of T)})
Public Function Add(x As T, y As T) As T
Return x + y
End Function
End ClassPublic Class MathOperations(Of T As {INumber(Of T)})
Public Function Add(x As T, y As T) As T
Return x + y
End Function
End ClassOgraniczajac 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.8MathOperations<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.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));' 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