본문으로 바로가기

C# 수업#12

category C# 2020. 6. 4. 15:16

안녕하세요 문쑹입니다 :)

오늘은 C# 기본문법 마지막 강의입니다 ㅠㅠ 이 다음 포스팅은 SQL을 포스팅 하게 될 것 같습니다!

 

클래스 간의 형변환

타입을 정의하는 것은 "단위(unit)"를 빈번하게 사용하는 프로그램에서 유용합니다. 예시로써, 통화(currency)단위를 생각해보면 원, 달러, 엔화가 있을 때 이를 단순히 decimal 하나로 지정한다면 중요한 금전 계산에 오류를 발생할 수 있다.

암시적으로 정한 규칙은 코드를 유지보수하는 동안 버그를 발생시킬 수 있다.

 

 * 외국 개발자들은 코드가 버그를 쉽게 유발할 수 있는 상황을 두고 "코드에서 냄새가 난다(code smells)"라는 표현을 사용한다고 합니다!

 

통화를 타입으로 예시를 작성해보겠습니다.

 public class Currency
    {        
        decimal money;
        public decimal Money 
        {
            get { return money; }
        }
        public Currency(decimal money)
        {
            this.money = money;
        }
    }
    public class Won : Currency
    {
        public Won(decimal money) : base(money) { }
        public override string ToString()
        {
            return Money + "Won";
        }
    }
    public class Dollar : Currency
    {
        public Dollar(decimal money) : base(money) { }
        public override string ToString()
        {
            return Money + "Dollar";
        }
    }
    public class Yen : Currency
    {
        public Yen(decimal money) : base(money) { }
        public override string ToString()
        {
            return Money + "Yen";
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
                 Won won = new Won(1000);
            Dollar dollar = new Dollar(1);
            Yen yen = new Yen(13);

            won = yen; //yen 과 won이 타입이 다르기 때문에 컴파일 시에 오류 발생
        }
    }

Won과 Yen 사이에 형변환이 가능하도록 하고 싶다면 어떻게 해야 될까? 예를 들어, 13원이 Yen 단위에 대입될 때는 1엔으로 되고 그 반대의 변환도 일정한 환율에 따라 정의될 수 있다. 이렇게 하려면 =(대입 연산자)를 정의해야 하지만 위의 코드처럼 C#에서는 허용되지 않는다. 하지만 대체 구문으로 explicit, implicit 메서드를 정의하면 가능하다!

public class Yen : Currency
    {
        static public implicit operator Won(Yen yen)
        {
            return new Won(yen.Money * 13m);    // 1엔당 13원으로 가정
        }

        public Yen(decimal money) : base(money) { }
        public override string ToString()
        {
            return Money + "Yen";
        }
    }

 implicit operator를 오버로드했으므로 암시적 형변환을 할 수 있고, 암시적인 형변환이 가능하므로 명시적으로 캐스팅 연산자를 쓰는 것도 가능하다.


 public class Dollar : Currency
{
      static public explicit operator Won(Dollar dollar)
        {
              return new Won(dollar.Money * 1000m);
        }
        public Dollar(decimal money) : base(money) { }
        public override string ToString()
           {
              return Money + "Dollar";
            }
}

class Program
{
        static void Main(string[] args)
        {
            Dollar dollar = new Dollar(1);

            //Won won3 = dollar;         //암시적(implicit) 형변환 불가능(컴파일 오류 발생)
            Won won4 = (Won)dollar;    //명시적(explicit) 형변환 가능

            Console.WriteLine(won4);
}

explicit은 암시적 형변환은 불가능 하므로 컴파일을 할 때 오류가 발생한다. 그래서 캐스팅을 해주어야 컴파일이 가능하다.

 

C#의 클래스 확장

중첩클래스(nested class) 중첩클래스는 클래스 내부에 또 다른 클래스를 정의하는 것이다.

 

 추상 클래스(abstract class)추상 메서드(abstract method)

    class Point
    {
        int x, y;

        public Point(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
        public override string ToString()
        {
            return "X : " + x+" , " + "Y : " + y;
        }
    }

    abstract class DrawingObject    //추상 클래스
    {
        public abstract void Draw();    // 추상 메서드(코드 없는 가상 메서드)
        public void Move() { Console.WriteLine("Move"); }   //일반 메서드도 정의 가능
    }

    class Line : DrawingObject          // 추상 클래스를 상속받는 Line 클래스
    {
        Point pt1, pt2;
        public Line(Point pt1, Point pt2)
        {
            this.pt1 = pt1;
            this.pt2 = pt2;
        }
        public override void Draw()     // 추상 클래스를 구현했다면 추상 메서드를 반드시 정의해야 함
        {
            Console.WriteLine("Line " + pt1.ToString() + " ~ " + pt2.ToString());
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            DrawingObject line = new Line(new Point(10, 10), new Point(20, 20));
            line.Draw();    //다형성에 따라 Line.Draw가 호출됨
        }
    }

추상 메서드에는 접근 제한자로 private을 지정할 수 없다. 추상클래스를 new로 인스턴스화할 수 없다는 것은 이해가 된다. 왜냐하면 추상 클래스 내부에 구현 코드가 없는 메서드, 즉 추상 메서드가 있어서이다. 추상 클래스에 반드시 추상 메서드가 포함돼 있어야 하는 것은 아니지만 그래도 여전히 추상 클래스는 new로 인스턴스화할 수 없다.

 

델리게이트

대상이 될 메서드의 반환 타입 및 매개변수 목록과 일치하는 델리게이트 타입을 정의한다.

접근제한자 delegate 대상_메서드의_반환타입 식별자( 대상_메서드의_매개변수_목록);

Delegate 타입의 정의 방법

간단하게 말하자면 delegate는 영어로 "대리인"이라는 뜻이 있습니다. 누군가를 대신해서 일을 해주는 사람이라는 의미이기도 합니다.

  public class Mathematics
    {
        delegate int CalcDelegate(int x, int y);

        static int Add(int x, int y) { return x + y; }
        static int Sub(int x, int y) { return x - y; }
        static int Mul (int x, int y) { return x * y; }
        static int Div(int x, int y) { return x / y; }

        CalcDelegate[] method;

        public Mathematics()
        {
            //static 메서드를 가리키는 델리게이트 배열 초기화
            method = new CalcDelegate[] { Mathematics.Add, Mathematics.Sub, Mathematics.Mul, Mathematics.Div };
        }
        // methods 배열에 담긴 델리게이트를 opCode 인자에 따라 호출
        public void Calculate(char opCode, int Operand1, int Operand2)
        {
            switch(opCode)
            {
                case '+':
                    Console.WriteLine("+ : " + method[0](Operand1, Operand2));
                    break;
                case '-':
                    Console.WriteLine("- : " + method[1](Operand1, Operand2));
                    break;
                case '*':
                    Console.WriteLine("* : " + method[2](Operand1, Operand2));
                    break;
                case '/':
                    Console.WriteLine("/ : " + method[3](Operand1, Operand2));
                    break;
            }
        }
    }
    class Program
    {
        // 3개의 매개변수를 받고 void를 반환하는 델리게이트 정의
        // 매개변수의 타입이 중요할 뿐 매개변수의 이름은 임의로 정할 수 있음.

        delegate void WorkDelegate(char arg1, int arg2, int arg3);

        static void Main(string[] args)
        {
            Mathematics math = new Mathematics();
            WorkDelegate work = math.Calculate;
            work('+', 10, 5);
            work('-', 10, 5);
            work('*', 10, 5);
            work('/', 10, 5);
        }
    }

델리게이트가 타입이라는 점을 잘 기억해야할 것 같습니다. 이 때문에 변수가 사용되는 곳이라면 델리게이트 또한 함께 사용됩니다.

  • 메서드의 반환값으로 델리게이트를 사용할 수 있다.
  • 메서드의 인자로 델리게이트를 전달할 수 있다.
  • 클래스의 멤버로 델리게이트를 정의할 수 있다.

다시 한번 델리게이트가 메서드를 가리키는 것을 떠올려본다면

  • 메서드의 반환값으로 메서드를 사용할 수 있다.
  • 메서드의 인자로 메서드를 전달할 수 있다.
  • 클래스의 멤버로 메서드를 정의할 수 있다.

위의 복잡한 코드로 MulticastDelegate라는 내부 타입을 사용한다면 간략하게 사용할 수 있다. 지금까지의 예제에서 델리게이트 인스턴스는 하나의 메서드만 가리키고 있었는데 MulticastDelegate의 이름에서 알 수 있듯이, 여러 개의 메서드를 가리키는 것도 가능하다.

class Program
    {
        delegate void CalcDelegate(int x, int y);

        static void Add(int x, int y) { Console.WriteLine(x + y); }
        static void Sub(int x, int y) { Console.WriteLine(x - y); }
        static void Mul(int x, int y) { Console.WriteLine(x * y); }
        static void Div(int x, int y) { Console.WriteLine(x / y); }

        static void Main(string[] args)
        {
            CalcDelegate calc = Add;
            calc += Sub;
            calc += Mul;
            calc += Div;
            calc(10, 5);
            //출력 결과 15, 5, 50, 2
        }
    }

위의 예제와 같이 += 연산자를 사용하여 모든 결과를 호출할 수 있다. 반대로 원하는 메서드만 빼서 호출할 수도 있다.

class Program
    {
        delegate void CalcDelegate(int x, int y);

        static void Add(int x, int y) { Console.WriteLine(x + y); }
        static void Sub(int x, int y) { Console.WriteLine(x - y); }
        static void Mul(int x, int y) { Console.WriteLine(x * y); }
        static void Div(int x, int y) { Console.WriteLine(x / y); }

        static void Main(string[] args)
        {
            CalcDelegate calc = Add;
            calc += Sub;
            calc += Mul;
            calc += Div;

            Console.WriteLine("+=");
            calc(10, 5);                // Add, Sub, Mul, Div 메서드 모두 호출

            calc -= Mul;
            Console.WriteLine("-=");    // 목록에서 Mul 메서드 제거
            calc(10, 5);                // Add, Sub, Div 메서드만 호출
        }
    }

이상 포스팅을 마치겠습니다 :) 감사합니다

Hasta Luego~!

'C#' 카테고리의 다른 글

Delegate  (0) 2020.09.15
C# 수업#11  (0) 2020.06.03
C# 수업#10  (0) 2020.06.02
C# 수업#9  (0) 2020.06.01
C# 수업#8  (0) 2020.05.29