카테고리 없음

C# 문법 정리 3

빈코더 2022. 6. 16. 11:07
728x90

### 연산자

- C#은 다른 프로그래밍 언어와 비슷하게 수식 연산자, 논리 연산자, 조건 연산자등 다양한 연산자들을 제공하고 있다. 아래 표는 각 카테코리별 연산자 및 그 샘플을 예시한 것이다.

연산자 타입연산자예제

산술 연산자 +, -, *, /, % int a = (x + y - z) * (b / c) % d;
할당 연산자 =, +=, -=, *=, /=, %= int a = 100;
sum += a;
[설명] sum += a 는 sum = sum + a 를 축약한 표현이다.
증감 연산자 ++, -- int i = 1;
i++;
[설명] i++ 는 i = i + 1 를 축약한 표현이다.
논리 연산자 && (And), || (Or), ! (Not) if ((a > 1 && b < 0) || c == 1 || !d)
관계/비교 연산자 <, >, ==, !=, >=, <= if (a <= b)
비트 연산자 & (AND), | (OR), ^ (XOR) byte a=7;
byte b=(a & 3) | 4;
[설명] 비트 연산에서 & 는 둘이 1인 경우만 1이 되고 (예: 1 & 1 = 1), | 는 둘 중에 하나라도 1인 경우 1이 되며, ^ 는 둘 중에 하나만 1 인 경우 1이 된다.
Shift 연산자 >>, << int i=2;
i = i << 5;
[설명] i의 값을 왼쪽으로 5 비트 이동한다. 결과값은 2의 6승 즉 64가 된다.
조건 연산자 ?
?? (C# 3.0 이상만 지원)
int val = (a > b) ? a : b;
[설명] a가 b보다 크면 val에 a 값을 대입하고, 같거나 작으면 b 값을 대입한다
string s = str ?? "(널)";
[설명] 변수 str가 null 이면 "(널)" 이라는 문자열을 s 에 대입한다. null 이 아니면 str의 값을 s 에 대입.

## ?? 연산자

- ?? 연산자는 Null-coalescing operator라고 불리우는 특별한 연산자로서 C# 3.0 이상에서 지원하는 연산자이다. 

- ?? 연산자는 ?? 왼쪽 피연산자의 값이 NULL인 경우 ?? 뒤의 피연산자 값을 리턴하고, 아니면 그냥 ?? 앞의 피연산자 값을 리턴한다. 

- ?? 연산자는 왼쪽 피연산자가 NULL이 허용되는 데이타 타입인 경우에만 사용된다. 예를 들어, int 타입은 NULL을 가질 수 없으므로 허용되지 않지만, Nullable<int> 즉 int? 타입은 허용된다.

int? i = null;
i = i ?? 0;

string s = null;
s = s ?? string.Empty;

### if 조건문

- if 문은 조건식이 참, 거짓인지에 따라 서로 다른 블럭의 코드를 실행하게 한다. 예를 들면, if (조건식) { 블럭1 } else { 블럭2 } 문장의 경우, 조건식이 참이면 블럭1을 실행하고, 거짓이면 블럭2를 실행한다.

int a = -11;

if (a>=0)
{
    val = a;
}
else
{
    val = -a;
}

// 출력값 : 11
Console.Write(val);

### switch 조건문

- switch 문은 조건값이 여러 값들을 가질 경우 각 case 별 다른 문장들을 실행할 때 사용된다. 각각의 경우에 해당하는 값을 case 문 뒤에 지정하며, 어떤 경우에도 속하지 않는 경우는 default 문을 사용해 지정한다. 각 case문 내에서 break 문을 사용하게 되면 해당 case 블럭의 문장들을 실행하고 switch 문을 빠져 나오게 된다.

switch (category)
{
   case "사과":
      price = 1000;
      break;
   case "딸기":
      price = 1100;
      break;
   case "포도":
      price = 900;
      break;
   default:
      price = 0;
      break;
}

### for 반복 구문

- C# for 문은 루프 안에 있는 문장들을 반복적으로 실행할 때 사용한다. for 루프는 일반적으로 카운터 변수를 이용해 일정 범위 동안 for 루프 안의 블럭을 실행한다.

class Program
{
    static void Main(string[] args)
    {
        // for 루프
        for (int i = 0; i < 10; i++)
        {
           Console.WriteLine("Loop {0}", i);
        }
    }
}

### foreach 반복 구문

- C# foreach 문은 배열이나 컬렉션에 주로 사용하는데, 컬렉션의 각 요소를 하나씩 꺼내와서 foreach 루프 내의 블럭을 실행할 때 사용된다. 다음 예제는 문자열 배열을 foreach를 사용하여 각 문자열 요소를 하나씩 출력하는 코드이다.

static void Main(string[] args)
{
    string[] array = new string[] { "AB", "CD", "EF" };

    // foreach 루프
    foreach (string s in array)
    {
       Console.WriteLine(s);
    }
}

### for vs. foreach

- C#에서 for와 foreach를 비교하는 것은 흔히 성능적 측면과 코드 가독성 측면을 고려하는데, 성능적 측면은 for가 경우에 따라 약간 빠를 수 있지만 대부분의 경우 성능적 차이는 크지 않으며, foreach는 for보다 훨신 간결한 코드를 제공한다는 장점이 있다.
- 특히, 루프에서 가장 많이 사용되는 C# 배열(array)의 경우, foreach가 내부적인 최적화를 거쳐 for 루프와 동일한 성능이므로 더 간결한 foreach를 사용할 것을 권장한다. 예를 들어, 2차배열, 3차배열 등의 다중 배열을 처리할 경우, for루프는 배열 차수만큼 여러번 루프를 돌려야 하지만, foreach는 아래와 같이 단순히 한 루프 문장으로 이를 처리할 수 있어 편리하다.

static void Main(string[] args)
{
    // 3차배열 선언
    string[,,] arr = new string[,,] { 
            { {"1", "2"}, {"11","22"} }, 
            { {"3", "4"}, {"33", "44"} }
    };

    //for 루프 : 3번 루프를 만들어 돌림
    for (int i = 0; i < arr.GetLength(0); i++)
    {
        for (int j = 0; j < arr.GetLength(1); j++)
        {
            for (int k = 0; k < arr.GetLength(2); k++)
            {
                Debug.WriteLine(arr[i, j, k]);
            }
        }
    }

    //foreach 루프 : 한번에 3차배열 모두 처리
    foreach (var s in arr)
    {
        Debug.WriteLine(s);
    }
}

### while 반복 구문

- C# while 문은 while 조건식이 true인 동안 계속 while 블럭을 실행할 때 사용한다.

static void Main(string[] args)
{
    int i=1;

    // while 루프
    while (i <= 10)
    {
       Console.WriteLine(i);
       i++;
    }
}

### do while 반복 구문

- do ~ while은 위의 while문과 거의 비슷하나, 마지막 while 조건식까지 가기 전에 do ~ while 사이의 블럭을 미리 한번 실행한다는 점에서 차이가 있다.

static void Main(string[] args)
{
    int i=1;

    // do ~ while 루프
    do
    {
       Console.WriteLine(i);
       i++;
    } while (i < 10);
}

### yield

 - C#의 yield 키워드는 호출자(Caller)에게 컬렉션 데이타를 하나씩 리턴할 때 사용한다. 흔히 Enumerator(Iterator)라고 불리우는 이러한 기능은 집합적인 데이타셋으로부터 데이타를 하나씩 호출자에게 보내주는 역할을 한다.

- yield는 yield return 또는 yield break의 2가지 방식으로 사용되는데, (1) yield return은 컬렉션 데이타를 하나씩 리턴하는데 사용되고, (2) yield break는 리턴을 중지하고 Iteration 루프를 빠져 나올 때 사용한다.

using System;
using System.Collections.Generic;

class Program
{
    static IEnumerable<int> GetNumber()
    {
        yield return 10;  // 첫번째 루프에서 리턴되는 값
        yield return 20;  // 두번째 루프에서 리턴되는 값
        yield return 30;  // 세번째 루프에서 리턴되는 값
    }

    static void Main(string[] args)
    {
        foreach (int num in GetNumber())
        {
            Console.WriteLine(num);
        }             
    }
}

### yield와 Enumerator

- C#에서 yield 가 자주 사용되는 곳은 집합적 데이타를 가지고 있는 컬렉션 클래스이다. 일반적으로 컬렉션 클래스는 데이타 요소를 하나 하나 사용하기 위해 흔히 Enumerator(Iterator) 를 구현하는 경우가 많은데, Enumerator를 구현하는 한 방법으로 yield 를 사용할 수 있다.

 

### Exception 예외 처리

- C#을 포함한 모든 .NET 프로그래밍 언어는 .NET Framework의 Exception 메카니즘에 따라 Exception을 처리한다. .NET의 System.Exception은 모든 Exception의 Base 클래스이며, 예외 처리는 이 Exception 객체를 기본으로 처리하게 된다.
만약 Exception이 발생하였는데 이를 프로그램 내에서 처리하지 않으면 (이를 Unhandled Exception이라 부른다) 프로그램은 Crash하여 종료하게 된다.
- C#에서는 try, catch, finally라는 키워드를 사용하여 Exception을 핸들링하게 되며, 또한 throw라는 C# 키워드를 통해 Exception을 만들어 던지거나 혹은 기존 Exception을 다시 던질 수 있다.

try
{
   // 실행하고자 하는 문장들
   DoSomething();
}
catch (Exception ex)
{
   // 에러 처리/로깅 등
   Log(ex);
   throw;
}

### try-catch-finally

- try 블럭은 실제 실행하고 싶은 프로그램 명령문들을 갖는 블럭이다. 만약 여기서 어떤 에러가 발생하면, 이는 catch 문에서 잡히게 된다. catch문은 모든 Exception을 일괄적으로 잡거나 혹은 특정 Exception을 선별하여 잡을 수 있다.

- 모든 Exception을 잡고 싶을 때는 catch { ... } 와 같이 하거나 catch (Exception ex) { ... }처럼 모든 Exception의 베이스 클래스인 System.Exception를 잡으면 된다.
- 특정 Exception을 잡기 위해서는 해당 Exception Type을 catch하면 된다. 즉, Argument와 관련된 Exception을 잡고 싶으면, catch (ArgumentException ex) { ... } 와 같이 잡게된다.

- catch 블럭은 하나 혹은 여러 개 일 수 있다. 여러 catch를 사용하는 이유는 각 Exception 유형에 따라 서로 다른 에러 핸들링을 하기 위함이다.
- finally는 Exception이 발생했던 발생하지 않았던 상관없이 마지막에 반드시 실행되는 블럭이다. 예를 들어, try 블럭에서 SQL Connection객체를 만든 경우, finally 블럭에서 Connection 객체의 Close() 메서드를 호출하면, 에러 발생 여부와 상관없이 항상 Connection객체가 닫히게 된다.

try
{
   //실행 문장들
}
catch (ArgumentException ex)
{
   // Argument 예외처리
}
catch (AccessViolationException ex)
{
   // AccessViolation 예외처리
}
finally
{
   // 마지막으로 실행하는 문장들
}

# throw

- try 블럭에서 Exception이 발생하였는데 이를 catch 문에서 잡었다면, Exception은 이미 처리된 것으로 간주된다. 때때로 catch문에서 기존의 Exception을 다시 상위 호출자로 보내고 싶을 때가 있는데, 이때 throw 를 사용한다.

- throw 문은 크게 3가지로 구별될 수 있다. 즉, (1) throw 문 다음에 catch에서 전달받은 Exception 객체를 쓰는 경우, (2) throw 문 다음에 새 Exception 객체를 생성해서 전달하는 경우, (3) throw 문 다음에 아무것도 없는 경우 등이 있다.

try
{
    // 실행 문장들
    Step1();
    Step2();
    Step3();
}
catch(IndexOutOfRangeException ex)
{
    // 새로운 Exception 생성하여 throw
    throw new MyException("Invalid index", ex);
}
catch(FileNotFoundException ex)
{    
    bool success = Log(ex);
    if (!success)
    {
        // 기존 Exception을 throw
        throw ex;
    }
}
catch(Exception ex)
{    
    Log(ex);
    // 발생된 Exception을 그대로 호출자에 전달
    throw;
}
728x90