본문 바로가기

C#

2023.09.06 C# 정리

닷넷 프레임워크

응용 프로그램 개발환경
가상머신 역할을 하는 CLR(Common Language Runtime) 구성요소가 실행될 수 있는 환경이 윈도우에 마련됨
C# 컴파일러는 IL이라는 중간 언어로 EXE/DLL 파일 내부에 생성
C# 애플리케이션을 실행하면 내부적으로 CLR이 먼저 로드됨
이어서 EXE파일 내에 있는 IL코드를 로드해서 본격적인 실행

 

자료형

long - 부호있는 64비트 정수
decimal 반올림 오차가 허용되지 않음, 회계 계산에 적합
float 형식의 뒤에는 f가 붙고, decimal소수점을 포함하는 경우 m이 붙는다. (double은 없음)
Ex) float f = 5.2f; // double d = 10.5; // decimal money = 200.099m;
string text = “\”Hello world\”” ; // 이스케이프 시퀀스를 사용하여 큰따옴표 출력
  ex) 출력 -> “Hello world”
 

명시적 변환

ushort u = 65;

char c = (char) u;

Console.WriteLine(c); // 출력 결과 : A

 

값 / 참조 형식

 

스택과 힙

  • 값/참조 형식의 차이점을 이해하려면 스택과 힙을 이해할 필요가 있다.
  • 개별 스레드마다 전용으로 사용할 수 있는 저장소가 메모리에 할당되는데 그 영역을 스택이라고 한다.
  • 변수를 선언하는 것으로 스택 내 메모리 영역을 사용할 수 있게 할당 및 해제됨
  • 은 프로그램에서 필요에 의해 메모리를 사용하겠다고 요청했을 때 사용할 수 있는 저장소 
  • 힙에 메모리를 할당되면 해제 과정이 필요한데 C#에서는 관리 환경 내의 가비지 수집기가 담당한다.

값 형식

값 형식을 가리키는 변수의 경우 값 자체가 스택에 할당이 되고 변수는 그 메모리를 가리키는 프로그램 내의 식별자이다.

스택에 저장된 값 형식의 데이터

참조 형식

string, 배열, 클래스, object 등.

참조 형식을 위한 변수는 그 값을 담기 위해 별도로 힙 영역의 메모리를 할당하고,

스택의 변수 값은 다시 힙의 데이터 주소를 가리키게 된다.

데이터를 가리키는 참조형 변수를 위해 할당된 메모리

초기화되지 않은 모든 참조형 변수는 null값을 가진다.

또는 참조형 변수가 더는 사용되지 않음을 명시하기 위해 null을 할당하기도 함

 

int n1 = 5;
int n2 = n1;

Console.WriteLine(n1); // 5출력
Console.WriteLine(n2); // 5출력

---------------------------------------------

string txt1 = "C#";
string txt2 = txt1;

Console.WriteLine(txt1); // C#출력
Console.WriteLine(txt2); // C#출력

n1과 n2가 같은 값이고, txt1과 txt2역시 같은 값을 출력하지만

값 형식과 참조 형식에 따라 메모리의 표현 방식은 서로 다르다.

 

값 형식의 경우 스택 영역에 n1의 값이 복사되어 n2영역에 들어가지만

참조 형식의 경우 스택 영역에는 "C#"이라는 문자열의 주소가 들어가고,

힙 영역에는 하나의 "C#" 문자열만 존재한다.

 

배열

new키워드 - 동적할당에 사용, 힙에 할당

 

다차원 배열

int [ , ] arr2 = new int [10, 5]; // 2차원 배열

int [ , , ] arr3 = new int[10, 5, 4]; //3차원 배열

 

객체지향

생성자

클래스에 생성자 메서드를 추가하면 객체가 "생성"되는 시점에 해당 메서드가 자동으로 호출된다.

모든 클래스는 생성자를 가지고 있다. 생성자를 명시적으로 정의하지 않는다면 컴파일러는

빈 생성자를 클래스에 집어넣고 컴파일한다.

매개변수가 하나도 없는 생성자를 기본 생성자라고 해서 다른 생성자와 구분하기도 한다.

개발자가 명시적으로 생성자를 정의한 경우 컴파일러는 기본 생성자를 추가하지 않는다.

 

종료자

해당 객체가 제거되는 시점에 실행될 종료자도 있을 것이다.

C#에서는 delete 같은 예약어가 없으므로 CLR 내부에서 GC를 도입해서 이를 해결한다.

모든 참조형 변수를 생성할 때는 GC가 관여하게 되고, GC는 요청된 변수의 타입이 요구하는

메모리를 "관리 힙"에 할당한다. 적절한 시기에 관리 힙을 청소하는데 이때 객체가 더는 사용되고 있지 않으면

객체의 데이터를 해제해 버린다.

 

인스턴스

어떤 타입을 실체화한 객체

new 연산자를 거쳐서 메모리에 할당된 객체라고 할 수 있다.

그 객체와 관련된 멤버를 인스턴스 멤버라고 하며, 지금까지 설명한 필드, 메서드, 생성자는 모두 여기 속한다.

하지만, 개별 인스턴스 수준이 아닌 해당 인스턴스의 타입 전체에 걸쳐 전역적으로 적용되는 필드, 메서드,

생성자가 필요할 수 있는데, 이러한 멤버를 인스턴스 멤버와 구분해 정적 멤버라고 한다.

 

정적 필드

예를 들어, 클래스의 객체가 생성될 때마다 횟수를 증가시키는 필드를 정의한다고 해보자.

인스턴스의 멤버로 이런 기능을 구현할 수 있을까?

 class Person
    {
        public int CountOfInstance;
        public string _name;
        public Person(string name)
        {
            CountOfInstance++;
            _name = name;
        }
        
    }
    class Program
    {
        static void Main(string[] args)
        {
            Person person1 = new Person("홍길동");
            Console.WriteLine(person1.CountOfInstance); // 출력 : 1
            
            Person person2 = new Person("홍길순");
            Console.WriteLine(person2.CountOfInstance); // 출력 : 1
        }
    }

new로 할당받은 객체마다 고유의 메모리를 확보하기 때문에 클래스 전역적으로 값이 유지되지 않는다.

이 같은 요구사항을 만족하려면 클래스 단위의 필드를 정의해야 하고, 그것이 바로 정적 필드이다.

 

class Person
    {
        static public int CountOfInstance; // static 예약어로 정적 필드로 만듦
        public string _name;
        public Person(string name)
        {
            CountOfInstance++;
            _name = name;
        }
        
    }
    class Program
    {
        static void Main(string[] args)
        {
            
            Console.WriteLine(Person.CountOfInstance); // 출력 : 0
            
            Person person1 = new Person("홍길동");
            Person person2 = new Person("홍길순");
            
            Console.WriteLine(Person.CountOfInstance); // 출력 : 2
        }
    }

변경 사항 1) static 예약어 사용

                 2) 클래스 밖에서 이 필드를 사용할 때 [클래스이름].[정적필드] 형태로 접근

정적 필드의 값은 new로 할당된 인스턴스와 상관없이 존재한다.

 

정적 필드와 인스턴스 필드의 메모리 표현

정적 필드를 사용하는 전형적인 패턴 가운데 대표적으로 특정 클래스의 인스턴스를 의도적으로 단 한 개만 만들고 싶은

경우다. 이 경우 클래스 밖에서 해당 클래스의 인스턴스를 만들지 못하게끔 생성자를 private 접근 제한자로 명시하고 단 하나의 인스턴스만 클래스 내부에서 미리 생성해 두는 것으로 원하는 바를 이룰 수 있다.

 class Person
    {
        static public Person President = new Person("대통령"); // public 정적 필드
        string _name;

        private Person(string name) // private 인스턴스 생성자
        {
            _name = name;
        }

        public void DisplayName() // public 인스턴스 메서드
        {
            Console.WriteLine(_name);
        }
        
    }
    class Program
    {
        static void Main(string[] args)
        {

            Person.President.DisplayName(); // 정적 필드로 단일 인스턴스 접근

            Person person1 = new Person("홍길동"); // 생성자가 private이므로 오류 발생
        }
    }

이렇게 인스턴스가 단 하나만 존재하는 타입을 특별하게 싱글턴 클래스라고 하며, 이따금 단일 시스템 자원을

책임지는 타입이 필요할 때 싱글턴 클래스를 만들어 다른 클래스에 기능을 노출하는 용도로 사용한다.

 

정적 메서드

일반 메서드에 static 예약어를 붙여서 정의

정적 메서드 역시 new로 객체를 생성하는 것과 무관하게 사용할 수 있으므로 [클래스이름].[정적메서드] 형태로 호출

class Person
    {
        static public int CountOfInstance; // static 예약어로 정적 필드로 만듦
        public string _name;
        public Person(string name)
        {
            CountOfInstance++;
            _name = name;
        }

        static public void OutputCount() // public 정적 메서드
        {
            Console.WriteLine(CountOfInstance); // 정적 메서드에서 정적 피드에 접근
        }

    }
    class Program
    {
        static void Main(string[] args)
        {

            Person.OutputCount();

            Person person1 = new Person("홍길동");
            Person person2 = new Person("홍길순");

            Person.OutputCount(); // 출력 : 2

        }
    }

정적 메서드 안에서는 인스턴스 멤버에 접근할 수 없다는 특징이 있다.

이는 정적 메서드가 new로 할당된 객체가 없는 상태에서도 호출되는 메서드라는 점을 생각하면  쉽게 이해할 수 있다.

 

정적 생성자

기본 생성자에 static 예약어를 붙인 경우로 클래스에 단 한 개만 존재할 수 있고,

주로 정적 멤버를 초기화하는 기능을 하기 때문에 형식 이니셜라이저라고도 한다.

 

※ 정적 생성자는 단 한 개만 정의할 수 있고 매개변수를 포함할 수 없다. 참고로 정적생성자에 실행되는 코드는 오류를 발생시키지 않도록 주의해야 한다. 오류 발생 시 해당 클래스 자체를 사용할 수 없게 되고, 오류의 원인을 찾는 것 또한 쉽지 않기 때문

 

C# 컴파일러는 정적 필드를 초기화하는 코드를 자동으로 정적 생성자로 옮겨서 컴파일한다.

 

 class Person
    {
        static public Person President; // = new Person("대통령")초기화 코드를
        								// 정적 생성자로 이전해서 컴파일
        string _name;

        private Person(string name) // private 인스턴스 생성자
        {
            _name = name;
        }

        static Person()
        {
        	President = new Person("대통령"); // 정적 필드 초기화
        }        
    }

정적 필드에 초기화 코드도 포함돼 있고, 동시에 정적 생성자도 정의해 뒀다면 C# 컴파일러는 사용자가 정의한 정적 생성자의 코드와 초기화 코드를 자동으로 병합해서 정의한다. 이 규칙은 인스턴스 필드와 기본 생성자 간에도 동일하게 적용된다.

 

정적 생성자는 클래스의 어떤 멤버든 최초로 접근하는 시점에 단 한 번만 실행된다는 점을 기억해 두자. 정적 멤버를 처음 호출할 경우이거나 인스턴스 생성자를 통해 객체가 만들어지는 시점이 되면 그 어떤 코드보다도 우선적으로 실행된다.

 

class Person
{
    public string _name;
    public Person(string name)
    {        
        _name = name;
        Console.WriteLine("ctor 실행");
    }

    static Person() // public 정적 메서드
    {
        Console.WriteLine("cctor 실행"); // 정적 메서드에서 정적 필드에 접근
    }

}

namespace Week1_3
{

    
    class Program
    {
        static void Main(string[] args)
        {

            Person person1 = new Person("");
            Console.WriteLine("-----------");
            Person person2 = new Person("");

        }
    }
}

 

캡슐화

객체의 밖에서 알아야 할 필요가 없는 내부 멤버를 숨기는 것

 

접근 제한자

private

protected - 내부에서의 접근과 함께 파생 클래스에서만 접근 허용 (상속할 때 씀)

public

internal - 동일한 어셈블리 내에서는 public에 준한 접근을 허용한다. 다른 어셈블리에서는 접근할 수 없다.

internal protected - 동일 어셈블리 내에서 정의된 클래스이거나 다른 어셈블리라면 파생 클래스인 경우에 한해

                                접근을 허용한다.

 

class 정의에서 접근 제한자를 생략한 경우 기본적으로 internal로 설정되는 반면 class 내부 멤버들은 private로 설정된다.

 

정보 은닉

클래스 입장에서 정보는 멤버 변수를 일컫는다. 외부에서 이 멤버 변수를 직접 접근할 수 없게 만드는 것이 바로

정보 은닉에 해당한다.

필드에 읽고 쓰기가 적용될 때는 관례적으로 get과 set이라는 단어를 각각 사용한다.

get/set 기능을 하는 메서드를 특별히 접근자 메서드 (getter), 설정자 메서드(setter)라고 한다.

 

일례로 Circle 클래스의 pi 변수에 대해 접근자 및 설정자 메서드를 구현하면 다음과 같다.

class Circle
{
    double pi = 3.14;
    
    pulbic double GetPi()
    {
    	return pi;
    }
    
    public double SetPi(double value)
    {
    	pi = value;
    }
    
    //.....생략....
 }

접근자/설정자 메서드가 나오게 된 이유 중 하나는 향후 코드에 대한 유지보수를 쉽게 하기 위해서다. 

나중에 호출 스택을 구하는 방법이나 통합 개발 환경 등을 이용해

디버거를 사용할 수 있는 때가 되면 설정자 메서드를 구현해 뒀을 때 훨씬 쉽게 문제를 파악할 수 있다.

 

정보 은닉의 원칙은 간단하다.

  • 특별한 이유를 제외하고는 필드를 절대 public으로 선언하지 않는다.
  • 접근이 필요할 때는 접근자/설정자 메서드를 만들어 외부에서 접근하는 경로를 클래스 개발자의 관리하에 둔다.

프로퍼티

접근자/설정자 메서들를 둬서 필드 접근에 대한 단일창구를 제공하는 것은 바람직하지만 호출을 위해 정의를 일일이 코드로 작성하자면 번거롭다. 이 문제를 보완하기 위해 프로퍼티라는 문법을 제공한다.

 

class Circle
{
    double pi = 3.14;
    
    public double Pi
    {
    	get{ return pi; }
        set{ pi = value; }
    }
    // ...생략...
}

Pi프로퍼티의 정의를 보면 대체로 어렵지 않지만 set의 value는 도대체 어디서 온 것일까?

설정자 메서드는 사용자가 전달하는 값을 매개변수명으로 구눕할 수 있지만 프로퍼티 정의에서는 매개변수가 없으므로

컴파일러가 프로퍼티에 대입되는 값을 가리킬 수 없다는 문제가 발생한다 이문제를 해결하기 위해 별도로 set 블록 내부에서만 사용할 수 있는 value 예약어가 도입된 것이다.

 

https://www.sysnet.pe.kr/0/0

 

J & J - 정성태의 닷넷 이야기: Digital Stories

 

www.sysnet.pe.kr

https://ridibooks.com/books/1160000100

 

시작하세요! C# 10 프로그래밍

시작하세요! C# 10 프로그래밍 작품소개: 이 책의 목표는 여러분이 C#을 이용해 프로그래밍 기초를 탄탄하게 다질 수 있게 하는 것이다. 이를 위해 C# 언어의 최신 버전인 C# 10의 문법까지 구체적인

ridibooks.com