카테고리 없음

C# 문법 정리 4

빈코더 2022. 6. 16. 14:31
728x90

### 네임스페이스

- .NET Framework은 무수하게 많은 클래스들을 가지고 있는데, 예를 들면 .NET 4.0은 약 11,000개의 클래스를 가지고 있다. 이렇게 많은 클래스들을 충돌없이 보다 편리하게 관리/사용하기 위해 .NET에서 네임스페이스를 사용한다. C#에서도 이러한 개념을 적용하여 클래스들이 대개 네임스페이스 안에서 정의된다. 비록 클래스가 네임스페이스 없이도 정의 될 수는 있지만, 거의 모든 경우 네임스페이스를 정의하는 것이 일반적이다.

- 네임스페이스를 사용하기 위해서는 두가지 방식이 있다. 첫째는 클래스명 앞에 네임스페이스 전부를 적는 경우와 둘째는 프로그램 맨 윗단에 해당 using을 사용하여 C# (.cs) 파일에서 사용하고자 하는 네임스페이스를 한번 설정해 주고, 이후 해당 파일 내에서 네임스페이스 없이 직접 클래스를 사용하는 경우이다. 실무에서는 주로 두번째 방식을 사용한다.

 

### Value Type vs Reference Type

- C#은 Value Type과 Reference Type을 지원한다. C#에서는 struct를 사용하면 Value Type을 만들고, class 를 사용하면 Reference Type을 만든다.
- C# .NET의 기본 데이타형들은 struct로 정의되어 있다. 즉, int, double, float, bool 등의 기본 데이타 타입은 모두 struct로 정의된 Value Type이다. Value Type은 상속될 수 없으며, 주로 상대적으로 간단한 데이타 값을 저장하는데 사용된다.
- Reference Type은 class를 정의하여 만들며 상속이 가능하고, 좀 더 복잡한 데이타와 행위들을 정의하는 곳에 많이 사용된다. Value Type의 파라미터 전달은 데이타를 복사(copy)하여 전달하는 반면, Reference Type은 Heap 상의 객체에 대한 레퍼런스(reference)를 전달하여 이루어진다. 구현에 있어 어떤 Type을 선택하는가는 해당 Type의 특성을 고려해서 결정해야 하는 문제이다.

 

### struct 구조체

- C# struct는 구조체를 생성하고 Value Type을 정의하기 위해 사용된다. 많은 경우 C#에서 클래스를 사용하지만, 경우에 따라 클래스보다 상대적으로 가벼운 오버헤드를 지닌 구조체가 필요할 수 있다. C#의 구조체는 클래스와 같이 메서드, 프로퍼티 등 거의 비슷한 구조를 가지고 있지만, 상속은 할 수 없다. 하지만 C# 구조체가 상속(inheritance)은 할 수는 없어도, 클래스와 마찬가지로 인터페이스(interface)를 구현할 수는 있다.

- C#에서 배열은 Reference Type이고 모든 C# 배열은 System.Array 라는 추상클래스로부터 파생된 클래스의 오브젝트라 볼 수 있다. 

 

### class (클래스)

- C# class 키워드는 Reference Type을 정의하는데 사용된다. 클래스는 메서드 (Method), 속성 (Property), 필드 (Field), 이벤트 (Event) 등을 멤버로 포함하는 소프트웨어 단위로서 보통 이 클래스 정의로부터 객체 (Object)를 생성해서 사용하게 된다. 클래스를 정의할 때 중요한 멤버는 공용(public) 메서드와 속성인데, 이 public 멤버들은 외부 객체와의 상호작용을 위해 사용되어 진다.

클래스 멤버종류
메서드 (Method) 클래스에서 실제 행동을 일으키는 코드 블럭. 대개 동사 혹은 동사+명사 식으로 메서드명을 정함. 예) Calculate(), DeleteData()
속성 (Property) 클래스의 내부 데이타를 외부에서 사용할 수 있게 하거나, 외부에서 클래스 내부의 데이타를 간단하게 설정할 때 사용한다.
필드 (Field) 클래스의 내부 데이타는 필드에 저장하게 되며, 필드들은 클래스 객체의 상태를 유지하는데 이용된다. 클래스는 동일하더라도 클래스로부터 생성된 여러 객체들은 다른 필드값을 가짐에 따라 서로 다른 객체 상태를 갖게 된다. 필드는 접근제한자(Access Modifier)에 따라 외부 객체 혹은 상속 객체에서 보여질 수 있다. (public 필드를 만들어 문법적으로 필드를 외부에 노출할 수는 있지만, 이는 객체 지향 프로그래밍 방식에 어긋난다. 이 경우 주로 private 필드를 만들고 public 프로퍼티를 이용해 필드값을 외부에 전달하는 방식을 사용한다)
이벤트 (Event) 이벤트는 객체 내부의 특정 상태를, 혹은 어떤 일이 있어났다는 이벤트를 외부로 전달하는데 이용된다. 예를 들어 Button 클래스의 경우 버튼이 클릭되면, 버튼클릭 이벤트에 가입한 모든 외부 객체들에게 그 사실(이벤트)을 통보하게 된다.
public class MyCustomer
{
    // 필드
    private string name;
    private int age;

    // 이벤트 
    public event EventHandler NameChanged;

    // 생성자 (Constructor)
    public MyCustomer()
    {
        name = string.Empty;
        age = -1;
    }

    // 속성
    public string Name
    {
        get { return this.name; }
        set 
        {
            if (this.name != value)
            {
                this.name = value;
                if (NameChanged != null)
                {
                    NameChanged(this, EventArgs.Empty);
                }
            }                
        }
    }
    public int Age
    {
        get { return this.age; }
        set { this.age = value; }
    }

    // 메서드
    public string GetCustomerData()
    {
        string data = string.Format("Name: {0} (Age: {1})", 
                    this.Name, this.Age);
        return data;
    }
}

### Nullable 타입의 도입

- C#에서 정수, 부동자릿수, 구조체 등의 Value Type은 NULL을 가질 수 없다. 예를 들어, 정수 int i 가 있을 때 변수 i에는 null을 할당할 수 없으며, 따라서 변수 i는 어떤 값이 할당되지 않은 상태 (missing value)를 가질 수 없다.
만약 정수형 변수 i에 값이 설정되지 않은 상태를 할당하려면, 개발자는 2가지 방법을 사용할 수 있을 것이다. 즉, (1)프로그램에서 사용될 것 같지 않은 특정 값을 추정하여 할당하던지 (예를 들어, int i = int.MaxValue;) (2) 아니면 또 하나의 변수를 두어 변수 i가 missing임을 나타내게 할 수 있다 (예를 들어, bool iHasValue = false;). 이 두번째 방식이 Nullable의 기본 아이디어이다.
- C# 에서는 Value Type에도 null을 할당할 수 있는 Nullable 타입을 지원한다. Nullable 타입은 Value 값을 갖고 있으면서 NULL 상태를 체크할 수 있는 기능(HasValue)을 함께 가지고 있는 struct 이다. 따라서 Nullable 타입은 struct(구조체)이므로 Value Type이다.
- C#에서 int? 와 같이 해당 Value Type 뒤에 물음표를 붙이면, 해당 정수형 타입이 Nullable 정수형 타입임을 의미한다. 즉, 이 변수에는 NULL을 할당할 수 있다. C#의 이러한 특별한 문법은 .NET의 Nullable<T> 구조체로 컴파일시 변환된다. 즉, int?는 Nullable<int>와 동일하다

int? i = null;
bool? b = null;
int?[] a = new int?[100];

- C#에서 int?, bool?, DateTime? 와 같은 T? 의 표현은 .NET의 Nullable<T>와 같은 표현이며, Nullable<T> 구조체는 값을 가지고 있는지를 체크하는 (즉, missing value가 아닌지를 체크하는) HasValue 속성과 실제 값을 나타내는 Value 속성을 가지고 있다.

- Value 타입이 아닌 레퍼런스 타입은 Nullable을 쓸 필요가 없는데, 그것은 모든 레퍼런스 타입은 이미 NULL을 허용하기 때문이다.

 

### 메서드

- 클래스내에서 일련의 코드 블럭을 실행시키는 함수를 메서드라 부른다. 메서드는 0 ~ N개의 인수를 갖을 수 있으며, 하나의 리턴 값을 갖는다. 리턴 값이 없으면 리턴 타입을 void로 표시한다. 또한 public, private 같은 접근 제한자를 리턴 타입 앞에 둘 수 있다.

 

### Pass by Value

- C#은 메서드에 인수를 전달할 때, 디폴트로 값을 복사해서 전달하는 Pass by Value 방식을 따른다. 만약 전달된 인수를 메서드 내에서 변경한다해도 메서드가 끝나고 함수가 리턴된 후, 전달되었던 인수의 값은 호출자에서 원래 값 그대로 유지된다.

class Program
{
    private void Calculate(int a)
    {
        a *= 2;
    }

    static void Main(string[] args)
    {
        Program p = new Program();

        int val = 100;
        p.Calculate(val);  
        // val는 그대로 100        
    }
}

### Pass by Reference

- 메서드에 파라미터를 전달할 때, 만약 레퍼런스(참조)로 전달하고자 한다면 C# 키워드 ref를 사용한다. ref를 사용할 경우 메서드 내에서 변경된 값은 리턴 후에도 유효하다. ref를 사용하기 위해서는 ref로 전달되는 변수가 사전에 초기화되어져야 한다.
- C#의 ref와 비슷한 기능을 하는 것으로 C# out 키워드가 있다. out을 사용하는 파라미터는 메서드 내에서 그 값을 반드시 지정하여 전달하게 되어 있다. C#의 ref는 해당 변수가 사전에 초기화되어야 하지만, C# out은 사전에 변수를 초기화할 필요는 없다.

static double GetData(ref int a, ref double b)
{ return ++a * ++b; }

// out 정의
static bool GetData(int a, int b, out int c, out int d)
{
    c = a + b;
    d = a - b;
    return true;
}

static void Main(string[] args)
{
    // ref 사용. 초기화 필요.
    int x = 1;
    double y = 1.0;
    double ret = GetData(ref x, ref y);

    // out 사용. 초기화 불필요.
    int c, d;
    bool bret = GetData(10, 20, out c, out d);
}

### Named 파라미터

- 어떤 메서드의 파라미터가 디폴트 값을 갖고 있다면, 메서드 호출시 이러한 파라미터를 생략하는 것을 허용하였다. 이렇게 디폴트 값을 가진 파라미터를 Optional 파라미터라 부른다. Optional 파라미터는 반드시 파라미터들 중 맨 마지막에 놓여져야 한다. 복수개의 Optional 파라미터가 있는 경우 반드시 Optional 이 아닌 파라미터들 뒤에 위치해야 한다.

class Program
{
    // Optional 파라미터: calcType
    int Calc(int a, int b, string calcType = "+")
    {
        switch (calcType)
        {
            case "+":
                return a + b;
            case "-":
                return a - b;
            case "*":
                return a * b;
            case "/":
                return a / b;
            default:
                throw new ArithmeticException();
        }
    }

    static void Main(string[] args)
    {
        Program p = new Program();
        int ret = p.Calc(1, 2);
        ret = p.Calc(1, 2, "*");
    }
}

### params

- 일반적으로 메서드의 파라미터 갯수는 고정되어 있다. 하지만 어떤 경우는 파라미터의 갯수를 미리 알 수 없는 경우도 있는데, 이런 경우 C# 키워드 params를 사용한다. 이 params 키워드는 가변적인 배열을 인수로 갖게 해주는데, 파라미터들 중 반드시 하나만 존재해야 하며, 맨 마지막에 위치해야 한다.

//메서드
 int Calc(params int[] values)

//사용
int s = Calc(1,2,3,4);
s = Calc(6,7,8,9,10,11);

### 이벤트

이벤트는 클래스내에 특정한 일(event)이 있어났음을 외부의 이벤트 가입자(subscriber)들에게 알려주는 기능을 한다. C#에서 이벤트는 event라는 키워드를 사용하여 표시하며, 클래스 내에서 일종의 필드처럼 정의된다.

이벤트에 가입하는 외부 가입자 측에서는 이벤트가 발생했을 때 어떤 명령들을 실행할 지를 지정해 주는데, 이를 이벤트 핸들러라 한다. 이벤트에 가입하기 위해서는 += 연산자를 사용하여 이벤트핸들러를 이벤트에 추가한다. 반대로 이벤트핸들러를 삭제하기 위해서는 -= 연산자를 사용한다. 하나의 이벤트에는 여러 개의 이벤트핸들러들을 추가할 수 있으며, 이벤트가 발생되면 추가된 이벤트핸들러들을 모두 차례로 호출한다.

// 클래스 내의 이벤트 정의
class MyButton
{
   public string Text;
   // 이벤트 정의
   public event EventHandler Click;

   public void MouseButtonDown()
   {
      if (this.Click != null)
      {
         // 이벤트핸들러들을 호출
         Click(this, EventArgs.Empty);
      }
   }
}

// 이벤트 사용
public void Run()
{
   MyButton btn = new MyButton();
   // Click 이벤트에 대한 이벤트핸들러로
   // btn_Click 이라는 메서드를 지정함
   btn.Click += new EventHandler(btn_Click);
   btn.Text = "Run";
   //....
}

void btn_Click(object sender, EventArgs e)
{
   MessageBox.Show("Button 클릭");
}


- 이벤트 Click은 외부에서 엑세스할 수 있게 public 필드로 정의되어 있다.
- MouseButtonDown() 메소드에서 이벤트를 외부로 보내기 전에 이벤트에 가입한 가입자들이 있는지 체크하기 위해 if (Click != null)을 사용한다.
- 이벤트에 가입하거나 탈퇴하기 위해서는 += (subscribe) 또는 -= (unsubscribe)연산자를 사용한다.
- 여기서 void btn_Click(object sender, EventArgs e) 메서드는 이벤트핸들러로 사용되고 있다.

### 이벤트: add와 remove

- C# 클래스의 속성(Property)에서 get, set 을 사용하듯이 event 에서는 add, remove 를 사용할 수 있다. 속성의 get, set에서 값을 대입하거나 리턴하는 일 이외에 간단한 체크 코드를 실행하는 것처럼 event 의 add, remove문에서도 이러한 코드를 넣을 수 있다. 아래 예제에서, add 문은 += 를, remove 문은 -= 을 사용하고 있는데, 이는 _click 이벤트 안에 있는 내부 리스트에 (주: 이를 InvocationList라 하고 _click.GetInvocationList()를 사용해서 리스트를 얻을 수 있다) value 델리게이트를 추가하거나 삭제하는 기능을 한다. 여러 개의 델리게이트를 가지고 순서대로 호출하는 것을 멀티캐스트(Multicast) 델리게이트라 하는데, 델리게이트는 멀티캐스트를 디폴트로 한다.

만약 add 문에서 +- 대신 = 을 사용하면 이는 기존 InvocationList의 내용을 지우고 할당된 새 value 델리게이트 하나만 추가하게 된다. 이는 특별한 용도로 Singlecast를 구현할 때 사용한다.

class MyButton
{
    // 이벤트 정의하는 다른 방법
    private EventHandler _click;
    public event EventHandler Click
    {
        add
        {
            _click += value;
            // _click = value;   // 싱글캐스트
        }
        remove
        {
            _click -= value;
        }
    }

    public void MouseButtonDown()
    {
        if (this._click != null)
        {
            // 이벤트핸들러들을 호출
            _click(this, EventArgs.Empty);                                
        }
    }

    /* 속성 정의
    private string _name;
    public string Name 
    {
        get
        {
            return _name;
        }
        set
        {
            _name = value;
        }
    }
    */
}
728x90