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(); }
}