2016년 4월 13일 수요일

C# Class 특성, 상속, 다형성 관련 정리

[C# Class 예제]


namespace ProgrammingGuide
{
    // Class definition.
    public class MyCustomClass
    {
        // Class members:
        // Property.
        public int Number { get; set; }

        // Method.
        public int Multiply(int num)
        {
            return num * Number;
        }

        // Instance Constructor.
        public MyCustomClass()
        {
            Number = 0;
        }
    }
    // Another class definition. This one contains
    // the Main method, the entry point for the program.
    class Program
    {
        static void Main(string[] args)
        {
            // Create an object of type MyCustomClass.
            MyCustomClass myClass = new MyCustomClass();

            // Set the value of a public property.
            myClass.Number = 27;

            // Call a public method.
            int result = myClass.Multiply(4);
        }
    }
}

[C# Class Features]

- C++과 다르게 struct는 값형식(복사시 값들이 복사 된다), class는 참조형식(복사시 참조만 복사 된다.) 
 : C#에서는 struct는 단지 값을 저장하기 위한 단순 용도로 사용해야 할 것 같음. 

- 접근 제한자로 public, private, protected, internal protected internal 이 있음

- Constructor, destructor 정의는 대부분 유사하고 상속된 클래스들의 destructor들은 자동으로 자식부터 부모 순으로 호출 됨.

- getter, setter를 위해 손쉬운 property라는 방법을 제공함

- 단일 상속만 허용
 : 하지만 인터페이스를 두개 이상 구현할 수 있음.

- 최상위 객체는 object 임

- 상속에서 virtual, override, sealed, new, abstract 등의 keyword들을 사용

[Access Modifiers, 접근 제한자]


일반적으로 public, private, protected만 봤었는데 어셈블리 레벨에서 접근을 제하하는 internal, protected internal 이 있음.

C# 한정자
정의

동일한 어셈블리의 다른 코드나 해당 어셈블리를 참조하는 다른 어셈블리의 코드에서 형식이나 멤버에 액세스할 수 있습니다.
동일한 클래스의 코드에서만 형식이나 멤버에 액세스할 수 있습니다.

동일한 클래스의 코드나 파생 클래스의 코드에서만 형식이나 멤버에 액세스할 수 있습니다.
동일한 어셈블리의 코드에서는 형식이나 멤버에 액세스할 수 있지만 다른 어셈블리의 코드에서는 액세스할 수 없습니다.

protected internal
동일한 어셈블리의 코드 또는 다른 어셈블리의 파생 클래스에서 형식이나 멤버에 액세스할 수 있습니다.

다음 예제에는 두 개의 파일 Assembly1.cs 및 Assembly1_a.cs가 포함되어 있습니다. 첫 번째 파일에는 내부 기본 클래스 BaseClass가 있습니다. 두 번째 파일에서 BaseClass를 인스턴스화하려고 시도하면 오류가 발생합니다.

// Assembly1.cs
// Compile with: /target:library
internal class BaseClass 
{
   public static int intM = 0;
}
// Assembly1_a.cs
// Compile with: /reference:Assembly1.dll
class TestAccess 
{
   static void Main() 
   {
      BaseClass myBase = new BaseClass();   // CS0122
   }
}

[Constructor(생성자), Destructor(소멸자)]


Constructor, 생성자

 : 클래스 이름으로 표현, 반환 선언 없음.

- Default Constructor
 : 아무 매개변수를 가지지 않는 constructor.
 : 명시되지 않았을 경우 static constructor를 제외하고 컴파일러에서 자동 생성

- base
 : 상속한 부모 class의 constructor를 명시적으로 호출하거나(1) 자신의 default constructor를 호출할 때 사용(2)

(1) 상속 관계에서 부모(Employee)의 constructor를 호출하는 예제

public class Manager : Employee
{
    public Manager(int annualSalary)
        : base(annualSalary)
    {
        //Add further instructions here.
    }
}
(2) 자신의 default constructor를 호출하는 예제

public class Employee
{
    public int salary;

    public Employee(int annualSalary)
    {
        salary = annualSalary;
    }

    public Employee(int weeklySalary, int numberOfWeeks)
    {
        salary = weeklySalary * numberOfWeeks;
    }
}
다음 문 중 하나를 사용하여 이 클래스를 만들 수 있습니다.
Employee e1 = new Employee(30000);
Employee e2 = new Employee(500, 52);
생성자에서는 base 키워드를 사용하여 기본 클래스의 생성자를 호출할 수 있습니다.예를 들면 다음과 같습니다.
public class Manager : Employee
{
    public Manager(int annualSalary)
        : base(annualSalary)
    {
        //Add further instructions here.
    }
}

- this
 : 자신이 가진 constructor를 호출할 때 사용 가능

public Employee(int weeklySalary, int numberOfWeeks)
    : this(weeklySalary * numberOfWeeks)
{
}

- Private Constructor
 : Instance화가 필요없이 static member만 제공할 경우 사용하는 방법

class NLog
{
    // Private Constructor:
    private NLog() { }

    public static double e = Math.E;  //2.71828...
}

- Static Constructor
 : 정적 데이터를 초기화 하거나 생성자에서 수행하는 작업을 한번만 수행하기 위해서 사용 
 : 첫번째 인스턴스가 만들어지기 전이나 정적 멤버가 참조되기 전에 클래스를 초기화 하기 위해 자동으로 호출 됨.

class SimpleClass
{
    // Static variable that must be initialized at run time.
    static readonly long baseline;

    // Static constructor is called at most one time, before any
    // instance constructor is invoked or member is accessed.
    static SimpleClass()
    {
        baseline = DateTime.Now.Ticks;
    }
}

- Copy Constructor
 : C#에서는 Copy Constructor를 제공하지 않지만 아래와 같이 만들 수 있음.

class Person
{
    // Copy constructor.
    public Person(Person previousPerson)
    {
        Name = previousPerson.Name;
        Age = previousPerson.Age;
    }

    //// Alternate copy constructor calls the instance constructor.
    //public Person(Person previousPerson)
    //    : this(previousPerson.Name, 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();
    }
}

Destructor, 소멸자

 : ~와 클래스 이름으로 표현, 접근제한자/반환/매개변수 선언 없음.
 : 클래스에서만 사용 가능하고 구조체에 정의할 수 없음.
 : 하나의 소멸자만 정의 가능, 상속/오버로드 불가
 : 상속관계에서 소멸자는 자식부터 부모순으로 호출 된다. 
 : 소멸자에서 자동으로 GC를 호출하므로 빈 소멸자는 정의 하지 않는 것이 추천 됨.

class Car
{
    ~Car()  // destructor
    {
        // cleanup statements...
    }
}
소멸자는 개체의 기본 클래스에서 암시적으로 Finalize를 호출합니다. 따라서 위의 소멸자 코드는 암시적으로 다음과 같은 코드로 변환됩니다.
protected override void Finalize()
{
    try
    {
        // Cleanup statements...
    }
    finally
    {
        base.Finalize();
    }
}
즉, Finalize 메서드는 최대 파생 인스턴스부터 최소 파생 인스턴스까지 상속 체인에 있는 모든 인스턴스에 대해 재귀적으로 호출됩니다.
System_CAPS_note참고
빈 소멸자는 사용하지 않는 것이 좋습니다. 클래스에 소멸자가 포함된 경우 Finalize 큐에 엔트리가 만들어집니다. 소멸자가 호출되면 큐 처리를 위해 가비지 수집기가 호출됩니다. 그러므로 빈 소멸자는 성능 저하를 가져올 뿐입니다.

Object Initiailizer
 : https://msdn.microsoft.com/ko-kr/library/bb397680.aspx
 : 생성자를 명시적으로 호출하지 않고 선언적 방식으로 형식적 개체를 초기화 할 수 있음.
 : 적절한 생성자를 호출하고 없다면 기본 생성자 호출 후 멤버 초기화

public class Program
{
    public static void Main()
    {

        // Declare a StudentName by using the constructor that has two parameters.
        StudentName student1 = new StudentName("Craig", "Playstead");

        // Make the same declaration by using an object initializer and sending 
        // arguments for the first and last names. The default constructor is 
        // invoked in processing this declaration, not the constructor that has
        // two parameters.
        StudentName student2 = new StudentName
        {
            FirstName = "Craig",
            LastName = "Playstead",
        };

        // Declare a StudentName by using an object initializer and sending 
        // an argument for only the ID property. No corresponding constructor is
        // necessary. Only the default constructor is used to process object 
        // initializers.
        StudentName student3 = new StudentName
        {
            ID = 183
        };

        // Declare a StudentName by using an object initializer and sending
        // arguments for all three properties. No corresponding constructor is 
        // defined in the class.
        StudentName student4 = new StudentName
        {
            FirstName = "Craig",
            LastName = "Playstead",
            ID = 116
        };

        System.Console.WriteLine(student1.ToString());
        System.Console.WriteLine(student2.ToString());
        System.Console.WriteLine(student3.ToString());
        System.Console.WriteLine(student4.ToString());
    }

    // Output:
    // Craig  0
    // Craig  0
    //   183
    // Craig  116
}

public class StudentName
{
    // The default constructor has no parameters. The default constructor 
    // is invoked in the processing of object initializers. 
    // You can test this by changing the access modifier from public to 
    // private. The declarations in Main that use object initializers will 
    // fail.
    public StudentName() { }

    // The following constructor has parameters for two of the three 
    // properties. 
    public StudentName(string first, string last)
    {
        FirstName = first;
        LastName = last;
    }

    // Properties.
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int ID { get; set; }

    public override string ToString()
    {
        return FirstName + "  " + ID;
    }
}

[Property, Setter, Getter]

 - get은 member 변수를 반환하고 set은 새 값을 할당하는데 사용

class TimePeriod
{
    private double seconds;

    public double Hours
    {
        get { return seconds / 3600; }
        set { seconds = value * 3600; }
    }
}


class Program
{
    static void Main()
    {
        TimePeriod t = new TimePeriod();

        // Assigning the Hours property causes the 'set' accessor to be called.
        t.Hours = 24;

        // Evaluating the Hours property causes the 'get' accessor to be called.
        System.Console.WriteLine("Time in hours: " + t.Hours);
    }
}
// Output: Time in hours: 24

 - 각가 다른 접근 제한자를 가질 수 있음.
  : get은 property의 기본 접근 제한자인 public 임

private string name = "Hello";

public string Name
{
    get
    {
        return name;
    }
    protected set
    {
        name = value;
    }
}
 - value는 set 접근자에서 할당하는 값을 의미
 - set을 구현하지 않는 속성은 읽기 전용
 - set, get을 위한 별도 구현이 필요 없을 경우 아래 처럼 자동 구현 속성을 사용할 수 있음.

// This class is mutable. Its data can be modified from
// outside the class.
class Customer
{
    // Auto-Impl Properties for trivial get and set
    public double TotalPurchases { get; set; }
    public string Name { get; set; }
    public int CustomerID { get; set; }

    // Constructor
    public Customer(double purchases, string name, int ID)
    {
        TotalPurchases = purchases;
        Name = name;
        CustomerID = ID;
    }
    // Methods
    public string GetContactInfo() {return "ContactInfo";}
    public string GetTransactionHistory() {return "History";}

    // .. Additional methods, events, etc.
}

class Program
{
    static void Main()
    {
        // Intialize a new object.
        Customer cust1 = new Customer ( 4987.63, "Northwind",90108 );

        //Modify a property
        cust1.TotalPurchases += 499.99;
    }
}

[Inheritance(상속), Polymorphism(다형성)]


객체 지향 프로그래밍(Object-Oriented Programming)

 : 개념이 너무 방대하여 자세한 내용은 적지 못하고 위 링크를 참고....

Inheritance, 상속

 : 특정 클래스를 이용하여 새로운 클래스를 정의할 때 사용 되며 자식(파생) 클래스는 부모(기본) 클래스의 멤버를 접근 제한자에 따라서 접근할 수 있고 재정의 할 수 있다..


Polymorphism, 다형성


- Run-time에 자식 클래스는 다형성의 방법을 통해 부모 클래스의 형태로 처리 될 수 있고 이는 collection에 자식 클래스들을 넣고 처리할 경우 유용하다.

- 부모 클래스는 virtual method를 구현할 수 있고 이를 자식 클래스들에서는 재정의 하여 구현에서 자식 클래스의 종류에 상관없이 필요한 기능을 실행/사용 할 수 있다.

- 예제 설명
 : Shape라는 부모 클래스를 만들고 Rectanble, Circle 및 Triangle과 같은 자식 클래스를 만듭니다. 
 : Shape 클래스에 Draw라는 가상 메서드를 제공하고, 각 자식 클래스들에서 재정의하여 자식 클래스가 나타내는 특정 도형을 그림. 
 : 부모 클래스의 Shape 형태의 List<Shape> 개체를 만들고 Shape를 상속한 Circle, Triangle 및 Rectangle을 추가할 수 있음.
 : 화면을 업데이트하기 위해 foreach 루프를 사용하여 목록의 각 Shape 개체에서 가상 상속하고 있는 Draw 메서드를 호출합니다. 
  . 이 때 호출되는 목록의 형태는 Shape이더라도 호출되는 런타임 형식은 각 파생 클래스에서 재정의한 Draw 메서드입니다.


public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Polymorphism at work #1: a Rectangle, Triangle and Circle
        // can all be used whereever a Shape is expected. No cast is
        // required because an implicit conversion exists from a derived 
        // class to its base class.
        System.Collections.Generic.List<Shape> shapes = new System.Collections.Generic.List<Shape>();
        shapes.Add(new Rectangle());
        shapes.Add(new Triangle());
        shapes.Add(new Circle());

        // Polymorphism at work #2: the virtual method Draw is
        // invoked on each of the derived classes, not the base class.
        foreach (Shape s in shapes)
        {
            s.Draw();
        }

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

}

/* Output:
    Drawing a rectangle
    Performing base class drawing tasks
    Drawing a triangle
    Performing base class drawing tasks
    Drawing a circle
    Performing base class drawing tasks
 */


- virtual, override
 : 부모 클래스는 자식 클래스가 재정의 할 수 있도록 virtual로 명시함.
  . virtual method가 호출 되면 개체의 run-time 형식이 검사 됨.
  . virtual 한정자는 static, abstract, private, override 한장자와 함께 사용 불가

 : 자식 클래스는 Rum-time시 가상 호출이 될 수 있도록 override를 명시함.
  . override로는 virtual, abstract, override 속성의 method를 재정의 가능하지만 non virtual, static 속성의 method는 재정의 불가
  . virtual method의 접근 제한자를 변경 할 수 없음.

public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}

 : 자식 클래스가 부모 클래스의 instance로 사용 되는 경우에도 override된 method가 호출 됨.

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Also calls the new method.

- new
 : 상속된 부모 클래스의 member를 숨길 경우 사용

public class BaseClass
{
    public void DoWork() { WorkField++; }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { WorkField++; }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}
 : 부모 클래스로 캐스팅하여 부모 클래스의 method를 호출 가능

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

- sealed
 : 상속된 클래스간에 가상 상속은 무제한으로 가능함.
 : 자식 클래스에서 더이상의 가상 상속을 방지하기 위해서는 sealed를 선언함.

public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}

public class C : B
{
    public sealed override void DoWork() { }
}

- base
 : 자식 클래스에서 부모 클래스의 method나 속성에 접근할 때 사용

public class Base
{
    public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
    public override void DoWork()
    {
        //Perform Derived's work here
        //...
        // Call DoWork on base class
        base.DoWork();
    }
}

- abstract
 : abstract 클래스에 포함된 abstract 멤버는 파생되는 자식 클래스에서 구현되어야 함.
 : abstract 클래스는 인스턴스화 될 수 없음.
 : abstract 클래스는 seal 한정자와 함께 사용 불가 (의미가 상충)
 : abstract method는 abstract class에서만 허용
 : virtual method와 abstract method의 차이점
   . static 속성에는 abstract 한정자 사용 불가
   . 상속된 abstract 속성은 override 한정자를 사용하여 자식 클래스에서 재정의 가능

abstract class ShapesClass
{
    abstract public int Area();
}
class Square : ShapesClass
{
    int side = 0;

    public Square(int n)
    {
        side = n;
    }
    // Area method is required to avoid
    // a compile-time error.
    public override int Area()
    {
        return side * side;
    }

    static void Main() 
    {
        Square sq = new Square(12);
        Console.WriteLine("Area of the square = {0}", sq.Area());
    }
}

- is
 : 객체가 주어진 형식에 맞는지 확인할 수 있음.
 : Run-time 시 확인 가능하고 true/false 리턴
 : reference conversions, boxing/unboxing conversions에만 허용됨.

class Class1 {}
class Class2 {}
class Class3 : Class2 { }

class IsTest
{
    static void Test(object o)
    {
        Class1 a;
        Class2 b;

        if (o is Class1)
        {
            Console.WriteLine("o is Class1");
            a = (Class1)o;
            // Do something with "a."
        }
        else if (o is Class2)
        {
            Console.WriteLine("o is Class2");
            b = (Class2)o;
            // Do something with "b."
        }

        else
        {
            Console.WriteLine("o is neither Class1 nor Class2.");
        }
    }
    static void Main()
    {
        Class1 c1 = new Class1();
        Class2 c2 = new Class2();
        Class3 c3 = new Class3();
        Test(c1);
        Test(c2);
        Test(c3);
        Test("a string");
    }
}
/*
Output:
o is Class1
o is Class2
o is Class2
o is neither Class1 nor Class2.
*/

- as
 : 변환 가능한 reference 형식으로 변환하거나 nullable 형식으로 변환함.
 : as는 cast와 유사하며 변환이 불가할 경우 exception을 발생하지 않고 null을 리턴함.

class ClassA { }
class ClassB { }

class MainClass
{
    static void Main()
    {
        object[] objArray = new object[6];
        objArray[0] = new ClassA();
        objArray[1] = new ClassB();
        objArray[2] = "hello";
        objArray[3] = 123;
        objArray[4] = 123.4;
        objArray[5] = null;

        for (int i = 0; i < objArray.Length; ++i)
        {
            string s = objArray[i] as string;
            Console.Write("{0}:", i);
            if (s != null)
            {
                Console.WriteLine("'" + s + "'");
            }
            else
            {
                Console.WriteLine("not a string");
            }
        }
    }
}
/*
Output:
0:not a string
1:not a string
2:'hello'
3:not a string
4:not a string
5:not a string
*/

댓글 없음:

댓글 쓰기