나는 이것에 대한 많은 게시물이 있다는 것을 알고 있지만 여전히 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
대신 을 사용해야합니다 . 데이터 소스가 대신 메서드에서 가져 오도록 변경하면 메서드가 유지됩니다.IList
List
onlySomeInts
IList
대신 List
매개 변수 로 사용 하는 이유 는 많은 것들이 구현 IList
(List와 [], 두 가지 예)하지만 한 가지만 구현하기 때문 List
입니다. 인터페이스에 코딩하는 것이 더 유연합니다.
값을 열거하는 경우 IEnumerable
. 둘 이상의 값을 보유 할 수있는 모든 유형의 데이터 유형은 IEnumerable
메소드를 구현 (또는해야 함)하고 매우 유연하게 만듭니다.
답변
List 대신 IList를 사용하면 단위 테스트를 훨씬 쉽게 작성할 수 있습니다. ‘Mocking’라이브러리를 사용하여 데이터를 전달하고 반환 할 수 있습니다.
인터페이스를 사용하는 또 다른 일반적인 이유는 객체 사용자에게 필요한 최소한의 지식을 노출하기 위해서입니다.
IList를 구현하는 데이터 개체가있는 (기인 된) 경우를 고려하십시오.
public class MyDataObject : IList<int>
{
public void Method1()
{
...
}
// etc
}
위의 함수는 목록을 반복 할 수 있는지에 만 관심이 있습니다. 이상적으로는 누가 그 목록을 구현하는지 또는 어떻게 구현하는지 알 필요가 없어야합니다.
귀하의 예에서 IEnumerable은 생각했던 것처럼 더 나은 선택입니다.