[C# 5.0] Class
1] 클래스의 선언과 객체의 생성
: "class" 키워드를 이용해서 클래스를 선언하고, new 키워드를 이용해서 인스턴스를 생성한다.
class 클래스이름
{
// 데이터와 메소드
}
Cat kitty = new Cat();
Cat 클래스를 비롯한 모든 클래스는 복합 데이터 타입이다. 그리고 복합 데이터 타입은 참조타입이다.
Cat kitty;
위 선언에서 kitty는 null을 가리킨다. kitty 자체에 메모리가 할당되는 것이 아니고 kitty는 참조로써 객체가 있는 곳을 가리킬 뿐이다.
*new 연산자와 생성자는 모든 데이터 타입에 사용할 수 있다. C# 에서는 모든 타입이 클래스이기 때문에 생성자를 가지고 있다.
int a = new int();
a = 3;
string b = new string ( new char []{'한', '글'} );
2] 생성자와 소멸자
: 생성자는 클래스 이름과 똑같은 이름을 가진 메서드 이고, 오버로딩이 가능하다.
소멸자는 ~클래스이름으로 선언하고 인스턴스의 메모리가 소멸될 때 불린다.
: 생성자는 부모클래스의 생성자부터 호출하고 자신의 생성자를 호출한다. 소멸자는 그 반대다.
class Cat
{
public string Name;
public string Color;
public Cat()
{
Name = "";
Color = "";
}
public Cat(string _Name, string _Color)
{
Name = _Name;
Color = _Color;
}
~Cat()
{
Console.WriteLine("{0} : 잘가", Name);
}
}
프로그래머가 생성자를 정의하지 않으면 컴파일러가 파라미터 없는 생성자를 기본으로 생성 해준다. 하지만, 생성자를 따로 정의하면 컴파일러는 기본 생성자를 제공해 주지 않는다.
소멸자는 명시적으로 호출할 수 없다. CLR의 가비지 컬렉터가 객체 소멸시점을 판단해서 소멸자를 호출해준다.
즉, 소멸자가 호출되는 시점이 명확하지 않다는 것이 문제다. 그래서 소멸자를 사용하는것을 가급적 권하지 않는다. 그뿐 아니라, 명시적으로 소멸자가 구현되어 있으면 가비지 컬렉터가 object로 부터 상속받은 Finalize() 메소드를 클래스의 족보를 타고 올라가며 호출하기 때문에 대개의 경우 성능저하만 가져올 확률이 높다.
3] 깊은복사와 얕은복사
: 얕은복사는 클래스의 인스턴스를 복사한다고 할 때, 인스턴스의 주소값만 복사하는 것이다. 그러므로 복사된 인스턴스의 멤버변수 값을 바꾸면 원본의 멤버변수의 값도 바뀐다.
: 깊은복사는 주소값을 복사하는것이 아니라 인스턴스의 멤버변수의 값을 복사한다는 것이다. 그러므로 복사된 인스턴스의 멤버변수 값을 바꿔도 원본의 멤버변수의 값이 바뀌지 않는다.
*ICloneable.Clone() 메소드
: .NET 프레임워크의 System 네임스페이스에는 ICloneable 이라고 하는 인터페이스가 있다. 깊은복사 기능을 가질 클래스가 ICloneable를 상속받아 Clone() 메서드를 구현해놓으면 .NET 프레임워크의 다른 유틸리티 클래스나 다른 프로그래머가 작성한 코드와의 호환이 가능해진다.
4] this, base
: this는 객체가 자신을 지칭할 때 사용하는 키워드이다. Java의 this, Objective-C의 self와 같다.
this는 생성자를 호출할 때도 사용하며, 이를 이용해서 중복 코드를 줄일 수 있다.
: base는 부모 클래스를 가리킨다. java, objective-c의 super와 같다.
class MyClass
{
int a, b, c;
public MyClass()
{
this.a = 5425;
Console.WriteLine("1");
}
public MyClass(int b) : this()
{
this.b = b;
Console.WriteLine("2");
}
public MyClass(int b, int c) : this(b)
{
this.c = c;
Console.WriteLine("3");
}
}
MyClass c = new MyClass(10, 20);
위 코드를 실행하면 1, 2, 3 순서대로 출력이 된다. 고로 생성자 호출 순서는 상속받은 자식 생성자가 나중에 불리는 구조다.
class Base
{
protected string Name;
public Base(string Name)
{
this.Name = Name;
}
}
class Derived : Base
{
public Derived(string Name) : base(Name)
{
//nothing
}
}
위 코드와 같이 부모 클래스의 생성자를 호출할때도 별반 다르지 않다.
* 상속이 불가능 하도록 클래스를 구현하려면 class 키워드 앞에 "sealed" 키워드를 명시하면 된다.
5] 접근 한정자
접근 한정자로 수식하지 않은 클래스의 멤버는 private로 지정된다.
6] 부모 클래스와 자식 클래스 사이의 타입 변환(is, as)
- is : 객체가 해당 타입에 해당하는지 검사하여 그 결과를 bool 값으로 반환한다.
- as : 타입 변환 연산자와 같은 역할을 한다. 다만 타입 변환 연산자가 변환에 실패하는 경우 예외를 던지는 반면, as 연산자를 객체 참조를 null로 만든다.
Mammal 클래스가 Cat클래스의 부모클래스라고 해보자.
Mammal mammal2 = new Cat();
Cat cat = mammal2 as Cat;
if (cat != null)
{
cat.Meow();
}
위 코드에서 as로 사용한 변환이 실패하면 cat 변수에는 null이 대입된다.
7] 오버라이딩
: 오버라이딩을 하기 위해서는 첫째로, 부모클래스의 메서드에 "virtual" 키워드를 이용해 가상메서드라고 지정해주거나 "abstract" 키워드를 이용해 추상 메서드로 지정한경우 또는 "override" 키워드를 이용해 부모클래스를 오버라이딩한 메서드여야 한다. 둘째로, 자식클래스의 override 메서드에 "override" 키워드를 이용해 override하는 메서드라고 알려줘야 한다.
class ArmorSuite
{
public virtual void Initialize()
{
Console.WriteLine("Armored");
}
}
class IronMan: ArmorSuite
{
public override void Initialize()
{
base.Initialize();
Console.WriteLine("IronMan");
}
}
1. 메소드 숨기기
메소드 숨기기라는 개념이 있다. 부모 클래스에서 virtual 키워드로 지정을 안해서 오버라이드가 불가능한 상황이라면 자식 클래스의 메서드에서 "new" 키워드를 사용해서 새로운 메서드인것 처럼 사용해서 오버라이드 하는 효과를 낼 수 있는 개념이다.
class Base
{
public void MyMethod()
{
Console.WriteLine("Base.MyMethod()");
}
}
class Derived : Base
{
public new void MyMethod()
{
Console.WriteLine("Derived.'MyMethod()");
}
}
다만 new 키워드를 이용해서 메서드를 정의했을 때 주의할 점이 있다.
Base base = new Base();
base.MyMethod();
위 코드를 실행하면 Base 클래스의 MyMethod가 호출된다. 정상적으로 오버라이드 했을 경우랑 결과가 틀려진다.
2. 오버라이딩 봉인하기
: 클래스를 상속이 안되도록 봉인하는 것처럼 메서드도 오버라이딩이 안되게 봉인하는 방법이 있다. 다만, 모든 메소드를 봉인할 수 있는것은 아니고 virtual로 선언된 가상 메소드를 오버라이딩한 버전의 메소드만 가능하다.
class Base
{
public virtual void SealMe()
{
//...
}
}
class Derived : Base
{
public sealed void SealMe()
{
//...
}
}
위 코드로 Derived의 SealMe 메서드는 더이상 오버라이딩이 불가능하다.
자식클래스에서 오버라이딩 한경우 해당 메서드는 무조건 오버라이딩이 가능하다. 자식클래스 작성자가 이를 막기위해서 제공하는 문법이다.
8] 중첩 클래스
: 클래스안에 선언되어 있는 클래스를 말한다. 중첩 클래스는 자신이 선언된 Outer 클래스의 멤버에 접근이 가능하다.(Private 멤버도 접근 가능하다.)
class OuterClass
{
private int OuterMember;
class NestedClass
{
OuterClass outer = new OuterClass();
outer.OuterMember = 10;
}
}
중첩 클래스를 쓰는 이유
- 클래스 외부에 공개하고 싶지 않은 형식을 만들고자 할때
- 현재의 클래스의 일부분처럼 표현할 수 있는 클래스를 만들고자 할때
9] 분할 클래스
: 클래스의 구현이 너무 길어지면 이를 여러 파일에 나눠서 구현할 수 있게 함으로써 소스 코드 관리의 편의를 제공한다.
partial class MyClass
{
public void Method1() {}
public void Method2() {}
}
partial class MyClass
{
public void Method3() {}
public void Method4() {}
}
MyClass myClass = new MyClass();
myClass.Method1();
myClass.Method2();
myClass.Method3();
myClass.Method4();
10] 확장 메소드
: 기존의 클래스의 기능을 확장하는 기법이다. Objective-C의 카테고리와 비슷하다. 예를들어, int 타입의 제곱하는 메서드를 하나 추가할 수 있다.
namespace 네임스페이스이름
{
public static class 클래스이름
{
public static 반환타입 메소드이름 (this 대상타입 식별자, 매개_변수_목록)
{
// 구현
}
}
}
다음은 확장 메서드의 예이다.
namespace MyExtension
{
public static class IntegerExtension
{
public static int Power(this int myInt, int exponent)
{
int result = myInt;
for (int i = 1; i < exponent; i ++)
{
result = result *myInt;
}
return result;
}
}
}
using MyExtension;
int a = 2;
Console.WriteLine( a.power(3) );
위처럼 사용하면 마치 Power() 함수가 int 타입의 메서드인것 처럼 사용 가능하다.
11] 구조체
: 필드와 메서드를 가질 수 있다. 구조체는 데이터를 담기 위한 자료구조로 사용되기 때문에 굳이 은닉성을 비롯한 객체지향의 원칙을 적용하지는 않는 편이다. 따라서 편의를 위해 필드를 public으로 선언해서 사용하는 경우가 대다수다.
struct MyStruct
{
public int MyField1;
public int MyFiled2;
public void MyMethod()
{
// ...
}
}
MyStruct s;
s.MyField1 = 1;
s.MyField2 = 2;
s.MyMethod();
위는 클래스와 구조체의 차이점이다. 구조체는 값 형식이기 때문에 구조체의 인스턴스는 Stack에 할당된다. 그러므로 가비지 컬렉터가 필요없다는 면에서 성능상의 이점을 가져온다.
구조체는 값 형식이기 때문에 "=" 을 통해 모든 필드가 그대로 깊은복사가 된다.
구조체는 매개 변수가 없는 생성자는 선언할 수 없다. 구조체의 각 필드는 CLR이 기본값으로 초기화를 해준다.