본문 바로가기
책/Effective C#

Effective C# - Item3 캐스트보다는 is,as가 좋다

by 코딩하는 돼징 2023. 11. 2.
반응형

C#은 정적 타이핑을 수행하는 언어로 변수나 표현식의 데이터 타입을 컴파일 시에 결정하고, 타입 불일치를 방지하기 위해 컴파일러가 타입 검사를 수행한다. 이로 인해 런타임에 타입 검사를 자주 수행할 필요가 적어진다. 그러나 때로는 런타임에서 반드시 타입을 확인하거나 형변환해야 하는 경우가 있다.


C#에서 형변환을 수행하는 두 가지 주요방법이 있다.

캐스트(Cast)연산자

'(타입)표현식'과 같이 명시적으로 형변환을 수행한다. 이는 컴파일러에게 형변환을 요청하는 방식이며 불일치가 발생하면 런타임 예외가 발생한다.


캐스트 연산자 동작

명시적으로 형변환을 요청하고 형변환이 실패하면 InvalidCastException이 발생한다. 그러므로 예외처리가 필요하다. 캐스팅 연선자는 객체를 새로운 형식으로 변환할 수 있지만 원래 객체를 변경하지 않고 새로운 객체를 생성한다. 이는 메모리 소비가 높을 수 있으며 예외 처리를 필요로 하기 때문에 주의가 필요하다.

object o = Factory.GetObject();

try
{
    MyType t;
    t = (MyTpye)o;
	// MyType 타입의 t객체 사용
}
catch(InvaildCastException)
{
    // 오류 보고
}

 


as연산자

as연산자를 통해 형변환을 시도하며 형변환이 실패하면 null을 반환한다. 이는 예외를 던지지 않으므로 안전한 방법이지만 사용자 정의 형변환이 수행되지 않을 수도있다. 따라서 정확히 일치하는 타입에만 사용해야한다.


as 연산자 동작

런타임에서 객체의 타입을 확인하고 필요에 따라 형변환을 수행하지만 객체의 원래 형식을 변경하지 않으며 새로운 객체를 생성하지 않는다. 따라서 메모리 측면에서 효율적이다.

object o = Factory.GetObject();

MyType t = o as MyType;

if(t != null)
{
	// MyType 타입의 t객체 사용
}
else
{
    // 오류 보고
}

사용자 정의 형변환 연산자가 객체의 런타임 타입이 아닌 컴파일 타임 타입에 따라 동작한다는 점은 중요한 점이다. 이는 컴파일러가 형변환 연산자를 찾을 때 변수의 선언된 타입을 기반으로 검토한다는 것을 의미한다.

컴파일 타임 타입을 기반으로 동작하는 사용자 정의 형변환 연산자

01 캐스팅을 사용한 경우

컴파일러는 변수 st가 어떤 타입으로 선언되었느냐에 관계없이 명시적인 형변환을 시도한다. 따라서 st가 실제로 MyType의 인스턴스인지 여부나 상속 관계와는 무관하게 컴파일러는 MyType으로의 형변환을 하려고 한다.
이로 인해 st가 MyType의 인스턴스가 아닌 경우 형변환에 실패하며 InvalidCastException 예외가 발생할 수 있다.

t = (MyType)st;

02 as를 사용한 경우

st가 어떤 타입으로 선언되었든 항상 동일한 결과를 반환하므로 st가 MyType의 인스턴스인 경우 t에는 해당 인스턴스가 할당되고 그렇지 않은 경우 t에는 null이 할당된다. 이로 인해 예외가 발생하지 않으며 코드는 예측 가능하게 동작한다. t의 값에 따라 st의 런타임 타입에 따라 결과가 달라지지 않는다.

t = st as MyType;

결과적으로 as연산자를 사용하는 경우 예외를 피할 수 있고 안전하게 형변환을 시도할 수 있으며 코드의 일관성을 높일 수 있다.


as를 사용할 수 없는 경우

int와 같이 null이 될 수 없는 타입에는 사용할 수 없다. 사용하고 싶은 경우 nullable타입으로 형변환을 수행한 후 그 값이 null인지 확인하면 된다.

object o = Factory.GetValue;
var i = o as int?; // int?는 nullable값 타입을 나타내는 특별한 타입이다.
if(i != null)
    Console.WriteLine(i.Value);

 

int?(nullable int)이 뭔가요?

nullable값 타입을 나타내는 특별한 타입이다. int타입의 변수가 null을 가질 수 있는 형태로 정의된다.


foreach에서는 어떤 것을 사용하는 것이 좋을까?

foreach에서는 generic타입이 아니라 IEnumerable인터페이스를 이용한다. IEnumerable을 사용하면 컬렉션의 요소를 열거하고 foreach 루프에서 형변환을 수행할 수 있다. 이를 통해 값 타입과 참조 타입 모두에 대한 형변환을 지원할 수 있다.

public void UseCollection(IEnumerable theCollection)
{
    foreach(MyType t in theCollection)
        t.DoStuff();
}

 

그러나 IEnumerable을 사용할 때 주의할 점은 InvalidCastException과 같은 예외가 발생할 수 있다.

IEnumerator.Current가 System.Object 타입의 객체를 반환하므로 컬렉션 내부의 요소가 어떤 타입인지, 그 타입을 루프 변수 타입으로 형변환 가능한지 등을 foreach 루프 자체에서 확인하지 않는다. foreach 루프는 요소가 System.Object 타입으로 형변환 가능한지와 System.Object 타입의 객체를 다시 루프 변수 타입으로 형변환 가능한지만을 확인한다.

따라서 형변환을 수행하기 전에 형변환이 안전한지 확인하는 as 연산자나 다른 방법을 사용하여 예외를 방지하는 것이 중요할 수 있습니다.


Generic과 IEnumerable이 뭐에요?

Generic

제네릭 코드는 형식 매개변수(타입 파리미터)를 사용하여 일반적으로 사용 가능한 기능을 정의한다. 데이터 형식에 대한 안정성을 확보하고 형변환이 필요하지 않다.

List<int> list = new List<int>(); // 제네릭 컬렉션

foreach (var number in list) // 제네릭 IEnumerable 사용
{
   Console.WriteLine(number); // 형변환이 필요하지 않음
}

IEnumerable

비제네릭이므로 요소의 데이터 형식에 대한 정보를 포함하고 있지 않다. 따라서 요소에 대한 형변환이 필요할 수 있다.

ArrayList list = new ArrayList(); // 비제네릭 컬렉션

foreach (var item in list) // IEnumerable 사용
{
    int number = (int)item; // 형변환 필요
    Console.WriteLine(number);
}

Enumerable.Cast<T>

컬렉션의 요소를 지정한 타입으로 형변환할 때 사용된다. IEnumerable 인터페이스를 구현하는 컬렉션에 대해 각 요소를 명시한 타입으로 형변환할 수 있게 해주는 LINQ 메서드이다.


Collection에서 int타입으로 형변환된 요소를 필터링하는 예시 코드

01 쿼리식 사용

쿼리 식을 사용하여 collection에서 item을 가져와 item이 5 미만인지 확인한 후 선택한다. 이 코드는 컴파일러가 LINQ 쿼리를 내부적으로 메서드 호출로 변환하고, item을 int로 형변환한 다음 조건을 확인한다.

IEnumerable collection = new List<int>(){1,2,3,4,5,6,7,8,9,10};
var small = from int item in collection
            where item < 5
            select item;

 


02 Enumerable.Cast

collection에 대해 Cast<int>()를 호출하여 컬렉션의 각 요소를 int로 형변환한 후, Where 메서드를 사용하여 조건을 확인하고 선택한다. 이 코드는 명시적인 형변환이 필요하므로, 형변환에 실패하면 예외가 발생할 수 있다.

IEnumerable collection = new List<int>(){1,2,3,4,5,6,7,8,9,10};
var small2 = collection.Cast<int>().Where(item => item < 5).Select(n => n);

 

 

제네릭 컬렉션에 대해서는 Cast<>를 호출할 수 없음에도 주의해야한다. 즉 int형 스퀀스에 대하여 Cast<double>()을 수행하면 실패한다. C# 4.0부터는 dynamic 키워드를 사용하여 더 유연한 런타임 타입 확인을 수행할 수 있다.


결론

가능하면 형변환을 피하는 것이 좋지만 불가피한 경우 is와 as연산자를 사용하는 것이 좋다.

 

 

 

 

 

 

본 게시글은 Effective C#을 읽고 정리하였습니다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

댓글