CodeGym /행동 /C# SELF /.NET 표준 라이브러리의 인터페이스

.NET 표준 라이브러리의 인터페이스

C# SELF
레벨 24 , 레슨 3
사용 가능

1. 주요 인터페이스 분류

혹시 인터페이스가 아키텍트랑 "클린 코드" 좋아하는 사람들만 쓰는 장난감이라고 생각한다면, 놀라지 마! 진지하게 C# 프로젝트를 하다 보면 인터페이스에 푹 빠지게 돼. 왜냐면 .NET 표준 라이브러리의 거의 모든 부분이 인터페이스로 만들어져 있거든! 인터페이스 없으면 컬렉션도 못 쓰고, 파일도 못 읽고, LINQ로 필터링도 못 해.

인터페이스는 .NET에서 다형성과 확장성의 기본이야. 프레임워크의 모든 "블록"이 서로 잘 맞물리게 해주는 게 바로 인터페이스지.

.NET 표준 라이브러리는 진짜 인터페이스 투성이야. 이 바다에서 안 빠지려면, 아래처럼 분류해볼게 (물론 이게 다는 아니야 — 다 외우려면 인생이 모자라!):

카테고리 인터페이스 뭐에 쓰는 거야?
컬렉션
IEnumerable
,
IEnumerator
,
IList
,
ICollection
,
IDictionary
순회, 수정, 인덱스로 접근, 키-값 쌍 작업
리소스 작업
IDisposable
리소스 해제 (파일, 연결, 스트림)
비교
IComparable
,
IComparer
,
IEquatable
정렬, 비교, 객체의 유일성
직렬화
ISerializable
객체를 바이트 스트림으로 변환 (그리고 다시)
LINQ와 쿼리
IQueryable
,
IQueryProvider
복잡한 쿼리 지원 (예: DB 쿼리)
비동기
IAsyncEnumerable
,
IAsyncDisposable
비동기 순회 및 리소스 해제
이벤트와 알림
INotifyPropertyChanged
,
INotifyCollectionChanged
속성/컬렉션 변경에 반응
날짜와 시간
IFormattable
커스텀 문자열 포맷팅
자료구조
IStructuralComparable
,
IStructuralEquatable
컬렉션과 튜플의 깊은 비교
스트림/입출력
IStream
,
IAsyncDisposable
,
IObserver
,
IObservable
스트림 작업, push/pull 알림

중요! 위에 나온 많은 인터페이스들은 타입 파라미터를 써: List<string>. 보통 컬렉션이나 컬렉션 인터페이스에서 많이 써 Int[] → List<int>. 자세한 건 컬렉션 배울 때 다시 볼 거야.

2. 컬렉션 인터페이스

IEnumerableIEnumerator — foreach의 입장권

.NET의 거의 모든 컬렉션은 IEnumerable 또는 그 제네릭 버전 IEnumerable<T>을 구현해. 이 인터페이스 덕분에 foreach라는 마법 같은 문법을 쓸 수 있지:

List<int> numbers = new List<int> { 1, 2, 3 };
foreach (int n in numbers) // List<int>가 IEnumerable<int>를 구현해서 동작함
{
    Console.WriteLine(n);
}

이 인터페이스의 최소 버전은 이렇게 생겼어:

public interface IEnumerable
{
    IEnumerator GetEnumerator();
}

IEnumerator는 실제로 컬렉션을 돌아다니는 "순회자" 인터페이스야:

public interface IEnumerator
{
    bool MoveNext();
    object Current { get; }
    void Reset();
}

실제로는 진짜 자주 IEnumerable<T> 타입으로 파라미터를 넘기거나 값을 반환하게 될 거야. 예를 들어, 배열에서 짝수만 반환하는 메서드:

public IEnumerable<int> GetEvenNumbers(int[] array)
{
    foreach (var x in array)
        if (x % 2 == 0) yield return x;
}

맞아, yield 키워드가 마법을 부려서 인터페이스 구현을 대신 해줘. 이건 나중에 더 자세히 볼 거야.

ICollectionIList — 인덱스 접근과 수정 가능한 컬렉션

단순히 순회만 하는 게 아니라, 요소를 추가/삭제하거나 인덱스로 접근하고 싶으면 좀 더 특화된 인터페이스가 필요해:

  • ICollection<T>Add, Remove 메서드랑 Count 속성이 추가됨.
  • IList<T> — 인덱스 접근 (this[int index]) 등 더 많은 기능이 추가됨.
public void PrintFirstItem(IList<string> list)
{
    if (list.Count > 0)
        Console.WriteLine(list[0]);
}

List<T>IEnumerable<T>, ICollection<T>, IList<T>를 다 구현해. 그래서 단순히 순회만 하거나, 다양한 메서드를 다 쓸 수도 있어서 엄청 편해.

IDictionary<TKey, TValue> — "키-값" 쌍

딕셔너리 작업 좋아한다면 (진짜 자주 쓰지!), IDictionary<TKey, TValue> 인터페이스를 써. 이걸로 키로 값을 찾거나, 반대로도 가능해.

public void PrintAllPairs(IDictionary<string, int> ages)
{
    foreach (var pair in ages)
        Console.WriteLine($"{pair.Key}: {pair.Value}");
}

여기서 agesDictionary<string, int>일 수도 있고, SortedDictionary<string, int>일 수도 있어 — 중요한 건 둘 다 이 인터페이스를 구현한다는 거!

3. IDisposable 인터페이스: 리소스 제대로 다루기

모든 입출력, 파일 작업, 네트워크 연결, DB 작업은 .NET에서 IDisposable 인터페이스에 달려 있어. 이 인터페이스는 진짜 중요한 약속을 정해: 객체에 관리되지 않는 리소스가 있으면, 사용 후 "정리"해야 해. 맞아, 이 친구 덕분에 using 문법이 가능해진 거지:

using (StreamReader reader = new StreamReader("file.txt"))
{
    // 파일 작업, using 끝나면 파일이 무조건 닫힘!
    string line = reader.ReadLine();
}

인터페이스 자체는 엄청 간단해:

public interface IDisposable
{
    void Dispose();
}

근데 "진짜" 라이브러리들은 다 이걸 구현해! 더 좋은 사용법은 공식 문서 참고해봐.

4. 비교와 유일성을 위한 인터페이스

IComparable<T>IComparer<T>

컬렉션의 객체들을 정렬하고 싶으면, 객체끼리 서로 비교할 수 있어야 해. 그럴 때 IComparable<T> 인터페이스를 쓰는 거지:

public class Student : IComparable<Student>
{
    public string Name { get; set; }
    public int Score { get; set; }

    public int CompareTo(Student? other)
    {
        // 점수 내림차순 정렬
        if (other == null) return 1;
        return other.Score.CompareTo(this.Score);
    }
}

// 이제 학생들을 정렬할 수 있음:
var students = new List<Student> { ... };
students.Sort();

Microsoft 문서에서 더 보기.

IComparer<T>는 클래스 밖에서 비교 기준을 정할 수 있게 해줘 — 예를 들어, 학생을 이름으로 정렬하거나 점수로 정렬하고 싶을 때:

public class NameComparer : IComparer<Student>
{
    public int Compare(Student? x, Student? y)
    {
        return string.Compare(x?.Name, y?.Name);
    }
}

IEquatable<T> — 동등성 비교

객체가 HashSet<T>에 들어가서 (즉, 컬렉션에서 "유일"하게 취급되길) 원하면, IEquatable<T>를 구현해:

public class Person : IEquatable<Person>
{
    public string Name { get; set; }
    public bool Equals(Person? other)
    {
        if (other == null) return false;
        return this.Name == other.Name;
    }
}

5. 이벤트와 알림용 인터페이스

프로그램이 객체의 속성이 바뀌었는지 "알고" 싶었던 적 있어? 예를 들어, 사용자가 성을 바꾸면 UI가 자동으로 갱신되길 바란다면? 바로 그럴 때 INotifyPropertyChanged 인터페이스가 있어. 이건 WPF나 Xamarin 같은 GUI 앱에서 엄청 많이 써.

인터페이스 시그니처는 간단해:

public interface INotifyPropertyChanged
{
    event PropertyChangedEventHandler? PropertyChanged;
}

구현 예시:

public class User : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;

    private string name;
    public string Name
    {
        get => name;
        set
        {
            if (name != value)
            {
                name = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
            }
        }
    }
}

이제 이 객체에 바인딩된 UI는 Name이 바뀔 때마다 자동으로 갱신돼. 더 궁금하면 공식 문서 참고!

6. 그 외 유용한 인터페이스

IFormattable 인터페이스: 유연한 포맷팅

객체를 예쁘게, 다양한 방식으로 문자열로 출력하고 싶으면 (예: 소수점 자리수 다르게), IFormattable 인터페이스를 구현해:

public class Temperature : IFormattable
{
    public double Celsius { get; }
    public Temperature(double celsius) => Celsius = celsius;

    public string ToString(string? format, IFormatProvider? formatProvider)
    {
        // 복잡하게 안 하고, 포맷에 따라 'C' 또는 'F'로 보여줌
        if (format == "F")
            return $"{Celsius * 9 / 5 + 32} F";
        return $"{Celsius} C";
    }
}

이제 temp.ToString("F", null) 또는 temp.ToString("C", null)을 부를 수 있어. 그래서 DateTime, double, decimal 같은 내장 타입들이 유연한 포맷팅을 지원하는 거야.

비동기용 인터페이스

C#에 비동기가 도입되면서, 비동기 세계에서 쓸 새로운 인터페이스가 필요해졌어. 예를 들어, 객체가 비동기로 리소스를 해제해야 한다면 IAsyncDisposable을 구현해:

public interface IAsyncDisposable
{
    ValueTask DisposeAsync();
}

이제 using 대신 await using을 쓰면 돼!

비동기 열거 (await foreach)는 IAsyncEnumerable<T> 인터페이스로 동작해. 이걸로 메인 스레드를 막지 않고 요소를 순회할 수 있어. 예를 들어, 큰 파일을 부분적으로 읽거나, 인터넷에서 데이터 스트림을 받을 때 써. 자세한 건 58레벨에서 :P

직렬화 인터페이스: ISerializable

객체를 바이트 스트림으로 만들어 저장/전송(예: 네트워크로)해야 한다면, .NET은 ISerializable 인터페이스를 제공해. 요즘은 더 편한 방법(속성이나 내장 직렬화기)이 많아서 자주 쓰진 않지만, 언급할 가치는 있어. 시그니처 예시:

public interface ISerializable
{
    void GetObjectData(SerializationInfo info, StreamingContext context);
}

7. 인터페이스 실제 적용: 진짜 앱 예시

예를 들어, 도서관 책 관리 콘솔 앱을 만든다고 해보자 (누군가는 해야지!). 책 목록 출력, 정렬, 필터, 데이터 로드/저장 기능을 넣고 싶어. .NET 인터페이스를 알면 이렇게 할 수 있어:

  • 다양한 컬렉션 타입 반환IEnumerable<Book>로 반환하면, 사용자는 배열인지 리스트인지 신경 안 써도 돼.
  • 여러 기준으로 정렬 추가: IComparable<Book>로 제목 정렬, IComparer<Book>로 저자 정렬 구현.
  • 파일 작업 시 리소스 효율적 해제IDisposable로 처리.
  • 장르나 저자별로 책 필터링 — LINQ로, IEnumerable<T>만 구현하면 다 됨.
  • 앱 확장성 확보 — 예를 들어, 파일 저장소 대신 DB 연결로 바꿀 때, 클래스가 인터페이스(IBookStorage)로 동작하면 구현만 바꾸면 됨.

8. 흔한 실수와 주의점

초보들이 제일 많이 하는 실수 중 하나는 변수나 메서드 인자 선언할 때 구체적인 구현체("List")를 쓰는 거야. 인터페이스("IEnumerable", "IList")로 선언해야 코드가 유연하고 확장하기 쉬워. 기억해: "인터페이스로 프로그래밍"하면 코드가 훨씬 유연해져.

어떤 수준의 인터페이스를 쓸지 고를 때도 신경 써야 해 — 예를 들어, 단순히 순회만 필요하면 IEnumerable<T>만 쓰고, IList<T>까지 요구하지 않는 게 좋아. 괜히 제한을 늘리지 말자.

또 한 가지: 클래스에 인터페이스가 많고, 그 사이에 메서드나 속성이 겹치면, 명시적 인터페이스 구현(explicit implementation)을 써야 해. 안 그러면 컴파일러가 헷갈려.

코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION