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


80

다음과 같은 질문에 대한 답변 : 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) 때때로 라이브러리 사용자가 캐스트를 사용하지 않으면 메서드가 완벽하게 유지 되더라도 메서드를 변경할 때 코드가 손상 될 수 있습니다.

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


9
그런 다음 돌아 가면 IEnumerable<T>컴파일 시간이 안전합니다. 일반적으로 성능을 특정 형식으로 캐스팅하여 성능을 최적화하려는 모든 LINQ 확장 메서드를 계속 사용할 수 있습니다 ( 열거하는 대신 속성 ICollection<T>을 사용하는 것과 유사 함 Count).
Tim Schmelter 2015

4
배열 IList<T>은 "핵"에 의해 구현 됩니다. 반환하는 메서드를 노출하려면 실제로 구현하는 형식을 반환합니다.
CodeCaster

3
지킬 수없는 약속은하지 마십시오. 클라이언트 프로그래머는 "I 'll return a list"계약서를 읽을 때 실망 할 가능성이 높습니다. 그렇게하지 마세요.
Hans Passant

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

3
@AlexanderDerck : 소비자가 목록에 항목을 추가 할 수 있도록 IEnumerable<T>하려면 애초에 만들지 마십시오 . 그런 다음 IList<T>또는 List<T>.
Tim Schmelter 2015

답변:


108

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

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

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


2
실제로 대답은 아니지만, 좋은 경험 법칙과 나에게 매우 도움이되었습니다. :) 최근에 무엇을 반환할지 선택하는 데 많은 어려움을 겪고 있습니다. 건배!
Alexander Derck

1
이것은 참으로 올바른 원칙 +1입니다. 완전성을 위해 추가 IReadOnlyCollection<T>하고 ICollection<T>색인화 할 수없는 컨테이너에 대한 사용법을 포함합니다 (예를 들어 나중에는 사실상 EF 엔티티 하위 컬렉션 탐색 속성의 표준이 됨)
Ivan Stoev

@IvanStoev 추가 된 답변을 편집 할 수 있습니까? 나는 마침내 어떤 인터페이스를 사용하는 곳의 목록을 얻을 수 있다면 그것은 좋은 것입니다
알렉산더 Derck

3
또한 주목되어야 IReadOnlyCollection<T>하고 IReadOnlyList<T>는 C ++의이 같은 결함이 const수행한다. 두 가지 가능성이 있습니다. 컬렉션이 변경 불가능하거나 컬렉션에 대한 액세스 방법으로 만 컬렉션을 수정할 수 없습니다. 그리고 서로 말할 수 없습니다.
ACH

1
@Panzercrisis : 물론 두 번째 옵션을 선택합니다 IEnumerable<T>. API 개발자는 허용되는 사용 사례에 대한 완전한 지식을 가지고 있습니다. 메서드 결과가 열거 형으로 만 의도 된 경우을 노출 할 이유가 없습니다 IList<T>. 결과로 할 수있는 일이 적을수록 메서드 구현이 더 유연 해집니다. 예를 들어, 노출 IEnumerable<T>은 구현을 yield returns 로 변경할 IList<T>수 있지만 허용하지 않습니다.
Dennis

31

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

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

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

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

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


최신 정보

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


8
이러한 속성은 좋지만 제 생각에는 인터페이스의 목적을 여전히 훼손합니다. 나를 위해, 인터페이스는 수표 또는 무엇에 의존하지 말아야 계약이다
알렉산더 Derck

1
(고정되지 않은 크기 / 읽기 전용이 아닌) List<T>의미 체계 를 가진 추가 인터페이스를 제공했다면 개발자에게 정말 친절했을 것입니다 . 그러나 이것이 포함되지 않은 이유에 대한 합리적인 이유가있을 것입니다 .... 하지만 예, 다양한 기능을 다루는 여러 인터페이스에 대한 아이디어가 훨씬 더 건전한 선택이 될 것이라고 생각합니다.
지출

1
IList<T>팻 인터페이스이지만 배열 목록 에서 얻을 수있는 가장 많은 것 입니다. 따라서 여러 인터페이스로 나뉘었다면 사용하려는 모든 메서드가 지원 되더라도 (예 : IList.Count또는 인덱서) 목록 및 배열과 함께 일부 메서드를 사용할 수 없지만 그중 하나만 사용할 수 있습니다 . 그래서 제 생각에는 그것은 좋은 결정이었습니다. 어레이에서 NotSupportedException사용하고 싶은 경우가 거의 없다면 그것이 Add거래입니다.
Tim Schmelter 2015

1
@dotctor 우연의 일치, 어제 Liskov 대체 원칙에 대해 읽었으며 실제로 생각하게했습니다. :) 답변 주셔서 감사합니다
Alexander Derck

6
이 답변은 Dennis 규칙과 결합되어이 딜레마를 처리하는 방법에 대한 이해를 확실히 향상시킬 수 있습니다. 말할 필요도없이 Liskov 대체 원칙은 .NET 프레임 워크에서 여기 저기 위반되며 특별히 걱정할 필요가 없습니다.
Fabjan

13

모든 "인터페이스 대 구현"질문과 마찬가지로 공용 멤버 노출이 의미하는 바를 이해해야합니다.이 클래스의 공용 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>대신 노출하십시오 .


3
"실제 질문은"Array가 IList <T>를 구현하는 이유 "또는"왜 IList <T> 인터페이스가 많은 멤버를 가지고 있는가 "입니다."이는 실제로 제가 고심하고있는 부분입니다.
Alexander Derck

2
@Fabjan- IList<T>항목을 추가하고 제거하는 방법이 있으므로 배열을 구현하는 것이 좋지 않습니다. 이 IsReadOnly속성은 (적어도) 두 개의 인터페이스로 분할되어야 할 때 차이점을 간과하기위한 해킹입니다.
Lee

@Lee Whoops, 맞습니다, 부정확 한 댓글 삭제
Fabjan dec.

1
내가 이전에 요청했습니다 그것의 어떤 @AlexanderDerck stackoverflow.com/q/5968708/706456
oleksii

@oleksii 그것은 참으로 비슷하지만, 나는 그것을 중복 호출하지 것이다
알렉산더 Derck

8

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

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

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

종속성 반전 원리

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

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

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

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

Liskov 대체 원리

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

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

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

인터페이스 분리 원칙

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

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

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


Liskov의 원칙에 최대 읽기 나를 위해 처음에 혼동 걸 얻었습니다IList<T>
알렉산더 Derck

특별한 경우에는 LSP를 위반하는 것은 Array입니다. IList를 반환하는 경우이 위반이 발생하면 배열을 반환해서는 안됩니다. 항목을 추가 및 제거 할 수있는 목록을 다시 받고 있다는 호출자와 이미 계약을 맺었습니다.
craftworkgames

5

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

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

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

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

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

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


1
나는 정말로 동의하지 않는다. 인터페이스를 반환하는 것이 가능하다면 나는 그것이 필요한 유일한 것이므로 인터페이스를 반환 할 것이다. 메서드가 인터페이스를 예상하면 해당 인터페이스를 구현하는 클래스 만 전달할 수 있지만 인터페이스를 구현하는 클래스와 다른 것의로드도 전달할 수 있습니다. 인터페이스로는 기존의 일을 다시 작성하지 않고 응용 프로그램을 확장하는 것이 더 쉽습니다
알렉산더 Derck에게

2
"다른 사람이이 클래스 (라이브러리)를 사용한다면 어떨까요?"라는 사고 방식으로 모든 소프트웨어 (특히 클래스 및 클래스 라이브러리)를 개발하는 것은 정말 깨달을 수 있습니다. 및 "반환 할 구체적인 유형에 기능을 추가하려면 어떻게해야합니까?" . BCL 유형은 봉인 될 수 있으므로 확장 할 수 없습니다. 그런 다음 API가 인터페이스가 아닌 정확한 유형을 노출하겠다고 약속하기 때문에 다른 것을 반환하려면 멈춰 있습니다. 인터페이스를 반환하는 것은 거의 항상 구체적인 구현을 반환하는 것보다 낫습니다.
CodeCaster

1
나는 (정말로) 동의한다. "발신자에게 메시지를 명확히하기 위해 반환 된 객체의 기능을 본질로 제한"하는 세 번째 이유를 추가 했어야했다. 나도 컬렉션을 어셈블하는 데 사용되는 본격적인 개체보다는 가능할 때마다 IReadOnlyXxx 인터페이스를 반환합니다.
Martin Maat
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.