NGMsoftware

NGMsoftware
로그인 회원가입
  • 매뉴얼
  • 학습
  • 매뉴얼

    학습


    C# 17-2. C# 클래스의 기본 생성자 (Class's constructor)

    페이지 정보

    본문

    안녕하세요. 소심비형입니다. 이전 강좌에서 클래스를 구성하고 어떻게 생성하는지에 대해서 알아봤는데요. 오늘은 생성자(Constructor)와 생성자 오버로딩(Constructor overloading) 대해서 알아보도록 하겠습니다. 그 외에 자잘하게 이것 저것 추가가 되어 있지만, 기본 맥락에서 벗어나는 내용들은 이 후에 자세하게 알아볼 내용들입니다.

     

    C#은 인스턴스 생성자, 전용 생성자, 정적 생성자와 복사 생성자를 지원하고 있습니다. 클래스나 구조체를 new 키워드로 생성할 때 자동으로 생성자를 호출합니다. 클래스는 메서드 오버로딩처럼 서로 다른 인수를 사용하는 여러 생성자를 만들 수 있고 개발자는 생성자를 통해 기본값을 설정하고, 인스턴스화를 제한하며, 융통성 있고 읽기 쉬운 코드를 작성할 수 있습니다.

     

    이전 강좌에서 Cat 클래스의 생성자가 없는 것을 눈치 채셨을 수도 있습니다. 만약, 개발자가 작성한 클래스에 생성자를 제공하지 않으면 컴파일할 때 C#에서 개체를 인스턴스화하고 모든 맴버 변수에 기본값을 설정하는 생성자를 자동으로 만듭니다.

     

    생성자는 클래스와 같은 이름으로 만드는 메서드입니다. 일반적으로 생성자에서 개체의 데이터 맴버를 초기화합니다. 다음 예제에서는 기본 생성자를 사용하여 Taxi라는 클래스를 정의합니다. 그런 다음 new 연산자를 사용하여 이 클래스를 인스턴스화 합니다. 새 개체에 대한 메모리를 할당한 직후에 new 연산자에 의해 Taxi 생성자가 호출됩니다.

    public class Taxi
    {
        public bool isInitialized;
     
        public Taxi()
        {
            isInitialized = true;
        }
    }
     
    class TestTaxi
    {
        static void Main()
        {
            Taxi t = new Taxi();
            Console.WriteLine(t.isInitialized);
        }
    }
    

     

     

    위의 예제처럼 매개 변수를 사용하지 않는 생성자를 기본 생성자라고 합니다. 아래의 예제는 생성자 오버로딩을 통해 좀 더 효율적인 클래스를 구성하는 방법입니다. 이렇게하면 좀 더 융통성 있는 클래스의 인스턴스를 만들 수 있게 됩니다. 필요에 의해 여러개의 생성자를 오버로딩할 수 있습니다.

    using System;
    using System.Drawing;
     
    namespace Constructor
    {
        class Cat
        {
            public string Name; public Color Color; public Cat()
            {
                Name = string.Empty;
                Color = Color.White;
            }
     
            public Cat(string _Name, Color _Color)
            {
                Name = _Name;
                Color = _Color;
            }
     
            ~Cat()
            {
                Console.WriteLine("{0} : 잘가", Name);
            }
     
            public void Meow()
            {
                Console.WriteLine("{0} : 야옹", Name);
            }
        }
     
        class MainApp
        {
            static void Main(string[] args)
            {
                Cat kitty = new Cat("키티", Color.White);
                kitty.Meow();
                Console.WriteLine("{0} : {1}", kitty.Name, kitty.Color.Name);
                Cat nero = new Cat("네로", Color.Black);
                nero.Meow();
                Console.WriteLine("{0} : {1}", nero.Name, nero.Color.Name);
            }
        }
    }
    

     

     

    ※ 명확한 이해를 돕기 위해 클래스에는 public 필드가 포함되어 있습니다. public 필드를 사용하면 프로그램의 모든 메서드가 위치에 관계없이 내부 작업 확인을 거치지 않은 채 무제한으로 액세스할 수 있습니다. 실제로 필드(회사의 개발 환경)에서는 이렇게 코딩하지 않습니다. 맴버는 일반적으로 전용이어야 하며 클래스, 메서드 및 속성을 통해서만 접근 가능해야 합니다. OOP에서 이런 액세스 한정자를 통해 맴버 변수의 직접적인 접근을 제어하는 방법을 "필드 캡슐화"라고 부릅니다.

     

    ※ Visual Studio에서 공용 맴버 변수에 대한 필드 캡슐화 리팩터링을 하려면, Ctrl + R + E 단축키를 통해 쉽게 적용할 수 있습니다.

     

    ※ 20라인의 물결표시(틸드)는 소멸자(Destructor)입니다. 궁금할수도 있겠지만, 클래스 단원에 포함되어 있으므로 다음에 자세하게 다루도록 하겠습니다. 우선, 여기에서는 생성자와 반대로 인스턴스가 메모리에서 해제될 때 실행된다는 것만 알아두시면 되겠습니다.

     

    위의 예제에서 기본 생성자와 오버로딩한 생성자가 포함되어 있습니다. 이렇게 하면 특정 초기 값이나 기본값으로 개체를 초기화할 수 있으며 좀 더 융통성 있는 클래스를 구성할 수 있게 됩니다. 아래 예제는 일반적인 클래스의 구조를 잘 보여주고 있습니다.

    using System;
     
    namespace Structure
    {
        struct Point3D
        {
            public int X; public int Y; public int Z; public Point3D(int X, int Y, int Z)
            {
                this.X = X;
                this.Y = Y;
                this.Z = Z;
            }
     
            public override string ToString()
            {
                return string.Format("{0}, {1}, {2}", X, Y, Z);
            }
        }
     
        class MainApp
        {
            static void Main(string[] args)
            {
                Point3D p3d1;
                p3d1.X = 10;
                p3d1.Y = 20;
                p3d1.Z = 40;
                Console.WriteLine(p3d1.ToString());
                Point3D p3d2 = new Point3D(100, 200, 300);
                Point3D p3d3 = p3d2;
                p3d3.Z = 400;
                Console.WriteLine(p3d2.ToString());
                Console.WriteLine(p3d3.ToString());
            }
        }
    }

     

     

    위의 18라인을 보면 ToString 메서드를 제정의하여 개발자가 만든 클래스에서 처리하고 있습니다. 아직 오버라이딩에 대해서 학습하지 않은 내용으로 클래스 단원의 오버라이딩에서 진행할 내용입니다. 전체적인 클래스의 구조가 어떻게 구성되는지 간단하게 알 수 있습니다. 아직 캡슐화가 되어 있진 않지만 이런 내용은 차차 알아가야 할 부분입니다.

     

    Java에서는 접근 제어자 또는 접근 제한자라고 부르는 키워드가 포함되어 있습니다. C#도 Java와 동일한데(약간 다른 부분이 존재합니다.) 이런 접근 제한자를 통해 맴버 변수의 데이타에 접근할 수 없도록 제한할 수 있습니다. 아래는 맴버 변수에 접근 제한자를 이용하여 캡슐화하는 예제로 Java스타일로 만들어진 코드입니다.

    using System;
    namespace AccessModifier
    {
        class WaterHeater
        {
            protected int temperature; public void SetTemperature(int temperature)
            {
                if (temperature < -5 || temperature > 42)
                {
                    throw new Exception("Out of temperature range");
                }
     
                this.temperature = temperature;
            }
     
            internal void TurnOnWater()
            {
                Console.WriteLine("Turn on wanter : {0}", temperature);
            }
        }
     
        class MainApp
        {
            static void Main(string[] args)
            {
                try
                {
                    WaterHeater heater = new WaterHeater();
                    heater.SetTemperature(20);
                    heater.TurnOnWater();
                    heater.SetTemperature(-2);
                    heater.TurnOnWater();
                    heater.SetTemperature(50);
                    heater.TurnOnWater();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
     
                Console.Read();
            }
        }
    }

     

     

    위의 코드를 C# 스타일로 변경하면 아래와 같이 속성을 이용할 수 있습니다.

    using System;
    namespace AccessModifier
    {
        class WaterHeater
        {
            protected int temperature; public int Temperature
            {
                set
                {
                    if (value < -5 || value > 42)
                    {
                        throw new Exception("Out of temperature range");
                    }
     
                    this.temperature = value;
                }
            }
     
            internal void TurnOnWater()
            {
                Console.WriteLine("Turn on wanter : {0}", temperature);
            }
        }
     
        class MainApp
        {
            static void Main(string[] args)
            {
                try
                {
                    WaterHeater heater = new WaterHeater();
                    heater.Temperature = 20;
                    heater.TurnOnWater();
                    heater.Temperature = -2;
                    heater.TurnOnWater();
                    heater.Temperature = 50;
                    heater.TurnOnWater();
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
     
                Console.Read();
            }
        }
    }
    

     

     

    다음은 private 접근 제한자를 이용하여 전용 생성자를 만드는 방법입니다. 전용 생성자는 특수한 인스턴스 생성자입니다. 정적 맴버만 포함하는 클래스에서 일반적으로 사용됩니다. 클래스에 전용 생성자만 한 개 이상 있고 공용 생성자는 없을 경우 중첩 클래스를 제외한 다른 클래스는 이 클래스의 인스턴스를 만들 수 없습니다.

    using System;
    namespace AccessModifier
    {
        class NLog
        {
            // Private Constructor:     
            private NLog() { }
     
            public static double e = Math.E;  //2.71828...
        }
     
     
        class MainApp
        {
            static void Main(string[] args)
            {
     
            }
        }
    }

     

     

    빈 생성자를 선언하면 기본 생성자가 자동으로 생성되지 않습니다. 생성자에 액세스 한정자를 사용하지 않을 경우 클래스를 생성할때와 같이 전용 생성자가 됩니다. 하지만 클래스와는 다르게 전용 생성자를 만들려면 private 한정자를 명시적으로 적어주는게 좋습니다. 전용 생성자는 Math 클래스(.NET에서 제공하는 클래스)처럼 인스턴스 필드나 메서드가 없는 경우 또는 클래스의 인스턴스를 가져오기 위해 메서드가 호출되는 경우에 클래스의 인스턴스가 생성되지 않도록 방지하는데 사용됩니다. 만약, 모든 필드가 정적이면 클래스를 정적으로 만드는게 좋습니다.

     

    다음은 정적 생성자를 만드는 방법을 보여줍니다. 정적 생성자는 정적 데이타를 초기화하거나 한번만 수행하면 되는 특정 작업을 수행하는데 사용됩니다. 정적 생성자는 첫 번째 인스턴스가 만들어지기 전이나 정적 맴버가 참조되기 전에 자동으로 호출됩니다. 또한, 액세스 한정자를 사용하지 않고 매개 변수를 가질 수 없습니다.

    using System;
     
    namespace AccessModifier
    {
        public class Bus
        {
            protected static readonly DateTime globalStartTime;
            protected int RouteNumber { get; set; }
     
            // Static constructor.        
            static Bus()
            {
                globalStartTime = DateTime.Now;
                Console.WriteLine("Static constructor sets global start time to {0}", globalStartTime.ToLongTimeString());
            }
     
            // Instance constructor.         
            public Bus(int routeNum)
            {
                RouteNumber = routeNum;
                Console.WriteLine("Bus #{0} is created.", RouteNumber);
            }
     
            // Instance method.         
            public void Drive()
            {
                TimeSpan elapsedTime = DateTime.Now - globalStartTime;
                Console.WriteLine("{0} is starting its route {1:N2} minutes after global start time {2}.", 
                                  this.RouteNumber, elapsedTime.TotalMilliseconds, globalStartTime.ToString("HH:mm:ss.fff"));
            }
        }
        class StaticConstructor
        {
            static void Main(string[] args)
            {            
                // 이 인스턴스의 생성은 정적 생성자를 활성화합니다.            
                Bus bus1 = new Bus(71);
                // 두 번째 버스를 만듭니다.            
                Bus bus2 = new Bus(72);
                // 첫 번째 버스를 운행합니다.            
                bus1.Drive();
                // 두 번째 버스가 운행 준비가 완료될 때까지 대기합니다.            
                System.Threading.Thread.Sleep(1000);
                // 두 번째 버스를 운행합니다.            
                bus2.Drive();
                System.Console.WriteLine("Press any key to exit.");
                System.Console.ReadKey();
            }
        }
    }
    

     

     

    위 코드의 결과는 다음과 같습니다.

    T1OpV5K.png

     

     

    결과에서 알 수 있듯이 Bus의 첫 번째 인스턴스(bus1)를 만들면 정적 생성자가 호출되어 클래스를 초기화합니다. Bus의 두 번째 인스턴스(bus2)를 만들어도 정적 생성자가 한번만 실행되며, 인스턴스 생성자가 실행되기 전에 정적 생성자가 실행되는 것을 확인할 수 있습니다. 따라서 52라인에서 1초의 시간차를 두고 bus2를 운행해도 global start time은 동일하게 출력됩니다.

     

    마지막으로 Copy constructor(복사 생성자)에 대한 예제입니다. 다음의 예제에서 Person 클래스는 해당 인수로 Person인스턴스를 받아들이는 복사 생성자를 의미합니다. 인수의 속성 값은 Person의 새 인스턴스 속성에 할당됩니다.

    using System;
    namespace AccessModifier
    {
        class Person
        {
            // Copy constructor.         
            public Person(Person previousPerson) { Name = previousPerson.Name; Age = previousPerson.Age; }
            // Instance constructor.         
            public Person(string name, int age) { Name = name; Age = age; }
            public int Age { get; set; }
            public string Name { get; set; }
            public string Details() { return Name + " is " + Age.ToString(); }
        }
        class CopyConstructor
        {
            static void Main()
            {
                // 인스턴스 생성자를 사용하여 Person 객체를 생성합니다.            
                Person person1 = new Person("George", 40);
                // person1 복사, 다른 사람 개체를 만듭니다.            
                Person person2 = new Person(person1);
                // 각 사람의 나이를 변경합니다.            
                person1.Age = 39;
                person2.Age = 41;
                // person2의 이름을 변경합니다.            
                person2.Name = "Charles";
                // 이름과 나이 필드가 다르게 설정되는지 확인합니다.            
                Console.WriteLine(person1.Details());
                Console.WriteLine(person2.Details());
                Console.WriteLine("Press any key to exit.");
                Console.ReadKey();
            }
        }
    }

     

     

    위 예제의 결과입니다. 정상적으로 복사가 되었기 때문에 이름과 나이가 별개로 설정되어 있는것을 확인할 수 있습니다.

    ezq0j27.png

     

     

    C#은 개체에 대한 복사 생성자를 별도로 제공하지 않습니다. 그러나 위와같이 사용자가 직접 작성할 수 있습니다. 하지만, 참조 형식에 대한 복사는 위와 같은 방식으로 작성하지 않습니다. C#에서 제공하는 ICloneable인터페이스를 명시적으로 구현함으로써 복사(Deep copy)를 구현합니다. 나중에 진행할 얕은 복사(Shallow copy)와 깊은 복사(Deep copy)에서 좀 더 상세하게 다루도록 하겠습니다. 여기에서 배운 복사 생성자는 사실 사용되지는 않고 있지만 이렇게 처리할수도 있다는것 정도만 알아두시면 될거 같습니다.

     

    다음 시간에...

    • 네이버 공유하기
    • 페이스북 공유하기
    • 트위터 공유하기
    • 카카오스토리 공유하기
    추천0 비추천0

    댓글목록

    등록된 댓글이 없습니다.