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

나는 이것에 대한 많은 게시물이 있다는 것을 알고 있지만 여전히 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);
                    }
                }
            }

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



답변

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

공식 매개 변수 :

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

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

지역 변수 :

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

반환 유형 :

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


답변

내가 본 가장 실용적인 이유는 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());


답변

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>없지만 필요한 경우 확신 할 수 있습니다. 코드가 준비되었습니다.


답변

짧은 답변:

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

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

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


답변

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

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

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


답변

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

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

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


답변

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

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

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

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

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

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