2010-01-25 6 views
3

현재 다음 기본 컬렉션을 사용합니다.구현 IEnumerable <T> foreach 있지만 작동하지 않습니다 LINQ

abstract class BaseCollection : Collection<BaseRecord> 
    {  
    } 

아래의 제네릭 컬렉션으로 바꾸고 싶습니다. 그러나 현재 BaseCollection을 구현하는 파생 클래스의 양 때문에 이는 간단한 작업이 아닙니다.

대대적 인 정밀 검사 대신이 새로운 컬렉션을 천천히 소개하고 싶습니다.

abstract class BaseCollection<TRecord> : BaseCollection,IEnumerable<TRecord> where TRecord : BaseRecord,new() 
    { 
     public new IEnumerator<TRecord> GetEnumerator() 
     { 
     return Items.Cast<TRecord>().GetEnumerator(); 
     } 
    } 

foreach를 사용하여 컬렉션을 통해 열거 할 수 있지만 아래의 LINQ 문은 컴파일되지 않습니다. 이것이 가능한가? 나는 이것이 약간의 해킹 인 것을 알지만, 이것을 어떻게 할 수 있을지 잘 모르겠습니다.

class Program 
    { 
     static void Main(string[] args) 
     { 
     DerivedCollection derivedCollection = new DerivedCollection 
     { 
      new DerivedRecord(), 
      new DerivedRecord() 
     }; 
     foreach (var record in derivedCollection) 
     { 
      record.DerivedProperty = ""; 
     } 
     var records = derivedCollection.Where(d => d.DerivedProperty == ""); 
     } 
    } 

여기에 사용 된 두 개의 레코드가 있습니다. 감사.

class DerivedRecord : BaseRecord 
    { 
     public string DerivedProperty { get; set; } 
    } 

    abstract class BaseRecord 
    { 
     public string BaseProperty { get; set; } 
    } 

다음은 파생 된 컬렉션입니다.

class DerivedCollection : BaseCollection<DerivedRecord> 
    { 

    } 
+1

'DerivedCollection'은 어디에 정의되어 있습니까? –

답변

4

의 Foreach이 어떤 종류의 루프 이상을 알아 내기 위해 약간의 "마법"을 사용합니다. 컬렉션이 무엇이든 IEnumerable을 지원할 필요는 없습니다. 아마도 차이점을 설명 할 것입니다.

컴파일러가 Where의 제네릭 형식 매개 변수를 알아낼 수 없다는 것이 문제입니다. 이렇게하면 작동합니다.

IEnumerable<DerivedRecord> ie = derivedCollection; 
ie.Where(d => d.DerivedProperty == ""); 

사용 가능한 캐스트는 없지만 사용 가능한 선택 사항 집합이 1로 줄어 듭니다.

또는 명시 적 형식 매개 변수를 지정할 수 있습니다 :이

abstract class BaseCollection 
{ 
    private readonly Collection<BaseRecord> _realCollection = new Collection<BaseRecord>(); 

    public void Add(BaseRecord rec) 
    { 
     _realCollection.Add(rec); 
    } 

    public IEnumerable<BaseRecord> Items 
    { 
     get { return _realCollection; } 
    } 
} 

: 나는 당신이 IEnumerable<T>의 다른 버전을 상속 BaseCollection을 중지해야 할거야 문제를 해결하기 위해 생각

derivedCollection.Where<DerivedRecord>(d => d.DerivedProperty == ""); 

을 여전히 Collection<T>을 인스턴스화하고 있지만 기본 클래스 대신 별도의 객체로 사용합니다. 그런 다음 필요에 따라 API를 모방하기 위해 항목을 전달합니다.

파생 된 일반 버전은 IEnumerable<T>을 완전히 구현해야합니다. 그것은 단지와 함께 작업 한 IEnumerable<T> 구현을 가지고 있기 때문에

class BaseCollection<TRecord> : BaseCollection, IEnumerable<TRecord> 
            where TRecord : BaseRecord,new() 
{ 
    public IEnumerator<TRecord> GetEnumerator() 
    { 
     return Items.Cast<TRecord>().GetEnumerator(); 
    } 

    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 
    { 
     return GetEnumerator(); 
    } 
} 

그런 다음 샘플 코드의 나머지 부분은, 내가 처음에 게시 된 해결 방법없이 잘 작동합니다.

업데이트 자세한 내용은

여기에 모든 것을 처음부터 자신을 선언, IEnumerable 모든 링크를 깨는, 같은 상황의 정말 몸매는 여전 하구나 버전, 그래서 우리는 무슨 일이 일어나고 있는지 볼 수 있습니다.

public class R1 { } 
public class R2 { } 
public interface I<T> { } 
public class C1<T> : I<T> { } 
public class C2 : C1<R1>, I<R2> { } 

class Program 
{ 
    public static I<T> M<T>(I<T> i) { return i; } 

    static void Main(string[] args) 
    { 
     var c2 = new C2(); 
     var v = M(c2); // Compiler error - no definition for M 
    } 
} 

R1R2는 두 기록 클래스 같다. 귀하의 버전에서 R2R1을 상속합니다. 그러나이 문제와 관련이 없으므로 생략했습니다. I<T>IEnumerable<T>을 나타내는 일반적인 인터페이스입니다. 하지만 내 버전에는 방법이 없습니다! 또한이 문제와 관련이 없습니다.

그런 다음 컬렉션 클래스 상속 시스템을 단 2 개의 레이어로 축소했습니다. 기본 클래스 C1I<T>를 구현하고 파생 클래스 C2I<R2> 직접 I<R1> 다음 구현을 구현하는 C2을 요청하도록 선택합니다. 층 수는 차이가 없습니다. where으로 선언 된 형식 제약도 없으므로 여기서도 생략됩니다. I<R1>I<R2> :

결론적으로 C2I<T> 두 구현한다는 것이다. 하나는 C1에서 상속 받고, 다른 하나는 자체를 추가합니다.

마지막으로 나는 Linq에서 Where을 나타내는 M 메서드를 얻었습니다. 문제를 표시하는 확장 메서드 일 필요가 없으므로 명확성을 위해 일반적인 정적 메서드로 만들었습니다.

따라서 우리의 메서드 M을 호출하면 컴파일러는 T이 무엇인지 파악해야합니다. 이는 우리가 전달한 것을이 메소드의 유일한 매개 변수로 보았습니다.이 메소드는 I<T>을 지원해야합니다. 죄송 합니다만 I<R1>I<R2>을 지원하는 형식을 전달하므로 유형 유추 과정을 통해 이러한 유형을 선택할 수 있습니까? 그것은 할 수 없다.

제 인터페이스에 메서드가 없으므로 메서드 앞에 new을 넣으면 도움이되지 않으므로 도움이되지 않습니다. 이 문제는 인터페이스에서 호출 할 메소드를 결정하는 것이 아니라 M 인수를 I<R1> 또는 I<R2>으로 처리할지 여부를 결정합니다.

컴파일러가이 문제를 유형 유추 문제로보고하지 않는 이유는 무엇입니까? C# 3.0 사양에 따르면, 형식 유추가 먼저 실행되어 사용 가능한 과부하 집합을 생성 한 다음 과부하 해결 실행이 최선의 선택을 선택합니다. 타입 유추가 일반적인 메소드의 두 가지 가능한 확장 사이에서 결정할 수 없다면 두 메소드를 모두 제거하므로 과부하 해결은 결코 볼 수 없으므로 M이라는 적용 가능한 메소드가 없다는 오류 메시지가 표시됩니다.

(그러나 Resharper가있는 경우 IDE에 자세한 오류를 제공하는 자체 컴파일러가 있으며이 경우 구체적으로 "메서드 M의 형식 인수를 사용법에서 유추 할 수 없습니다" .)

이제 foreach이 다른 이유는 무엇입니까? 그것은 심지어 타입 안전하지 않기 때문에! 그것은 generics가 추가되기 전에 거슬러 올라갑니다. 그것은 심지어 인터페이스를 보지 않습니다.어떤 유형의 루프 가건간에 public 메서드 인 GetEnumerator을 찾습니다. 예 :

public class C 
{ 
    public IEnumerator GetEnumerator() { return null; } 
} 

컴파일러에 관한 한 완벽하게 좋은 컬렉션입니다. (물론 그것은 런타임에 null이 IEnumerator을 반환하기 때문에 폭발 할 것입니다.) 그러나 IEnumerable 또는 모든 제네릭이 없다는 것에 유의하십시오. 즉, foreach은 암시 적 캐스트를 수행합니다.

코드와 관련하여 Linq의 Cast 연산자를 사용하여 구현 한 BaseRecord에서 DerivedRecord까지 "다운 캐스트"가 있습니다. 글쎄, foreach 어쨌든 그렇게합니다. 위의 예에서 C은 실질적으로 object 유형의 항목 모음입니다. 그럼에도 불구하고 나는 쓸 수 있습니다 :

foreach (string item in new C()) 
{ 
    Console.WriteLine(item.Length); 
} 

컴파일러는 행복하게 object에서 string에 자동 캐스트를 삽입합니다. 그 물건들은 전혀 달라질 수 있습니다.

이것이 var의 출현 이유입니다. foreach 루프 변수를 선언 할 때는 항상 var을 사용하십시오. 그러면 컴파일러에서 캐스트를 삽입하지 않습니다. 변수를 컴파일 할 때 컬렉션에서 추론 할 수있는 가장 구체적인 유형으로 만듭니다.

+0

이것이 유형 유추 문제라면 "형식 인수는 .....에서 추론 할 수 없습니다"와 같은 오류가 발생할 것으로 예상됩니다. 대신 다음과 같은 오류가 발생합니다 ... 'ConsoleApplication1.DerivedCollection'에 'Where'및 확장 메서드가 없음 'ConsoleApplication1.DerivedCollection'형식의 첫 번째 인수를 수락 할 수있는 정의가 없습니다. – barry

+0

동의합니다 문제는 ... "BaseCollection이 IEnumerable 의 다른 버전을 상속하지 못하도록해야합니다." BaseCollection의 IEnumerable 이 LINQ와 함께 작동하지 않는다고 생각하십니까? – barry

+0

위의 더 긴 설명 ... –

0

조차 추악한,하지만 작동합니다

(IEnumerable<DerivedRecord>)derivedCollection).Where(d => d.DerivedProperty == "") 
0

는 영업 이익은 아마 이미이 문제를 해결했다,하지만 다른 사람을 도움이된다면이 두 가지를 IEnumerable 유형을 가지고 있기 때문에 당신이 BaseCollection를 사용하려고하면, 기존의 Linq를 바꿈 : (BaseCollection에서 상속 ->Collection<BaseRecord>) IEnumerable<TRecord>IEnumerable<BaseRecord>. 두 개의 IEnumerable 타입을 가지고있는 동안 기존 Linq를 컴파일하려면, IQueryable<TypeYouWantToUse>을 구현하여 Linq가 사용할 IEnumerable을 정의하십시오.

abstract class BaseCollection<TRecord> : BaseCollection, IEnumerable<TRecord>, IQueryable<TRecord> where TRecord : BaseRecord, new() 
{ 
    public new IEnumerator<TRecord> GetEnumerator() 
    { 
     return Items.Cast<TRecord>().GetEnumerator(); 
    } 

    #region IQueryable<TRecord> Implementation 
    public Type ElementType 
    { 
     get { return typeof(TRecord); } 
    } 

    public System.Linq.Expressions.Expression Expression 
    { 
     get { return this.ToList<TRecord>().AsQueryable().Expression; } 
    } 

    public IQueryProvider Provider 
    { 
     get { return this.ToList<TRecord>().AsQueryable().Provider; } 
    } 
    #endregion 
} 
관련 문제