2014-11-12 4 views
9

나는 것으로 Enumerable.Empty()의 구현을 기대 그냥이 :Enumerable.Empty()가 빈 배열을 반환하는 이유는 무엇입니까?

public static IEnumerable<TResult> Empty<TResult>() 
{ 
    yield break; 
} 

하지만 구현은이 같은 것입니다 :

public static IEnumerable<TResult> Empty<TResult>() 
{ 
    return EmptyEnumerable<TResult>.Instance; 
} 

internal class EmptyEnumerable<TElement> 
{ 
    private static volatile TElement[] instance; 

    public static IEnumerable<TElement> Instance 
    { 
     get 
     { 
      if (EmptyEnumerable<TElement>.instance == null) 
       EmptyEnumerable<TElement>.instance = new TElement[0]; 
      return (IEnumerable<TElement>)EmptyEnumerable<TElement>.instance; 
     } 
    } 
} 

왜 구현 한 라인보다 복잡하지 코드? 캐시 된 배열을 반환하는 이점이 있습니까? 요소가 반환되지 않습니다 (yield)?

참고 : 메서드의 구현 세부 정보는 절대로 사용하지 않지만 궁금합니다.

+10

'수율 break'는 C#에서 한 줄,하지만 당신이 보는 경우가 훨씬 더 복잡하다 생성 된 일리노이 코드에서. – Dirk

답변

6

코드의 상당수에서

public static IEnumerable<TResult> MyEmpty<TResult>() 
{ 
    yield break; 
} 

결과를 (최적화를 LINQpad를 사용하여 사용).

IEnumerable 인터페이스를 구현하는 상태 시스템이 생성됩니다. MyEmpty에 전화 할 때마다 해당 클래스의 새 인스턴스가 만들어집니다. 빈 배열의 동일한 인스턴스를 반환하는 것은 매우 저렴합니다.

EmptyEnumerable`1.get_Instance: 
IL_0000: volatile. 
IL_0002: ldsfld  16 00 00 0A 
IL_0007: brtrue.s IL_0016 
IL_0009: ldc.i4.0  
IL_000A: newarr  04 00 00 1B 
IL_000F: volatile. 
IL_0011: stsfld  16 00 00 0A 
IL_0016: volatile. 
IL_0018: ldsfld  16 00 00 0A 
IL_001D: castclass 01 00 00 1B 
IL_0022: ret 

그리고 MyEmpty 방법을 위해은 다음과 같습니다 :

EmptyEnumerable의 IL 코드는

MyEmpty: 
IL_0000: ldc.i4.s FE 
IL_0002: newobj  15 00 00 0A 
IL_0007: stloc.0  
IL_0008: ldloc.0  
IL_0009: ret   

<MyEmpty>d__0`1.System.Collections.Generic.IEnumerable<TResult>.GetEnumerator: 
IL_0000: call  System.Environment.get_CurrentManagedThreadId 
IL_0005: ldarg.0  
IL_0006: ldfld  0E 00 00 0A 
IL_000B: bne.un.s IL_0022 
IL_000D: ldarg.0  
IL_000E: ldfld  0F 00 00 0A 
IL_0013: ldc.i4.s FE 
IL_0015: bne.un.s IL_0022 
IL_0017: ldarg.0  
IL_0018: ldc.i4.0  
IL_0019: stfld  0F 00 00 0A 
IL_001E: ldarg.0  
IL_001F: stloc.0  
IL_0020: br.s  IL_0029 
IL_0022: ldc.i4.0  
IL_0023: newobj  10 00 00 0A 
IL_0028: stloc.0  
IL_0029: ldloc.0  
IL_002A: ret   

<MyEmpty>d__0`1.System.Collections.IEnumerable.GetEnumerator: 
IL_0000: ldarg.0  
IL_0001: call  11 00 00 0A 
IL_0006: ret   

<MyEmpty>d__0`1.MoveNext: 
IL_0000: ldarg.0  
IL_0001: ldfld  0F 00 00 0A 
IL_0006: stloc.0  // CS$0$0000 
IL_0007: ldloc.0  // CS$0$0000 
IL_0008: ldc.i4.0  
IL_0009: bne.un.s IL_0012 
IL_000B: ldarg.0  
IL_000C: ldc.i4.m1 
IL_000D: stfld  0F 00 00 0A 
IL_0012: ldc.i4.0  
IL_0013: ret   

<MyEmpty>d__0`1.System.Collections.Generic.IEnumerator<TResult>.get_Current: 
IL_0000: ldarg.0  
IL_0001: ldfld  12 00 00 0A 
IL_0006: ret   

<MyEmpty>d__0`1.System.Collections.IEnumerator.Reset: 
IL_0000: newobj  System.NotSupportedException..ctor 
IL_0005: throw  

<MyEmpty>d__0`1.System.IDisposable.Dispose: 
IL_0000: ret   

<MyEmpty>d__0`1.System.Collections.IEnumerator.get_Current: 
IL_0000: ldarg.0  
IL_0001: ldfld  12 00 00 0A 
IL_0006: box   04 00 00 1B 
IL_000B: ret   

<MyEmpty>d__0`1..ctor: 
IL_0000: ldarg.0  
IL_0001: call  System.Object..ctor 
IL_0006: ldarg.0  
IL_0007: ldarg.1  
IL_0008: stfld  0F 00 00 0A 
IL_000D: ldarg.0  
IL_000E: call  System.Environment.get_CurrentManagedThreadId 
IL_0013: stfld  0E 00 00 0A 
IL_0018: ret   
+0

캐싱에 필요한 IL-code도 추가하면 어떻게 비교할 수 있습니까? –

+0

@AlexSiepman 아주 작은, 나는 그것으로 답변을 업데이 트했습니다. – Dirk

+0

@Dirk 그것에 관한 중요한 점은 - 더 많은 일리노이가 생성되지만 빈 어레이와 마찬가지로 성능이 좋지 않은 이유는 아닙니다. 주요 성능 차이는 생성되는 개체의 양입니다. 얼마나 많은 코드가 반드시 생성되는지가 아닙니다. –

1

이 경우 메모리가 덜 필요한 같은 유형의 모든 빈 인스턴스에 대해 하나의 배열이되므로이 작업을 수행하는 것이 좋습니다. 이것이 단일 배열 인스턴스가 정적 인 이유입니다.

요소가없는 배열은 변경할 수 없으므로 코드에 의해 더러워 질 수 없습니다. 컴파일

+0

배열이 변경 불가능합니까? 언제부터? –

+2

특정 유형의 모든 빈 인스턴스에 대한 하나의 배열. – juharr

+4

특히, 그것은 * empty * 배열이기 때문에 변형 될 요소가 없기 때문에 더러워지는 것에 대해 걱정할 필요가 없습니다. 배열은 변경 가능하지 않지만 길이는 0이므로 요소가없는 배열은 효과적으로 변경되지 않습니다. –

관련 문제