C#은 객체지향 프로그래밍!!
Object Oriented Programming
객체들을 생성하고 객체들끼리 상호작용하며 프로그램이 진행
클래스
Class vs Object
Class는 청사진 (건물 도면)
Object는 그 청사진으로부터 건설된 건물
Field
클래스나 구조체의 멤버 변수를 Field라고 부릅니다.
readonly 수정자
필드에 readonly를 지정하면 인스턴스 생성 후에 그 필드를 변경할 수 없습니다. (런타임타입 상수)
이러한 readonly 필드는 해당 선언문 혹은 생성자 안에서만 값을 설정할 수 있습니다.
const랑은 다른 것.
C++의 const와 C#의 const는 동작이 조금 다르다. (컴파일타입 상수)
C#의 readonly와 C++의 const가 비슷한편.
필드 초기화
필드를 선언할 때 값을 초기화할 수도 있고 안할수도 있습니다.
선언시 초기화하지 않은 필드에는 해당 타입의 디폴트값이 설정됩니다.
Method
메서드(method)는 일련의 문장들을 실행하는 멤버입니다.
매개변수와 리턴타입이 있습니다.
ref나 out 매개변수를 통해서 자료를 호출자에 돌려줄 수도 있습니다.
한 클래스안에 서명(signature)가 동일한 메서드가 여러 개 있어서는 안됩니다.
서명은 메서드의 이름, 매개변수 형식들로 이루어집니다.
매개변수 이름과 리턴타입은 서명의 일부가 아닙니다.
Method Overloading (메서드 중복적재)
void Foo(int x) {...}
void Foo(double x) {...}
void Foo(int x, float y) {...}
void Foo(float x, int y) {...}
위 코드는 가능하다 서명이 겹치지 않는다.
void Foo(int x) {...}
float Foo(int x) {...}
void Goo(int[] x) {...}
void Goo(params int[] x) {...}
하지만 다음은 안된다. 서명이 겹치기 때문이다.
*params 키워드를 사용하면 개수의 제한 없이 매개변수를 넘길 수 있습니다.
가변 인자 매개변수 = 개수가 변하는 인자들을 넘길 수 있는 매개변수.
배열을 매개변수로 사용하는 것과 비슷하나 일반 단순 개체 (오브젝트) 이런것도 배열처럼 사용이 가능하다.
pass by value & pass by reference
매개변수가 값전달인지 참조전달인지도 서명에 포함됩니다.
그러므로 다음은 공존할 수 있습니다.
void Foo(int x) {...}
void Foo(ref int x) {...}
void Foo(int x) {...}
void Foo(out int x) {...}
하지만 ref와 out은 공존할 수 없습니다.
void Foo(ref int x) {...}
void Foo(out int x) {...} // 컴파일 오류
Ref 키워드란 무엇인가?
- 명시적으로 ref 키워드를 작성해야한다.
- 매개변수로 전달하기 전에 반드시 초기화 되어야한다.
Out 키워드란 무엇인가?
- 명시적으로 out 키워드를 작성해야한다.
- 매개변수로 전달되기 전에 초기화하지 않아도 상관없다.
- 전달된 함수 내에서 반드시 할당되어야한다.
어느 상황에 사용하면 좋을까?
ref 키워드는 메서드 내에서 전달하는 변수를 변경해야할 때 좋은 선택이다.
out 키워드는 메서드 내에서 전달하는 변수를 초기화할 때 좋은 선택이다.
ref 장점
매개변수를 레퍼런스로 전달하기 때문에 성능 향상을 기대할 수 있습니다. 레퍼런스로 전달한다는건 해당 변수에 직접 접근하여 복사가 일어나는 것을 방지합니다. 즉 시간과 메모리를 절약합니다. 특히 변수가 매우 큰 경우에 말이죠
out 장점
코드의 가독성을 높여주고 작성해야하는 코드는 양을 줄여줍니다. 메서드 내에서 반드시 할당해야하기 때문에 프로그래머가 실수하는 상황을 미연에 방지해줍니다.
Instance Constructor
객체를 초기화하는 특별한 함수
생성자의 중복적재
생성자도 중복적재가 가능합니다.
그러고 this 키워드를 사용해서 코드의 중복을 피할 수 있습니다.
public class Wine
{
public decimal _price;
public int _year;
public Wine(decimal price) { _price = price; }
public Wine(decimal price, int year) : this(price) { _year = year; }
}
public Wine(decimal price, int year) 생성자로 인스턴스로 생성할 경우,
public Wine(decimal price) 가 먼저 실행되고 public Wine(decimal price, int year)가 다음에 실행됩니다.
그리고 표현식을 넘겨주는 것도 가능합니다.
public Wine(decimal price, DateTime year) : this(price, year.Year) { }
대신 표현식 자체가 this를 참조하면 안됩니다.
왜냐면 인스턴스가 아직 초기화가 되지 않았기 때문입니다.
대신 static메서드는 호출할 수 있습니다.
매개변수 없는 생성자의 암묵적 정의
프로그래머가 어떠한 생성자도 정의하지 않았다면, 그리고 그럴때만,
C#이 매개변수 없는 공용 생성자를 자동으로 만들어줍니다.
생성자를 하나라도 정의하면 공용 생성자는 생성되지 않습니다.
생성자와 필드 초기화 순서
C++은 생성자에서 멤버 변수를 초기화해야합니다.
하지만 C#에서는 필드를 선언할 때 바로 초기 값을 설정할 수 있습니다.
그럴 경우는 생성자가 호출되기 전에 초기화가 되고 필드가 선언된 순서대로 초기화가 됩니다.
private 생성자
생성자를 public으로 두지않는 흔한 이유는 static 메서드로 객체의 생성을 제어하기 위함입니다.
예를 들면 매번 새 객체를 생성하는 대신 Pool에서 객체를 돌려주거나 여러 하위클래스중 하나를 선택해서 해당 인스턴스를 돌려주는 등의 맞춤형 생성 논리를 구현하기 위해 그런 static 메서드를 생성자처럼 활용할 수 있습니다.
public class Class1
{
private Class1() {}
public static Class1 Create()
{
//....
}
}
객체 초기치(초기화 문법)
public class Bunny
{
public string name;
public bool LikesCarrots;
public bool LikesHumans;
public Bunny() {}
public Bunny(string n) { name = n; }
}
Bunny b1 = new Bunny{ name="Bo", LikesCarrots=true, LikesHumans=false };
Bunny b2 = new Bunny("BO") { LikesCarrots=true, LikesHumans=false };
해당 초기화 문법은
Bunny temp1 = new Bunny();
temp1.name = "Bo";
//...
Bunny b1 = temp1
이 코드와 정확히 대응됩니다.
this 참조
C++의 this와 같습니다.
해당 인스턴스를 가리킵니다.
static이 아닌 메서드 내부에서만 유효합니다.
속성(Property)
public class Stock
{
decimal currentPrice;
public decimal CurrentPrice
{
get { return currentPrice; }
set { currentPrice = value; }
}
}
속성은 필드와 달리 그 값의 Get과 Set방식이 완전히 제어될 수 있다는 점에서 필드와는 다릅니다.
암묵적으로 set 접근자는 속성과 같은 형식의 value라는 매개변수가 전달됩니다.
흔히 그 매개변수를 속성의 전용 '지원(backing)' 필드에 배정합니다. (set { currentPrice = value; })
읽기전용 속성
public decimal CurrentPrice
{
get { return currentPrice; }
}
식 본문 속성
public decimal Worth => currentPrice * sharesOwned;
자동속성
public decimal CurrentPrice { get; set; }
이렇게 하면 컴파일러는 속성 값을 담을 private 필드를 '자동'으로 생성합니다.
컴파일러가 내부적으로 결정하기 때문에 코드에서 접근할 수는 없습니다.
읽기 전용으로 사용할 속성을 만들려면 set 접근자에 private이나 protected를 지정해주면 됩니다.
속성 초기치
public decimal CurrentPrice { get; set; } = 123;
필드에 초기치를 정하듯 속성에도 초기치를 정해줄 수 있습니다.
public int Maximum { get; } = 999;
위 코드 같이 읽기 전용인 자동 속성에도 불변형식, 즉 읽기 전용 형식을 만들 때 유용합니다.
get / set 접근자의 접근성
public decimal CurrentPrice { get; private set { current = Math.Round(value, 2); } } = 123;
보통 get은 그대로 두고, set만 private혹은 protected를 지정합니다.
CLR 속성 구현
C# 속성 접근자들은 내부적으로 이름이 get_XXX, set_XXX 형태인 메서드로 컴파일됩니다.
하지만 non-virtual 속성 접근자들은 JIT 컴파일러가 인라인화합니다.
그래서 속성 접근과 필드 접근의 성능상의 차이가 사라집니다.
인덱서
this 라는 이름으로 속성을 선언하되, 대괄호쌍 안에 색인 매개변수들을 지정하면됩니다.
class Sentence
{
string[] words = "The Quick brown fox".Split();
public string this [int wordNum]
{
get { return words [wordNum]; }
set { words[wordNum] = value; }
}
}
다음은 사용 예시입니다.
Sentence s = new Sentence();
Console.WriteLine(s[3]); // fox
s[3] = "kangaroo";
Console.WriteLine(s[3]); // kangaroo
상수(const)
const 필드는 컴파일 시점에서 정적으로 평가됩니다.
컴파일러는 상수가 쓰이는 곳마다 그 값을 집어 넣습니다.
대신 초기치를 꼭 지정해줘야합니다.
또한, 컴파일 시점에서 평가된다는 점 역시 다른 점입니다. readonly는 런타임에 평가됩니다.
static 생성자
정적 생성자는 인스턴스를 생성할 때마다 실행되는 것이 아니라 형식 자체에 대해 한번만 실행됩니다.
하나의 타입에 한 정적 생성자만 선언 가능하고 리턴타입은 역시 해당 타입입니다.
둘 중 한 경우가 발생한 경우 정적 생성자가 호출됩니다.
- 타입을 인스턴스화한다.
- 타입의 정적 멤버에 접근한다.
static 생성자와 static 필드 초기화 순서
정적 필드 초기화는 정적 생성자가 호출되기 전에 실행됩니다.
해당 클래스에 정적 생성자가 없으면 해당 형식이 사용되기 직전에 초기화될 수도 있고,
런타임의 판단에 따라서는 그보다 전의 임의의 시점에 초기화될 수도 있습니다.
정적 필드는 선언된 순서대로 초기화됩니다. 그래서 서로 의존성이 있는 경우 순서를 바꾸면 다른 값으로 초기화될 수 있습니다.
static 클래스
정적 멤버들로만 이루어진 클래스에 static 키워드를 붙일 수 있고 정적 클래스라고 합니다.
System.Console / System.Math 클래스가 정적클래스의 좋은 예시입니다.
partial 클래스와 partial 메서드
partial 키워드를 쓰면 여러 파일에 나눠서 정의할 수 있습니다.
대신 하나의 어셈블리에는 존재해야합니다.
'C#' 카테고리의 다른 글
C# - Coroutine (0) | 2024.07.06 |
---|---|
C# - 2 (0) | 2024.05.19 |
C# - Custom Type2 (2) | 2024.04.11 |
Virtual Table (0) | 2024.04.11 |
C# (0) | 2024.04.09 |