IList 또는 List를 사용하는 이유는 무엇입니까?


82

나는 이것에 대한 많은 게시물이 있다는 것을 알고 있지만 여전히 IList와 같은 인터페이스를 전달하고 구체적인 목록 대신 IList와 같은 인터페이스를 반환해야하는 이유를 혼란스럽게합니다.

나는 이것이 어떻게 나중에 구현을 변경하는 것을 더 쉽게 만드는지에 대한 많은 게시물을 읽었지만 그것이 어떻게 작동하는지 완전히 보지 못했습니다.

이 방법이 있는지 말해봐

  public class SomeClass
    {
        public bool IsChecked { get; set; }
    }

 public void LogAllChecked(IList<SomeClass> someClasses)
    {
        foreach (var s in someClasses)
        {
            if (s.IsChecked)
            {
                // log 
            }
        }
    }

IList를 사용하면 앞으로 어떻게 도움이 될지 모르겠습니다.

이미 방법에있는 경우에는 어떻습니까? 여전히 IList를 사용해야합니까?

public void LogAllChecked(IList<SomeClass> someClasses)
    {
        //why not List<string> myStrings = new List<string>()
        IList<string> myStrings = new List<string>();

        foreach (var s in someClasses)
        {
            if (s.IsChecked)
            {
                myStrings.Add(s.IsChecked.ToString());
            }
        }
    }

지금 IList를 사용하면 무엇을 얻을 수 있습니까?

public IList<int> onlySomeInts(IList<int> myInts)
    {
        IList<int> store = new List<int>();
        foreach (var i in myInts)
        {
            if (i % 2 == 0)
            {
                store.Add(i);
            }
        }

        return store;
    }

지금은 어때? 변경해야 할 int 목록의 새로운 구현이 있습니까?

기본적으로 IList를 사용하여 List를 모든 것에 적용하는 것보다 몇 가지 문제를 해결하는 방법에 대한 실제 코드 예제를 확인해야합니다.

내 독서에서 나는 물건을 반복하고 있기 때문에 IList 대신 IEnumberable을 사용할 수 있다고 생각합니다.

편집 그래서 나는 이것을하는 방법에 대한 몇 가지 방법을 가지고 놀았습니다. 여전히 반환 유형에 대해 잘 모르겠습니다 (더 구체적으로 또는 인터페이스로 만들어야하는 경우).

 public class CardFrmVm
    {
        public IList<TravelFeaturesVm> TravelFeaturesVm { get; set; }
        public IList<WarrantyFeaturesVm> WarrantyFeaturesVm { get; set; }

        public CardFrmVm()
        {
            WarrantyFeaturesVm = new List<WarrantyFeaturesVm>();
            TravelFeaturesVm = new List<TravelFeaturesVm>();
        }
}

 public class WarrantyFeaturesVm : AvailableFeatureVm
    {
    }

 public class TravelFeaturesVm : AvailableFeatureVm
    {
    }

 public class AvailableFeatureVm
    {
        public Guid FeatureId { get; set; }
        public bool HasFeature { get; set; }
        public string Name { get; set; }
    }


        private IList<AvailableFeature> FillAvailableFeatures(IEnumerable<AvailableFeatureVm> avaliableFeaturesVm)
        {
            List<AvailableFeature> availableFeatures = new List<AvailableFeature>();
            foreach (var f in avaliableFeaturesVm)
            {
                if (f.HasFeature)
                {
                                                    // nhibernate call to Load<>()
                    AvailableFeature availableFeature = featureService.LoadAvaliableFeatureById(f.FeatureId);
                    availableFeatures.Add(availableFeature);
                }
            }

            return availableFeatures;
        }

이제 다음과 같은 속성을 가진 도메인 모델에 이것을 추가한다는 간단한 사실에 대해 IList를 반환합니다.

public virtual IList<AvailableFeature> AvailableFeatures { get; set; }

위의 내용은 nhibernate와 함께 사용하는 표준으로 보이는 IList 자체입니다. 그렇지 않으면 IEnumberable을 반환했을 수도 있지만 확실하지 않습니다. 그래도 사용자가 100 % 필요로하는 것이 무엇인지 알 수 없습니다 (콘크리트를 반환하는 것이 유리한 부분입니다).

편집 2

또한 내 메서드에서 참조로 전달하려면 어떻게되는지 생각하고있었습니다.

private void FillAvailableFeatures(IEnumerable<AvailableFeatureVm> avaliableFeaturesVm, IList<AvailableFeature> toFill)
            {

                foreach (var f in avaliableFeaturesVm)
                {
                    if (f.HasFeature)
                    {
                                                        // nhibernate call to Load<>()
                        AvailableFeature availableFeature = featureService.LoadAvaliableFeatureById(f.FeatureId);
                        toFill.Add(availableFeature);
                    }
                }
            }

이것에 문제가 생길까요? 그들은 (고정 된 크기를 가진) 배열을 전달할 수 없기 때문에? 구체적인 목록이 더 좋을까요?

답변:


156

여기에는 세 가지 질문이 있습니다. 형식 매개 변수에 어떤 유형을 사용해야합니까? 지역 변수에는 무엇을 사용해야합니까? 반환 유형에는 무엇을 사용해야합니까?

공식 매개 변수 :

여기서 원칙은 필요한 것 이상을 요구하지 않는 것 입니다. IEnumerable<T>"이 시퀀스의 요소를 처음부터 끝까지 가져와야합니다."를 전달합니다. IList<T>"이 시퀀스의 요소를 임의의 순서로 가져와 설정해야합니다."를 전달합니다. List<T>"이 시퀀스의 요소를 임의의 순서로 가져오고 설정해야하며 목록 만 허용합니다. 배열은 허용하지 않습니다."

당신이 필요로하는 것보다 더 많은 것을 요구함으로써, 당신은 (1) 당신의 불필요한 요구를 충족시키기 위해 발신자가 불필요한 일을하도록하고, (2) 독자에게 거짓을 전달합니다. 무엇을 사용할 것인지 만 물어보십시오. 이렇게하면 호출자가 시퀀스가있는 경우 요청을 충족하기 위해 ToList를 호출 할 필요가 없습니다.

지역 변수 :

원하는 것을 사용하십시오. 당신의 방법입니다. 메소드의 내부 구현 세부 사항을 볼 수있는 사람은 당신뿐입니다.

반환 유형 :

이전과 같은 원칙이 반대입니다. 발신자가 요구하는 최소한의 정보를 제공합니다. 호출자가 시퀀스를 열거 할 수있는 기능 만 필요하면 IEnumerable<T>.


9
발신자에게 필요한 것이 무엇인지 어떻게 알 수 있습니까? 예를 들어 내 반환 유형 중 하나를 IList <>로 전환 한 다음 어쨌든 열거 할 것입니다. IEnumberable을 반환 할 수 있습니다. 그런 다음 내 뷰 (mvc)를 살펴본 결과 for 루프를 사용해야하므로 실제로 count 메서드가 필요하다는 것을 알았습니다. 그래서 내 자신의 응용 프로그램에서 실제로 필요한 것이 무엇인지 다른 사람이 필요로하거나 필요로하지 않는 것을 어떻게 예상합니까?
chobo2

@ chobo2 : 귀하의 특정 예에서 LINQ Count는 O (1)에서 작동 IEnumerable합니다 ICollection. 물론 foreach루프를 사용할 수도 있습니다 .
Brian

6
@ chobo2 : 음, 호출자에게 필요한 메서드를 어떻게 예상 합니까? 먼저 해결해야 할 문제인 것 같습니다. 아마도 당신은 그들을 부를 사람들을 위해 어떤 방법을 쓸지 아는 방법을 가지고있을 것입니다. 그 사람들에게 반환 할 방법이 무엇인지 물어보십시오. 귀하의 질문은 근본적으로 "작성할 소프트웨어를 어떻게 알 수 있습니까?"입니다. 고객이 해결해야하는 문제를 파악하고 문제를 해결하는 코드를 작성함으로써 알 수 있습니다.
Eric Lippert

1
@Eric-항목을 추가하기 만하면되는 ICollection의 예를 포함하도록 형식 매개 변수에 대한 답변을 업데이트해야합니다. 또한 반환 유형에 대한 설명은 '호출자가 수행 할 수있는 최소한의 것만 제공합니다'라는 줄을 따라 말해야합니다. 읽기 전용 목록이 IEnumerable 등 만 반환하는 경우
Charles Lambert

4
@kvb : 거의. 첫 번째 시나리오를 고려하십시오. 수행하여 알고리즘을 개선 할 수 있으며 items as IList<T>null이 아닌 경우 개선 된 코드를 사용합니다. 당신이 할 수있는 경우 여전히이 전달 무엇의 클라이언트 유연성을 제공하면서, 장점을 그런 식으로.
에릭 Lippert의

33

내가 본 가장 실용적인 이유는 C #을 통해 CLR에서 Jeffrey Richter가 제공 한 것입니다.

패턴은 인수에 대해 가능한 기본 클래스 또는 인터페이스 를 취하고 반환 유형에 대해 가능한 가장 구체적인 클래스 또는 인터페이스 를 반환하는 것입니다. 이렇게하면 호출자가 메서드에 형식을 전달할 때 가장 유연하고 반환 값을 캐스팅 / 재사용 할 수있는 가장 많은 기회를 얻을 수 있습니다.

예를 들어, 다음 방법

public void PrintTypes(IEnumerable items) 
{ 
    foreach(var item in items) 
        Console.WriteLine(item.GetType().FullName); 
}

enumerable로 형변환 할 수있는 모든 유형을 전달하여 메서드를 호출 할 수 있습니다 . 좀 더 구체적이라면

public void PrintTypes(List items)

그런 다음 배열이 있고 해당 유형 이름을 콘솔에 인쇄하려는 경우 먼저 새 목록을 만들고 유형으로 채워야합니다. 그리고 일반 구현을 사용한 경우 특정 유형의 개체 에만 모든 개체에 대해 작동하는 메서드를 사용할 수 있습니다 .

반환 유형에 대해 이야기 할 때 더 구체적 일수록 호출자가 더 유연하게 사용할 수 있습니다.

public List<string> GetNames()

이 반환 유형을 사용하여 이름을 반복 할 수 있습니다.

foreach(var name in GetNames())

또는 컬렉션에 직접 색인을 생성 할 수 있습니다.

Console.WriteLine(GetNames()[0])

반면에 덜 구체적인 유형으로 돌아 가면

public IEnumerable GetNames()

첫 번째 값을 얻으려면 반환 유형을 마사지해야합니다.

Console.WriteLine(GetNames().OfType<string>().First());

10
이 조언은 반환 유형에 대한 권장 사항에서 Eric Lippert의 답변과 모순됩니다. Jeffrey Richter의 접근 방식은 메서드의 소비자에게 반환 된 개체를 원하는대로 사용할 수있는 가장 큰 유연성을 제공하는 반면 Eric 's는 메서드의 관리자에게 메서드의 공개 표면을 수정하지 않고 구현을 변경할 수있는 가장 큰 유연성을 제공합니다. 나는 내부 코드에 대한 Jeffrey의 조언을 따르는 경향이 있지만, 공공 도서관의 경우 아마도 Eric을 따르는 경향이있을 것입니다.
phoog

3
@phoog : Eric이 어디에서 왔는지 고려할 때 그가 변경 사항을 깨는 것에 대해 더 신중한 것은 놀라운 일이 아닙니다. 그러나 그것은 확실히 유효한 포인트입니다.

매우 힘든. 양측 모두 사람이있는 것 같습니다. 불필요한 변환을 막는 데 도움이되는 매개 변수에 대해 수행하는 이유를 더 많이 이해합니다. 여전히 반환 유형으로 무엇을해야할지 잘 모르겠습니다.
chobo2

@ chobo2 : 정말, 일반적이지 않고 더 정확한 것을 지정하면 나중에 반환 유형을 변경해야할지 고려할 필요가 있습니다. 메서드를 고려할 수 있다면 반환 컬렉션 유형을 변경하지 않을 것임을 확인한 다음 더 정확한 유형을 반환하는 것이 안전 할 수 있습니다. 확실하지 않거나 나중에 변경하면 다른 사람의 코드를 깨뜨릴 까봐 두렵다면 좀 더 일반적으로 가십시오.

1
+1 퀴즈 :이 답변은 Postel의 법칙에 대한 멋진 CLR 관련 예입니다 . :)
Dan J

13

IEnumerable<T>컬렉션을 반복 할 수 있습니다. ICollection<T>이를 기반으로 항목을 추가하고 제거 할 수도 있습니다. IList<T>또한 특정 색인에서 액세스하고 수정할 수 있습니다. 소비자가 작업 할 것으로 기대하는 것을 노출함으로써 자유롭게 구현을 변경할 수 있습니다. List<T>이 세 가지 인터페이스를 모두 구현합니다.

당신이 당신의 재산을 List<T>또는 심지어 IList<T>당신이 원하는 것은 컬렉션을 반복하는 능력뿐입니다. 그런 다음 목록을 수정할 수 있다는 사실에 의존하게됩니다. 당신이에서 실제 데이터 저장소를 전환하기로 결정한 경우 나중에 List<T>A와 Dictionary<T,U>속성에 대한 실제 값으로 사전 키와 노출 (I 정확히이 전에 수행해야했다). 그러면 변경 사항이 클래스 내부에 반영 될 것으로 기대 한 소비자는 더 이상 해당 기능을 사용할 수 없습니다. 그것은 큰 문제입니다! 당신이 노출되면 List<T>int로서 IEnumerable<T>당신은 편안 컬렉션이 외부에서 수정되지 않는 것을 예측할 수있다. 그것은 List<T>위의 인터페이스 중 하나로 노출 되는 힘 중 하나입니다 .

이 추상화 수준은 메서드 매개 변수에 속할 때 다른 방향으로 이동합니다. 수락하는 메소드에 목록을 전달하면 IEnumerable<T>목록이 수정되지 않을 것임을 확신 할 수 있습니다. 당신이 메소드를 구현하는 사람이고 당신 IEnumerable<T>이해야 할 일은 그 목록을 반복하기 만하면되기 때문에 당신이 동의한다고 말할 때 . 그런 다음 메서드를 호출하는 사람은 열거 가능한 모든 데이터 유형을 사용하여 자유롭게 호출 할 수 있습니다. 이렇게하면 코드를 예상치 못했지만 완벽하게 유효한 방식으로 사용할 수 있습니다.

이를 통해 메서드 구현은 원하는대로 로컬 변수를 나타낼 수 있습니다. 구현 세부 사항은 노출되지 않습니다. 코드를 호출하는 사람들에게 영향을주지 않고 코드를 더 나은 것으로 자유롭게 변경할 수 있습니다.

당신은 미래를 예측할 수 없습니다. 속성의 유형이 항상 유익하다고 가정하면 List<T>코드에 대한 예상치 못한 기대에 적응하는 능력이 즉시 제한됩니다. 예, 해당 데이터 유형을 a에서 변경할 수는 List<T>없지만 필요한 경우 확신 할 수 있습니다. 코드가 준비되었습니다.


9

짧은 답변:

사용하는 인터페이스의 구체적인 구현에 관계없이 코드에서 지원하도록 인터페이스를 전달합니다.

구체적인 목록 구현을 사용하는 경우 동일한 목록의 다른 구현이 코드에서 지원되지 않습니다.

상속과 다형성 에 대해 조금 읽어보십시오 .


8

예를 들면 다음과 같습니다. 목록이 매우 커져서 큰 개체 힙이 조각화되어 성능이 저하되는 프로젝트가있었습니다. List를 LinkedList로 대체했습니다. LinkedList에는 배열이 포함되어 있지 않으므로 갑자기 큰 개체 힙을 거의 사용하지 않았습니다.

IEnumerable<T>어쨌든 우리는 목록을으로 사용 했기 때문에 더 이상 변경할 필요가 없습니다. (그리고 그렇습니다. 참조를 열거하는 것이 전부라면 IEnumerable로 참조를 선언하는 것이 좋습니다.) 몇 군데에서 목록 인덱서가 필요했기 때문에 IList<T>연결 목록 주위에 비효율적 인 래퍼를 작성했습니다 . 목록 인덱서가 드물게 필요했기 때문에 비 효율성은 문제가되지 않았습니다. 만약 그렇다면, 우리는 IList의 다른 구현을 제공 할 수 있었을 것입니다. 아마도 충분히 작은 배열의 모음으로서 큰 개체를 피하면서 더 효율적으로 인덱싱 할 수 있었을 것입니다.

결국 어떤 이유로 든 구현을 교체해야 할 수도 있습니다. 성능은 하나의 가능성입니다. 이유에 관계없이 가능한 최소 파생 유형을 사용하면 개체의 특정 런타임 유형을 변경할 때 코드를 변경할 필요성이 줄어 듭니다.


3

메서드 내에서 또는 var대신 을 사용해야합니다 . 데이터 소스가 대신 메서드에서 가져 오도록 변경하면 메서드가 유지됩니다.IListListonlySomeInts

IList대신 List매개 변수 로 사용 하는 이유 는 많은 것들이 구현 IList(List와 [], 두 가지 예)하지만 한 가지만 구현하기 때문 List입니다. 인터페이스에 코딩하는 것이 더 유연합니다.

값을 열거하는 경우 IEnumerable. 둘 이상의 값을 보유 할 수있는 모든 유형의 데이터 유형은 IEnumerable메소드를 구현 (또는해야 함)하고 매우 유연하게 만듭니다.


13
"var"를 사용해야한다는 말은 완전히 잘못되었습니다. 그가 거기에서 무엇을 사용하든 상관 없습니다. 이것은 스타일 문제입니다. 메서드의 시그니처에는 영향을 미치지 않으며 컴파일 타임에 설정됩니다. 대신 IList foo = new List와 같이 지역을 선언하는 것에 대한 그의 혼란을 극복하도록 도와야합니다. 이것이 그의 혼란이 분명히있는 곳입니다.
x0n

그래서 기본적으로 당신은 그들이 배열로 보내고 싶다면 먼저 .ToList를 할 필요가 없다는 사실을 위해 IList를 받아들이라고 말합니까? 반품은 어떻습니까? vars를 사용할 때 "메서드가 살아남을 것"이라는 의미를 이해하지 못합니까? 좀 더 많은 작업이 될 것이라는 것을 알고 있지만 새로운 유형으로도 변경해야하지 않습니까? 그렇다면 IList <int>에서 IList <String>으로?
chobo2

사실, "use var"의 요점은 메서드 내부에서 걱정하지 않고 소비자에게 어떻게 보이는지에 더 집중하라는 제안이었습니다.
Bryan Boettcher 2012 년

@ x0n에 동의합니다. var는 과도하게 사용되었으며 더 명확하게하는 데 도움이되지 않습니다.
그 척 가이

1

List 대신 IList를 사용하면 단위 테스트를 훨씬 쉽게 작성할 수 있습니다. 'Mocking'라이브러리를 사용하여 데이터를 전달하고 반환 할 수 있습니다.

인터페이스를 사용하는 또 다른 일반적인 이유는 객체 사용자에게 필요한 최소한의 지식을 노출하기 위해서입니다.

IList를 구현하는 데이터 개체가있는 (기인 된) 경우를 고려하십시오.

public class MyDataObject : IList<int>
{
    public void Method1()
    {
       ...
    }
    // etc
}

위의 함수는 목록을 반복 할 수 있는지에 만 관심이 있습니다. 이상적으로는 누가 그 목록을 구현하는지 또는 어떻게 구현하는지 알 필요가 없어야합니다.

귀하의 예에서 IEnumerable은 생각했던 것처럼 더 나은 선택입니다.


1

가능한 한 코드 간의 종속성을 줄이는 것이 항상 좋은 생각입니다.

이를 염두에두고 가능한 외부 종속성이 가장 적은 유형을 전달하고 동일한 값을 반환하는 것이 가장 합리적입니다. 그러나 이는 메서드 및 해당 서명의 가시성에 따라 다를 수 있습니다.

메서드가 인터페이스의 일부를 구성하는 경우 해당 인터페이스에서 사용할 수있는 유형을 사용하여 메서드를 정의해야합니다. 구체적 유형은 인터페이스에서 사용할 수 없으므로 콘크리트가 아닌 유형을 반환해야합니다. 예를 들어 프레임 워크를 만드는 경우이 작업을 수행 할 수 있습니다.

그러나 프레임 워크를 작성하지 않는 경우 가능한 가장 약한 유형 (예 : 기본 클래스, 인터페이스 또는 대리자)으로 매개 변수를 전달하고 구체적인 유형을 리턴하는 것이 유리할 수 있습니다. 이를 통해 호출자는 반환 된 객체를 인터페이스로 캐스트하더라도 최대한 많은 작업을 수행 할 수 있습니다. 그러나 반환 된 개체 유형을 변경하면 호출 코드가 손상 될 수 있으므로 메서드가 더 취약 해집니다. 그러나 실제로는 일반적으로 큰 문제가 아닙니다.


1

호출자가 다른 구체적인 유형을 인수로 제출할 수 있도록 인터페이스를 메소드의 매개 변수로 허용합니다. 예제 메소드 LogAllChecked가 주어지면 someClasses 매개 변수는 다양한 유형이 될 수 있으며 메소드를 작성 하는 사람 의 경우 모두 동일 할 수 있습니다 (즉, 매개 변수 유형에 관계없이 정확히 동일한 코드를 작성합니다). 그러나 메서드를 호출 하는 사람 에게는 큰 차이를 만들 수 있습니다. 배열이 있고 목록을 요청하는 경우 메서드를 호출 할 때마다 배열을 목록 또는 vv로 변경해야합니다. 프로그래머와 성능 POV 모두에서 시간.

인터페이스 또는 구체적인 유형을 반환하는지 여부는 호출자가 생성 한 객체로 무엇을 할 수 있는지에 따라 다릅니다. 이것은 API 디자인 결정이며 엄격하고 빠른 규칙은 없습니다. 개체 기능의 일부를 쉽게 사용할 수있는 능력 (물론 개체를 최대한 활용 하려는지 여부)과 비교하여 개체를 최대한 활용하는 능력을 평가해야합니다. 예를 들어 IEnumerable을 반환하면 반복으로 제한하는 것입니다. 개체에서 항목을 추가하거나 제거 할 수 없으며 개체에 대해서만 작동 할 수 있습니다. 클래스 외부에 컬렉션을 노출해야하지만 호출자가 컬렉션을 변경하지 않도록하는 것이 한 가지 방법입니다. 반면에 채우기를 기대 / 원하는 빈 컬렉션을 반환하는 경우


0

.NET 4.5+ 세계 에서 내 대답은 다음과 같습니다 .

사용 은 IList <T>IReadonlyList <T> ,
              대신 목록 <T> , 때문에 ReadonlyList <T> 존재하지 않습니다.

IList <T>IReadonlyList <T> 와 매우 일치합니다.

  • 사용 을 IEnumerable <T> 의 경우 최소 노출 (속성) 또는 요구 사항 (매개 변수)에 대한 foreach는 가 사용하는 유일한 방법 인 경우).
  • Count[] 를 노출 / 사용해야하는 경우 IReadonlyList <T>를 사용하십시오 . 인덱서를.
  • 호출자가 요소를 추가 / 업데이트 / 삭제하도록 허용하는 경우 IList <T> 사용

List <T>IReadonlyList <T>를 구현 하기 때문에 , 그것은 명시 적 캐스팅이 필요하지 않습니다.

예제 클래스 :

// manipulate the list within the class
private List<int> _numbers;

// callers can add/update/remove elements, but cannot reassign a new list to this property
public IList<int> Numbers { get { return _numbers; } }

// callers can use: .Count and .ReadonlyNumbers[idx], but cannot add/update/remove elements
public IReadOnlyList<int> ReadonlyNumbers { get { return _numbers; } }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.