방프리

23.01.24 Chapter2. API 설계 (Item 11) 본문

C#/More Effective C#

23.01.24 Chapter2. API 설계 (Item 11)

방프리 2023. 1. 24. 23:40

Item 11 : API에는 변환 연산자를 작성하지 말라

 

처음 객체지향을 배울 때 상속이라는 개념을 배운다. 이를 토대로 C#에서는 모든 데이터 타입은 System.Object 타입을

상속받고 있다. 보통 상속이라는 개념을 설명할 때에는 도형을 자주 빗대어 설명한다.

public class Circle : Shape
{
    private Point center;
    private double radius;
    
    public Circle() : 
        this(new Point(), 0)
    {
    }
    
    public Circle(Point c, double r)
    {
        center = c;
        radius = r;
    }
    
    public override void Draw()
    {
        //...
    }
    
    static public implicit operator Ellipse(Circle c)
    {
        return new Ellipse(c.center, c.center, c.radius, c.radius);
    }
}

모든 원은 도형이 될 수 있으며 모든 타원은 operator Ellipse 메서드를 통해 암묵적 변환이 가능하도록 정의하였다.

해당 메서드를 통해 다음의 함수는 정상적으로 동작할 수 있게 되었다.

public static double ComputeArea(Ellipse e) => e.R1 * e.R2 * Math.PI;

Circle c1 = new Circle(new Point(3.0, 0), 5.0f);
ComputeArea(c1);

하지만 다음의 함수에서는 정상적으로 동작하지 않는다.

public static void Flatten(Ellipse e)
{
    e.R1 /= 2;
    e.R2 *= 2;
}

Circle c = new Circle(new Point(3.0, 0), 5.0f);
Flatten(c);

왜일까? 두 함수의 차이를 잘 생각해보면 될 것 같다. 위의 함수는 객체의 속성값을 통해 새로운 결과값을 반환한다.

하지만 두 번째 함수는 객체의 속성값을 변경한다.

Circle 클래스는 operator Ellipse 함수를 잘 보면 객체를 그대로 넘기는 것이 아닌 새로 생성 후 객체를 넘기고 있다.

이러면 객체 원본이 아닌 임시객체를 함수 인자로 넘기게 되기 때문이다. 그렇다면 해결방법은 없는걸까?

Circle c = new Circle(new Point(3.0, 0), 5.0f);
Flatten(c);
// 원 관련 처리
// ...

// 타원으로 변환
Ellipse e = new Ellipse(c);
Flatten(e);

아예 타원 객체를 생성 후 넘기는 것이다. 이렇게 되면 임시 객체를 생성하지 않고 Flatten 함수도 정상적으로 동작한다.

암묵적 변환은 정말 편리한 기능이다. 하지만 명시적이지 않았을 때 오류가 나게되면 찾기 어렵다는 점

그리고 이와 같이 사용할 수 있는 범위가 제한이 있다는 점을 인지하고 있는 상태에서 사용해야한다.

Comments