Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Petunjuk / Saran
Baru mengembangkan perangkat lunak? Mulai dengan tutorial Memulai terlebih dahulu. Anda akan menemukan tipe generik begitu Anda mulai menggunakan koleksi seperti List<T>.
Berpengalaman dalam bahasa lain? Generik C# mirip dengan generik di Java atau templat C++, tetapi dengan informasi tipe saat runtime penuh dan tanpa penghapusan tipe. Lewati ekspresi koleksi dan bagian kovarians dan kontravariansi untuk pola C#spesifik.
Generik memungkinkan Anda menulis kode yang dapat bekerja dengan jenis apa pun sambil tetap mempertahankan keamanan tipe secara penuh. Alih-alih menulis kelas atau metode terpisah untuk int, string, dan setiap jenis lain yang Anda butuhkan, tulis satu versi dengan satu atau beberapa parameter jenis (seperti T, atau TKey dan TValue) dan tentukan jenis aktual saat Anda menggunakannya. Pengkompilasi memeriksa jenis pada waktu kompilasi, sehingga Anda tidak memerlukan cast runtime atau risiko InvalidCastException.
Anda mengalami generik terus-menerus dalam C#sehari-hari. Koleksi, jenis pengembalian asinkron, delegasi, dan LINQ semuanya bergantung pada jenis generik:
List<int> scores = [95, 87, 72, 91];
Dictionary<string, decimal> prices = new()
{
["Widget"] = 19.99m,
["Gadget"] = 29.99m
};
Task<string> greeting = Task.FromResult("Hello, generics!");
Func<int, bool> isPositive = n => n > 0;
Console.WriteLine($"First score: {scores[0]}");
Console.WriteLine($"Widget price: {prices["Widget"]:C}");
Console.WriteLine($"Greeting: {await greeting}");
Console.WriteLine($"Is 5 positive? {isPositive(5)}");
Dalam setiap kasus, argumen jenis dalam tanda kurung sudut (<int>, <string>, <Product>) memberi tahu jenis generik data apa yang dipegang atau dioperasikannya. Kompilator memastikan keamanan tipe. Anda tidak dapat secara tidak sengaja menambahkan string ke List<int>.
Menggunakan jenis generik
Lebih sering, Anda menggunakan jenis generik pustaka kelas .NET daripada membuatnya sendiri. Bagian berikut menunjukkan jenis generik paling umum yang akan Anda gunakan.
Koleksi generik
Namespace System.Collections.Generic menyediakan kelas koleksi yang aman tipe. Selalu gunakan koleksi ini alih-alih koleksi nongenerik seperti ArrayList:
// A strongly typed list of strings
List<string> names = ["Alice", "Bob", "Carol"];
names.Add("Dave");
// names.Add(42); // Compile-time error: can't add an int to List<string>
// A dictionary mapping string keys to int values
var inventory = new Dictionary<string, int>
{
["Apples"] = 50,
["Oranges"] = 30
};
inventory["Bananas"] = 25;
// A set that prevents duplicates
HashSet<int> uniqueIds = [1, 2, 3, 1, 2];
Console.WriteLine($"Unique count: {uniqueIds.Count}"); // 3
// A FIFO queue
Queue<string> tasks = new();
tasks.Enqueue("Build");
tasks.Enqueue("Test");
Console.WriteLine($"Next task: {tasks.Dequeue()}"); // Build
Koleksi generik mencegah kesalahan jenis pada runtime karena kesalahan muncul pada waktu kompilasi sebagai gantinya. Koleksi ini juga menghindari pembungkusan untuk tipe nilai, yang meningkatkan kinerja.
Metode generik
Metode generik mendeklarasikan parameter jenisnya sendiri. Pengkompilasi sering menyimpulkan argumen jenis dari nilai yang Anda lewati, sehingga Anda tidak perlu menentukannya secara eksplisit:
static void Print<T>(T value) =>
Console.WriteLine($"Value: {value}");
Print(42); // Compiler infers T as int
Print("hello"); // Compiler infers T as string
Print(3.14); // Compiler infers T as double
Dalam panggilan Print(42), pengkompilasi menyimpulkan T sebagai int dari argumen. Anda dapat menulis Print<int>(42) secara eksplisit, tetapi inferensi jenis menjaga kode lebih bersih.
Ungkapan koleksi
Ekspresi koleksi (C# 12) menyediakan sintaks ringkas untuk membuat koleksi. Gunakan kurung siku alih-alih panggilan konstruktor atau sintaks penginisialisasi:
// Create a list with a collection expression
List<string> fruits = ["Apple", "Banana", "Cherry"];
// Create an array
int[] numbers = [1, 2, 3, 4, 5];
// Works with any supported collection type
IReadOnlyList<double> temperatures = [72.0, 68.5, 75.3];
Console.WriteLine($"Fruits: {string.Join(", ", fruits)}");
Console.WriteLine($"Numbers: {string.Join(", ", numbers)}");
Console.WriteLine($"Temps: {string.Join(", ", temperatures)}");
Operator spread (..) memasukkan elemen dari satu koleksi ke dalam koleksi lain, yang berguna untuk mengombinasikan urutan:
List<int> first = [1, 2, 3];
List<int> second = [4, 5, 6];
// Spread both lists into a new combined list
List<int> combined = [.. first, .. second];
Console.WriteLine(string.Join(", ", combined));
// Output: 1, 2, 3, 4, 5, 6
// Add extra elements alongside spreads
List<int> withExtras = [0, .. first, 99, .. second];
Console.WriteLine(string.Join(", ", withExtras));
// Output: 0, 1, 2, 3, 99, 4, 5, 6
Ekspresi koleksi berfungsi dengan array, List<T>, Span<T>, ImmutableArray<T>, dan jenis apa pun yang mendukung pola penyusun koleksi. Untuk mendapatkan referensi sintaks lengkapnya, lihat Ekspresi koleksi.
Inisialisasi kamus
Anda dapat menginisialisasi kamus secara ringkas dengan penginisialisasi pengindeks. Sintaks ini menggunakan tanda kurung siku untuk mengatur pasangan kunci-nilai:
Dictionary<string, int> scores = new()
{
["Alice"] = 95,
["Bob"] = 87,
["Carol"] = 92
};
foreach (var (name, score) in scores)
{
Console.WriteLine($"{name}: {score}");
}
Anda dapat menggabungkan kamus dengan menyalinnya dan menerapkan penimpaan:
Dictionary<string, int> defaults = new()
{
["Timeout"] = 30,
["Retries"] = 3
};
Dictionary<string, int> overrides = new()
{
["Timeout"] = 60
};
// Merge defaults and overrides into a new dictionary
Dictionary<string, int> config = new(defaults);
foreach (var (key, value) in overrides)
{
config[key] = value;
}
Console.WriteLine($"Timeout: {config["Timeout"]}"); // 60
Console.WriteLine($"Retries: {config["Retries"]}"); // 3
Mengetik batasan
Batasan membatasi argumen jenis mana yang diterima jenis atau metode generik. Batasan memungkinkan Anda memanggil metode atau mengakses properti pada parameter jenis yang tidak dapat diakses pada object sendiri.
static T Max<T>(T a, T b) where T : IComparable<T> =>
a.CompareTo(b) >= 0 ? a : b;
Console.WriteLine(Max(3, 7)); // 7
Console.WriteLine(Max("apple", "banana")); // banana
static T CreateDefault<T>() where T : new() => new T();
var list = CreateDefault<List<int>>(); // Creates an empty List<int>
Console.WriteLine($"Empty list count: {list.Count}"); // 0
Batasan yang paling umum adalah:
| Batasan | Meaning |
|---|---|
where T : class |
T harus berupa jenis referensi |
where T : struct |
T harus berupa tipe nilai yang tidak dapat diubah ke null |
where T : new() |
T harus memiliki konstruktor tanpa parameter publik |
where T : BaseClass |
T harus berasal dari BaseClass |
where T : IInterface |
T harus mengimplementasikan IInterface |
Anda dapat menggabungkan batasan: where T : class, IComparable<T>, new(). Batasan yang kurang umum termasuk where T : System.Enum, where T : System.Delegate, dan where T : unmanaged untuk skenario khusus. Untuk daftar lengkapnya, lihat Batasan pada parameter jenis.
Kovarian dan kontravarian
Kovariansi dan kontravariansi menggambarkan bagaimana jenis generik berperilaku dengan pewarisan. Mereka menentukan apakah Anda dapat menggunakan argumen jenis yang lebih khusus atau kurang khusus daripada yang awalnya ditentukan.
// Covariance: IEnumerable<Dog> can be used as IEnumerable<Animal>
// because IEnumerable<out T> is covariant
List<Dog> dogs = [new("Rex"), new("Buddy")];
IEnumerable<Animal> animals = dogs; // Allowed because Dog derives from Animal
foreach (var animal in animals)
{
Console.WriteLine(animal.Name);
}
// Contravariance: Action<Animal> can be used as Action<Dog>
// because Action<in T> is contravariant
Action<Animal> printAnimal = a => Console.WriteLine($"Animal: {a.Name}");
Action<Dog> printDog = printAnimal; // Allowed because any Animal handler can handle Dog
printDog(new Dog("Spot"));
-
Kovariansi (
out T): DapatIEnumerable<Dog>digunakan di manaIEnumerable<Animal>diharapkan karenaDogberasal dariAnimal. Kata kuncioutpada parameter tipe memungkinkan ini. Parameter tipe kovarian hanya dapat muncul dalam posisi keluaran (tipe pengembalian). -
Kontravariansi (
in T): SebuahAction<Animal>bisa digunakan sebagai penggantiAction<Dog>karena tindakan apa pun yang menanganiAnimaljuga dapat menanganiDog. Katainkunci mengaktifkan ini. Parameter jenis kontravarian hanya dapat muncul di posisi input (parameter).
Banyak antarmuka dan delegasi bawaan yang sudah menjadi varian: IEnumerable<out T>, , IReadOnlyList<out T>Func<out TResult>, dan Action<in T>. Anda mendapat manfaat dari varians secara otomatis saat bekerja dengan jenis-jenis ini. Untuk pembahasan mendalam tentang merancang antarmuka varian dan delegasi, lihat Kovariansi dan kontravariansi.
Membuat jenis generik Anda sendiri
Anda dapat menentukan kelas, struktur, antarmuka, dan metode generik Anda sendiri. Contoh berikut menunjukkan daftar tertaut generik sederhana untuk ilustrasi. Dalam praktiknya, gunakan List<T> atau koleksi bawaan lainnya:
public class GenericList<T>
{
private class Node(T data)
{
public T Data { get; set; } = data;
public Node? Next { get; set; }
}
private Node? head;
public void AddHead(T data)
{
var node = new Node(data) { Next = head };
head = node;
}
public IEnumerator<T> GetEnumerator()
{
var current = head;
while (current is not null)
{
yield return current.Data;
current = current.Next;
}
}
}
var list = new GenericList<int>();
for (var i = 0; i < 5; i++)
{
list.AddHead(i);
}
foreach (var item in list)
{
Console.Write($"{item} ");
}
Console.WriteLine();
// Output: 4 3 2 1 0
Jenis generik tidak terbatas pada kelas. Anda dapat menentukan jenis interface, struct, dan record generik. Untuk informasi selengkapnya tentang merancang algoritma generik dan kombinasi batasan kompleks, lihat Generics di .NET.