programing

이 방법으로 인해 무한 루프가 발생하는 이유는 무엇입니까?

nasanasas 2020. 11. 21. 11:20
반응형

이 방법으로 인해 무한 루프가 발생하는 이유는 무엇입니까?


동료 중 한 명이이 방법에 대한 질문으로 무한 루프가 발생했습니다. 실제 코드는 여기에 게시하기에는 너무 복잡하지만 기본적으로 문제는 다음과 같습니다.

private IEnumerable<int> GoNuts(IEnumerable<int> items)
{
    items = items.Select(item => items.First(i => i == item));
    return items;
}

해야 그냥 목록의 복사본을 만들 수있는 매우 비효율적 인 방법이 (당신이 생각하는 것). 나는 그것을 다음과 같이 불렀다.

var foo = GoNuts(new[]{1,2,3,4,5,6});

결과는 무한 루프입니다. 이상한.

매개 변수를 수정하는 것은 스타일이 나쁜 일이라고 생각하므로 코드를 약간 변경했습니다.

var foo = items.Select(item => items.First(i => i == item));
return foo;

작동했습니다. 즉, 프로그램이 완료되었습니다. 예외 없음.

더 많은 실험에서 이것이 효과가 있음을 보여주었습니다.

items = items.Select(item => items.First(i => i == item)).ToList();
return items;

간단하게

return items.Select(item => .....);

궁금한.

문제가 매개 변수 재 할당과 관련이 있다는 것은 분명하지만 평가가 해당 문을 넘어서 연기되는 경우에만 해당됩니다. 내가 추가하면 ToList()작동합니다.

나는 무엇이 잘못되고 있는지에 대한 일반적이고 모호한 아이디어를 가지고 있습니다. Select자체 출력을 반복 하는 것처럼 보입니다 . 그것은 그 자체로 조금 이상합니다. 왜냐하면 일반적으로 IEnumerable그것이 반복되는 컬렉션이 변경되면 던질 것이기 때문 입니다.

내가 이해하지 못하는 것은 이것이 작동하는 방식의 내부에 대해 잘 알지 못하기 때문에 매개 변수를 다시 할당하면이 무한 루프가 발생하는 이유입니다.

여기에서 무한 루프가 발생하는 이유를 기꺼이 설명 할 내부에 대한 더 많은 지식을 가진 사람이 있습니까?


이에 대한 답의 핵심은 지연된 실행 입니다. 당신이 이것을 할 때

items = items.Select(item => items.First(i => i == item));

메서드에 전달 된 배열을 반복 하지 않습니다items . 대신 IEnumerable<int>자신을 다시 참조 하는 new를 할당 하고 호출자가 결과를 열거하기 시작할 때만 반복을 시작합니다.

그렇기 때문에 다른 모든 수정 사항이 문제를 처리 한 이유입니다.해야 할 일은 IEnumerable<int>자체 피드백을 중지 하는 것입니다.

  • 사용하여 var foo다른 변수를 사용하여 구분 자체 참조,
  • 사용 return items.Select...모두에서 중간 변수를 사용하지 않음으로써 휴식을 자기 참조를,
  • ToList()지연된 실행을 피하여 자체 참조 중단을 사용 합니다. 시간 items이 재 할당 될 때까지 old items가 반복되므로 일반 메모리가 List<int>됩니다.

그러나 그것이 스스로 먹이를 먹는다면 어떻게 아무것도 얻습니까?

맞습니다, 아무것도 얻지 못합니다! 반복을 시도 items하고 첫 번째 항목을 요청하는 순간 지연된 시퀀스는 처리 할 첫 번째 항목에 대해 공급 된 시퀀스를 요청합니다. 즉, 시퀀스가 ​​처리 할 첫 번째 항목을 요청한다는 의미입니다. 이 시점에서 거북은 끝까지 내려 갑니다. 처리 할 첫 번째 항목을 반환하려면 시퀀스가 ​​먼저 처리 할 첫 번째 항목을 가져와야하기 때문입니다.


Select가 자체 출력을 반복하는 것처럼 보입니다.

당신이 올바른지. 자체적으로 반복 되는 쿼리반환 합니다.

핵심은 items 람다 내에서 참조한다는 것 입니다. items참조하는 시점에서 쿼리를 반복, 때까지 ( "이상 폐쇄") 해결되지 items이제 소스 모음 대신 쿼리를 참조합니다. 그것이 자기 참조가 발생하는 곳입니다.

카드 앞에라고 표시된 표지판이있는 카드 더미를 상상해보십시오 items. 이제 카드 덱 옆에 서있는 한 남자가라는 컬렉션을 반복하는 임무를 맡고 있다고 상상해보십시오 items. 그러나 당신은 갑판에서 남자 에게 표지판을 옮깁니다 . 남자에게 첫 번째 "항목"을 요청하면 "항목"으로 표시된 컬렉션을 찾습니다. 이제 그 사람입니다! 그래서 그는 순환 참조가 발생하는 첫 번째 항목을 스스로에게 묻습니다.

결과를 변수에 할당하면 다른 컬렉션에 대해 반복되는 쿼리가 있으므로 무한 루프가 발생하지 않습니다.

를 호출 ToList하면 쿼리를 새 컬렉션으로 수화하고 무한 루프를 얻지 않습니다.

순환 참조를 깨는 다른 것들 :

  • 호출 하여 람다 내의 항목 수화ToList
  • items다른 변수에 할당 하고 람다 내에서 참조 합니다 .

주어진 두 가지 대답을 연구하고 조금 훑어 본 후 문제를 더 잘 설명하는 작은 프로그램을 생각해 냈습니다.

    private int GetFirst(IEnumerable<int> items, int foo)
    {
        Console.WriteLine("GetFirst {0}", foo);
        var rslt = items.First(i => i == foo);
        Console.WriteLine("GetFirst returns {0}", rslt);
        return rslt;
    }

    private IEnumerable<int> GoNuts(IEnumerable<int> items)
    {
        items = items.Select(item =>
        {
            Console.WriteLine("Select item = {0}", item);
            return GetFirst(items, item);
        });
        return items;
    }

다음과 같이 전화하면 :

var newList = GoNuts(new[]{1, 2, 3, 4, 5, 6});

최종적으로를 얻을 때까지이 출력을 반복적으로 얻을 수 StackOverflowException있습니다.

Select item = 1
GetFirst 1
Select item = 1
GetFirst 1
Select item = 1
GetFirst 1
...

What this shows is exactly what dasblinkenlight made clear in his updated answer: the query goes into an infinite loop trying to get the first item.

Let's write GoNuts a slightly different way:

    private IEnumerable<int> GoNuts(IEnumerable<int> items)
    {
        var originalItems = items;
        items = items.Select(item =>
        {
            Console.WriteLine("Select item = {0}", item);
            return GetFirst(originalItems, item);
        });
        return items;
    }

If you run that, it succeeds. Why? Because in this case it's clear that the call to GetFirst is passing a reference to the original items that were passed to the method. In the first case, GetFirst is passing a reference to the new items collection, which hasn't yet been realized. In turn, GetFirst says, "Hey, I need to enumerate this collection." And thus begins the first recursive call that eventually leads to StackOverflowException.

Interestingly, I was right and wrong when I said that it was consuming its own output. The Select is consuming the original input, as I would expect. The First is trying to consume the output.

Lots of lessons to be learned here. To me, the most important is "don't modify the value of input parameters."

Thanks to dasblinkenlight, D Stanley, and Lucas Trzesniewski for their help.

참고URL : https://stackoverflow.com/questions/31993443/why-does-this-method-result-in-an-infinite-loop

반응형