C# 사용 방법

[기본 문법]

  • Keywords

= abstract: abstract class 혹은 abstract method임을 명시; abstract method를 재정의할 때는 override를 사용

= as: "변수 as 형식"으로 쓰여서, 변수가 해당 형식에 맞으면 변수를 return, 아니면 null을 return

= base: base class의 reference

= const: 상수 변수로 선언; static과 함께 쓰일 수 없음(const는 당연히 static 특성임)

= decimal: 10진수 기준으로 표현하는 부동 소수점 변수; 10진수를 정확하게 표현; decimal 입력은 0.1m처럼 마지막에 m을 붙여야 함; m은 decimal literal value를 상징

= event: event 발생을 감시하는 객체를 선언할 때 사용

= internal: class의 접근 한정자로서 같은 assembly(같은 cs file or DLL)에서는 public으로, 다른 assembly(다른 cs file or DLL)에서는 private으로 작용; internal은 물리적 접근 제한자로 간주함

= is: "변수 is 형식"으로 쓰여서, 변수가 해당 형식과 호환될지를 bool로 return

= nameof: 변수의 이름을 알려줌

= new: instance 생성에 사용; method 정의에 쓰이면 base method를 무효화해서 새로운 method를 현재 class에 정의할 때 사용

= object: 모든 객체의 원조 객체; override가 없는 경우, 할당될 때 reference가 전달됨

-. object를 항상 nullable이 되게 하려면: object? obj;

-. object?를 쓰지 않으려면 생성자에서 object를 초기화해야 함

= out: 함수의 매개변수가 reference 형태이며 출력으로 사용됨을 명시; 함수 내부에서 out 변수를 초기화하므로 입력 전달 전에 초기화할 필요 없음; 함수는 static으로 선언되어야 함

= override: 재정의; abstract class를 위한 override 함수를 명시; virtual로 설정된 method를 override함

= readonly: 변수를 읽기 전용으로 선언; const와 비슷하지만 readonly 변수는 생성자에서 초기화될 수 있음

= ref: 함수의 매개변수를 reference 형태로 전달; 함수 입력으로 전달 전에 ref 변수는 초기화되어 있음; 함수는 static으로 선언되어야 함

= reflection: 다른 assembly에 있는 member를 참조하는 방법

= sealed: 다른 class의 base가 될 수 없도록 봉인할 때 사용; method를 재정의하지 못하게 할 때도 사용 가능

= this: 현재 class의 reference

= using: namespace 사용; try-catch-finally 구문을 간단히 using (...) {...}으로 대체 가능하며 자원 해제도 자동으로 됨

= var: compiler가 data type을 결정함; 개발자가 굳이 data type을 결정할 필요 없음

= virtual: 가상 함수; virtual 선언한 method는 상속을 받았을 때, new, override에 의해 교체될 수 있음

= .NET assembly: 응용 program을 구성하는 기본 component; exe or DLL

  • C# types

= Type 종류

-. Value type: bool, byte, char, int, double, enum(나열형), struct(구조체), System.ValueType 등

-. Reference type: object, string, class, System.Array 등

= object(System.Object의 alias)와 string(System.String의 alias)을 제외한 모든 type은 구조체(structure)임; 예를 들어, int(System.Int32의 alias)는 구조체로 선언됨

= 구조체로 된 type(primitive type)은 할당될 때 value가 넘어감: System.ValueType의 파생 type

-. string은 할당될 때, copy constructor가 호출되어 reference가 아닌 value가 넘어감

= object은 할당될 때 reference가 넘어감

= Boxing: int n = 4; object obj = n; // obj의 type을 보려면 GetType() 호출; 그러면 Sytem.Int32가 return

= Unboxing: int m = (int)obj // obj는 object

= Type 확인 함수

-. class의 type 확인 - typeof(): class Animal {}; typeof(Animal)

-. 변수의 type 확인 - GetType(): class Animal {}; Animal animal = new(); animal.GetType()

  • 배열(array)

= 배열은 System.Array로부터 상속됨

= 1차원 배열: 배열의 크기는 arr.Length로 획득, arr.GetLength(0)도 동일한 결과

int[] arr = new int[5] { 1, 2, 3, 4, 5 }; // new int[] { 1, 2, 3, 4, 5 }도 가능; arr의 type은 System.Int32[]

foreach (int i in arr) Console.WriteLine(i);

= 2차원 배열: 배열의 전체 크기는 arr2.Length로 획득

int[,] arr2 = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6, } };

for (int i = 0; i < arr2.GetLength(0); i++) // 첫번째 배열 크기: 2

for (int j = 0; j < arr2.GetLength(1); j++) // 두번째 배열 크기: 3

Console.WriteLine(arr2[i, j]); // 1-6 순으로 출력됨

-. 2차원 배열은 int [][]로 선언도 가능: 이때 arr2.Length는 arr2.GetLength(0)과 동일한 결과; int[][]는 두번째 배열의 크기는 서로 같을 필요 없음

int[][] arr2 = new int[][] { new int[] { 1, 2, 3 }, new int[] { 4, 5 } };

for (int i = 0; i < arr2.Length; i++) // arr2의 크기: 2

for (int j = 0; j < arr2[i].Length; j++) // arr2[0]의 크기: 3, arr2[1]의 크기: 2

Console.WriteLine(arr2[i][j]); // 1-5 순으로 출력됨

  • 함수

= 함수의 인수 이름을 사용해서 입력 매개변수를 설정 가능; 인수 이름을 생략하면 C와 같은 호출이 됨

class MyNum

{

public int num, Num;

public void SetVal(int num1, int num2)

{ this.num = num1; this.Num = num2; } // num = num1; Num = num2;도 가능

}

myNum.SetVal(1, 2);

myNum.SetVal(num1: 1, num2: 2);

  • class

= Reference type

= 접근 한정자(access modifier)가 없으면 private으로 간주

= 기본 생성자(입력이 없는 생성자)와 정적 생성자(static constructor) 정의 가능

= 소멸자 정의 가능

= public class vs. class

-. 그냥 class로 정의하면, internal class라서 다른 영역에서 이 class를 참조할 수 없음

-. public class는 다른 영역에서 참조할 수 있음

= 정적 생성자(static constructor)

-. 정적 함수는 정적 생성자가 호출된 후에 정적 함수가 호출됨: 아래 예에서 StaticCall()이 호출되고 그 다음에 fun()이 호출됨

class StaticCall

{

static StaticCall()

{ Console.WriteLine("constructor call."); }

public static void fun()

{ Console.WriteLine("function call."); }

}

StaticCall.fun();

= class의 출력을 변경

class MyNum

{

public int num;

public int Num { get { return num + 10; } set { num = value + 30; } }

public override string ToString()

{ return $"num: {num}, Num: {Num}"; }

}

= 연산자 중복 정의

public static bool operator ==(MyNum num1, int num2) // class 안에서 정의함

{ return num1.num == num2; }

public static bool operator !=(MyNum num1, int num2)

{ return num1.num != num2; }

= 상속할 때, base를 class와 interface(member 구현 없이 선언만 있는 구조) 모두 사용 가능

= 상속(inheritance)

-. 생성자 초기화가 필요하면: Child(string name) : base(name) // class Man의 생성자로 Man(string name) 정의 필요

-. Base class는 단 하나만 설정 가능

public class Man

{

protected string name;

public Man() { Console.WriteLine("man"); }

public Man(string name)

{

this.name = name;

Console.WriteLine($"man: {name}");

}

public void Do() { Console.WriteLine("A man is doing."); }

}


public class Child : Man

{

public Child() { Console.WriteLine("child"); }

public Child(string name) : base(name) { Console.WriteLine($"child: {base.name}"); }

public new void Do() { Console.WriteLine("A child is playing."); }

}

-. Base method를 무효화하려면, 현재 class method 정의에 new를 사용함

= 추상 class(abstract class)

abstract class Animal

{

public Animal() { }

public abstract void Do();

}

class Dog : Animal

{

public override void Do() { Console.WriteLine("I am a dog."); }

}

  • struct

= Value type

= class처럼 접근 한정자(access modifier)가 없으면 private으로 간주

= 기본 생성자(입력이 없는 생성자) 정의 불가; 정적 생성자(static constructor) 정의 가능

= 소멸자 정의 불가

= 연산자 중복 정의 가능

= 상속할 때, base는 interface만 사용 가능

  • interface

= Member의 구체적인 구현은 없고 선언만 존재

= interface는 instance를 생성할 수 없음; interface를 상속 받은 class나 struct를 이용해 instance를 생성해야 함

= interface의 접근 한정자는 생략하며 public으로 간주

= class는 여러 개의 interface를 상속 받을 수 있음; class는 하나의 base class와 여러 개의 interface를 동시에 상속받을 수 있음

-. 여러 개의 interface가 같은 이름을 쓰면 묵시적 구현으로 method를 재정의해야 함

= interface 정의에 변수와 indexer도 사용 가능; 다만 변수와 indexer는 상속받은 class에서 다시 선언해야 함

interface IStudy

{

void Subject();

}

class Math : IStudy

{

public void Subject() { Console.WriteLine("I study math."); } // 이건 묵시적 구현; IStudy.Subject() 형태인 명시적 구현도 가능

}

  • Property

= Property는 변수와 method의 조합으로 작용

= get, set: getter와 setter로 작용: value는 set에 입력되는 변수

class MyNum

{

public int num;

public int Num { get { return num + 10; } set { num = value + 30; } }

public MyNum() { }

}

= Indexer

class MyIndex

{

public int[] nums = new int[3];

public MyIndex()

{ for (int i = 0; i < nums.Length; i++) nums[i] = i * 10; }

public int this[int index]

{

get { return nums[index]; }

set { nums[index] = value; }

}

}

  • Pointer

= C#은 pointer를 사용할 수 없음; 예외적으로 unsafe keyword를 쓰면 C와 같은 pointer 사용 가능

= 대신 Project Properties에 설정 필요: Build > General > Unsafe code를 선택

int i = 1;

unsafe

{

int* k = &i;

*k = 10;

}

Console.WriteLine($"i:{i}");

  • string

= string은 non-nullable이기 때문에 반드시 초기화를 해야 함: string name = "";

= 초기화를 null로 하려면: string? name;

-. string?를 쓰지 않으려면 생성자에서 string을 초기화해야 함

  • 예외 처리

= try-catch-finally 사용

-. finally 구문: 예외 여부에 관계없이 항상 실행되는 code

-. catch 구문: 예외를 처리; 주로 throw new Exception()으로 처리

try

{

int n;

Console.Write("number: ");

if (!int.TryParse(Console.ReadLine(), out n)) throw new Exception("error!");

Console.WriteLine(n);

}

catch (Exception ex)

{ Console.WriteLine(ex); }

  • 단일체(singleton)

= 아래 code에서 static constructor가 호출된 후, private constructor가 호출됨

class App

{

public string Name { get; set; }

public static App Singleton { get; private set; }

static App()

{

Console.WriteLine("static constructor.");

Singleton = new();

}

private App()

{

Console.WriteLine("private constructor.");

Name = "";

}

}

public class MyProgram

{

static void Main()

{

App app = App.Singleton;

app.Name = "Cho";

Console.WriteLine(app.Name);

}

}

  • 대리자(delegate)

= 대리자는 함수를 인수로 전달할 때 사용

delegate int DelegateSum(int a, int b);

static int Sum(int a, int b)

{ return a + b; }

public static void Main()

{

DelegateSum ds = Sum;

Console.WriteLine($"method = {ds(4, 5)}");

Console.WriteLine($"invoke method = {ds.Invoke(4, 5)}");

}

  • class의 접근 한정자(access modifier)

[Collection]

  • 객체를 융통성 있게 모으는 방식이 collection; 다양한 자료 구조를 collection으로 만들 수 있음

= 사용하려면: using System.Collections

  • C#에서 제공되는 collection은 IEnumerable, IList, IDictionary, ICollection 등임

= 상속 관계: Object > IEnumerable > ICollection > IList, IDictionary

  • IEnumerable

using System.Collections;

public class MyProgram

{

class Member

{

public int Id { get; private set; }

public string? Name { get; private set; }

public Member(int id, string name)

{ Id = id; Name = name; }

public override string ToString()

{ return $"id: {Id}, name: {Name}"; }

}

class MemberSet : IEnumerable

{

ArrayList arMember = new();

IEnumerator IEnumerable.GetEnumerator()

{ return arMember.GetEnumerator(); }

public void Add(Member member)

{ arMember.Add(member); }

public void Print()

{ foreach (Member member in arMember) Console.WriteLine(member); }

}

public static void Main()

{

MemberSet memSet = new();

memSet.Add(new Member(10, "Cho"));

memSet.Add(new Member(20, "Kim"));

memSet.Print();

}

}

  • ICollection

public static void Print(ICollection col)

{

Console.WriteLine($"count: {col.Count}");

foreach (object obj in col) Console.WriteLine(obj);

}

  • IComparable

= 정렬을 위해 사용하는 interface

class Member : IComparable

{

...

public int CompareTo(object? obj)

{

Member? member = obj as Member;

if (member == null) throw new ArgumentNullException("Wrong member");

else return Name.CompareTo(member.Name);

}

}

  • Collection 정리

[I/O]

  • Console에서 숫자 입력

Console.WriteLine("입력 숫자?");

int num;

if (int.TryParse(Console.ReadLine(), out num)) Console.WriteLine(num);

else Console.WriteLine("error");

  • File에서 binary read/write

Member member = new(20, "Cho"); // Member는 class로 정의됨

FileStream fs = new("mem.txt", FileMode.Create);

BinaryWriter bw = new BinaryWriter(fs);

bw.Write(member.Id);

bw.Write(member.Name);

fs.Close();


FileStream fs2 = new("mem.txt", FileMode.Open);

BinaryReader br = new BinaryReader(fs2);

int id = br.ReadInt32();

string name = br.ReadString();

fs2.Close();

Member member2 = new(id, name);

Console.WriteLine(member2);

  • XML로 serialize/deserialize

public class Member

{

public int Id { get; set; }

public string Name { get; set; }

public Member() { Name = ""; }

public Member(int id, string name)

{ Id = id; Name = name; }

public override string ToString() { return $"id: {Id}, name: {Name}"; }

}

...

Member member = new(20, "Cho");

Member? member2;

XmlSerializer xs = new(typeof(Member));

using (StreamWriter sw = new("mem.xml")) { xs.Serialize(sw, member); } // sw는 자동 해제

using (StreamReader sr = new("mem.xml")) { member2 = xs.Deserialize(sr) as Member; } // sr도 자동 해제

Console.WriteLine(member2);

[Event]

  • delegate인 AddMemberEvent를 event handler로 지정해서, 회원이 등록될 때 event가 발생하게 함

= Event handler의 입력 변수는 EventArgs를 상속받은 class로 사용

= Event를 더할 때는 += 연산자를 쓰면 됨

delegate void AddMemberEvent(object obj, AddMemberEventArgs arg);

class Member

{

public int Id { get; private set; }

public string Name { get; private set; }

public Member(int id, string name)

{ Id = id; Name = name; }

public override string ToString()

{ return $"id: {Id}, name: {Name}"; }

}

class AddMemberEventArgs : EventArgs

{

public Member Member { get; private set; }

public AddMemberEventArgs(Member member)

{ Member = member; }

}

class MemberMan // Member manager

{

public static MemberMan Singleton { get; private set; }

public event AddMemberEvent AddMemberEventHnl = null;

Dictionary<string, Member> members = new();

static MemberMan()

{ Singleton = new(); }

private MemberMan()

{ }

protected void OnAddMember(Member member)

{

members[member.Name] = member;

if (AddMemberEventHnl != null)

AddMemberEventHnl(this, new AddMemberEventArgs(member));

}

public bool AddMember(string name, int id)

{

if (members.ContainsKey(name)) return false;

else { OnAddMember(new Member(id, name)); return true; }

}

public void ViewMembers()

{

Console.WriteLine("===");

//foreach (var item in members) Console.WriteLine($"[{item.Key}]: {item.Value}");

foreach (Member member in members.Values) Console.WriteLine(member);

Console.WriteLine("===");

}

}

class MemberAdd

{

public void AddMember()

{

MemberMan mm = MemberMan.Singleton;

Console.Write("name: ");

string name = Console.ReadLine();

Console.Write("id: ");

int id = int.Parse(Console.ReadLine());

if (mm.AddMember(name, id) == false)

{ Console.WriteLine("already member!"); }

}

}

class MemberView

{

public MemberView()

{

MemberMan mm = MemberMan.Singleton;

mm.AddMemberEventHnl += new AddMemberEvent(AddMemberEvent);

}

public void AddMemberEvent(object obj, AddMemberEventArgs arg)

{

Console.WriteLine($"New member is added: id = {arg.Member.Id}, name = {arg.Member.Name}");

MemberMan mm = MemberMan.Singleton;

mm.ViewMembers();

}

}

class App

{

public static App Singleton { get; private set; }

MemberAdd? ma = null;

MemberView? mv = null;

static App()

{ Singleton = new(); }

private App()

{ ma = new(); mv = new(); }

public void Run()

{

bool result = false;

do

{

Console.WriteLine("[1] add member, [others] exit");

result = Console.ReadKey().Key == ConsoleKey.D1;

if (result) { Console.WriteLine(); ma.AddMember(); }

} while (result);

}

}

public class MyProgram

{

static void Main()

{ App app = App.Singleton; app.Run(); }

}