다른 글은 코드 -> 요약 순서인데 이건 요약 -> 코드 순서로 써봄. 뭐가 더 보기 좋을지 몰라서 바꿔봤다.
예외 처리
- 프로그램이 실패했을 때의 처리
- 실패한 함수에서 프로세스 종료
- 비추천. 보통 호출자에게 보고하는 것이 좋음.
- 실패한 함수가 실패 코드 반환
- 1번보다는 나은데, 보고를 받은 쪽에서 무시하고 킵고잉할 수 있음 → 나중에 더 큰 오류 생김
- 호출자가 반드시 오류를 처리하도록 강제하기 위해, 오류를 처리하지 않으면 프로그램 강제종료
- 직접 예외를 던져 → catch 안 하면 죽어버릴거야
- 호출자가 모르게 프로세스를 종료하지 않으면서, 처리하지 않으면 진행을 못하게 할 수 있음
- catch 후에 직접 종료하면 사용자는 적어도 왜 꺼졌는지는 볼 수 있음
- 수제 커스텀 예외로 친절하게 던지기
- 예외 클래스 만들어서, 예외에 대한 설명과 정보를 담아서 던지기
- catch에서 예외 종류에 따라 다른 처리 가능
- 실패한 함수에서 프로세스 종료
- 객체지향 언어의 예외 처리 방식
- 함수가 실패하면 예외를 던진다 → 호출자가 catch해주지 않으면 프로그램은 죽어버릴거야 못지나가
- catch 후 처리 못하겠으면 직접 종료하기
- 특정 경로에 로그 남기기
- 예외 클래스 Exception
- System.Exception 클래스는 모든 예외의 기본 클래스
- 예외 클래스는 보통 예외에 대한 설명을 담는 Message 프로퍼티를 가짐
- 파생 클래스 만들어서 쓰기
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
using static System.Console;
using System.Diagnostics;
class DBBackupException : Exception {
public string backupfilename = "temp.txt";
public DBBackupException(string message) : base(message) { }
}
class NetworkException : Exception {
public string url = "127.0.0.1";
public NetworkException(string message) : base(message) { }
}
class Database {
public Database(string dbname) { }
// 실패한 함수에서 프로세스 종료
public void Backup1() {
Process p = Process.GetCurrentProcess();
p.Kill();
}
// 실패한 함수가 실패 코드 반환
public bool Backup2() {
bool success = false;
if (!success)
return false;
return true;
}
// 직접 예외를 던져
public void Backup3() {
bool success = false;
if (!success) {
throw new Exception("백업 실패");
}
}
// 수제 커스텀 예외로 친절하게 던지기
public void Backup4() {
bool success = false;
if (!success) {
throw new DBBackupException("백업 실패");
}
else
throw new NetworkException("네트워크 오류");
}
public void Remove() => WriteLine("Remove DB");
}
class Program {
public static void Main() {
Database db = new Database("product.db");
// db.Backup1();
// bool ret = db.Backup2();
// if (!ret) {
// WriteLine("백업 실패");
// return;
// }
// db.Backup3(); // 예외 안받아줘서 죽어버릴거야
// try {
// db.Backup3();
// }
// catch (Exception ex) {
// WriteLine("DB 백업 실패");
// WriteLine(ex.Message);
// Process.GetCurrentProcess().Kill();
// }
try {
db.Backup4();
}
catch (DBBackupException ex) {
WriteLine("DB 백업 실패");
WriteLine(ex.Message);
WriteLine(ex.backupfilename);
Process.GetCurrentProcess().Kill();
}
catch (NetworkException ex) {
WriteLine("네트워크 오류");
WriteLine(ex.Message);
WriteLine(ex.url);
Process.GetCurrentProcess().Kill();
}
catch (Exception ex) {
WriteLine("알 수 없는 오류");
WriteLine(ex.Message);
Process.GetCurrentProcess().Kill();
}
db.Remove();
}
}
- 사용자의 실수 처리하기
- 똑바로 해올 때까지 다시 시켜
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
int age = 0;
while (true) {
Console.WriteLine("나이 입력");
string s = Console.ReadLine();
// age = int.Parse(s); // 사용자가 숫자 말고 다른 걸 쓰면 프로그램이 죽어버림
try {
age = int.Parse(s);
}
catch (FormatException ex) {
Console.WriteLine("숫자만 써라");
Console.WriteLine(ex.Message);
continue;
}
catch (Exception ex) {
Console.WriteLine("알 수 없는 오류");
Console.WriteLine(ex.Message);
}
break;
}
Console.WriteLine($"당신의 나이는 {age}살입니다.");
parameter modifier
- 참조 전달과 값 전달
- C#에서 매개변수는 기본적으로 값 전달입니다. 즉, 메서드에 인수를 전달할 때, 해당 인수의 값을 복사하여 메서드로 전달합니다. 따라서 메서드 내에서 매개변수의 값을 변경해도 원래의 변수에는 영향을 미치지 않습니다.
- 예를 들어, 다음과 같은 코드가 있다고 가정해봅시다:
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
using static System.Console;
class MyMath {
public static void Inc1(int x) {
++x;
}
public static void Inc2(ref int x) {
++x;
}
}
class Program {
public static void Main() {
int n1 = 0;
int n2 = 0;
WriteLine($"init n1: {n1}, n2: {n2}");
MyMath.Inc1(n1);
MyMath.Inc2(ref n2);
WriteLine($"n1: {n1}, n2: {n2}");
}
}
- 위 코드에서
Inc1메서드는 매개변수x를 증가시키지만,n1의 값은 여전히 0입니다. 이는x가n1의 값을 복사하여 전달받았기 때문입니다. 따라서Inc1메서드 내에서x를 변경해도n1에는 영향을 미치지 않습니다. - 만약 매개변수를 참조로 전달하고 싶다면,
ref키워드를 사용할 수 있습니다. 이렇게 하면 메서드 내에서 매개변수의 값을 변경하면 원래의 변수에도 영향을 미치게 됩니다.ref키워드는 함수 정의와 호출 모두에 표기되어야 합니다. - 위 코드에서
Inc2메서드는ref키워드를 사용하여 매개변수x를 참조로 전달받습니다. 따라서Inc2메서드 내에서x를 증가시키면n1의 값도 변경되어 1이 됩니다. - C/C++에서 포인터로 표기되는 것과 같은 개념임.
- in/ref/out parameter
- in parameter: main에서 보낸 값을 함수 안에서 사용하는 파라미터. 파라미터 전달 시 기본값.
- ref parameter: main에서 참조 전달로 보낸 파라미터에 값을 담아줌. 읽고 쓰기를 전제하기 때문에 전달 시 초기화는 되어 있어야 함.
++같은 연산자는 사실x = x + 1과 동일하기 때문에 읽고 쓰기가 동반됨.
- out parameter: main에서 참조 전달로 보낸 파라미터에 값을 담아줌. 쓰기 전용이기 때문에 전달 시 초기화는 필요 없음.
- 대입 이외의 연산이 필요한 경우 따로 초기화해줄 필요가 있음.
- 함수 호출 시
func(out type varname)과 같이 선언과 동시에 전달할 수 있음
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using static System.Console;
class MyMath {
public static int AddSub1(int a, int b, ref int ret) {
ret = a - b;
return a + b;
}
public static int AddSub2(int a, int b, out int ret) {
ret = a - b;
return a + b;
}
}
class Program {
public static void Main() {
int ret1 = 0;
// int ret1_1;
int ret2 = MyMath.AddSub1(5, 3, ref ret1);
int ret2_1 = MyMath.AddSub2(5, 3, out int ret1_1);
WriteLine($"ret1(a - b): {ret1}\nret2(a + b): {ret2}\n");
WriteLine($"ret1_1(a - b): {ret1_1}\nret2_1(a + b): {ret2_1}");
}
}
- 좀 더 제한적이고 간결한 예외 처리
int.Parse(): 문자열을 정수로 변경. 실패 시 예외 발생int.TryParse(): 문자열을 정수로 변경. 실패 시 false 반환
- 좀 더 일반적인 정리
Something(): 실패 시 예외 발생TrySomething(): 실패 시 false 반환 → try/catch 없이 if로 추가 처리 가능- 쓰기만 하면 된다면 out으로 전달
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using static System.Console;
// try parse
class Program {
public static void Main() {
// #1. int.Parse() : 문자열을 정수로 변경
int n1 = int.Parse("10"); // ok. 성공
// int n2 = int.Parse("Hello"); // 실패. 예외 발생
// #2. int.TryParse()
int n3;
bool result1 = int.TryParse("10", out n3); // ok. 성공
WriteLine($"result1: {result1}, n3: {n3}");
bool result2 = int.TryParse("Hello", out int n4); // 실패. false 반환
WriteLine($"result2: {result2}, n4: {n4}");
}
}
- 파라미터 수정자 활용
- ref 키워드로 참조에 의한 전달을 구현할 수 있다.
- ref 키워드로 참조에 의한 전달을 구현할 때는 메서드 정의와 메서드 호출 양쪽 모두에서 ref 키워드를 사용해야 한다.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using static System.Console;
class MyUtil {
public static void Swap(ref int x, ref int y) {
int temp = x;
x = y;
y = temp;
}
}
class Program {
public static void Main() {
int x = 1;
int y = 2;
MyUtil.Swap(ref x, ref y);
WriteLine($"{x}, {y}"); // 2, 1
}
}
- 파라미터 수정자 종류별 사용 시 차이
- no modifier parameter: 일반적인 파라미터로, 초기화된 상태로 전달됩니다. 따라서 메서드 내에서 해당 파라미터의 값을 읽을 수 있습니다.
- out parameter: out 키워드로 선언된 파라미터는 초기화되지 않은 상태로 전달됩니다. 따라서 메서드 내에서 해당 파라미터의 값을 읽을 수 없습니다. 대신, 메서드 내에서 해당 파라미터에 값을 할당해야 합니다.
- ref parameter: ref 키워드로 선언된 파라미터는 초기화된 상태로 전달됩니다. 따라서 메서드 내에서 해당 파라미터의 값을 읽을 수 있습니다. 또한, 메서드 내에서 해당 파라미터에 값을 할당할 수 있습니다.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Example {
public static void no_modifier_parameter(int x) {
int n = x; // no modifier parameter는 초기화된 상태로 전달되므로, x의 값을 읽을 수 있습니다. 따라서 이 줄은 정상적으로 컴파일됩니다.
x = 0;
}
public static void out_parameter(out int x) {
// int n = x; // out parameter는 초기화되지 않은 상태로 전달되므로, x의 값을 읽을 수 없습니다. 따라서 이 줄은 컴파일 오류를 발생시킵니다.
x = 0;
}
public static void ref_parameter(ref int x) {
int n = x; // ref parameter는 초기화된 상태로 전달되므로, x의 값을 읽을 수 있습니다. 따라서 이 줄은 정상적으로 컴파일됩니다.
x = 0;
}
}
class Program {
public static void Main() {
int n1;
int n2 = 0;
Example.out_parameter(out n1);
Example.out_parameter(out n2);
// Example.ref_parameter(ref n1); // ref parameter는 초기화된 상태로 전달되어야 하므로, n1은 초기화되지 않은 상태로 전달됩니다. 따라서 이 줄은 컴파일 오류를 발생시킵니다.
// 다만 현재 코드를 순서대로 실행하면 앞서 초기화되기 때문에 실행이 되긴 함.
Example.ref_parameter(ref n2);
Example.out_parameter(out int n3);
}
}
- 클래스와 구조체의 파라미터 전달
- 클래스는 참조형이므로, 메서드에 전달할 때 참조값이 전달된다.
- 구조체는 값형이므로, 메서드에 전달할 때 값이 복사되어 전달된다.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class CPoint { public int X { get; set; } = 0; public int Y { get; set; } = 0; }
struct SPoint { public int X { get; set; } public int Y { get; set; } }
class Program {
public static void F1(CPoint pt) { pt.X = 10; pt.Y = 20; }
public static void F2(SPoint pt) { pt.X = 10; pt.Y = 20; }
public static void Main() {
CPoint cpt = new CPoint{X = 0, Y = 0};
SPoint spt = new SPoint{X = 0, Y = 0};
F1(cpt); Console.WriteLine($"class edited: {cpt.X}, {cpt.Y}");
F2(spt); Console.WriteLine($"struct edited: {spt.X}, {spt.Y}");
}
}
오버로딩
- 메서드 오버로딩(Method Overloading)
- 같은 이름의 메서드를 여러 개 정의하는 것
- 매개변수의 타입, 개수, 순서가 다르면 같은 이름의 메서드를 여러 개 정의할 수 있습니다.
- 메서드 오버로딩을 사용하면 코드의 가독성이 향상되고, 유지보수가 쉬워집니다.
- 내부적으로는 함수가 여러 개 정의되었지만 사용자 입장에서는 함수가 1개로 보이기 때문에 일관된 라이브러리 구축에 좋음.
- C#, C++, 자바, swift는 오버로딩이 되지만 C, 파이썬, rust는 안된다. 사유는 모든 코드는 명확해야 한다는 철학 때문 (C는 너무 오래 전에 나와서 당시엔 문법 자체가 없었음).
- 의외로 파이썬은 진짜 이런 방식의 오버로딩은 불가하고, 대신 가변인자(
*args,**kwargs)를 이용해서 비슷한 효과를 낼 수는 있음. 혹은multipledispatch라이브러리 활용.
- 의외로 파이썬은 진짜 이런 방식의 오버로딩은 불가하고, 대신 가변인자(
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Math {
public int Square(int x) {
return x * x;
}
public double Square(double x) {
return x * x;
}
}
class Program {
public static void Main() {
Math m = new Math();
var ret1 = m.Square(3);
var ret2 = m.Square(3.3);
}
}
네임드 파라미터
- 파라미터에 이름 짓기
- 메서드의 매개변수에 이름을 붙이는 기능
- 매개변수의 이름을 명시적으로 지정하여 가독성을 높일 수 있습니다.
- 매개변수의 순서를 바꿔서 호출할 수 있습니다.
- 선택적 매개변수와 함께 사용할 때 유용합니다.
- 일부 매개변수만 이름을 표기해도 되지만, 반드시 이름이 표기되지 않은 매개변수가 먼저 와야 한다.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Rect {
public void Set(int x, int y, int width, int height) => Console.WriteLine($"x: {x}, y: {y}, width: {width}, height: {height}");
}
class Program {
public static void Main() {
Rect rc = new Rect();
rc.Set(1, 1, 10, 10);
rc.Set(x: 1, y: 1, width: 10, height: 10);
rc.Set(width: 10, height: 10, x: 1, y: 1);
rc.Set(1, 1, width: 10, height: 10);
}
}
- 선택적 매개변수
- 메서드의 매개변수에 기본값을 지정하는 기능
- 메서드를 호출할 때 해당 매개변수를 생략할 수 있습니다.
- 생략된 매개변수는 기본값으로 초기화됩니다.
- 선택적 매개변수는 반드시 매개변수 목록의 마지막에 위치해야 합니다.
- 오버로딩과 구조가 겹칠 경우, 컴파일러가 구분할 수 있으면 실행되고 구분하지 못하면 비정상 종료
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using static System.Console;
class Example {
public void M1(int a, int b = -1, int c = -1) {
WriteLine($"a: {a}, b: {b}, c: {c}");
}
}
class Program {
public static void Main() {
Example e = new Example();
e.M1(1, 2, 3);
e.M1(1, 2);
e.M1(1);
}
}
generic
- 로직이 전부 똑같은데 타입만 다르다고 굳이 똑같은 코드를 복붙해서 오버로딩을 해야겠니?
- 제네릭을 사용하면, 타입에 상관없이 로직이 똑같은 메서드를 하나만 작성할 수 있다.
- 제네릭 메소드 호출 시 타입을 명시적으로 지정할 수도 있고, 컴파일러가 호출 시 전달되는 인자의 타입을 보고 자동으로 유추하도록 할 수도 있다.
- C++에서는 템플릿이라고 부른다. C#에서는 제네릭이라고 부른다.
- 하나의 제네릭 메소드 안에 여러 타입이 필요한 경우 T1, T2, … 등으로 여러 타입 매개변수를 사용할 수 있다. T는 Type의 약자이다.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Program {
public static void Main() {
int n1 = 10, n2 = 20;
double d1 = 1.1, d2 = 2.3;
string s1 = "Hello", s2 = "World";
Swap<int>(ref n1, ref n2);
Swap(ref d1, ref d2);
Swap(ref s1, ref s2);
PrintSomething<int, string>(123, "abc");
PrintSomething(3.14, 123);
}
public static void Swap<T>(ref T a, ref T b) {
T tmp = a;
a = b;
b = tmp;
}
public static void PrintSomething<T1, T2>(T1 a, T2 b) {
Console.WriteLine($"a: {a}({a.GetType()}), b: {b}({b.GetType()})");
}
}
- 제너릭 클래스
- 변수 타입도 제너릭으로 선언할 수 있다
- 대신 기본값 초기화도 제너릭으로 해야 한다
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Point<T> {
private T x = default(T);
private T y = default(T);
public Point(T a, T b) {
(x, y) = (a, b);
}
}
class Program {
public static void Main() {
Point<int> p1 = new Point<int>(1, 2);
Point<double> p2 = new Point<double>(1.1, 2.1);
Point<string> p3 = new Point<string>("1", "2");
}
}
제네릭 제약조건
- 제네릭 제약조건(Generic Constraint)
- 제네릭 타입 매개변수에 대한 제약조건을 설정할 수 있다.
- 제네릭 메서드에도 제약조건을 설정할 수 있다.
- 제네릭 제약조건의 종류
- where T : struct - T는 값 형식이어야 한다.
- where T : class - T는 참조 형식이어야 한다.
- where T : class? - T는 참조 형식이되, null 가능
- where T : notnull - T는 null 불가능 타입이어야 한다.
- where T : unmanaged - T는 언매니지드 타입이어야 한다.
- where T : new() - T는 매개변수가 없는 생성자가 있어야 한다.
- where T :
- T는 특정 클래스의 파생 클래스여야 한다. - where T :
- T는 특정 인터페이스를 구현해야 한다.
- 사용자 타입을 제네릭 제약조건에 맞게 만들면 같이 사용할 수 있다.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
using static System.Console;
class Point : IComparable<Point> {
public int X{ set; get; } = 0;
public int Y{ set; get; } = 0;
public Point(int x, int y) => (X, Y) = (x, y);
public int CompareTo(Point other) {
if (other == null) return 1;
int result = X.CompareTo(other.X);
if (result == 0) {
result = Y.CompareTo(other.Y);
}
return result;
}
}
class List<T> where T : struct { // T 는 value type 이어야 한다.
//
}
class Program {
public static void Main() {
WriteLine($"{Max(10, 20)}");
WriteLine($"{Max("AAA", "CC")}");
List<int> c1 = new List<int>(); // ok
// List<string> c2 = new List<string>(); // error: string 은 value type이 아니다.
Nullable<int> n = null;
}
public static T Max<T>(T a, T b) where T : IComparable<T> {
return a.CompareTo(b) > 0 ? a : b;
}
}
partial
- partial class : 클래스의 정의를 여러 파일로 나누어 작성할 수 있도록 하는 기능
- 기본적으로 여러 개의 파일에 여러 개의 클래스를 정의해도 Main 진입점이 하나만 존재한다면 프로그램이 정상적으로 실행된다.
- 하나의 클래스 구현 사항이 너무 많아질 때, 또는 여러 명/사람+기계가 협업하여 하나의 클래스를 구현할 때, partial class를 사용하여 클래스 정의를 여러 파일로 나누어 작성할 수 있다.
- 다만 partial class로 나누어 작성된 클래스는 컴파일 시 하나의 클래스로 합쳐지므로, 클래스의 멤버 변수나 메서드가 중복되지 않도록 주의해야 한다.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// partial1.cs
class Button { }
partial class Window {
public void Show() {
Console.WriteLine("Window is shown");
}
}
class Program {
static void Main() {
Button b1= new Button();
Slider s1 = new Slider();
Window w = new Window();
w.Show();
w.Hide();
}
}
1
2
3
4
5
6
7
8
9
10
// partial2.cs
class Slider { }
class TextBox { }
partial class Window {
public void Hide() {
Console.WriteLine("Window is hidden");
}
}
namespace
- 네임스페이스 (Namespace)
- 목적
- 다른 소스와의 이름 충돌 방지
- 클래스, 인터페이스, 구조체, 열거형 등을 그룹화
- 문법
- 정의:
namespace GroupName { ... }(중괄호 안에 타입 정의) - 중첩:
namespace Outer { namespace Inner { ... } } - 참조:
Outer.Inner.TypeName
- 정의:
- using 지시문
- 파일 최상단에 작성
- 참조 시 호출에서 네임스페이스 이름 생략 가능
- 참조된 타입 간 이름이 겹치면 네임스페이스를 명시해야 함
- C# 표준 클래스는 기본적으로 System 네임스페이스에 속함.
- 목적
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using Graphic;
using Graphic3D;
namespace Graphic {
class Point {
public int X { get; set; } = 0;
public int Y { get; set; } = 0;
public Point(int a, int b) => (X, Y) = (a, b);
public override string ToString() => $"{GetType().Name}({X}, {Y})";
}
namespace Engine {
class Card {
public static void Test() {
Console.WriteLine("Card.Test");
}
}
}
}
namespace Graphic3D {
class Point {
public int X { get; set; } = 0;
public int Y { get; set; } = 0;
public int Z { get; set; } = 0;
public Point(int a, int b, int c) => (X, Y, Z) = (a, b, c);
public override string ToString() => $"{GetType().Name}({X}, {Y}, {Z})";
}
}
class Program {
static void Main() {
Graphic.Point p1 = new Graphic.Point(1, 2);
Graphic3D.Point p2 = new Graphic3D.Point(1, 2, 3);
Console.WriteLine(p1.ToString());
Console.WriteLine(p2.ToString());
Graphic.Engine.Card c1 = new Graphic.Engine.Card();
Graphic.Engine.Card.Test();
}
}
비주얼 스튜디오의 솔루션 관리 방식
- 하나의 솔루션 하위에 여러 프로젝트가 들어갈 수 있다.
- slnx 파일만 잘 쓰면 비주얼 스튜디오가 아니어도 솔루션은 만들 수 있다.
- 예를 들면 다음과 같이
코드 보기
1
2
3
4
5
6
7
8
9
10
<!-- DAY5.slnx -->
<Solution>
<Project Path="CodeOnly1/CodeOnly1.csproj" />
<Project Path="lecture1/lecture1.csproj" />
<Project Path="WpfSample1/WpfSample1.csproj" />
<Project Path="WpfSample2/WpfSample2.csproj" Id="f62729d9-918f-4b17-9cb7-2ba1e36580d1" />
<Project Path="WpfSample3/WpfSample3.csproj" Id="f85307cf-4f8e-4259-9451-4d7ff34ff746" />
<Project Path="WpfSample4/WpfSample4.csproj" Id="be5cd5cb-cbb8-40fe-ba3e-2999bd5d4ab6" />
<Project Path="WpfSample5/WpfSample5.csproj" Id="630f37ac-3497-4ded-9658-197b2e11851b" />
</Solution>
WPF 똑바로 만들기
- 좀 더 똑바로 WPF를 만들어보자
- Window: 프로그램의 주 윈도우를 만들때 사용. UI 담당
- xaml 파일로 UI 작성해서 불러올 수 있음. (XamlReader.Load)
- Application: 프로그램의 시작, 끝, event 루프를 담당. 프로그램의 life cycle 주기에서 사용자가 하고 싶은 구현 작성
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.IO;
class MainWindow : Window {
public MainWindow() {
this.Title = "Hello WPF";
// 기본적인 code only UI 만들기
StackPanel sp = new();
this.Content = sp;
Button btn1 = new Button{Content = "OK 1"};
Button btn2 = new Button{Content = "OK 2"};
sp.Children.Add(btn1);
sp.Children.Add(btn2);
btn1.Click += Btn1_Click;
btn2.Click += Btn2_Click;
// 파일로 UI 만들기
FileStream fs = new FileStream("UI1.xaml", FileMode.Open, FileAccess.Read);
Button btn3 = (Button)System.Windows.Markup.XamlReader.Load(fs);
fs.Close();
sp.Children.Add(btn3);
}
private void Btn1_Click(object sender, RoutedEventArgs e) {
MessageBox.Show("버튼1이 클릭되었습니다.");
}
private void Btn2_Click(object sender, RoutedEventArgs e) {
MessageBox.Show("버튼2가 클릭되었습니다.");
}
}
class APP : Application {
protected override void OnStartup(StartupEventArgs e) {
base.OnStartup(e);
Console.WriteLine("프로그램 시작");
}
protected override void OnExit(ExitEventArgs e) {
base.OnExit(e);
Console.WriteLine("프로그램 종료");
}
[STAThread]
public static void Main()
{
MainWindow w = new MainWindow();
w.Show();
APP app = new APP();
app.Run();
}
}
1
<Button xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Content="UI1 Button" Background="LightBlue" FontSize="32" FontFamily="D2Coding"></Button>
- xaml로 모든 UI를 작성할 수 있다면 굳이 Window 클래스를 새로 구현할 필요가 없지
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.IO;
class APP : Application {
[STAThread]
public static void Main()
{
FileStream fs = new FileStream("UI2.xaml", FileMode.Open, FileAccess.Read);
Window w = (Window)System.Windows.Markup.XamlReader.Load(fs);
fs.Close();
w.Show();
APP app = new APP();
app.Run();
}
}
1
2
3
4
5
6
7
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<StackPanel>
<Button Content="OK 1"></Button>
<Button Content="OK 2"/>
<Button>OK 3</Button>
</StackPanel>
</Window>
- xaml로 UI를 작성하고, 기능은 C# 코드로 구현.
- 다만 이 코드는 오류가 있어서(해결 못해서) 주석처리해둠
- Window를 따로 구현한 경우 namespace를 활용해서 xaml에서 Window 클래스를 참조할 수 있도록 해야 함.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.IO;
namespace CustomUI {
class MainWindow : Window {
// public void Foo(object sender, System.Windows.Input.MouseButtonEventArgs e) {
// MessageBox.Show("Hello World!");
// }
}
}
class APP : Application {
[STAThread]
public static void Main()
{
FileStream fs = new FileStream("UI3.xaml", FileMode.Open, FileAccess.Read);
Window w = (Window)System.Windows.Markup.XamlReader.Load(fs);
fs.Close();
w.Show();
APP app = new APP();
app.Run();
}
}
1
2
3
4
5
6
7
8
<local:MainWindow xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:CustomUI"><!-- MouseLeftButtonDown="Foo" -->
<StackPanel>
<Button Content="OK 1"></Button>
<Button Content="OK 2"/>
<Button>OK 3</Button>
</StackPanel>
</local:MainWindow>
본디 WPF는 xaml과 cs가 2쌍 필요하다
- 비주얼 스튜디오 WPF 프로젝트 만들어서 이해하기
- WPF는 윈도우와 APP 각각 cs와 xaml의 쌍으로 구성된다. 고로 2쌍의 cs와 xaml이 필요한 것.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// MainWindow.xaml.cs
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfSample1 {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void Button_Click(object sender, RoutedEventArgs e) {
MessageBox.Show("Button Clicked");
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- MainWindow.xaml -->
<Window x:Class="WpfSample1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfSample1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid>
<Button Content="Button" HorizontalAlignment="Center" Margin="0,197,0,0" VerticalAlignment="Top" Click="Button_Click"/>
</Grid>
</Window>
- xaml에는 다양한 방식으로 요소를 선언할 수 있고, 태그 안에 속성을 선언해서 요소의 속성을 설정할 수 있다. 또한, 태그 안에 다른 요소를 중첩시켜서 계층 구조를 만들 수도 있다.
- WPF에서는 이벤트 핸들러를 사용하여 사용자 상호 작용에 응답할 수 있다. 예를 들어, 버튼 클릭 이벤트에 대한 핸들러를 작성하여 버튼이 클릭될 때 특정 작업을 수행하도록 할 수 있다.
- WPF는 데이터 바인딩(
Name)을 지원하여 UI 요소와 데이터 소스 간의 연결을 쉽게 할 수 있다. 이를 통해 UI 요소의 속성을 데이터 소스의 값에 바인딩하여 자동으로 업데이트되도록 할 수 있다.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfSample2 {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void Button1_Click(object sender, RoutedEventArgs e) {
MessageBox.Show("Button 1 clicked");
btn3.Content = "Clicked!";
}
private void Button4_Click(object sender, RoutedEventArgs e) {
MessageBox.Show("Button 4 clicked");
btn3.Content = "NO";
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Window x:Class="WpfSample2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfSample2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Button Content="OK 1" Click="Button1_Click" FontFamily="D2Coding" FontSize="20"></Button>
<Button Content="OK 2" FontFamily="D2Coding" FontSize="20"/>
<Button Name="btn3" FontFamily="D2Coding" FontSize="20">OK 3</Button>
<Button Content="OK 4" Click="Button4_Click" FontFamily="D2Coding" FontSize="20"></Button>
</StackPanel>
</Window>
- WPF에서 슬라이더 컨트롤을 사용하여 폰트 크기를 조절하는 예제입니다. 슬라이더의 값이 변경될 때마다 폰트 크기가 업데이트되도록 이벤트 핸들러를 작성하였습니다.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfSample3 {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void fontsld_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) {
if (fontbtn != null) {
fontbtn.FontSize = e.NewValue;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
<Window x:Class="WpfSample3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfSample3"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<StackPanel>
<Slider Name="fontsld" Minimum="10" Maximum="200" Value="12" ValueChanged="fontsld_ValueChanged"/>
<Button Name="fontbtn" Content="OK" FontSize="12" Height="Auto"/>
</StackPanel>
</Window>
- 창 하나당 cs와 xaml의 쌍 하나씩. xaml은 창의 레이아웃과 디자인을 정의하는 파일이고, cs는 그 창의 동작과 이벤트 처리를 담당하는 코드 파일입니다.
- 여러 개의 창을 정의하고 각각의 창을 바꿔 로드하는 예제.
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
// WindowNavigator.xaml.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
namespace WpfSample4 {
public partial class WindowNavigator : UserControl {
private readonly List<WindowTypeItem> _windowTypes = new();
public WindowNavigator() {
InitializeComponent();
Loaded += OnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e) {
var currentWindow = Window.GetWindow(this);
var currentType = currentWindow?.GetType();
var allTypes = typeof(App).Assembly.GetTypes()
.Where(t => typeof(Window).IsAssignableFrom(t))
.Where(t => !t.IsAbstract)
.Where(t => t.Namespace == typeof(App).Namespace)
.Where(t => t != currentType)
.OrderBy(t => t.Name);
_windowTypes.Clear();
foreach (var type in allTypes) {
_windowTypes.Add(new WindowTypeItem(type.Name, type));
}
var hasItems = _windowTypes.Count > 0;
WindowComboBox.ItemsSource = _windowTypes;
WindowComboBox.DisplayMemberPath = nameof(WindowTypeItem.DisplayName);
WindowComboBox.SelectedIndex = hasItems ? 0 : -1;
WindowComboBox.IsEnabled = hasItems;
NavigateButton.IsEnabled = hasItems;
UpdateSelectedText();
}
private void WindowComboBox_OnSelectionChanged(object sender, SelectionChangedEventArgs e) {
UpdateSelectedText();
}
private void NavigateButton_OnClick(object sender, RoutedEventArgs e) {
var item = GetSelectedItem();
if (item is null) {
return;
}
var nextWindow = (Window)Activator.CreateInstance(item.Type)!;
nextWindow.Show();
var currentWindow = Window.GetWindow(this);
currentWindow?.Close();
}
private void UpdateSelectedText() {
var item = GetSelectedItem();
SelectedWindowText.Text = item is null ? "전환할 창이 없습니다." : item.DisplayName;
}
private WindowTypeItem? GetSelectedItem() {
return WindowComboBox.SelectedItem as WindowTypeItem;
}
private sealed record WindowTypeItem(string DisplayName, Type Type);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- WindowNavigator.xaml -->
<UserControl x:Class="WpfSample4.WindowNavigator"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="200" d:DesignWidth="400">
<Border Padding="16" CornerRadius="8" Background="#22000000">
<StackPanel>
<TextBlock Text="이동할 창 선택" FontSize="16" FontWeight="SemiBold" Foreground="White"/>
<TextBlock x:Name="SelectedWindowText" Margin="0,8,0,8" FontSize="14" Foreground="White"/>
<ComboBox x:Name="WindowComboBox"
MinWidth="240"
SelectionChanged="WindowComboBox_OnSelectionChanged"/>
<Button x:Name="NavigateButton"
Margin="0,12,0,0"
Padding="12,6"
Content="확인"
Click="NavigateButton_OnClick"/>
</StackPanel>
</Border>
</UserControl>
1
2
<!-- 실행되는 모든 창에 다음의 요소 추가 -->
<local:WindowNavigator HorizontalAlignment="Center" VerticalAlignment="Center"/>
- 흰 배경에 빨간색으로 마우스 그림그리기
코드 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace WpfSample5 {
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private Point ptfrom;
private void canvas_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
// MessageBox.Show("LBUTTON");
// 마우스 클릭시 좌표 보관
ptfrom = e.GetPosition(this);
}
private void canvas_MouseMove(object sender, MouseEventArgs e) {
// WPF 는 아주 잘만든 객체지향 라이브러리
// => 선을 그린다는것은
// => 선 객체를 만들어서 canvas 의 자식으로
if (e.LeftButton == MouseButtonState.Pressed) {
Line line = new Line();
line.Stroke = new SolidColorBrush(Colors.Red);
line.StrokeThickness = 5;
Point to = e.GetPosition(this);
line.X1 = ptfrom.X;
line.Y1 = ptfrom.Y;
line.X2 = to.X;
line.Y2 = to.Y;
// Canvas 에 자식으로 추가
canvas.Children.Add(line);
// 현재 점을 다시 시작점으로
ptfrom = to;
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
<Window x:Class="WpfSample5.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfSample5"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Canvas Background="White" Name="canvas"
MouseLeftButtonDown="canvas_MouseLeftButtonDown" MouseMove="canvas_MouseMove">
</Canvas>
</Window>
raw 환경에서 C# 쓰는 방법
- 메모장+컴파일러
- 텍스트 파일을 직접 만들고 저장
developer command prompt실행- 코드가 있는 폴더로 이동
csc filename.cs로 빌드filename.exe로 실행- 다만 외부 라이브러리 사용 제한됨
- dotnet tools 사용 (권장됨)
- 폴더 만들기
developer command prompt실행 (사실 이건 아무 터미널에서나 해도 됨)- 폴더 이동
dotnet new console명령 실행 → 비주얼 스튜디오에서도 쓸 수 있는 C# 프로젝트 생성됨dotnet new wpf하면 WPF 프로젝트 만들어짐
- vscode나 원하는 IDE로 알아서 쓰면 됨 ← vscode는 비주얼 스튜디오처럼 보기 좋은 편집기는 아님
dotnet build하면 빌드만 되고dotnet run하면 빌드 후 실행됨