C# 소소한 정리

2022년 06월 19일
제작기간 2022년 06월 19일
태그 csharp

객체지향 프로그래밍

C#은 객체지향 프로그래밍 개념을 채택한다.

OOP 개념

  • Object Oriented Programming
  • Encapsulation (캡슐화)
    • 메소드와 속성을 묶어 하나의 unit으로 구성하는 방식.
    • 객체 내부를 외부에 감추는 방식으로 기능을 단순화할 수 있음.
  • Polymorphism (다형성)
    • 동일한 요청에 서로 다른 방식으로 작동할 수 있도록 하는 성질.
    • Overloading: 같은 이름이지만 return타입, parameter 등이 다른 메소드. 컴파일 타임에 호출할 메소드가 결정됨.
    • Overriding: 부모로부터 상속받은 메소드에 대한 재정의. 런타임에 호출할 메소드가 동적으로 결정되기 떄문에 런타임에 메모리 할당이 일어남.
  • Inheritance (상속)
    • 상위 클래스에서 사용하는 기능과 속성을 하위 클래스에 제공하고, 하위 클래스는 그를 확장하여 기능할 수 있는 방식.
    • 상위 클래스는 공통된 속성이나 추상화된 기능만 가지고 있어야 함.
    • Abstract로 정의할 수 있음. 추상클래스로 정의된 경우, 메소드 정의를 포함하지 않아도 됨.

값 타입 & 참조 타입

  • 값 타입은 선언과 동시에 할당, 참조 타입은 객체를 생성해야 할당됨.
  • Boxing
    • 값 타입을 참조타입으로 변환하는 것.
    • 힙에 메모리 할당 -> 값 타입이 힙으로 복사됨 -> 메모리 주소가 반환됨 -> 주소를 스택에 저장
    • 언박싱 시에는 참조타입을 casting하여 값 타입으로 변환.
  • C#에서 ArrayList는 element를 object로 다루기 때문에 데이터를 사용할 때 박싱, 언박싱이 발생.
  • Generic은 박싱, 언박싱으로 인한 오버헤드가 발생하지 않음.
  • Casting
    • Implicit conversion: 값이 생략될 필요가 없는 경우의 변환. (ex. int -> long)
    • Explicit conversion: 값이 손상되는 변환. (ex. double -> float)
    • as 키워드를 이용하여 캐스팅이 가능함.
object o = 47;
var i = o as int?;
Console.WriteLine(i ?? -1); // 47

o = "47";
i = o as int?;
Console.WriteLine(i ?? -1); // -1

부동소수점 (floating point)

double d = 123.123123;
float f = (float)d; // 123.12312
d = f;  // 123.12312316894531
  • 부동소수점 참고 자료
  • 실수를 컴퓨터상에서 근사하여 표현할 때, 소수점의 위치를 고정하지 않고 그 위치를 나타내는 수를 따로 적는 것.
  • 2진수로 다 표현할 수 없는 값에 대해, 근사로 표현.
  • 부호(1bit) + 지수(8bit) + 가수(23bit) (총 4byte)로 구성.
  • 가수 * 밑수 ^ 지수 (밑수가 10인 경우에 E로 표기하기도 함)
  • 고정소수점은 정수 표현부, 소수 표현부의 비트 수를 고정하는 방식.

Anonymous type

  • 타입을 암시적으로 var 키워드를 통해서 선언할 수 있음.
  • 컴파일러에게 적절한 타입을 선택하도록 위임하여 성능상의 이득을 취할 수 있음.
    • Queryable. (ex. Queryable.Where)는 다수의 LINQ 쿼리문을 단일 SQL 쿼리로 합친 후 단번에 수행하지만, IEnumerable는 LINQ 쿼리가 즉각 수행됨.

Operator Overloading

Book book1 = new Book(234, 1);
Book book2 = new Book(234, 1);
var book3 = book1 + book2;
Console.WriteLine(book3.ToString());  // 468p. x 2

public class Book
{
    private int _pagesCount;
    private int _count;

    public Book(int pagesCount, int count)
    {
        _pagesCount = pagesCount;
        _count = count;
    }

    public override string ToString()
    {
        return $"{_pagesCount}p. x {_count}";
    }

    public static Book operator +(Book book1, Book book2)
    {
        return new Book(book1._pagesCount + book2._pagesCount, book1._count + book2._count);
    }
}

Interface & Abstract

  • Interface
    • 내부 구조나 동작 방법을 공개하지 않고도 사용할 수 있도록 기능을 제공함.
    • 따라서 내부 구조, 동작 방법을 변경하여도 사용자에게 영향을 주지 않을 수 있음.
  • C#
    • Class, Struct, Record는 interface를 복수개 implement할 수 있음.
    • implement: interface 내부의 모든 member를 구현함.
    • Implicitly implement(암묵적 구현)
    • Explicitly implement(명시적 구현): 구현 시에 Access modifier를 명시하지 않으며 인터페이스로 캐스팅하여 접근 가능. private로 객체를 통해 접근이 불가함.
    • 하나의 메소드에 대해 암묵적 구현과 명시적 구현이 동시에 이뤄질 수 있음. 암묵적 구현만 된 경우에는 반드시 public이어야 함.
    • virtual 메소드는 인터페이스 내부에서 구현되어야하며, Implementation Class에서 재정의할 때는 override 키워드를 사용하지 않음.

sample

Printer printer = new Printer();
printer.PrintMessage();
IPrinter iPrinter = printer;
iPrinter.PrintMessage();

interface IPrinter
{
    void PrintMessage();
}

public class Printer : IPrinter
{
    public void PrintMessage()
    {
        Console.WriteLine("Print Implicit Message");
    }

    void IPrinter.PrintMessage()
    {
        Console.WriteLine("Print Explicit Message");
    }
}

output

Print Implicit Message
Print Explicit Message

상수

  • const: 컴파일타임 상수
  • readonly: 런타임 상수
  • 컴파일타임 상수는 컴파일 후(IL), 값이 지정된 상수로 대체됨. 따라서 내장 자료형(숫자, 문자열, enum…)에만 사용 가능함.
  • 런타임 상수는 런타임에 값이 평가됨. 상수에 대한 참조로 컴파일됨.

Static

  • 정적 클래스
  • 정적 생성자
    • CLR은 특정 타입에 접근하는 경우, 정적 생성자를 우선으로 호출
    • 정적 생성자는 access modifier가 허용되지 않음
Fruit fruit = new();

public class Fruit
{
    public Fruit()
    {
        Console.WriteLine("Fruit()");
    }

    static Fruit()
    {
        Console.WriteLine("Static Fruit()");
    }
}

// output
// Static Fruit()
// Fruit()
  • 정적 멤버
    • 생성된 인스턴스의 수와 관계없이 유일함.
    • 비정적 클래스도 정적 멤버를 포함할 수 있음.
    • 정적 메소드는 비정적 필드, 이벤트에 액세스 불가함.
    • 클래스에만 속해있고 인스턴스에 속해있지는 않기 때문에, overloading만 가능하고 overriding은 불가함.
    • const는 static 키워드를 함께 쓸 수 없지만, 정적으로 동작함.
    • 호출 -> 정적 멤버 초기화 -> 정적 생성자 호출(없으면 pass)
  • 확장 메소드
    • 확장 메소드는 추가하려는 기능이 타입에 포함되는 경우에 사용하는 것이 맞음.
using StringExtensions;

var targetString = "ah";
Console.WriteLine(targetString.GetRepeatedString(3)); // ahahah

namespace StringExtensions
{
    public static class StringPrinter
    {
        public static string GetRepeatedString(this string str, int count)
        {
            StringBuilder sb = new();
            for (int i = 0; i < count; i++)
                sb.Append(str);
            return sb.ToString();
        }
    }
}

Partial Class

  • Partial Class 문서
  • Class, Struct, Interface, Method의 정의를 나눌 수 있음.
  • Method의 경우 Definition과 Implementation을 나눌 수 있음.
    • static, unsafe 사용 불가함.
  • 장점
    • 큰 프로젝트에서 많은 사용자가 개별로 편집할 수 있음.
    • 자동으로 생성된 소스로 작업할 시 파일 재생성 없이 코드 추가 가능.
Printer printer = new();
printer.PrintMainMessage();
printer.PrintSubMessage();
printer.PrintEndMessage();

public partial class Printer
{
    private string _name = "YUREI";
    private int _count = 0;

    public void PrintMainMessage()
    {
        Console.WriteLine($"{_name}: Main Message - {_count}");
        AddCount();
    }

    public partial void PrintEndMessage();
}

public partial class Printer
{
    public int Count
    {
        get => _count;
    }

    public void PrintSubMessage()
    {
        Console.WriteLine($"{_name}: Sub Message - {_count}");
        AddCount();
    }

    private void AddCount()
    {
        _count++;
    }

    public partial void PrintEndMessage()
    {
        Console.WriteLine($"{_name}: End Message - {_count}");
    }
}
YUREI: Main Message - 0
YUREI: Sub Message - 1
YUREI: End Message - 2