C# - foreach에서 열거 변수의 타입을 var로 쓰면 object로 추론하는 문제
아래와 같은 질문이 있는데요,
var를 사용할 수 없는 이유가 궁금합니다!
; https://www.sysnet.pe.kr/3/0/5452
실제로 위의 글에 실린 예제에서 foreach 열거 변수의 타입을 var로 바꾸면,
using System;
using System.Collections;
class Book
{
public long ISBN { get; set; }
public string Writer { get; set; } = string.Empty;
public string PublishingCompany { get; set; } = string.Empty;
}
class Bookcase : IEnumerable
{
ArrayList _books = new ArrayList();
public void Add(Book book)
{
_books.Add(book);
}
public IEnumerator GetEnumerator()
{
return _books.GetEnumerator();
}
}
class Program
{
static void Main(string[] args)
{
var bookcase = new Bookcase();
bookcase.Add(new Book()
{
ISBN = 9791158391805,
Writer = "JungSeongtae",
PublishingCompany = "wikibooks",
});
foreach (var item in bookcase)
{
Console.WriteLine(item.ISBN); // 컴파일 오류: Error CS1061 'object' does not contain a definition for 'ISBN' and no accessible extension method 'ISBN' accepting a first argument of type 'object' could be found (are you missing a using directive or an assembly reference?)
}
}
}
해당 변수의 ISBN 필드를 접근하는 코드에서 컴파일 오류가 발생합니다. 즉, 위의 코드는 사실상 다음과 같이 인식되므로,
foreach (object item in bookcase)
{
Console.WriteLine(item.ISBN);
}
당연히 object 타입의 ISBN 필드는 존재하지 않아 컴파일 오류가 발생하는 것입니다. 그리고, C# 컴파일러가 그런 식으로 추론할 수밖에 없는 이유는 IEnumerable 인터페이스로부터 어떠한 타입 정보도 얻을 수 없기 때문입니다.
따라서, 해결 방법은 타입 정보를 알 수 있는 IEnumerable<T> 인터페이스를 구현하면 되는 것입니다. 가령, 위의 Bookcase 타입은 다음과 같은 식으로 코드를 추가할 수 있습니다.
class Bookcase : IEnumerable<Book>
{
List<Book> _books = new List<Book>();
public void Add(Book book)
{
_books.Add(book);
}
public IEnumerator GetEnumerator()
{
return _books.GetEnumerator();
}
public IEnumerator<Book> GetEnumerator()
{
return _books.GetEnumerator();
}
}
그런데, 저렇게 구현하면 동일한 이름의 GetEnumerator 메서드를 2개 정의한 것이므로 컴파일 오류가 발생합니다. 따라서, 최종적으로는 다음과 같이 IEnumerable의 GetEnumerator 버전을 숨기도록 "
명시적인 인터페이스 구현" 방식을 사용해야 합니다.
class Bookcase : IEnumerable<Book>
{
List<Book> _books = new List<Book>();
public void Add(Book book)
{
_books.Add(book);
}
// 명시적인 인터페이스 구현으로 타입에서 IEnumerable 버전의 GetEnumerator() 메서드를 숨김 처리
IEnumerator IEnumerable.GetEnumerator()
{
return _books.GetEnumerator();
}
// 따라서 C# 컴파일러는 IEnumerable<Book> 버전을 선택하므로 Book 타입 정보를 구함
IEnumerator<Book> IEnumerable<Book>.GetEnumerator()
{
return _books.GetEnumerator();
}
}
이후 다시 빌드하면 정상적으로 "var item"이 "Book item"으로 인식되는 것을 확인할 수 있습니다.
(
첨부 파일은 이 글의 예제 코드를 포함합니다.)
참고로, 만약 대상 타입이 소스 코드를 변경할 수 없는 경우라면 어떻게 해야 할까요?
어쩔 수 없습니다. 이런 경우는 확장 메서드를 사용해 IEnumerable<T>를 반환하는 별도의 메서드를 정의해 그걸 사용해야 합니다. 그리고 그런 확장 메서드는 이미 만들어진 것이 있으므로 그냥 다음과 같이 사용할 수 있습니다.
using System.Linq; // Cast가 Linq의 확장 메서드이므로.
foreach (var item in bookcase.Cast<Book>())
{
Console.WriteLine(item.ISBN);
}
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]