컬렉션

.NET 런타임은 관련 개체 그룹을 저장하고 관리하는 다양한 컬렉션 형식을 제공합니다. 일부 컬렉션 형식(예: System.Array, System.Span<T>, System.Memory<T>)은 C# 언어로 인식됩니다. 또한 System.Collections.Generic.IEnumerable<T> 같은 인터페이스는 컬렉션의 요소를 열거하기 위한 언어로 인식됩니다.

컬렉션은 더 유연한 개체 그룹 방식을 제공합니다. 다양한 컬렉션을 다음과 같은 특성으로 분류할 수 있습니다.

  • 요소 액세스: 모든 컬렉션을 열거하고 각 요소에 순서대로 액세스할 수 있습니다. 일부 컬렉션은 정렬된 컬렉션에서 요소의 위치인 인덱스를 통해 요소에 액세스합니다. 가장 일반적인 예는 System.Collections.Generic.List<T>입니다. 다른 컬렉션은 요소를 통해 액세스하며, 이 경우 하나의 이 하나의 와 연결됩니다. 가장 일반적인 예는 System.Collections.Generic.Dictionary<TKey,TValue>입니다. 앱이 요소에 액세스하는 방법에 따라 이러한 컬렉션 형식 중에서 선택할 수 있습니다.
  • 성능 프로필: 모든 컬렉션은 요소 추가, 요소 찾기 또는 요소 제거와 같은 작업에 대한 성능 프로필이 다릅니다. 앱에서 가장 많이 사용하는 작업에 따라 컬렉션 형식을 선택할 수 있습니다.
  • 동적으로 확장 및 축소: 요소를 동적으로 추가하거나 제거하는 대부분의 컬렉션입니다. 특히 Array, System.Span<T>System.Memory<T>은(는) 그렇지 않습니다.

이러한 특성 외에도 런타임은 요소를 추가 또는 제거하거나 컬렉션의 요소를 수정하지 못하게 하는 특수 컬렉션을 제공합니다. 다른 특수 컬렉션은 다중 스레드 앱에서 동시 액세스에 대한 보안을 제공합니다.

.NET API 참조에서 모든 컬렉션 형식을 찾을 수 있습니다. 자세한 내용은 일반적으로 사용되는 컬렉션 형식컬렉션 클래스 선택을 참조하세요.

참고 항목

이 문서의 예제에서는 System.Collections.GenericSystem.Linq 네임스페이스에 대한 using 지시문을 추가해야 할 수 있습니다.

배열은System.Array(으)로 표현되고 C# 언어로 구문이 지원됩니다. 이 구문은 배열 변수에 대해 더욱 간결한 선언을 제공합니다.

System.Span<T>은(는) 해당 요소를 복사하지 않고 요소 시퀀스에 대한 스냅샷을 제공하는 ref struct 형식입니다. 컴파일러는 참조하는 시퀀스가 더 이상 범위에 없게 된 후에 Span에 액세스할 수 없도록 안전 규칙을 적용합니다. 이는 많은 .NET API에서 성능을 향상시키는 데 사용됩니다. ref struct 형식을 사용할 수 없는 경우 Memory<T>이(가) 비슷한 동작을 제공합니다.

C# 12부터 모든 컬렉션 형식은 컬렉션 식을 사용하여 초기화할 수 있습니다.

인덱싱 가능한 컬렉션

인덱싱 가능한 컬렉션은 인덱스로 각 요소에 액세스할 수 있는 컬렉션입니다. 인덱스는 시퀀스에서 앞에 오는 요소의 수입니다. 따라서 0 인덱스에 의한 요소 참조는 첫 번째 요소이고, 1 인덱스는 두 번째 요소입니다. 다음 예제에서는 List<T> 클래스를 사용합니다. 이는 가장 일반적인 인덱싱 가능한 컬렉션입니다.

다음 예제에서는 문자열 목록을 만들고 초기화하고, 요소를 제거하고, 목록 끝에 요소를 추가합니다. 수정할 때마다 foreach 문 또는 for 루프를 사용하여 문자열을 반복합니다.

// Create a list of strings by using a
// collection initializer.
List<string> salmons = ["chinook", "coho", "pink", "sockeye"];

// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook coho pink sockeye

// Remove an element from the list by specifying
// the object.
salmons.Remove("coho");


// Iterate using the index:
for (var index = 0; index < salmons.Count; index++)
{
    Console.Write(salmons[index] + " ");
}
// Output: chinook pink sockeye

// Add the removed element
salmons.Add("coho");
// Iterate through the list.
foreach (var salmon in salmons)
{
    Console.Write(salmon + " ");
}
// Output: chinook pink sockeye coho

다음 예제에서는 인덱스로 목록에서 요소를 제거합니다. foreach 문 대신 내림차순으로 반복하는 for 문을 사용합니다. RemoveAt 메서드는 제거된 요소 뒤의 요소가 더 낮은 인덱스 값을 갖게 만듭니다.

List<int> numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];

// Remove odd numbers.
for (var index = numbers.Count - 1; index >= 0; index--)
{
    if (numbers[index] % 2 == 1)
    {
        // Remove the element by specifying
        // the zero-based index in the list.
        numbers.RemoveAt(index);
    }
}

// Iterate through the list.
// A lambda expression is placed in the ForEach method
// of the List(T) object.
numbers.ForEach(
    number => Console.Write(number + " "));
// Output: 0 2 4 6 8

List<T>의 요소 형식에 대해 고유한 클래스를 정의할 수도 있습니다. 다음 예제에서, List<T>에서 사용되는 Galaxy 클래스는 코드에서 정의됩니다.

private static void IterateThroughList()
{
    var theGalaxies = new List<Galaxy>
    {
        new (){ Name="Tadpole", MegaLightYears=400},
        new (){ Name="Pinwheel", MegaLightYears=25},
        new (){ Name="Milky Way", MegaLightYears=0},
        new (){ Name="Andromeda", MegaLightYears=3}
    };

    foreach (Galaxy theGalaxy in theGalaxies)
    {
        Console.WriteLine(theGalaxy.Name + "  " + theGalaxy.MegaLightYears);
    }

    // Output:
    //  Tadpole  400
    //  Pinwheel  25
    //  Milky Way  0
    //  Andromeda  3
}

public class Galaxy
{
    public string Name { get; set; }
    public int MegaLightYears { get; set; }
}

키/값 쌍 컬렉션

다음 예제에서는 Dictionary<TKey,TValue> 클래스를 사용합니다. 이는 가장 일반적인 사전 컬렉션입니다. 사전 컬렉션을 사용하면 각 요소의 키를 통해 컬렉션의 요소에 액세스할 수 있습니다. 사전에 추가하는 각 항목은 값과 관련 키로 이루어져 있습니다.

다음 예제에서는 Dictionary 컬렉션을 만들고 foreach 문을 사용하여 사전을 반복합니다.

private static void IterateThruDictionary()
{
    Dictionary<string, Element> elements = BuildDictionary();

    foreach (KeyValuePair<string, Element> kvp in elements)
    {
        Element theElement = kvp.Value;

        Console.WriteLine("key: " + kvp.Key);
        Console.WriteLine("values: " + theElement.Symbol + " " +
            theElement.Name + " " + theElement.AtomicNumber);
    }
}

public class Element
{
    public required string Symbol { get; init; }
    public required string Name { get; init; }
    public required int AtomicNumber { get; init; }
}

private static Dictionary<string, Element> BuildDictionary() =>
    new ()
    {
        {"K",
            new (){ Symbol="K", Name="Potassium", AtomicNumber=19}},
        {"Ca",
            new (){ Symbol="Ca", Name="Calcium", AtomicNumber=20}},
        {"Sc",
            new (){ Symbol="Sc", Name="Scandium", AtomicNumber=21}},
        {"Ti",
            new (){ Symbol="Ti", Name="Titanium", AtomicNumber=22}}
    };

다음 예제에서는 DictionaryContainsKey 메서드 및 Item[] 속성을 사용하여 키를 통해 항목을 신속하게 찾습니다. Item 속성을 사용하면 C#에서 elements[symbol]를 통해 elements 컬렉션의 항목에 액세스할 수 있습니다.

if (elements.ContainsKey(symbol) == false)
{
    Console.WriteLine(symbol + " not found");
}
else
{
    Element theElement = elements[symbol];
    Console.WriteLine("found: " + theElement.Name);
}

다음 예제에서는 대신 TryGetValue 메서드를 사용하여 키를 통해 항목을 빠르게 찾습니다.

if (elements.TryGetValue(symbol, out Element? theElement) == false)
    Console.WriteLine(symbol + " not found");
else
    Console.WriteLine("found: " + theElement.Name);

반복기

반복기는 컬렉션에 대해 사용자 지정 반복을 수행하는 데 사용됩니다. 반복기는 메서드 또는 get 접근자일 수 있습니다. 반복기는 yield return 문을 사용하여 한 번에 하나씩 컬렉션의 각 요소를 반환합니다.

foreach 문을 사용하여 반복기를 호출합니다. 각각의 foreach 루프의 반복이 반복기를 호출합니다. yield return 문이 반복기 메서드에 도달하면 식이 반환되고 코드에서 현재 위치는 유지됩니다. 다음에 반복기가 호출되면 해당 위치에서 실행이 다시 시작됩니다.

자세한 내용은 반복기(C#)를 참조하세요.

다음 예제에서는 반복기 메서드를 사용합니다. 반복기 메서드에는 yield return 루프 내에 있는 for 문이 있습니다. ListEvenNumbers 메서드에서 foreach 문 본문을 반복할 때마다 다음 yield return 문으로 진행하는 반복기 메서드에 대한 호출이 생성됩니다.

private static void ListEvenNumbers()
{
    foreach (int number in EvenSequence(5, 18))
    {
        Console.Write(number.ToString() + " ");
    }
    Console.WriteLine();
    // Output: 6 8 10 12 14 16 18
}

private static IEnumerable<int> EvenSequence(
    int firstNumber, int lastNumber)
{
    // Yield even numbers in the range.
    for (var number = firstNumber; number <= lastNumber; number++)
    {
        if (number % 2 == 0)
        {
            yield return number;
        }
    }
}

LINQ 및 컬렉션

LINQ(Language-Integrated Query)를 사용하여 컬렉션에 액세스할 수 있습니다. LINQ 쿼리는 필터링, 정렬 및 그룹화 기능을 제공합니다. 자세한 내용은 C#에서 LINQ 시작을 참조하세요.

다음 예제에서는 제네릭 List에 대해 LINQ 쿼리를 실행합니다. LINQ 쿼리는 결과를 포함하는 다른 컬렉션을 반환합니다.

private static void ShowLINQ()
{
    List<Element> elements = BuildList();

    // LINQ Query.
    var subset = from theElement in elements
                 where theElement.AtomicNumber < 22
                 orderby theElement.Name
                 select theElement;

    foreach (Element theElement in subset)
    {
        Console.WriteLine(theElement.Name + " " + theElement.AtomicNumber);
    }

    // Output:
    //  Calcium 20
    //  Potassium 19
    //  Scandium 21
}

private static List<Element> BuildList() => new()
    {
        { new(){ Symbol="K", Name="Potassium", AtomicNumber=19}},
        { new(){ Symbol="Ca", Name="Calcium", AtomicNumber=20}},
        { new(){ Symbol="Sc", Name="Scandium", AtomicNumber=21}},
        { new(){ Symbol="Ti", Name="Titanium", AtomicNumber=22}}
    };