의 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
}
}
R1
및 R2
는 두 기록 클래스 같다. 귀하의 버전에서 R2
은 R1
을 상속합니다. 그러나이 문제와 관련이 없으므로 생략했습니다. I<T>
은 IEnumerable<T>
을 나타내는 일반적인 인터페이스입니다. 하지만 내 버전에는 방법이 없습니다! 또한이 문제와 관련이 없습니다.
그런 다음 컬렉션 클래스 상속 시스템을 단 2 개의 레이어로 축소했습니다. 기본 클래스 C1
는 I<T>
를 구현하고 파생 클래스 C2
는 I<R2>
직접 I<R1>
다음 도 구현을 구현하는 C2
을 요청하도록 선택합니다. 층 수는 차이가 없습니다. where
으로 선언 된 형식 제약도 없으므로 여기서도 생략됩니다. I<R1>
와 I<R2>
:
결론적으로 C2
가 I<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
을 사용하십시오. 그러면 컴파일러에서 캐스트를 삽입하지 않습니다. 변수를 컴파일 할 때 컬렉션에서 추론 할 수있는 가장 구체적인 유형으로 만듭니다.
'DerivedCollection'은 어디에 정의되어 있습니까? –