본문 바로가기

자기개발/C#

지금까지 공부한 내용(c#)

밑에는 지금까지 공부했던 것을 Typora를 이용해서 정리했던 내용들이다. 앞으로는 Typora말고 blog를 통해서 공부한 내용을 정리하려 한다.


 

변수

  • C# 변수는 메서드안에서 해당 메서드의 로컬변수로 선언 or 클래스 안에서 클래스 내의 멤버들이 사용하는 전역적 변수(Field)
  • 로컬 변수는 해당 메서드내에서만 사용(메서드 호출이 끝나면 소멸)
  • 필드는 클래스의 객체가 살아있는 한 계속 존속, 다른 메서드들에서 필드를 참조 할 수 있다.
  • static 필드는 클래스 Type이 처음으로 런타임에 의해 로드될 때 해당 Type객체(타입 메타정보를 갖는 객체)에 생성되어 프로그램이 종료될 때 까지 유지
  • const
    • 반드시 선언 시 그 값을 할당
    • 한번 값이 할당되면 이후 변경 불가능
    • 자동으로 static
  • readonly
    • 선언시 값을 할당하지 않아도 가능
    • 생성자에서 한번 더 그 값을 변경 가능
    • static이 아님
    • 참조 형식 상수 선언(읽기 전용 필드)

배열

  • 다차원 배열에서 각 차원의 배열의 크기가 같다면 [ , ]처럼 콤마로 분리할 수 있음
  • 다차원 배열에서 각 차원의 배열의 크기가 다르다면 [ ] [ ]와 같이 각 차원마다 괄호를 별도로 사용
  • 가변 배열은 첫 번째 차원의 크기가 컴파일 타임에 확정되어야 하고, 그 이상 차원은 런타임시 동적으로 서로 다른 크기의 배열로 지정 가능
  • 배열은 레퍼런스(Reference) 타입

문자열

  • C#에서 문자열은 Immutable이다(한번 설정되면 값을 바꿀 수 없음)
  • s = "C#"; 이라고 한 후 다시 s = "F#;"이라고 실행하면, .NET 시스템은 새로운 string 객체를 생성하여 "F#"이라는 데이터로 초기화 한 후 이를 변수명 s에 할당한다.

yield

  • yield 키워드는 호출자(Caller)에게 컬렉션 데이터를 하나씩 리턴할 때 사용한다. 흔히 Enumerator(Iterator)라고 불리는 기능은 집합적인 데이터 셋으로부터 하나씩 호출자에게 보내주는 역할을 한다.
  • yield return 또는 yield break 2가지 방식이 존재
  • yield return : 컬렉션 데이터를 하나씩 리턴하는데 사용
  • yield break : 리턴을 중지하고 Iteration 루프를 빠져 나올 때 사용
  • Enumeration이 가능한 클래스를 Enumerable 클래스라 부르고 이는 C#/.NET에서 Enumerable 클래스는 IEnumerable 인터페이스를 구현해야함
  • C#에서 Iterator 방식으로 yield를 사용하면, 명시적으로 별도의 Enumerator 클래스를 작성할 필요가 없음

Namespace

  • .NET Framework엔 4.0 기준 약 11,000개의 클래스가 있다.
  • 클래스/ 구조체가 겹치는 경우도 있다.(실제로 System.Drawing.Point와 System.Windows.Point가 겹침)
  • Namespace를 사용하면 이런 이름 충돌을 줄일 수 있다.
  • xmlns:anything = "clr-namespace:some;assembly:MyAssembly"의 경우 assembly:MyAssembly 부분은 선택 사항이며, 네임 스페이스가 XAML파일과 다른 프로젝트의 일부인 경우에만 사용함

Delegate

  • C의 함수 포인터와 비슷한 개념
  • 인자에 메소드/함수를 넣고 싶을 때 주로 사용된다.
  • delegate 키워드를 이용해 만들 수 있다.

     

    ex)

    delegate int FooDelegate(int a, int b); => int 두 개를 인자로 받고, int를 반환하는 함수나 메소드를 담을 수 있는 delegate를 선언

    int Addition(int a, int b){ return a + b ;} => Addition은 int 두 개를 인자로 받고, int를 반환하는 메소드

    FooDelegate foo = new FooDelegate(Addition) or FooDelegate foo = Addition; 으로 메소드를 변수에 저장할 수 있다.

Anonymous Method

  • 메소드가 일회용이고 간단할 때, 별도의 메소드를 만들지 않기 위해 사용된다 .
delegate bool CompareDelegate(int lhs, int rhs);
void Check(int lhs, int rhs, CompareDelegate cirteria)
{
    Console.WriteLine(criteria(lhs,rhs));
}
void Foo(){
    Check(3,5,
    delegate(int lhs,int hrs){ // 반환값은 적지 않고, 인자만 적어준다.
    return lhs > rhs;
    });
}
// delegate키워드를 이용하여 익명 메소드를 작성한다.

Lambda Expression

  • 익명 메소드보다 조금 더 진보한 버전
  • 익명 메소드와 마찬가지로 익명 함수/ 메소드를 표현하기 위한 방법
  • (인자) => {블록};의 형식을 따른다
delegate void FooDelegate();
void Foo(){
    FooDelegate foo = () => {Console.WriteLine("Hello World");};
   // 인자가 없는 람다식의 예시
    foo();
}

FooDelegate foo = (str) => {Console.WriteLine(str);};처럼 인자를 받고 싶을 땐 데이터 타입을 하지 않고 인자 이름만 써도 된다.

Event

  • 특정 상태가 어떤 일이 일어났는지를 외부에 알리는데 이용
  • delegate 변수와 다르게 클래스 외부에서는 호출이 불가능
  • += 연산자로 이벤트 핸들러 추가를, -= 연산자로 이벤트 핸들러 제거
  • 이벤트가 발생되면 모든 이벤트 핸들러를 호출

 

namespace ConsoleApp1
{
   delegate void MyDelegate(int a);
   class EventManager
  {
       public MyDelegate test = (a) => Console.WriteLine("Delegate 호출");
       public event MyDelegate eventCall;
       public void NumberCheck(int num)
      {
           if (num % 2 == 0)
               eventCall(num);
      }
  }
   class Program
  {
      //클래스 밖에서 정의하는 이벤트 핸들러
       static void EventNumber(int num)
      {
           Console.WriteLine("{0}는 짝수", num);
      }
       static void Main(string[] args)
      {
           EventManager em = new EventManager();
           //delegate로 참조한 함수는 클래스 외부에서도 호출이 가능하지만 이벤트는 이벤트 가입 또는 해지만 가능
           em.test(2);
           //따라서 외부에서 구현한 이벤트 핸들러를 등록하고
           em.eventCall += new MyDelegate(EventNumber);
           //이벤트를 발생시키는 함수를 호출해야한다.
           int num = Convert.ToInt32(Console.ReadLine());
           em.NumberCheck(num);
      }
  }
}

 

Partial

  • 클래스, 구조체, 인터페이스를 나누어서 만들 수 있다.
  • 한 개의 클래스를 여러명이 동시 제작할 수 있다.
partial class MyClass
{
    public void M1(){}

}
partial class MyClass
{
    public void M2(){}
}

Generic

데이터 타입을 확정하지 않고 사용자가 타입을 정하도록 할 수 있다.

예를 들어 두 값을 받아 더해주는 메소드를 만들 때 double 버전, int 버전을 따로 만드는 건 비효율적이므로 이런 상황에서 한 번에 두 타입을 지원할 수 있도록 할 수 있다.

class MyStack<T>
{
    private T[] elem_ = new T[100];
    int pos = 0;
    public void Push(T elem)
    {
        elem_[++pos] = elem;
    }
    public T Pop()
    {
        return elem_[pos--];
    }
}
  • 데이터 제약의 예시

    Class MyClass<T> where T : class{}
    /* 데이터 타입에 제약을 걸어줄 수도 있다.
    where T : 제약 조건의 형식으로 하면 된다. */
    // 만약 MyClass<int> cls = new MyClass<int>();를 하게 되면 int는 구조체이므로 제약에 걸려 오류가 나게된다.
    class MyClass<T,U>
        where T : class
        where U : struct
    // 위에 처럼 여러 타입에 대해 각각 제약 조건을 걸어 줄 수도 있다.

Interface

  • 인터페이스 내에서는 메소드, 이벤트, 인덱서, 속성을 사용할 수 있다.
  • 인터페이스에서는 필드를 포함할 수 없다.
  • 모든 멤버는 public으로 접근 권한이 기본 지정.
  • Body가 정의되어 있지 않은 추상적인 멤버를 가진다.
  • 인터페이스는 다른 인터페이스를 상속하거나, 클래스에서 인터페이스 하나를 여러 차례 상속할 수 있음.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApplication32
{
   interface IMyInterfaceA
  {
       void print();
  }

   interface IMyInterfaceB
  {
       void print();
  }

   class MyClass : IMyInterfaceA, IMyInterfaceB
  {
       static void Main(string[] args)
      {
           MyClass mc = new MyClass();

           IMyInterfaceA imca = mc;
           imca.print();

           IMyInterfaceB imcb = mc;
           imcb.print();
      }

       void IMyInterfaceA.print()
      {
           Console.WriteLine("IMyInterfaceA.print() 호출.");
      }

       void IMyInterfaceB.print()
      {
           Console.WriteLine("IMyInterfaceB.print() 호출.");
      }
  }
}

try - catch

  • catch 문에서 기존의 Exception을 다시 상위 호출자로 보내려면 throw를 사용

Value Type vs Reference Type

  • C#은 Value type과 Reference Type을 지원
  • struct를 사용하면 Value Type을 만들고, class 를 사용하면 Reference Type을 만듬
  • int, double, float, bool등의 기본 데이터 타입은 모두 struct로 정의된 Value Type, 이는 상속될 수 없음
  • 메소드에 파라미터를 전달할 때, 참조로 전달하고자 한다면 키워드 ref를 사용한다.

Indexer

  • C# Indexer는 특별한 문법인 this[]를 써서 클래스 속성처럼 get과 set을 정의
  • 입력 파라미터인 인덱스도 여러 데이터 타입을 사용할 수 있는데 주로 int나 string타입을 하여 인덱스 값을 주는 것이 일반적
class MyClass
{
  private const int MAX = 10;
  private string name;

  // 내부의 정수 배열 데이타
  private int[] data = new int[MAX];

  // 인덱서 정의. int 파라미터 사용
  public int this[int index]
  {
     get
    {            
        if (index < 0 || index >= MAX)
        {
           throw new IndexOutOfRangeException();
        }
        else
        {
           // 정수배열로부터 값 리턴
           return data[index];
        }
    }
     set
    {
        if (!(index < 0 || index >= MAX))
        {
           // 정수배열에 값 저장
           data[index] = value;
        }
    }
  }
}

class Program
{
  static void Main(string[] args)
  {
     MyClass cls = new MyClass();

     // 인덱서 set 사용
     cls[1] = 1024;

     // 인덱서 get 사용
     int i = cls[1];
  }
}

전처리기 지시어(Preprocessor Directive)

  • 실제 컴파일이 시작되기 전에 컴파일러에게 특별한 명령을 미리 처리하도록 지시하는 것
  • 모든 전처리기는 #으로 시작되며 한 라인에 한 개의 전처리기 명령만 사용
  • 세미 콜론은 붙이지 않음

property(속성)

  • 속성은 변수가 아닌 메소드
  • get 속성 접근자는 속성 값을 반환하는 데 사용, set속성 접근자는 새 값을 할당하는 데 사용
  • C# 7.0부터 get 및 set 접근자 모두 식 본문 멤버로 구현할 수 있음

문자열 보간

  • 문자열 보간 기능은 복합 서식 지정 기능을 기반으로 빌드
  • 식별하기 위해서는 문자열 앞에 $기호를 사용하여 추가
double a = 3;
double b = 4;
Console.WriteLine($"Area of the right triangle with legs of {a} and {b} is {0.5*a*b}")

Upcasting/ DownCasing

  • 다음 예시를 생각해보자. 군대에는 여러가지의 총 종류가 있는데 이를 중대장이 'K1 사격', 'K2 사격', 'K3 사격' 처럼 개별 명령이 아닌 한번에 '사격'이란 명령을 통해 각기 다른 총을 가진 군인이 일제히 사격할 수 있도록 하는 것 이 upcasting의 예시
"Up casting"

class Program
{
    static void Main(string[] args)
    {
        Gun[] Soldiers = { new K1(), new K2(), new K3()};

        foreach (Gun soldier in Soldiers)
        {
            soldier.fire();
        }
    }   
}

 

down casting은 반대로 여러 군인을 한 번에 통제하는 것에 집중하는 것이 아니라 각 차별화된 총기를 가진 군인처럼 개별적인 능력에 집중하는 것이다. 근데 Child object = new Child() 는 문법적으로 불가능하기 때문에 upcasting을 한 다음 다시 downcasting을 해준다.

"Down casting"

class Program
{
    static void Main(string[] args)
    {
        Gun[] Soldiers = { new K1(), new K2(), new K3()};
K1 k1_soldier;
        foreach (Gun soldier in Soldiers)
        {
            soldier.fire();
            if(soldier is K1){
                k1_soldier = (K1)soldier;
                k1_soldier.fire_other_bullet();
            }
        }
    }   
}
  • as 는 형변환이 가능하면 형변환을 수행하고, 그렇지 않으면 null값을 대입
  • is 연산자는 형변환이 가능한 여부를 boolean형으로 결과값을 반환