Program Tip

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

programtip 2020. 10. 20. 08:01
반응형

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> allows you to iterate through a collection. ICollection<T> builds on this and also allows for adding and removing items. IList<T> also allows for accessing and modifying them at a specific index. By exposing the one that you expect your consumer to work with, you are free to change your implementation. List<T> happens to implement all three of those interfaces.

If you expose your property as a List<T> or even an IList<T> when all you want your consumer to have is the ability to iterate through the collection. Then they could come to depend on the fact that they can modify the list. Then later if you decide to convert the actual data store from a List<T> to a Dictionary<T,U> and expose the dictionary keys as the actual value for the property (I have had to do exactly this before). Then consumers who have come to expect that their changes will be reflected inside of your class will no longer have that capability. That's a big problem! If you expose the List<T> as an IEnumerable<T> you can comfortably predict that your collection is not being modified externally. That is one of the powers of exposing List<T> as any of the above interfaces.

This level of abstraction goes the other direction when it belongs to method parameters. When you pass your list to a method that accepts IEnumerable<T> you can be sure that your list is not going to be modified. When you are the person implementing the method and you say you accept an IEnumerable<T> because all you need to do is iterate through that list. Then the person calling the method is free to call it with any data type that is enumerable. This allows your code to be used in unexpected, but perfectly valid ways.

From this it follows that your method implementation can represent its local variables however you wish. The implementation details are not exposed. Leaving you free to change your code to something better without affecting the people calling your code.

You cannot predict the future. Assuming that a property's type will always be beneficial as a List<T> is immediately limiting your ability to adapt to unforeseen expectations of your code. Yes, you may never change that data type from a List<T> but you can be sure that if you have to. Your code is ready for it.


Short Answer:

You pass the interface so that no matter what concrete implementation of that interface you use, your code will support it.

If you use a concrete implementation of list, another implementation of the same list will not be supported by your code.

Read a bit on inheritance and polymorphism.


Here's an example: I had a project once where our lists got very large, and resulting fragmentation of the large object heap was hurting performance. We replaced List with LinkedList. LinkedList does not contain an array, so all of a sudden, we had almost no use of the large object heap.

Mostly, we used the lists as IEnumerable<T>, anyway, so there was no further change needed. (And yes, I would recommend declaring references as IEnumerable if all you're doing is enumerating them.) In a couple of places, we needed the list indexer, so we wrote an inefficient IList<T> wrapper around the linked lists. We needed the list indexer infrequently, so the inefficiency was not a problem. If it had been, we could have provided some other implementation of IList, perhaps as a collection of small-enough arrays, that would have been more efficiently indexable while also avoiding large objects.

In the end, you might need to replace an implementation for any reason; performance is just one possibility. Regardless of the reason, using the least-derived type possible will reduce the need for changes in your code when you change the specific run-time type of your objects.


Inside the method, you should use var, instead of IList or List. When your data source changes to come from a method instead, your onlySomeInts method will survive.

The reason to use IList instead of List as parameters, is because many things implement IList (List and [], as two examples), but only one thing implements List. It's more flexible to code to the interface.

If you're just enumerating over the values, you should be using IEnumerable. Every type of datatype that can hold more than one value implements IEnumerable (or should) and makes your method hugely flexible.


Using IList instead of List makes writing unit tests significantly easier. It allows you to use a 'Mocking' library to pass and return data.

The other general reason for using interfaces is to expose the minimum amount of knowledge necessary to the user of an object.

Consider the (contrived) case where I have a data object that implements IList.

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

Your functions above only care about being able to iterate over a list. Ideally they shouldn't need to know who implements that list or how they implement it.

In your example, IEnumerable is a better choice as you thought.


It is always a good idea to reduce the dependencies between your code as much as possible.

Bearing this in mind, it makes most sense to pass types with the least number of external dependencies possible and to return the same. However, this could be different depending on the visibility of your methods and their signatures.

If your methods form part of an interface, the methods will need to be defined using types available to that interface. Concrete types will probably not be available to interfaces, so they would have to return non-concrete types. You would want to do this if you were creating a framework, for example.

However, if you are not writing a framework, it may be advantageous to pass parameter with the weakest possible types (i.e. base classes, interfaces, or even delegates) and return concrete types. That gives the caller the ability to do as much as possible with the returned object, even if it is cast as an interface. However, this makes the method more fragile, as any change to the returned object type may break the calling code. In practice though, that generally isn't a major problem.


You accept an Interface as a parameter for a method because that allows the caller to submit different concrete types as arguments. Given your example method LogAllChecked, the parameter someClasses could be of various types, and for the person writing the method, all might be equivalent (i.e. you'd write the exact same code regardless of the type of the parameter). But for the person calling the method, it can make a huge difference -- if they have an array and you're asking for a list, they have to change the array to a list or v.v. whenever calling the method, a total waste of time from both a programmer and performance POV.

Whether you return an Interface or a concrete type depends upon what you want to let your callers do with the object you created -- this is an API design decision, and there's no hard and fast rule. You have to weigh their ability to make full use of the object against their ability to easily use a portion of the objects functionality (and of course whether you WANT them to be making full use of the object). For instance, if you return an IEnumerable, then you are limiting them to iterating -- they can't add or remove items from your object, they can only act against the objects. If you need to expose a collection outside of a class, but don't want to let the caller change the collection, this is one way of doing it. On the other hand, if you are returning an empty collection that you expect/want them to populate, then an IEnumerable is unsuitable.


Here's my answer in this .NET 4.5+ world.

Use IList<T> and IReadonlyList<T>,
              instead of List<T>, because ReadonlyList<T> doesn't exist.

IList<T> looks so consistent with IReadonlyList<T>

  • Use IEnumerable<T> for minimum exposure (property) or requirement (parameter) if foreach is the only way to use it.
  • Use IReadonlyList<T> if you also need to expose/use Count and [] indexer.
  • Use IList<T> if you also allow callers to add/update/delete elements

because List<T> implements IReadonlyList<T>, it doesn't need any explicit casting.

An example class:

// 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; } }

참고URL : https://stackoverflow.com/questions/8717582/why-use-ilist-or-list

반응형