[c#] IList <T> 반환이 T [] 또는 List <T> 반환보다 나쁘나요?

다음과 같은 질문에 대한 답변 : List <T> 또는 IList <T>는 항상 인터페이스를 반환하는 것이 컬렉션의 구체적인 구현을 반환하는 것보다 낫다는 데 동의하는 것 같습니다. 그러나 나는 이것으로 어려움을 겪고 있습니다. 인터페이스를 인스턴스화하는 것은 불가능하므로 메서드가 인터페이스를 반환하는 경우 실제로는 여전히 특정 구현을 반환합니다. 두 가지 작은 방법을 작성하여 이것을 약간 실험했습니다.

public static IList<int> ExposeArrayIList()
{
    return new[] { 1, 2, 3 };
}

public static IList<int> ExposeListIList()
{
    return new List<int> { 1, 2, 3 };
}

그리고 내 테스트 프로그램에서 사용하십시오.

static void Main(string[] args)
{
    IList<int> arrayIList = ExposeArrayIList();
    IList<int> listIList = ExposeListIList();

    //Will give a runtime error
    arrayIList.Add(10);
    //Runs perfectly
    listIList.Add(10);
}

두 경우 모두 새 값을 추가하려고 할 때 컴파일러에서 오류가 발생하지 않지만 배열을로 노출하는 메서드는 내가 IList<T>뭔가를 추가하려고 할 때 런타임 오류를 발생시킵니다. 그래서 내 방법에서 무슨 일이 일어나고 있는지 모르고 여기에 값 을 추가해야하는 사람들은 오류 위험없이 값을 추가 할 수 있도록 먼저 my IList를 a List에 복사해야 합니다. 물론 그들은과 그들이 경우있는 거 거래 볼 수있는 유형 체킹을 수행 할 수 있습니다 List또는를 Array하지만 그렇게하지 않으면, 그들은 컬렉션에 항목을 추가 할 그들이 다른 선택의 여지가 복사되지해야 IListA를 List하더라도, 이미List . 배열은 절대로 노출되지 않아야 IList합니까?

나의 또 다른 관심사는 연결된 질문 (강조 내) 에 대한 수용된 답변에 기반합니다 .

다른 사람들이 사용할 라이브러리를 통해 클래스를 노출하는 경우 일반적으로 구체적인 구현이 아닌 인터페이스를 통해 클래스 를 노출하려고합니다. 나중에 다른 구체적인 클래스를 사용하기 위해 클래스 구현을 변경하기로 결정한 경우 도움이됩니다. 이 경우 인터페이스가 변경되지 않으므로 라이브러리 사용자는 코드를 업데이트 할 필요가 없습니다.

내부적으로 만 사용하는 경우별로 신경 쓰지 않을 수 있으며 List를 사용해도 괜찮을 수 있습니다.

누군가가 실제로 값을 추가 / 제거하기 위해 IList<T>ExposeListIlist()방법 에서 얻은 내 방법을 사용했다고 상상해보십시오 . 모든 것이 잘 작동합니다. 그러나 이제 대답에서 알 수 있듯이 인터페이스를 반환하는 것이 더 유연하기 때문에 List 대신 배열을 반환합니다 (내쪽에는 문제가 없습니다!).

TLDR :

1) 인터페이스를 노출하면 불필요한 캐스트가 발생합니까? 상관 없어?

2) 때때로 라이브러리 사용자가 캐스트를 사용하지 않으면 메서드가 완벽하게 유지 되더라도 메서드를 변경할 때 코드가 손상 될 수 있습니다.

나는 아마도 이것을 지나치게 생각하고 있지만, 구현을 반환하는 것보다 인터페이스를 반환하는 것이 선호된다는 일반적인 합의를 얻지 못했습니다.



답변

이것이 귀하의 질문에 직접적으로 대답하는 것은 아니지만 .NET 4.5 이상에서는 공개 또는 보호 된 API를 설계 할 때 다음 규칙을 따르는 것이 좋습니다.

  • IEnumerable<T>열거 형 만 사용할 수있는 경우 return을 수행 합니다.
  • IReadOnlyCollection<T>열거 및 항목 수를 모두 사용할 수 있으면 반환 하십시오.
  • IReadOnlyList<T>열거, 항목 수 및 인덱싱 된 액세스를 사용할 수있는 경우 return을 수행 합니다.
  • ICollection<T>열거, 항목 수 및 수정이 가능하면 반환 하십시오.
  • IList<T>열거, 항목 수, 인덱싱 된 액세스 및 수정을 사용할 수있는 경우 반환 합니다.

마지막 두 옵션은 해당 메서드가 IList<T>구현으로 배열을 반환해서는 안된다고 가정합니다 .


답변

아니요, 소비자는 IList 가 정확히 무엇인지 알아야합니다 .

IList는 ICollection 인터페이스의 자손이며 모든 비 제네릭 목록의 기본 인터페이스입니다. IList 구현은 읽기 전용, 고정 크기 및 가변 크기의 세 가지 범주로 나뉩니다. 읽기 전용 IList는 수정할 수 없습니다. 고정 크기 IList는 요소의 추가 또는 제거를 허용하지 않지만 기존 요소의 수정은 허용합니다. 가변 크기 IList를 사용하면 요소를 추가, 제거 및 수정할 수 있습니다.

당신은 확인할 수 IList.IsFixedSizeIList.IsReadOnly당신이 그것으로 원하는 일을.

나는 IList팻 인터페이스의 예 라고 생각 하며 여러 개의 작은 인터페이스로 분할되어야 하며 배열을 .NET으로 반환 할 때 Liskov 대체 원칙을 위반 합니다 IList.

인터페이스 반환에 대한 결정을 내리려면 더 읽어보세요.


최신 정보

더 파고 나는 그 발견 IList<T>구현하지 않습니다 IListIsReadOnly기본 인터페이스를 통해 액세스 할 수 ICollection<T>있지만 거기 IsFixedSize에 대해 IList<T>. 제네릭 IList <>가 제네릭이 아닌 IList를 상속하지 않는 이유 에 대해 자세히 알아보십시오 .


답변

모든 “인터페이스 대 구현”질문과 마찬가지로 공용 멤버 노출이 의미하는 바를 이해해야합니다.이 클래스의 공용 API를 정의합니다.

당신이 노출되면 List<T>멤버 (필드, 재산, 방법, …)로, 해당 회원의 소비자에게이 방법을 액세스하여 얻은 유형 입니다List<T> 그의 유래, 또는 뭔가.

이제 인터페이스를 노출하면 구체적인 유형을 사용하여 클래스의 “구현 세부 정보”를 숨 깁니다. 물론 당신은 인스턴스화 할 수 없습니다 IList<T>,하지만 당신은을 사용할 수 있습니다 Collection<T>, List<T>, 유도 또는 그 자신의 유형 구현 IList<T>.

실제 질문은 “왜 않습니다 Array구현 IList<T> , 또는 “왜는이 IList<T>많은 회원 그래서 인터페이스를” .

또한 해당 구성원의 소비자가 원하는 작업에 따라 다릅니다. 실제로 회원을 통해 내부 회원을 반환하는 경우, 어쨌든를 반환하고 Expose...싶을 것 new List<T>(internalMember)입니다. 그렇지 않으면 소비자가이를 IList<T>통해 내부 회원을 캐스트 하고 수정할 수 있기 때문입니다.

소비자가 결과를 반복하기를 기대한다면 IEnumerable<T>또는 IReadOnlyCollection<T>대신 노출하십시오 .


답변

문맥에서 벗어난 담요 따옴표에주의하십시오.

인터페이스를 반환하는 것이 구체적인 구현을 반환하는 것보다 낫습니다.

이 인용문은 SOLID 원칙 의 맥락에서 사용되는 경우에만 의미가 있습니다 . 5 가지 원칙이 있지만이 논의의 목적을 위해 마지막 3 가지에 대해서만 이야기하겠습니다.

종속성 반전 원리

하나는“추상화에 의존해야합니다. 결심에 의존하지 마십시오.”

제 생각에는이 원칙이 가장 이해하기 어렵습니다. 그러나 견적을주의 깊게 살펴보면 원래 견적과 매우 흡사합니다.

인터페이스 (추상화)에 따라 다릅니다. 구체적인 구현 (콘크리트)에 의존하지 마십시오.

이것은 여전히 ​​약간 혼란 스럽지만 다른 원칙을 함께 적용하기 시작하면 훨씬 더 이해하기 시작합니다.

Liskov 대체 원리

“프로그램의 객체는 해당 프로그램의 정확성을 변경하지 않고 하위 유형의 인스턴스로 대체 할 수 있어야합니다.”

지적했듯이을 반환하는 Array것은 List<T>둘 다 구현하더라도 a를 반환하는 것과 분명히 다른 동작 IList<T>입니다. 이것은 가장 확실한 LSP 위반입니다.

깨달아야 할 중요한 것은 인터페이스가 소비자 에 관한 것이라는 것 입니다. 인터페이스를 반환하는 경우 프로그램의 동작을 변경하지 않고 해당 인터페이스의 모든 메서드 또는 속성을 사용할 수 있다는 계약을 만든 것입니다.

인터페이스 분리 원칙

“많은 클라이언트 별 인터페이스가 하나의 범용 인터페이스보다 낫습니다.”

인터페이스를 반환하는 경우 구현에서 지원하는 가장 클라이언트 별 인터페이스를 반환해야합니다. 즉, 클라이언트가 Add메서드 를 호출 할 것으로 기대하지 않는 경우 메서드가있는 인터페이스를 반환해서는 안됩니다 Add.

안타깝게도 .NET 프레임 워크 (특히 초기 버전)의 인터페이스가 항상 이상적인 클라이언트 특정 인터페이스는 아닙니다. @Dennis가 그의 답변에서 지적했듯이 .NET 4.5 이상에는 더 많은 선택 사항이 있습니다.


답변

인터페이스를 반환하는 것이 컬렉션의 구체적인 구현을 반환하는 것보다 반드시 낫지는 않습니다. 구체적인 유형 대신 인터페이스를 사용해야하는 좋은 이유가 항상 있어야합니다. 귀하의 예에서는 그렇게하는 것이 무의미한 것 같습니다.

인터페이스를 사용하는 유효한 이유는 다음과 같습니다.

  1. 인터페이스를 반환하는 메서드의 구현이 어떻게 생겼는지 알 수 없으며 시간이 지남에 따라 개발 될 수 있습니다. 다른 회사에서 작성한 다른 사람 일 수 있습니다. 따라서 필요한 사항에 동의하고 기능을 구현하는 방법은 그들에게 맡기면됩니다.

  2. 유형이 안전한 방식으로 클래스 계층 구조와 독립적 인 몇 가지 공통 기능을 노출하려고합니다. 동일한 메서드를 제공해야하는 다른 기본 유형의 개체는 인터페이스를 구현합니다.

1과 2는 기본적으로 같은 이유라고 주장 할 수 있습니다. 궁극적으로 동일한 요구로 이어지는 두 가지 시나리오입니다.

“계약입니다”. 계약이 본인과 있고 애플리케이션이 기능과 시간 모두에서 종료 된 경우 인터페이스를 사용할 필요가없는 경우가 많습니다.


답변