다음을 통해 공유


인터페이스 - 여러 형식에 대한 동작 정의

팁 (조언)

소프트웨어 개발이 새로운가요? 먼저 시작하기 자습서부터 시작하세요. 서로 관련 없는 형식 간에 공동 동작을 정의해야 하는 경우 인터페이스를 사용하게 됩니다.

다른 언어로 경험하신 적 있나요? C# 인터페이스는 Java 인터페이스 또는 Swift의 프로토콜과 유사합니다. C#관련 패턴에 대한 명시적 구현 섹션을 건너 들이세요.

인터페이스는 계약을 정의합니다. 즉, class 또는 struct가 구현해야 할 관련 메서드, 속성, 이벤트 및 인덱서의 그룹입니다. 인터페이스를 사용하면 C#에서 여러 클래스 상속을 지원하지 않으므로 단일 형식이 여러 계약을 구현할 수 있습니다. 구조체는 다른 구조체 또는 클래스에서 상속할 수 없으므로 인터페이스는 구조체 형식 간에 공유 동작을 추가하는 유일한 방법입니다.

다음 예제에서는 인터페이스와 인터페이스를 구현하는 클래스를 선언합니다.

interface IEquatable<T>
{
    bool Equals(T obj);
}
public class Car : IEquatable<Car>
{
    public string? Make { get; set; }
    public string? Model { get; set; }
    public string? Year { get; set; }

    public bool Equals(Car? car) =>
        car is not null &&
        (Make, Model, Year) == (car.Make, car.Model, car.Year);
}

구현하는 모든 클래스 또는 구조체는 IEquatable<T> 인터페이스 서명과 Equals 일치하는 메서드를 제공해야 합니다. 구현 IEquatable<T>는 구체적인 형식에 관계없이 동등성 비교를 지원한다고 믿을 수 있습니다. 이러한 예측 가능성은 인터페이스의 핵심 값입니다.

인터페이스 선언

키워드를 사용하여 인터페이스를 정의합니다 interface . 규칙에 따라 인터페이스 이름은 대문 I자로 시작합니다.

interface ILogger
{
    void Log(string message);
    string Name { get; }
}

인터페이스에는 메서드, 속성, 이벤트 및 인덱서가 포함될 수 있습니다. 인터페이스에는 인스턴스 필드, 인스턴스 생성자 또는 종료자가 포함될 수 없습니다. 멤버는 기본적으로 public 상태입니다. 필요한 경우 다른 접근성 한정자를 지정할 수 있습니다. 예를 들어 어셈블리 외부에 표시되지 않아야 하는 멤버에 사용합니다 internal .

인터페이스 구현

클래스 또는 구조체는 선언에서 콜론 다음에 구현하는 인터페이스를 나열합니다. 클래스는 인터페이스에 선언된 모든 멤버에 대한 구현을 제공해야 합니다.

public class ConsoleLogger : ILogger
{
    public string Name => "Console";

    public void Log(string message) =>
        Console.WriteLine($"[{Name}] {message}");
}

public class FileLogger : ILogger
{
    public string Name => "File";

    public void Log(string message)
    {
        // In a real app, write to a file
        Console.WriteLine($"[{Name}] Writing to file: {message}");
    }
}

클래스는 쉼표로 구분된 여러 인터페이스를 구현할 수 있습니다. 나열된 모든 인터페이스의 모든 멤버에 대한 구현을 제공해야 합니다.

명시적 구현

클래스의 공용 API에 속하지 않고 인터페이스 멤버를 구현해야 하는 경우도 있습니다. 명시적 구현은, 멤버를 인터페이스 이름으로 한정합니다. 멤버는 인터페이스 형식의 변수를 통해서만 액세스할 수 있습니다.

interface IMetric
{
    double GetDistance(); // Returns meters
}

interface IImperial
{
    double GetDistance(); // Returns feet
}

public class Runway(double meters) : IMetric, IImperial
{
    // Explicit implementation for IMetric
    double IMetric.GetDistance() => meters;

    // Explicit implementation for IImperial
    double IImperial.GetDistance() => meters * 3.28084;
}

명시적 구현은 두 인터페이스가 같은 이름의 멤버를 선언하거나 클래스의 공용 표면을 깨끗하게 유지하려는 경우에 유용합니다. 자세한 내용은 명시적 인터페이스 구현을 참조하세요.

인터페이스 상속

인터페이스는 하나 이상의 인터페이스에서 상속할 수 있습니다. 파생 인터페이스를 구현하는 클래스는 파생 인터페이스의 모든 멤버와 모든 기본 인터페이스를 구현해야 합니다.

interface IDrawable
{
    void Draw();
}

interface IShape : IDrawable
{
    double Area { get; }
}

public class Circle(double radius) : IShape
{
    public double Area => Math.PI * radius * radius;

    public void Draw() =>
        Console.WriteLine($"Drawing circle with area {Area:F2}");
}

IShape를 구현하는 클래스는 IShapeIShape를 상속하므로 IDrawable로 암시적으로 변환될 수 있습니다.

인터페이스 및 추상 클래스

인터페이스 및 추상 클래스는 파생 형식이 충족해야 하는 계약을 정의합니다.

  • 관련 형식이 상태(필드), 생성자 또는 public이 아닌 멤버를 공유하는 경우 추상 클래스를 사용합니다. 추상 클래스를 사용하면 기존 파생 형식을 중단하지 않고 기본 동작으로 새 멤버를 추가하여 계층 구조를 발전시킬 수 있습니다.
  • 타입이 관계없는 여러 계층에 걸쳐 있는 계약을 준수해야 하거나 여러 계약을 구현해야 할 때 인터페이스를 사용합니다. 인터페이스는 인스턴스 필드 또는 생성자를 선언할 수 없으므로 이미 기본 클래스가 있는 형식에 기능을 추가하는 데 가장 적합합니다. 고급 시나리오의 경우 인터페이스는 기본 멤버 구현도 지원합니다.

클래스는 하나의 기본 클래스에서만 상속할 수 있지만 여러 인터페이스를 구현할 수 있습니다. 그러한 차이점은 종종 인터페이스가 형식 계층을 넘나드는 능력을 정의하는 데 더 나은 선택이 되게 합니다.

내부 인터페이스 작업

인터페이스 서명의 모든 형식에 공개적으로 액세스할 수 있는 한 일반적으로 공용 멤버를 사용하여 내부 인터페이스를 구현할 수 있습니다. 인터페이스가 멤버 서명에서 내부 형식을 사용하는 경우 구현 멤버는 내부 형식을 노출하는 동안 공용일 수 없으므로 명시적 구현을 사용해야 합니다.

internal class InternalConfiguration
{
    public string Setting { get; set; } = "";
}

internal interface ILoggable
{
    void Log(string message);
}

internal interface IConfigurable
{
    void Configure(InternalConfiguration config);
}

public class ServiceImplementation : ILoggable, IConfigurable
{
    // Implicit implementation: ILoggable uses only public types in its signature
    public void Log(string message) =>
        Console.WriteLine($"Log: {message}");

    // Explicit implementation: IConfigurable uses internal types
    void IConfigurable.Configure(InternalConfiguration config) =>
        Console.WriteLine($"Configured with: {config.Setting}");
}

앞의 예제 IConfigurable 에서는 메서드 서명에 내부 형식 InternalConfiguration 을 사용합니다. ServiceImplementation 는 해당 멤버에 대해 명시적 구현을 사용합니다. 반면, ILoggable 해당 서명에 public 형식(string)만 사용하며 암시적으로 구현할 수 있습니다.

기본 인터페이스 멤버 및 정적 추상 멤버

인터페이스는 기본 계약을 넘어서는 두 가지 고급 기능을 지원합니다.

  • 기본 인터페이스 멤버 를 사용하면 인터페이스가 메서드 본문을 제공할 수 있습니다. 형식 구현은 기본 구현을 상속하며 필요에 따라 재정의할 수 있습니다. 자세한 내용은 기본 인터페이스 메서드를 참조하세요.
  • 정적 추상 멤버 는 정적 멤버를 제공하기 위해 형식을 구현해야 하며 이는 연산자 계약 또는 팩터리 패턴을 정의하는 데 유용합니다. 자세한 내용은 인터페이스의 정적 추상 멤버를 참조하세요.

두 기능 모두 언어 참조의 인터페이스 에 대한 문서에서 다룹니다. 대부분의 일상적인 인터페이스 사용에는 이 문서의 앞부분에서 설명한 선언 및 구현 패턴이 포함됩니다.

인터페이스 요약

  • 인터페이스는 메서드, 속성, 이벤트 및 인덱서의 계약을 정의합니다.
  • 인터페이스를 구현하는 클래스 또는 구조체는 선언된 모든 멤버에 대한 구현을 제공해야 합니다(인터페이스가 기본 구현을 제공하지 않는 한).
  • 인터페이스를 직접 인스턴스화할 수 없습니다.
  • 클래스 또는 구조체는 여러 인터페이스를 구현할 수 있습니다. 클래스는 기본 클래스를 상속할 수 있으며 하나 이상의 인터페이스를 제공할 수도 있습니다.
  • 인터페이스 이름은 일반적으로 .로 I시작합니다.