팁 (조언)
소프트웨어 개발이 새로운가요? 먼저 시작하기 자습서부터 시작하세요.
List<T> 컬렉션을 사용하는 즉시 제네릭을 접하게 됩니다.
다른 언어로 경험하신 적 있나요? C# 제네릭은 Java 또는 C++의 템플릿의 제네릭과 유사하지만 전체 런타임 형식 정보와 형식 지우기를 사용하지 않습니다. C# 특유의 패턴을 위해 컬렉션 식과 공변성 및 반공변성 섹션을 훑어보세요.
제네릭을 사용하면 모든 형식의 보안을 유지하면서 모든 형식에서 작동하는 코드를 작성할 수 있습니다. 필요한 다른 모든 형식에 대해 intstring별도의 클래스 또는 메서드를 작성하는 대신 하나 이상의 형식 매개 변수(예: 또는 T 및TKey)를 TValue사용하여 한 버전을 작성하고 사용할 때 실제 형식을 지정합니다. 컴파일러는 컴파일 시간에 형식을 확인하므로 런타임 캐스트 또는 위험이 InvalidCastException필요하지 않습니다.
일상적인 C#에서 제네릭이 지속적으로 발생합니다. 컬렉션, 비동기 반환 형식, 대리자 및 LINQ는 모두 제네릭 형식을 사용합니다.
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)}");
각 경우에 꺾쇠 괄호(<int>, <string>, <Product>)의 형식 인수는 제네릭 형식에 보유하거나 작동하는 데이터의 종류를 알려줍니다. 컴파일러는 형식 안전성을 강화합니다. 실수로 string를 List<int>에 추가할 수 없습니다.
제네릭 형식 사용
더 자주 고유한 형식을 만드는 대신 .NET 클래스 라이브러리에서 제네릭 형식을 consume 합니다. 다음 섹션에서는 사용할 가장 일반적인 제네릭 형식을 보여줍니다.
제네릭 컬렉션
네임스페이스 System.Collections.Generic 는 형식이 안전한 컬렉션 클래스를 제공합니다. 다음과 같은 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
제네릭 컬렉션은 대신 컴파일 시간에 오류가 표시되므로 런타임 시 형식 오류를 방지합니다. 또한 이러한 컬렉션은 값 형식에 대한 boxing을 방지하여 성능을 향상시킵니다.
제네릭 메서드
제네릭 메서드는 자체 형식 매개 변수를 선언합니다. 컴파일러는 전달한 값에서 형식 인수를 유추 하는 경우가 많으므로 명시적으로 지정할 필요가 없습니다.
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
호출Print(42)에서 컴파일러는 인수로부터 T를 int로 추론합니다. 명시적으로 Print<int>(42)를 작성할 수 있지만, 타입 추론은 코드를 더 깔끔하게 유지합니다.
수집 표현식
컬렉션 식(C# 12)은 컬렉션을 만들기 위한 간결한 구문을 제공합니다. 생성자 호출 또는 이니셜라이저 구문 대신 대괄호를 사용합니다.
// 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)}");
스프레드 연산자(..)는 한 컬렉션의 요소를 다른 컬렉션에 인라인으로 삽입하여 시퀀스를 결합하는 데 유용합니다.
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
컬렉션 식은 배열, List<T>, Span<T>ImmutableArray<T>및 컬렉션 작성기 패턴을 지원하는 모든 형식에서 작동합니다. 전체 구문 참조는 컬렉션 식을 참조하세요.
사전 초기화
인덱서 이니셜라이저를 사용하여 사전을 간결하게 초기화할 수 있습니다. 이 구문은 대괄호를 사용하여 키-값 쌍을 설정합니다.
Dictionary<string, int> scores = new()
{
["Alice"] = 95,
["Bob"] = 87,
["Carol"] = 92
};
foreach (var (name, score) in scores)
{
Console.WriteLine($"{name}: {score}");
}
사전을 복사하고 오버라이드를 적용하여 사전을 병합할 수 있습니다.
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
형식 제약 조건
제약 조건은 제네릭 형식 또는 메서드가 허용하는 형식 인수를 제한합니다. 제약 조건을 사용하면 object 에서 단독으로 사용할 수 없는 형식 매개 변수의 메서드를 호출하거나 속성에 접근할 수 있습니다.
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
가장 일반적인 제약 조건은 다음과 같습니다.
| 제약 조건 | Meaning |
|---|---|
where T : class |
T 참조 형식이어야 합니다. |
where T : struct |
T null을 허용하지 않는 값 형식이어야 합니다. |
where T : new() |
T 공용 매개 변수 없는 생성자가 있어야 합니다. |
where T : BaseClass |
T 에서 파생되어야 합니다. BaseClass |
where T : IInterface |
T 는 IInterface를 구현해야 합니다. |
제약 조건을 결합할 수 있습니다. where T : class, IComparable<T>, new() 덜 흔한 제약 조건으로는 where T : System.Enum, where T : System.Delegate, 및 where T : unmanaged이 있으며, 이는 특수한 시나리오에서 사용됩니다. 전체 목록은 형식 매개 변수의 제약 조건을 참조하세요.
공변성 및 반공변성
공변성 및 반공변성 에서는 제네릭 형식이 상속과 함께 동작하는 방식을 설명합니다. 원래 지정한 것보다 파생된 형식 인수를 더 많이 사용할 수 있는지 또는 더 적게 파생된 형식 인수를 사용할 수 있는지 여부를 결정합니다.
// 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"));
-
공변성(
out T):IEnumerable<Dog>는IEnumerable<Animal>가 예상되는 곳에서 사용할 수 있습니다. 이는Dog가Animal에서 파생되었기 때문입니다.out형식 매개 변수의 키워드를 사용하면 이 기능을 사용할 수 있습니다. 공변 형식 매개 변수는 출력 위치(반환 형식)에만 나타날 수 있습니다. -
반공변성(
in T):Action<Dog>가 필요한 곳에Action<Animal>를 사용할 수 있습니다.Animal를 처리할 수 있는 어떤 작업이라도Dog를 처리할 수 있기 때문입니다. 키워드를in사용하면 이 기능을 사용할 수 있습니다. 반공변 형식 매개 변수는 입력 위치(매개 변수)에만 나타날 수 있습니다.
많은 기본 제공 인터페이스 및 대리자는 이미 변형IEnumerable<out T>IReadOnlyList<out T>Func<out TResult>Action<in T>입니다. 이러한 형식으로 작업할 때 자동으로 분산을 활용할 수 있습니다. 변형 인터페이스 및 대리자 디자인에 대한 심층적인 처리는 공변성 및 반공변성(contravariance)을 참조하세요.
고유한 제네릭 형식 만들기
고유한 제네릭 클래스, 구조체, 인터페이스 및 메서드를 정의할 수 있습니다. 다음 예제는 예시를 위한 간단한 일반 연결 리스트를 보여줍니다. 실제로 List<T> 또는 다른 기본 제공 컬렉션을 사용합니다.
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
제네릭 형식은 클래스로 제한되지 않습니다. 제네릭 interface및 structrecord 형식을 정의할 수 있습니다. 제네릭 알고리즘 및 복잡한 제약 조건 조합을 디자인하는 방법에 대한 자세한 내용은 .NET
참고하십시오
.NET