내 코드에서 IEnumerable<>
여러 번 사용해야 하므로 “가능한 다중 열거 가능”의 Resharper 오류가 발생 IEnumerable
합니다.
샘플 코드 :
public List<object> Foo(IEnumerable<object> objects)
{
if (objects == null || !objects.Any())
throw new ArgumentException();
var firstObject = objects.First();
var list = DoSomeThing(firstObject);
var secondList = DoSomeThingElse(objects);
list.AddRange(secondList);
return list;
}
objects
매개 변수를 변경 한List
다음 가능한 다중 열거를 피할 수 있지만 처리 할 수있는 가장 높은 객체를 얻지 못합니다.- 내가 할 수있는 또 다른 점은 변환하는 것입니다
IEnumerable
에List
방법의 시작 부분에 :
public List<object> Foo(IEnumerable<object> objects)
{
var objectList = objects.ToList();
// ...
}
그러나 이것은 어색하다 .
이 시나리오에서 무엇을 하시겠습니까?
답변
복용의 문제 IEnumerable
를 매개 변수로는 발신자를 알려줍니다 “나는이를 열거 할”것입니다. 몇 번이나 열거하고 싶은지 알려주지 않습니다.
objects 매개 변수를 List로 변경 한 다음 가능한 다중 열거를 피할 수 있지만 처리 할 수있는 가장 높은 개체를 얻지 못합니다 .
가장 높은 목표를 취하는 목표는 고귀하지만 너무 많은 가정의 여지가 남아 있습니다. 누군가가 LINQ to SQL 쿼리를이 메소드에 전달하고 싶을 때만 두 번만 열거하면됩니다 (매번 다른 결과를 얻을 수 있습니까?)
여기서 의미 론적 누락은 메소드의 세부 사항을 읽는 데 시간이 걸리지 않는 호출자가 한 번만 반복한다고 가정 할 수 있으므로 값 비싼 오브젝트를 전달한다는 것입니다. 메소드 서명은 어느 쪽도 나타내지 않습니다.
메소드 서명을 IList
/ 로 변경하면 ICollection
최소한 발신자에게 기대하는 바를 명확하게하고 값 비싼 실수를 피할 수 있습니다.
그렇지 않으면 메소드를보고있는 대부분의 개발자가 한 번만 반복한다고 가정 할 수 있습니다. 복용 IEnumerable
이 매우 중요한 .ToList()
경우 분석법 시작시 수행을 고려해야 합니다.
.NET에는 IEnumerable + Count + Indexer 인 인터페이스가 없기 때문에 Add / Remove 등의 방법이 없습니다.이 문제를 해결할 것으로 생각됩니다.
답변
데이터를 항상 반복 할 수 있다면 걱정하지 않아도됩니다. 그러나 언 롤링 할 수도 있습니다. 이는 들어오는 데이터가 클 수있는 경우에 특히 유용합니다 (예 : 디스크 / 네트워크에서 읽기).
if(objects == null) throw new ArgumentException();
using(var iter = objects.GetEnumerator()) {
if(!iter.MoveNext()) throw new ArgumentException();
var firstObject = iter.Current;
var list = DoSomeThing(firstObject);
while(iter.MoveNext()) {
list.Add(DoSomeThingElse(iter.Current));
}
return list;
}
참고 DoSomethingElse의 의미를 약간 변경했지만 주로 롤링되지 않은 사용법을 보여주기위한 것입니다. 예를 들어 반복자를 다시 감쌀 수 있습니다. 반복자 블록으로 만들 수도 있습니다. 그런 다음 list
– 이 없으며 yield return
반환 할 목록에 추가하는 대신 항목을 가져올 때 항목을 가져옵니다.
답변
사용 IReadOnlyCollection<T>
또는 IReadOnlyList<T>
대신 메서드 서명에 IEnumerable<T>
, 당신이 반복하는 전에 수를 확인해야 할 수도 있습니다, 또는 다른 이유로 여러 번 반복하는 것을 명시 적으로 만드는 장점이있다.
그러나 인터페이스를 사용하기 위해 코드를 리팩토링하려고 할 때 (예를 들어 동적 프록시에 대한 테스트 및 친숙성을 높이기 위해) 문제를 일으킬 수있는 큰 단점이 있습니다. 요점은 IList<T>
에서 상속되지 않으며IReadOnlyList<T>
다른 컬렉션과 해당 읽기 전용 인터페이스에 대해서도 마찬가지입니다. (.NET 4.5은 이전 버전의 ABI 호환성을 유지하고 싶었다. 때문에 즉,이다 하지만 그들은 심지어 .NET의 핵심에서 변화에 대한 기회를하지 않았다. )
즉 IList<T>
, 프로그램의 일부에서 가져 와서을 기대하는 다른 부분으로 전달하려는 IReadOnlyList<T>
경우 할 수 없습니다! 당신은 그러나를 전달할 수 있습니다 IList<T>
int로서IEnumerable<T>
.
결국 IEnumerable<T>
모든 컬렉션 인터페이스를 포함한 모든 .NET 컬렉션에서 지원하는 유일한 읽기 전용 인터페이스입니다. 당신이 어떤 건축 선택에서 자신을 가두 었다는 것을 깨달았을 때 다른 대안이 다시 당신을 물 것입니다. 따라서 읽기 전용 컬렉션을 원한다는 것을 표현하기 위해 함수 서명에 사용하는 것이 올바른 유형이라고 생각합니다.
( IReadOnlyList<T> ToReadOnly<T>(this IList<T> list)
기본 유형이 두 인터페이스를 모두 지원하는 경우 단순 캐스트를 사용 하는 확장 메소드를 항상 작성할 수 있지만 리팩토링 할 때 IEnumerable<T>
항상 호환 되는 모든 위치에 수동으로 추가해야합니다 .)
우연히 여러 가지 열거 형이 재앙이 될 수있는 데이터베이스가 많은 코드를 작성하는 경우에는 이것이 절대적인 것은 아닙니다. 다른 절충안을 선호 할 수도 있습니다.
답변
Marc Gravell의 답변보다 여러 열거를 실제로 방지하는 것이 목표이지만 동일한 의미를 유지하면 중복 Any
과 First
호출을 간단하게 제거하고 다음을 수행 할 수 있습니다.
public List<object> Foo(IEnumerable<object> objects)
{
if (objects == null)
throw new ArgumentNullException("objects");
var first = objects.FirstOrDefault();
if (first == null)
throw new ArgumentException(
"Empty enumerable not supported.",
"objects");
var list = DoSomeThing(first);
var secondList = DoSomeThingElse(objects);
list.AddRange(secondList);
return list;
}
이것은 IEnumerable
일반적이지 않거나 최소한 참조 유형으로 제한되어 있다고 가정합니다 .
답변
이 상황에서는 일반적으로 IEnumerable 및 IList로 메서드를 오버로드합니다.
public static IEnumerable<T> Method<T>( this IList<T> source ){... }
public static IEnumerable<T> Method<T>( this IEnumerable<T> source )
{
/*input checks on source parameter here*/
return Method( source.ToList() );
}
IEnumerable을 호출하면 .ToList ()를 수행하는 메서드의 요약 설명에서 설명합니다.
프로그래머는 여러 작업이 연결되어있는 경우 더 높은 수준에서 .ToList ()를 선택한 다음 IList 오버로드를 호출하거나 IEnumerable 오버로드가 처리하도록 할 수 있습니다.
답변
첫 번째 요소 만 확인해야하는 경우 전체 컬렉션을 반복하지 않고 살펴볼 수 있습니다.
public List<object> Foo(IEnumerable<object> objects)
{
object firstObject;
if (objects == null || !TryPeek(ref objects, out firstObject))
throw new ArgumentException();
var list = DoSomeThing(firstObject);
var secondList = DoSomeThingElse(objects);
list.AddRange(secondList);
return list;
}
public static bool TryPeek<T>(ref IEnumerable<T> source, out T first)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
IEnumerator<T> enumerator = source.GetEnumerator();
if (!enumerator.MoveNext())
{
first = default(T);
source = Enumerable.Empty<T>();
return false;
}
first = enumerator.Current;
T firstElement = first;
source = Iterate();
return true;
IEnumerable<T> Iterate()
{
yield return firstElement;
using (enumerator)
{
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
}
}
답변
우선, 그 경고가 항상 그렇게 많은 것을 의미하지는 않습니다. 성능 병목이 아닌지 확인한 후 일반적으로 사용 중지했습니다. 그것은 단지 IEnumerable
두 번 평가 된다는 것을 의미하며 , evaluation
자체 시간이 오래 걸리지 않으면 일반적으로 문제가되지 않습니다 . 시간이 오래 걸리더라도이 경우 처음으로 하나의 요소 만 사용하십시오.
이 시나리오에서는 강력한 linq 확장 방법을 훨씬 더 활용할 수도 있습니다.
var firstObject = objects.First();
return DoSomeThing(firstObject).Concat(DoSomeThingElse(objects).ToList();
IEnumerable
이 경우 번거롭지 만 한 번만 평가할 수 있지만 프로파일을 먼저 작성하여 실제로 문제가 있는지 확인하십시오.