https://www.csharpstudy.com/CSharp/CSharp-Intro.aspx
1. 시작 구조
namespace StudyC
{
class Program
{
static void Main(string[] args) // Main() 메소드는 static으로 1개만
{
System.Console.Write("Hello World!");
}
}
}
C# 프로그램은 Main() 시작 함수를 1개만 가지고 있으며, static으로 선언해야한다.
string[] 문자열을 메소드 인자로 받는다.
System.Console : .NET Framework 클래스이며, WriteLine은 화면에 출력하게 한다.
2. 데이터 타입
- C# 데이터타입
데이터 타입 | .NET 데이터 타입 | 설명 |
bool | System.Boolean | True or False |
int | System.Int32 | 32비트 singed integer |
long | System.Int64 | 64비트 singed integer |
uint | System.UInt32 | 32비트 unsinged integer |
float | System.Single | 32비트 double precision 부동 소수점 숫자 |
double | System.Double | 64비트 double precision 부동 소수점 숫자 |
string | System.String | 유니코드 문자열 |
- 리터럴 데이터
C#에서는 123, true, "ABC"와 같은 값을 직접 써줄 수 있는데, 이를 리터럴이라고 한다. 따로 접미어 표시가 없는 경우에도 컴파일러가 할당해준다.
123 // int 리터럴
123.3 // double 리터럴
"A" // string 리터럴
'A' // char 리터럴
true // bool 리터럴
접미어를 표시하면 특정 데이터 타입을 지정할 수 있다. 접미어는 대소문자 구분이 필요없다.
C# 리터럴 데이타 타입 | Suffix (대소문자 모두 가능) | 예제 |
long | L | 1024L |
uint | U | 1024U |
ulong | UL | 1024UL |
float | F | 10.24F |
double | D | 10.24D 또는 10.24 |
decimal | M | 10.24M |
// Bool
bool b = true;
// Numeric
short sh = -32768;
int i = 2147483647;
long l = 1234L; // L 접미어
float f = 123.45F; // F 접미어 // F를 붙이지 않으면 double이 디폴트다.
double d1 = 123.45;
double d2 = 123.45D; // D 접미어
decimal d = 123.45M; // M 접미어
// Char/String
char c = 'A'; // '' 사용
string s = "Hello"; // "" 사용
// DateTime 2011-10-30 12:35
DateTime dt = new DateTime(2011, 10, 30, 12, 35, 0);
※ 각 타입의 최대/최소 값 : 데이터 타입.MinValue or 데이터 타입.MaxValue
- Null Type
C# 2.0부터 불가능한 타입에도 ?을 통해 NULL로 초기화할 수 있다. ?의 경우 Nullable<T>과 같은 표현이다.
// NULL
int? i = null; // int?는 Nullable<int> 와 같다.
i = 100;
bool? b = null;
Nullable<int> j = null;
j = 10;
int k = j.Value;
3. 변수와 상수
변수는 메소드내 로컬 변수와, 클래스 내 멤버 변수가 있다.
- 로컬 변수 : 해당 메서드내에서만 사용되며, 호출이 끝나면 소멸된다. 기본값을 할당하지 못하기 때문에 사용전 값을 할당해야한다.
- 멤버 변수(전역적 변수:필드) : 클래스 객체가 살아있는 한 계속 존속되며, 다른 메서들에서 참조 가능하다.
※ 변수의 이름은 대소문자를 구별한다.
lass Temp
{
int val; // 필드는 자동으로 초기화해준다.
const int MAX = 100; // 상수는 첫 할당 이후 값을 변경할 수 없다.
readonly int MIN; // 생성자에서 값을 할당한다.
public Temp() // 생성자에서만 할당할 수 있음
{
MIN = 10;
}
public void Func1()
{
int local = 300;
Console.WriteLine(val);
Console.WriteLine(local);
Console.WriteLine(MIN);
}
}
class Program
{
static void Main(string[] args)
{
Temp temp = new Temp();
temp.Func1();
}
}
※ 로컬변수는 반드시 사용전에 할당, readonly는 생성자에서 값을 변경할 수 있다.
4. 배열
동일한 데이터 타입 요소들로 구성된 집합이다. 각 Index를 통해 배열 요소에 접근할 수 있다.
// 1차원 배열
string[] regions = {"서울", "경기", "부산"};
// 2차원 배열
string[,] depts = {{"홍길동", "경리부"}, {"이용", "총무부"}};
// 3차원 배열
string[,,]
- 가변 배열
각 차원별 배열 요소 크기가 가변적일 경우 [][] 방식을 이용한다.
// 가변 배열로 1차원 배열 크기는 명시해야한다.
int[][] arr = new int[3][];
// 각 1차원 배열에 가변크기의 배열을 추가한다.
arr[0] = new int[2];
arr[1] = new int[3] {1, 2, 3};
arr[2] = new int[5] {10, 20, 30, 40, 50};
// 접근방식
arr[0][0] // 기본값 0
arr[1][1] // 2
arr[2][3] // 40
- 배열의 사용방법
배열은 래퍼런스 타입이기 때문에, 전달할 때 데이터를 복사하지 않고 참조 값만 전달한다.
static void Main(string[] args)
{
int[] scores = { 1, 2, 3, 4, 5 };
Console.WriteLine(GetSum(scores)); // 베열의 이름을 전달한다.
}
static int GetSum(int[] scores) // 레퍼런스 타입으로 전달함.
{
int sum = 0;
for (int i = 0; i < scores.Length; ++i) // Length : 배열 요수의 개수
{
sum += scores[i];
}
return sum;
}
5. 문자열
C# 문자열은 Immutable로 한번 설정되면 다시 변경할 수 없다.
변경이 되는것 처럼 보이는 이유는 .NET 시스템이 새로운 string 객체를 생성하고 할당하기 때문이다.
static void Main(string[] args)
{
// 문자열 변수
string str1 = "Hello";
string str2 = "World";
// 문자열 결합
string str3 = str1 + " " + str2;
Console.WriteLine("String: {0}", str3);
// Net 시스템이 새로운 string 객체를 생성하고, New 데이터로 초기화한 후 str3에 다시 할당한다.
str3 = "New"; // 즉 내부적으로 전혀 다른 메모리를 갖는 객체를 가리킨다.
Console.WriteLine("String: {0}", str3);
}
- StringBuilder 클래스
String 클래스는 갱신이 많이 필요한 프로그램에서는 적합하지 않다.
StringBuilder(Mutable)로 별도의 메모리를 생성/소멸 시키지 않고, 일정한 버퍼로 문자열을 효율적으로 관리하자.
static void Main(string[] args)
{
StringBuilder sb = new StringBuilder();
for (int i = 1; i < 9; i++)
{
sb.Append(i.ToString());
//sb.Append(System.Environment.NewLine); // 공백라인 추가하기
}
string str = sb.ToString();
Console.WriteLine("String: {0}", str);
}
6. enum
열거형 상수를 표현하기 위해 각 상수 숫자들에 의미를 부여해 읽기 쉽게 만든 것
public enum Category
{
Cake, // 0
IceCream, // 1
Bread // 2
}
enum은 순서대로 부여되지만 중간에 바꿀 수 있으며 숫자형 타입과 호환이 가능하다.
enum City
{
Seoul, // 0
Daegu, // 1
Busan = 3, // 3: 값을 정해줄 수 있다.
Jeju = 15 // 15
}
class Program
{
static void Main(string[] args)
{
City city;
city = City.Busan;
int citiValue = (int)city; // value: 3
Console.WriteLine("{0}", citiValue);
}
}
※ 필드의 경우 자동으로 1번 enum이 부여되지만, 로컬은 할당하지 않으면 컴파일 에러가 발생한다!
- 플래그 enum
[Flags] 속성으로 비트 필드(1,2,4,8,...)을 부여할 수 있다.
이후 OR AND 연산자를 이용해 다중값, 특정 멤버를 포함하고 있는지 확인할 수 있따.
[Flags]
enum Border
{
None = 0,
Top = 1,
Bottom = 2,
Right = 4,
Left = 8
}
class Program
{
static void Main(string[] args)
{
Border border = Border.Top | Border.Left; // 좌상단
if (border.HasFlag(Border.Top)) // Top 값을 보유했는가?
{
}
}
}
7. 연산자
종류 | 연산자 | 예제 |
산술 연산자 | +, -, *, /, % | int a = (x + y - z) * (b / c) % d; |
할당 연산자 | =, +=, -=, *=, /=, %= | sum += a; |
증감 연산자 | ++, -- | i++; |
논리 연산자 | && (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; // (111 & 011) | 100 // 011 | 100 // 111 // 7 |
Shift 연산자 | >>, << | int i=2; i = i << 5; // 0000 0010 -> 0100 0000 |
조건 연산자 | ? ?? (C# 3.0 이상만 지원) |
int val = (a > b) ? a : b; string s = str ?? "값"; // str이 null이면 값 문자열을 대입한다. |
- ?? 연산자
?? 왼쪽 피연산자의 값이 null이면 뒤의 피연산자 값을 대입한다.
// int 타입의 null
int? a = null;
int b = a ?? 3; // b=3
// string 타입의 null
string str1 = null;
string str2 = s1 ?? "Hello"; // str2="Hello"
8. 조건문
- if 조건문
if( ... ) // true or false
{
...
}
- switch case 조건문
switch(num)
{
case 0:
case 1: // 0과 1일때
break;
case 2: // 2일때
break;
default: // 아무것도 속하지 않을 때
break;
}
9. 반복문
- for 반복문
for(int i=0; i<10; ++)
- foreach
foreach(int n in arr) // arr 배열속의 arr[i] 값들을 n으로 하나씩 가져옴
- for vs foreach
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);
}
}
- whil
while(...) // true면 계속 실행
- dowhile
do
{
...
} while (...) // true면 다시 실행함.
10. yield
호출자에게 컬렉션 데이터를 하나씩 리턴할 때 사용한다. 흔히 Enumerator(반복자)라고 불리기도 한다.
즉 데이터를 호출자에게 1개씩 보내주는 역할이다.
- yield return : 컬렉션 데이터를 하나씩 리턴
- yield breakl : 리턴을 중지하고 iteration 루프를 바져나감
class Program
{
static IEnumerable<int> GetNumber()
{
yield return 10; // 1번째 리턴
yield return 20;
yield return 30;
}
static void Main(string[] args)
{
foreach (int num in GetNumber()) // 1~3까지 리턴함
{
Console.Write(num + " ");
}
}
}
}
그렇다면 언제 쓰이는가?
- 데이터의 양이 많아 하나씩 리턴하는 것이 효율적일 때
- 무제한의 데이터를 리턴하는 경우
- 모든 데이터를 미리 계산하면 느리기 때문에 분산 계산할 때
- yield와 Enumerator
IEnumerator 인터페이스는 Current MoveNext(), Reset() 3개의 멤버로 이루어져있다.
그중 Current(반환)와 MoveNext(다음 yield 반환하는 곳으로)는 반드시 구현해야한다.
public class MyList
{
private int[] data = { 1, 2, 3, 4, 5 };
public IEnumerator<int> GetEnumerator()
{
int i = 0;
while (i < data.Length)
{
yield return data[i]; // 1에서 반환하면, 다음 호출 시 1을 기억하기 때문에 2를 반환함
i++;
}
}
// ... Current와 MoveNext는 yield문을 만날 때, 자동으로 인터페이스의 내부 클래스를 만들어준다.
}
namespace StudyC
{
class Program
{
static void Main(string[] args)
{
// foreach를 이용
var list = new MyList();
foreach (var item in list)
{
Console.WriteLine(item);
}
// movenext를 이용
IEnumerator<int> it = list.GetEnumerator();
it.MoveNext();
Console.WriteLine(it.Current); // 1
it.MoveNext();
Console.WriteLine(it.Current); // 2
}
}
}
11. 예외처리
try, catch, finally 라는 키워드를 사용해 Exception을 핸들링한다.
또한, throw 키워드로 예외를 던질 수 있다.
try
{
Do();
}
// catch { ... } 와 같은 기능
catch(Exception ex) // 모든 예외처리
{
...
throw; // 다시 예외를 던짐
}
- try-catch-finally
- try : 실행시키고 싶은 프로그램의 명령문이 있는 블록
- ctach : 모든 Exception 이나 특정 Exception을 선별하여 잡는 곳
- finally : Exception 발생 여부와 관계없이 마지막에 반드시 실행되는 블록이다.
try
{
... // 실행할 문장들
}
catch (ArgumentException ex)
{
... // Argument에 관한 예외처리
}
catch (AccessViolationException ex)
{
... // AccessViolation에 관한 예외처리
}
fianlly
{
... // 마지막에 반드시 실행되는 문장
}
- throw
try 블럭에서 발생한 Exception은 이미 catch에서 처리된 것이다.
다시 Exception을 보내고 싶을 때 throw 키워드를 사용한다.
- throw문 다음 catch에서 전달받은 Exception 객체를 쓰는 경우
throw ex; // 기존 Call Stack 정보를 소실하기 떄문에 사용하지 않는 것을 추천함.
- throw문 다음에 새 Exception 객체를 생성해 전달하는 경우
throw new MyException(..., ex); // 기존 ex를 전달해 어느 라인에서 에러가 발생한지도 알려주자.
- throw문 다음에 아무것도 없는 경우
throw; // catch문에서 잡힌 ex를 그대로 상위 호출 함수로 전달하는 작업이다.
※ 다만 throw문과 동일한 메서드(다른 메서드의 위치는 앎)에서 에러가 발생하면 어느 라인에서 발생한지는 모른다.
try
{
Step1();
Step2();
Step3();
}
catch(IndexOutOfRangeException ex)
{
throw new MyException("Invalid index", ex); // 새로운 ex 생성
}
catch(FileNotFoundException ex)
{
...
if (...) // 기존 Exception을 throw
{
throw ex;
}
}
catch(Exception ex)
{
...
throw; // ex를 그대로 호출자에게 전달
}
12. 네임스페이스
클래스들을 충돌없이 관리하기 위해 사용하며, 없어도 클래스들을 정의할 수 있지만 보통 사용한다.
namespace My
{
class A
{
...
}
class B
{
...
}
}
- 참조
다른 네임스페이스를 사용하기 위해서는 클래스명 앞에 네임스페이스 전부를 쓰거나, using 키워드를 이용한다.
// 클래스명 앞에 네임스페이스 기입
static void Main(string[] args)
{
System.Console.WriteLine(); // 네임 스페이스.클래스.함수();
}
// using 이용
using System;
static void Main(string[] args)
{
Console.WriteLine(); // 클래스.함수();
}
13. 구조체
- Value Type, Reference Type
C#에서는 struct는 value type이고, class는 reference type으로 만든다.
이름 | 상속 여부 | 데이터 타입 | 파라미터 전달 |
struct | 불가능 | 벨류 : 기본 데이터 타입(int, double, float, double) | 데이터 Copy |
class | 가능 | 레퍼런스 : 보다 복잡한 데이터와 행위 | 객체(Heap)에 대한 레퍼런스 |
public struct Int32 { ... } // 벨류
public sealed class String { ... } // 레퍼런스
- 구조체
클래스와 같이 메소드, 프로퍼티 등 비슷한 구조를 가질 순 있으나 상속은 불가능하다.
대신 인터페이스를 구현할 순 있다.
struct Point
{
public int x; // default는 private이다.
public int y;
public Point(int a, int b)
{
this.x = a;
this.y = b;
}
public override string ToString()
{
return string.Format("{0},{1}", x, y);
}
}
14. 클래스
클래스는 메소드, 속성(프로퍼티), 필드, 이벤트 등을 멤버로 포함한다.
클래스 멤버종류설명
멤버 | 설명 |
메소드 | 대개 동사 혹은 동사+명사 식으로 메서드명을 정함. 예) Calculate(), DeleteData() |
속성(프로퍼티) | 클래스의 내부 데이터를 외부에서 사용 및 설정한다. |
필드 | 클래스의 내부 데이터는 필드에 저장한다. 데이터는 주로 private 필드로 만들고 public 프로퍼티를 이용한다. |
이벤트 | 객체 내부의 특정 상태를, 어떤 일이 있어났다는 이벤트를 외부로 전달한다. |
public class Customer // public이니 모든 객체로 부터 접근 가능
{
// 필드
string name;
int age;
// 이벤트
public event EventHandler nameChanged;
// 생성자
public Customer()
{
name = string.Empty;
Age = -1;
}
// 프로퍼티
public string Name
{
get => this.name;
set
{
if (this.name != value)
{
this.name = value;
if (nameChanged != null)
{
nameChanged(this, EventArgs.Empty); // 이벤트!
}
}
}
}
public int Age { get => this.age; set => this.age = value; }
}
- Partial 클래스
하나의 클래스를 2개 이상의 파일로 나누어 정리할 수 있는 기능이다.
public partial class MyClass { ... } // MyClass.cs와 MyClass.desinger.cs 파일이 생성
15. Nullable 타입
정수, 부동자릿수, 구조체 등의 Value Type은 NULL을 가질 수 없다. 다만 Nullable로 할당할 순 있다.
Nullable 타입은 Value 값과 Null 상태를 체크(HaslValue)할 수 있는 기능이 있는 struct 이다.
int? i = null; // Nullable<int> i = null
bool? b = null;
int?[] arr = new int?[100];
- Nullable<T>
실제 값을 나타내는 Value와 값을 가졌는지 체크하는 HasValue 프로퍼티가 있다.
해당 구조체는 타입 캐스트를 통한 변환, 암묵적 변환으로 Value Type으로 변환해준다.
double sum = 0;
DateTime time;
bool? bSelect;
public void CheckInput(int? i, double? d, DateTime? time, bool? selected)
{
if (i.HasValue && d.HasValue) // 값 체크
this.sum = (double)i.Value + (double)d.Value;
if (!time.HasValue) // 값 체크
throw new ArgumentException();
else
this.time = time.Value;
this.bSelect = select ?? false; // select가 null이면 false를 할당한다.
}
- Nullable 정적 클래스
System.Nullable 정적 클래스는 Nullable 객체를 비교하는데 도움이 된다.
void NullableTest()
{
int? a = null;
int? b = 0;
int res = Nullable.Compare<int>(a, b) // 큰지 작은지를 비교함.
// 결과 -1
double? c = 0.01;
double? c = 0.0100;
bool res2 = Nullable.Equals<double>(c, d); // 값이 값은가?
// 결과 true
}
16. 메서드 파라미터
// 접근 제한자, 리턴 타입, 함수 명칭(인자 1, 인자 2, ..., 인자 N)
public int GetData( ... );
- Call By Value
인수를 전달할 때 값을 복사해서 전달하는 방식이다. 리턴 후 값의 변경은 없다.
static void Add(int n)
{
n += 2;
}
static void Main(string[] args)
{
int val = 10;
Add(val); // val은 계쏙 100
}
- Call By Reference
C#에서는 레퍼런스 전달을 ref 키워드를 사용하며, 사전에 인자로 올 변수를 초기화해야한다.
또한, out 키워드로도 가능하다. out은 사전에 초기화를 하지 않아도 된다.
static void Main(string[] args)
{
int x = 1;
double y = 1.2;
int a, b;
// ref 키워드 : 초기화 O
double res = GetMul(ref x, ref y); // 매개변수로 ref 표시!
// out 키워드 : 초기화 X
GetDif(1, 2, out a, out b); // 매개변수로 out 표사!
Console.WriteLine(string.Format("Res = {0} // X = {1} // Y = {2}", res, x, y));
Console.WriteLine(string.Format("A = {0} // B = {1}", a, b));
}
static double GetMul(ref int a, ref double b) // 인자에 ref를 표시해야한다.
{
return ++a * ++b;
}
static void GetDif(int a, int b, out int r1, out int r2) // 인자에 out을 표시해야한다.
{
r1 = a + b;
r2 = a - b;
}
- Named 파라미터
파라미터의 위치에 따라 순차적으로 넘어가는데, C# 4.0 부터는 파라미터 명을 지정하여 전달할 수 있다.
Func(name : "John", age : 10, score : 90.0);
- Optional 파라미터 (디폴트 O)
C# 4.0 부터 메소드가 디폴트 값을 보유한다면 생략해도 된다. 디폴트는 파라미터의 맨 마지막에 배치해야한다.
int GetSum(int a, int b, int c=10, int d=20);
- params
파라미터의 갯수를 가변 크기일 때 parms 키워드를 사용한다.
int Calc(params int[] vals);
17. 이벤트
클래스에서 특정한 이벤트가 일어났음을 외부의 이벤트 가입자들에게 알려주는 것을 의미한다.
클래스 내에서 필드처럼 정의되며, event 키워드를 사용해 표시한다.
- 이벤트 핸들러 : 이벤트가 발생했을 때 어떤 명령을 실행할지를 지정함
- '+=' 연산자를 이용해 이벤터 핸들러를 이벤트에 추가한다.
- '-=' 연사자로 이벤트핸들러를 삭제한다.
class MyButton
{
public string text;
public event EventHandler onClicked; // 외부에서 엑세스할 수 있게 public
public void ButtonClicked()
{
if (onClicked != null) // 외부로 보내기전에 가입된 사용자가 있는지 체크한다.
{
onClicked(this, EventArgs.Empty); // 가입자가 있으면 이벤트핸들러 호출
}
}
}
public void Run()
{
MyButton button = new MyButton();
buttton.onClicked += new EventHandler(ButtonClick); // 이벤트 핸들러로 ButtonClick()을 등록
buttton.text = "Run";
...
}
static void ButtonClick(object sender, EventArgs e) // 이벤트핸들러로 사용됨
{
MessageBox.Show("Button 클릭");
}
- add와 remove
프로퍼티의 get, set 처럼 이벤트에서는 add(+=), remove(-=)가 있다.
private event EventHandler onClicked;
public event EventHandler OnClicked { add => onClicked += value; remove => onClicked -= value; }
※ 이벤트핸들러를 추가한 클래스에서만 삭제가능하다.
18. 전처리기
컴파일이 시작되기 전에 컴파일러에게 명령을 미리 처리하도록 지시하는 것이다.
- 지시어는 #으로 시작
- 한 라인에 1개의 전처리기 명령만 사용
- 지시어의 끝에는 ;를 붙이지 않는다.
- 해당 파일 안에서만 효력이 발생된다.
#define
#if
#else
#endif
- region 지시어
코드 블록을 논리적으로 묶을 때 유용하다. 예를 들어, public만 private 등등...
#region Public Methods
public void Run()
{
...
}
#endregion
- 기타 지시어
- #undef : #define과 반대로 지정된 심볼을 해제할 때 쓴다
- #elif : else if
- #line : 라인 번호를 임의로 변경, 파일명을 임의로 다르게 설정
- #error : 전처리시 Preprocessing을 중단하고 에러 메시지를 출력
- #warinig :" 경고 메시지를 출력하지만 Preprocessing은 계속 진행
// #warning 예제 -----------------------------------
#if (!ENTERPRISE_EDITION)
#warning This class should be used in Enterprise Edition
#endif
namespace App1
{
class EnterpriseUtility
{
}
}
// #error 예제 --------------------------------------
#define STANDARD_EDITION
#define ENTERPRISE_EDITION
#if (STANDARD_EDITION && ENTERPRISE_EDITION)
#error Use either STANDARD or ENTERPRISE edition.
#endif
namespace App1
{
class Class1
{
}
- pragma 지시어
컴파일러 제작업체가 고유하게 자신들의 것을 만들어 사용할 수 있는 지시어다.
즉 어떤 컴파일러에 따라 지원하는것이 달라진다.
댓글