컬렉션을 반복하는 방법은 여러 가지가 있습니다. 차이점이 있거나 왜 다른 방법으로 사용하는지 궁금합니다.
첫 번째 유형 :
List<string> someList = <some way to init>
foreach(string s in someList) {
<process the string>
}
다른 방법 :
List<string> someList = <some way to init>
someList.ForEach(delegate(string s) {
<process the string>
});
나는 내 머리 꼭대기에서 위에서 사용하는 익명 대리자 대신 지정할 수있는 재사용 가능한 대리자가 있다고 가정합니다 …
답변
둘 사이에는 중요하고 유용한 구별이 있습니다.
.ForEach는 for
루프를 사용하여 컬렉션을 반복하므로 유효합니다 (편집 : .net 4.5 이전 -구현이 변경되어 둘 다 발생 함).
someList.ForEach(x => { if(x.RemoveMe) someList.Remove(x); });
반면 foreach
열거자를 사용하므로 유효하지 않습니다.
foreach(var item in someList)
if(item.RemoveMe) someList.Remove(item);
tl; dr :이 코드를 응용 프로그램에 복사하여 붙여 넣지 마십시오!
이 예제는 모범 사례가 아니며 ForEach()
와 의 차이점을 보여주기위한 것 foreach
입니다.
for
루프 내의 목록에서 항목을 제거하면 부작용이있을 수 있습니다. 가장 일반적인 질문은이 질문에 대한 의견에 설명되어 있습니다.
일반적으로 목록에서 여러 항목을 제거하려는 경우 실제 제거에서 제거 할 항목을 결정하려는 경우가 있습니다. 코드를 컴팩트하게 유지하지는 않지만 항목을 놓치지 않도록 보장합니다.
답변
여기에 이전 엔지니어 들이 작성한 모든 코드 list.ForEach( delegate(item) { foo;});
대신 사용 하지 않는 코드가 VS2005 및 C # 2.0에 있습니다 foreach(item in list) {foo; };
. 예를 들어 dataReader에서 행을 읽기위한 코드 블록.
나는 왜 그들이 왜 그렇게했는지 정확히 모른다.
단점 list.ForEach()
은 다음 과 같습니다.
-
C # 2.0에서는 더 장황합니다. 그러나 C # 3부터는 ”
=>
“구문을 사용하여 간결하게 표현할 수 있습니다. -
익숙하지 않습니다. 이 코드를 유지해야하는 사람들은 왜 그렇게했는지 궁금 할 것입니다. 작가를 영리하게 보이게 만드는 것 외에는 이유가 없다고 결정하는 데 잠시 시간이 걸렸습니다.
})
델리게이트 코드 블록의 끝에 ” “가있어 읽기 가 어렵습니다. -
Bill Wagner의 저서 “효과적인 C # : C # 개선을위한 50 가지 구체적인 방법”에서 foreach가 for 또는 while 루프와 같은 다른 루프보다 선호되는 이유에 대해 설명합니다. 주요 요점은 컴파일러가 구성하는 가장 좋은 방법을 결정하게하는 것입니다 루프. 향후 버전의 컴파일러가 더 빠른 기술을 사용할 수 있다면 코드를 변경하지 않고 foreach 및 rebuild를 사용하여 무료로 얻을 수 있습니다.
-
foreach(item in list)
구조는 사용할 수 있습니다break
또는continue
당신이 반복하거나 루프를 종료해야하는 경우. 그러나 foreach 루프 내에서 목록을 변경할 수 없습니다.
나는 그것이 list.ForEach
조금 더 빠르다는 것을보고 놀랐습니다 . 그러나 이것이 전체적으로 그것을 사용해야하는 정당한 이유가 아닐 수 있으므로 조기 최적화가 될 것입니다. 응용 프로그램에서 루프 제어가 아닌 데이터베이스 또는 웹 서비스를 사용하는 경우 거의 항상 시간이 지나는 위치에있게됩니다. 그리고 for
루프에 대해서도 벤치마킹 했습니까? 는 list.ForEach
내부적으로 그것을 사용하고 빠른 때문일 수 있습니다 for
래퍼없이 루프가 더 빨리 될 것입니다.
나는 그 list.ForEach(delegate)
버전이 어떤 방식 으로든 “더 기능적”이라고 동의하지 않는다 . 함수에 함수를 전달하지만 결과 또는 프로그램 구성에는 큰 차이가 없습니다.
나는 생각하지 않는다 foreach(item in list)
“당신이 수행 할 방법을 정확하게 말한다”- for(int 1 = 0; i < count; i++)
루프가 작업을 수행하는 foreach
루프 잎 컴파일러 제어 최대의 선택.
새 프로젝트에서는 C # 3 ” “연산자를 사용하여보다 우아하고 간결하게 작업을 수행 할 수있는 경우 foreach(item in list)
일반적인 사용법과 가독성을 유지하기 위해 대부분의 루프에 사용하고 list.Foreach()
짧은 블록에만 사용 하는 것이 =>
좋습니다. 그런 경우에는보다 구체적인 LINQ 확장 방법이 이미있을 수 있습니다 ForEach()
. 경우를 참조하십시오 Where()
, Select()
, Any()
, All()
, Max()
다른 많은 LINQ 방법 또는 1이 이미 루프에서하고 싶은 일을하지 않습니다.
답변
재미를 위해 List를 리플렉터에 팝하고 결과 C #입니다.
public void ForEach(Action<T> action)
{
if (action == null)
{
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
}
for (int i = 0; i < this._size; i++)
{
action(this._items[i]);
}
}
마찬가지로 foreach에서 사용하는 열거 자의 MoveNext는 다음과 같습니다.
public bool MoveNext()
{
if (this.version != this.list._version)
{
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
if (this.index < this.list._size)
{
this.current = this.list._items[this.index];
this.index++;
return true;
}
this.index = this.list._size + 1;
this.current = default(T);
return false;
}
List.ForEach는 처리가 훨씬 적은 MoveNext보다 훨씬 잘 정리되어보다 효율적인 JIT가 될 것입니다.
또한 foreach ()는 무엇이든 새로운 열거자를 할당합니다. GC의 당신의 친구이지만, 반복적으로 같은 foreach 문을 수행하는 경우,이 같은 대리자를 재사용 반대로, 더 일회용 객체를 만들 것입니다 – 하지만 -이 정말 프린지 경우입니다. 일반적인 사용법에서는 차이가 거의 없거나 전혀 없습니다.
답변
나는 그것들을 다르게 만드는 두 가지 모호한 것을 알고 있습니다. 가요
첫째, 목록의 각 항목에 대한 대리인을 만드는 데있어 고전적인 버그가 있습니다. foreach 키워드를 사용하면 모든 대리인이 목록의 마지막 항목을 참조 할 수 있습니다.
// A list of actions to execute later
List<Action> actions = new List<Action>();
// Numbers 0 to 9
List<int> numbers = Enumerable.Range(0, 10).ToList();
// Store an action that prints each number (WRONG!)
foreach (int number in numbers)
actions.Add(() => Console.WriteLine(number));
// Run the actions, we actually print 10 copies of "9"
foreach (Action action in actions)
action();
// So try again
actions.Clear();
// Store an action that prints each number (RIGHT!)
numbers.ForEach(number =>
actions.Add(() => Console.WriteLine(number)));
// Run the actions
foreach (Action action in actions)
action();
List.ForEach 메서드에는이 문제가 없습니다. 반복의 현재 항목은 값으로 외부 람다에 대한 인수로 전달 된 다음 내부 람다는 해당 인수를 자체 클로저로 올바르게 캡처합니다. 문제 해결됨.
(슬프게도 ForEach는 확장 방법이 아니라 List의 멤버라고 생각하지만 직접 정의하기가 쉽지만 열거 가능한 유형 의이 시설을 갖습니다.)
둘째, ForEach 방법은 한계가 있습니다. yield return을 사용하여 IEnumerable을 구현하는 경우 람다 내에서 yield return을 수행 할 수 없습니다. 따라서이 방법으로는 반환 작업을 수행하기 위해 컬렉션의 항목을 반복하는 것이 불가능합니다. foreach 키워드를 사용해야하며 루프 내에서 현재 루프 값의 사본을 수동으로 만들어 클로저 문제를 해결해야합니다.
답변
나는 생각 someList.ForEach()
정상은 반면에 통화가 쉽게 병렬화 될 수있는 foreach
병렬 실행 쉬운 것이 아니다. 다른 코어에서 여러 개의 다른 델리게이트를 쉽게 실행할 수 있습니다 foreach
.
내 2 센트 만
답변
그들이 말하는 것처럼, 악마는 세부 사항에 있습니다 …
컬렉션 열거의 두 가지 방법의 가장 큰 차이점 foreach
은 상태 를 전달하는 반면 ForEach(x => { })
그렇지는 않습니다.
그러나 결정에 영향을 줄 수있는 몇 가지 사항이 있고 두 경우 모두를 코딩 할 때주의해야 할 사항이 있기 때문에 좀 더 깊이 파고 들겠습니다.
사용할 수 있습니다 List<T>
행동을 관찰하기 위해 우리의 작은 실험에서. 이 실험에서는 .NET 4.7.2를 사용하고 있습니다.
var names = new List<string>
{
"Henry",
"Shirley",
"Ann",
"Peter",
"Nancy"
};
foreach
먼저 이것을 반복합니다 :
foreach (var name in names)
{
Console.WriteLine(name);
}
이를 다음과 같이 확장 할 수 있습니다.
using (var enumerator = names.GetEnumerator())
{
}
열거자를 손에 넣은 다음 덮개를 살펴보십시오.
public List<T>.Enumerator GetEnumerator()
{
return new List<T>.Enumerator(this);
}
internal Enumerator(List<T> list)
{
this.list = list;
this.index = 0;
this.version = list._version;
this.current = default (T);
}
public bool MoveNext()
{
List<T> list = this.list;
if (this.version != list._version || (uint) this.index >= (uint) list._size)
return this.MoveNextRare();
this.current = list._items[this.index];
++this.index;
return true;
}
object IEnumerator.Current
{
{
if (this.index == 0 || this.index == this.list._size + 1)
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen);
return (object) this.Current;
}
}
두 가지가 즉시 명백해진다 :
- 기본 컬렉션에 대한 친밀한 지식을 갖춘 상태 저장 객체가 반환됩니다.
- 컬렉션의 복사본은 얕은 복사본입니다.
이것은 물론 스레드 안전이 아닙니다. 위에서 지적했듯이 반복하는 동안 컬렉션을 변경하는 것은 나쁜 모조입니다.
그러나 반복하는 동안 컬렉션을 모으는 외부의 수단을 통해 반복하는 동안 컬렉션이 무효화되는 문제는 어떻습니까? 모범 사례에서는 작업 및 반복 중에 컬렉션의 버전을 관리하고 기본 컬렉션이 변경되는시기를 감지하기 위해 버전을 확인하는 것이 좋습니다.
여기는 정말 어두운 곳입니다. Microsoft 설명서에 따르면 :
요소 추가, 수정 또는 삭제와 같이 컬렉션이 변경되면 열거 자의 동작이 정의되지 않습니다.
그게 무슨 뜻이야? 예를 들어, List<T>
예외 처리를 구현한다고해서 구현하는 모든 컬렉션이 IList<T>
동일하게 수행되는 것은 아닙니다 . 이는 Liskov 대체 원칙을 명백히 위반 한 것으로 보입니다.
수퍼 클래스의 객체는 응용 프로그램을 중단하지 않고 서브 클래스의 객체로 대체 가능해야합니다.
또 다른 문제는 열거자가 구현해야한다는 것 IDisposable
입니다. 즉, 호출자가 잘못했을 때뿐만 아니라 작성자가 Dispose
패턴을 올바르게 구현하지 않은 경우 잠재적 인 메모리 누수의 또 다른 원인이 됩니다.
마지막으로, 우리는 평생 문제가 있습니다 … 반복자가 유효하지만 기본 컬렉션이 사라지면 어떻게됩니까? 우리는 지금의 스냅 샷 무엇인지는 당신이 수집하고 반복자의 수명을 분리 할 때 …, 당신은 문제가 요구된다.
이제 살펴 보자 ForEach(x => { })
:
names.ForEach(name =>
{
});
이것은 다음과 같이 확장됩니다.
public void ForEach(Action<T> action)
{
if (action == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.match);
int version = this._version;
for (int index = 0; index < this._size && (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5); ++index)
action(this._items[index]);
if (version == this._version || !BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
return;
ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion);
}
중요한 사항은 다음과 같습니다.
for (int index = 0; index < this._size && ... ; ++index)
action(this._items[index]);
이 코드는 열거자를 할당하지 않으며 ( Dispose
)에 반복 하지 않고 일시 중지 하지 않습니다 .
이것은 또한 기본 콜렉션의 단순 사본을 수행하지만 콜렉션은 이제 스냅 샷입니다. 작성자가 컬렉션 변경 또는 ‘stale’에 대한 검사를 올바르게 구현하지 않은 경우 스냅 샷은 여전히 유효합니다.
이것은 어떤 식 으로든 평생 문제의 문제로부터 당신을 보호하지 않습니다 … 기본 컬렉션이 사라지면 이제는 무엇을 가리키는 얕은 사본이 있지만 적어도 Dispose
문제 는 없습니다. 고아 반복자 다루기 …
그렇습니다. 반복자는 … 때때로 상태를 갖는 것이 유리하다고 말했습니다. 데이터베이스 커서와 비슷한 것을 유지하고 싶다고 가정합시다. 아마도 여러 foreach
스타일 Iterator<T>
이 갈 길입니다. 나는 평생 문제가 너무 많기 때문에 개인적 으로이 스타일의 디자인을 싫어하고, 당신이 믿고있는 컬렉션의 저자의 은혜에 의존합니다 (문자 그대로 모든 것을 처음부터 작성하지 않는 한).
항상 세 번째 옵션이 있습니다 …
for (var i = 0; i < names.Count; i++)
{
Console.WriteLine(names[i]);
}
섹시하지는 않지만 치아가 있습니다 ( 톰 크루즈 와 영화 The Firm 에게 사과 )
그것은 당신의 선택이지만, 지금 당신은 알고 있으며 정보를 얻을 수 있습니다.
답변
익명 대리인의 이름을 지정할 수 있습니다 🙂
그리고 당신은 두 번째로 쓸 수 있습니다 :
someList.ForEach(s => s.ToUpper())
어떤 것을 선호하고 많은 타이핑을 저장합니다.
Joachim이 말했듯이 병렬 처리는 두 번째 양식에 적용하기가 더 쉽습니다.
