https://www.csharpstudy.com/CSharp/CSharp-Intro.aspx
1. C# Indexer
- Indexer
클래스 객체의 데이터를 배열 형태(인덱스로 엑세스 가능)로 만든 것이다.
즉 클래스 객체는 배열이 아니지만, 배열처럼 [] 연산자로 접근이 가능하다.
Sample sample = new Sample();
sample[0] = "First";
- 정의
인덱서는 this[]를 써서 클래스의 프로퍼티처럼 get,set을 정의한다.
class Sample
{
private const int MAX = 100;
private string name;
// 정수 배열 데이터
private int[] data = new int[MAX];
// 인덱서 정의, int 타입 사용
public int this[int index]
{
get
{
if (index < 0 || index >= MAX)
{
throw new IndexOutOfRangeException();
}
else
{
return data[index];
}
}
set
{
if(index >= 0 && index < MAX)
{
data[index] = value;
}
}
}
};
class Program
{
static void Main(string[] args)
{
Sample sample = new Sample();
// setter
sample[1] = 1024;
// getter
int i = sample[1];
}
}
2. 접근 제한자
- 접근 제한자
접근 제한자는 외부로부터 접글을 제한할 때 사용한다
접근 제한자 | 설명 |
public | 모든 외부에서 이 타입을 엑세스할 수 있다. |
internal | 동일한 Assembly 내에 있는 다른 타입들이 엑세스 할 수 있고, 다른 어셈블리에서는 접근이 불가하다. |
protected | 파생클래스에서 이 클래스 멤버를 엑세스할 수 있다. |
private | 동일 클래스/구조체 내의 멤버만 접근 가능하다. |
- 클래스 : 5가지 접근제한자(public, internal, private, protected, internal protected)가 가능하다. 멤버는 private, namespace안 클래스는 internal이 디폴트이다.
- 구조체 : 상속 X이기 떄문에 3가지 접근 제한자(public : internal, private)
- 인터페이스, 열거형 : 기본적으로 public이며 별도의 접근 제한자를 사용하지 않는다.
- 접근 제한자 사용
클래스 필드는 기본적으로 private을 사용한다.
internal class Sample // 동일 어셈블리 내의 다른 타입만 이 클래스로 접근가능함.
{
private int mId = 0;
public string Name { get; set; }
public void Run(int id) {}
protected void Execute() {} // MyClass와 MyClass의 자식에서 접근 가능
}
3. 클래스 상속
- 파생 클래스(자식)
부모 클래스로 부터 상속받아 만들어진 클래스이다.
// 베이스 클래스
public class Animal
{
public string Name { get; set; }
public int Age { get; set; }
}
// 파생클래스
public class Dog : Animal
{
public void HowOld()
{
// 베이스 클래스의 Age 속성 사용
Console.WriteLine("나이: {0}", this.Age);
}
}
public class Bird : Animal
{
public void Fly()
{
Console.WriteLine("{0}가 날다", this.Name);
}
}
- 추상 클래스
abstract 키워드를 붙여진 클래스르 의미한다. 추상클래스는 객체를 직접 생성(new 연산자)할 수 없다.
멤버에 붙여진 abstract 키워드는 반드시 구현해야하는 의미이다.
public abstract class PureBase // class 앞 abstract는 추상 클래스
{
public abstract int GetFirst(); // 멤버함수의 abstract는 반드시 구현하라는 의미
public abstract int GetNext();
}
public class Derive : PureBase
{
private int num = 1;
public override int GetFirst()
{
return num;
}
public override int GetNext()
{
return ++num;
}
}
- as 연산자와 is 연산자
- as : 객체를 지정된 클래스타입으로 변환하는데 사용한다. (class or null)
- is : is 앞의 객체가 특정 클래스 타입이나, 인터페이스를 갖고있는지 판단한다. (true or false)
class First { }
class Second : First { }
class Program
{
static void Main(string[] args)
{
Second second = new Second();
new Program().Test(second);
}
public void Test(object obj)
{
// as 연산자
First fst = obj as First; // First or null 리턴
// is 연산자
bool ok = obj is First; // true or false 리턴
// 캐스팅
First fst2 = (First)obj; // First or Exception 리턴하니, catch 필요!
}
}
4. 정적 static
- static 메소드
인스턴스 메서드와 달리 객체를 생성하지 않고 호출한다.
public class MyClass
{
...
public static int Run() // 정적 메소드
{
return 1;
}
}
int n = MyClass.Run();
- static 속성과 필드
메소드와 마찬가지로 [클래스 명.속성 명]과 같이 사용한다.
public static int id;
※ static 필드는 프로그램이 종료되기 전까지 4동일한 메모리를 계속해서 사용함
- static 클래스
클래스 멤버 모두 static 멤버로 구성되어있다. 이 또한 마찬가지로 [클래스명.ㅇㅇㅇ] 방식으로 이용한다.
public static class MyUtility
{
private static int ver;
// static 생성자
static MyUtility()
{
ver = 1;
}
public static string Convert(int i)
{
return i.ToString();
}
public static int ConvertBack(string s)
{
return int.Parse(s);
}
}
static void Main(string[] args)
{
string str = MyUtility.Convert(123);
int i = MyUtility.ConvertBack(str);
}
5. 제네릭
- 제네릭
C++의 템플릿과 비슷한 개념으로 클래스, 인터페이스, 메소드에 <T>와 같은 타입 파라미터를 붙여 구현한다.
런타임에 제네릭 타입으로부터 지정된 타입의 객체를 생성한다.
class MyStack<T> // <T> 만 있으면 된다.
{
T[] mElements
int pos = 0;
public MyStack()
{
mElements = new T[100];
}
public void Push(T t)
{
mElements[pos++] = t;
}
public T Pop()
{
return mElements[pos--];
}
}
// 타입만 명시하면 된다!
Stack<int> st1 = new Stack<int>();
Stack<double> st2 = new Stack<double>();
- NET 제네릭 클래스들
System.Collections.Generic 네임 스페이스는 모두 제네릭 타입이다.
List<T>, Dictionary<T>, LinkedList<T> 클래스가 있다.
List<string> lt = new List<string>();
lt.Add("홍길동");
lt.Add("이태백");
Dictionary<string, int> dic = new Dictionary<string, int>();
dic["길동"] = 100;
dic["태백"] = 90;
- 제네릭 타입 제약
타입 파라미터로 Value, Reference, 상속, 인터페이스 등을 지정할 수 잇따.
이때 Where T : 제약조건으로 제약을 가한다.
class MyClass<T> where T : struct {} // Value
class MyClass<T> where T : class {} // Reference
class MyClass<T> where T : new() {} // 디폴트 생성자
class MyClass<T> where T : MyBase {} // MyBase의 자식
class MyClass<T> where T : IComparable {} // 인터페이스
// 좀 더 복잡한 제약들
class EmployeeList<T> where T : Employee, IEmployee, IComparable<T>, new()
{
...
}
// 복수 타입 파라미터 제약
class MyClass<T, U> where T : class where U : struct
{
...
}
6. 인터페이스
인터페이스는 클래스와 비슷하지만 직접 구현하지않고, 단지 정의만 한것이다.
즉 추상멤버로만 구상된 추상 클래스와 개념이 유사하다. 그래서 추후 사용할 때 해당 클래스에서 구현해야한다.
public class MyConnection : Component, IDbConnection, IDiposable
{
// IDbConnection을 구현 필수!
// IDisposable을 구현 필수!
}
- 인터페이스의 정의와 구현
public interface IComparable // interface 키워드 사용
{
int CompareTo(object obj); // 접근 제한자 X
}
public class Sample : IComparable
{
private int key;
private int val;
public int CompareTo(object obj) // 인터페이스의 함수 구현
{
Sample sample = (Sample)obj;
return this.key.CompareTo(sample.key);
}
}
- 인터페이스 사용
인터페이스의 장점은 어떤 클래스인지 상관없이, 공통적으로 구현된 멤버 함수를 사용할 수 있다.
public void Run()
{
// 특정 DB Connection(인터페이스를 사용하니깐!)을 신경 쓸 필요가 없다
IDbConnection dbCon = GetDbConnection();
dbCon.Open();
if (dbCon.State == ConnectionState.Open)
{
dbCon.Close();
}
}
public IDbConnection GetDbConnection() // IDbConnection 인터페이스를 리턴
{
IDbConnection dbConn = null;
string cn = ConfigurationManager.AppSettings["Connection"];
switch (ConfigurationManager.AppSettings["DbType"])
{
case "SQLServer":
dbConn = new SqlConnection(cn);
break;
case "Oracle":
dbConn = new OracleConnection(cn);
break;
case "OleDB":
dbConn = new OleDbConnection(cn);
break;
}
return dbConn;
}
7. delegate
- 기초 이해
어떤 메소드를 다른 메소드에 전달할 수 있도록 만들어졌다.
class MyClass {}
void RunB(MyClass c) {}
// 위의 함수를 사용할려면?
MyClass c = new MyClass();
RunB(c);
// 파라미터로 정수, 객체등의 데이터를 메소드에 보내고있다.
// 다만 물리적 데이터가 아닌 메소드만 보낼 수 있을까? => 델리게이트
델리게이트를 정의하는 방법은 delgeate 키워드를 사용한다.
delegate int MyDelegeate(string s);
// 델리게이트 객체를 메소드 호출 파라미터에 넣어준다.
void Run(MyDelegate method) {}
MyDelegate d = new MyDelegate(메소드1); // 델리게이트도 일종의 클래스이므로 같은 방법으로 생성한다.
Run(d);
※ 입력 파라미터와 리턴타입이 델리게이트에서 중요하다!
※ 델리게이트는 .NET Delegate.MulticastDelegate 클래스를 사용하고, 이를 따로 파생할 순 없다.
델리게이트로 부터 실제 메소드를 호출하는 방법은?
i = m.Invoke("123"); // Invoke(), BeginInvoke() 메소드를 사용한다.
i = m("123"); // Invoke를 생략할 수 있다.
- 델리게이트 샘플
class Program
{
delegate int MyDelegate(string s);
static void Main(string[] args)
{
new Program().Test();
}
void Test()
{
MyDelegate d = new MyDelegate(StringToInit); // 델리게이트 객체 생성
Run(d); // 메소드 전달
}
int StringToInit(string s) // 델리게이트 대상이되는 메소드
{
return int.Parse(s);
}
void Run(MyDelegate d) // 델리게이트를 전달받는 메소드
{
int i = d("123");
Console.Write(i);
}
}
- 델리게이트 개념
C++의 함수 포인터와 비슷한 개념으로 메소드 파라미터와 리턴 타입에 대한 정의를 한다.
이후 동일한 파라미터와 리턴 타입을 가진 메소드를 서로 호환해서 사용한다.
class MyClass
{
private delegate void RunDelegate(int i);
private void RunThis(int val) // 1024
{
Console.WriteLine("{0}", val);
}
private void RunThat(int val) // 0x400
{
Console.WriteLine("0x{0:X}", value);
}
public void Perform()
{
RunDelegate run = new RunDelegate(RunThis);
run(1024); // run = RunThis와 같다.
//run = new RunDelegate(RunThat);
run = RunThat;
run(1024);
}
}
class Program
{
static void Main(string[] args)
{
MyClass mc = new MyClass();
mc.Perform();
}
}
※ 둘다 파라미터로 int 타입, 리턴은 void라 사용할 수 있다.
- 델리게이트로 메소드 파라미터로 전달하기
class MySort
{
public delegate int CompareDelegate(int i1, int i2); // int Delegate(int, int)
public static void Sort(int[] arr, CompareDelegate comp)
{
if (arr.Length < 2)
return;
Console.WriteLine("함수 Prototype: " + comp.Method); // 델리게이트.Method: 프로토 타입
int ret;
for (int i = 0; i < arr.Length - 1; i++)
{
for (int j = i + 1; j < arr.Length; j++)
{
ret = comp(arr[i], arr[j]);
if (ret == -1)
{
int tmp = arr[j];
arr[j] = arr[i];
arr[i] = tmp;
}
}
}
Display(arr);
}
static void Display(int[] arr)
{
foreach (var i in arr) // auto와 같음
Console.Write(i + " ");
Console.WriteLine();
}
}
class Program
{
static void Main(string[] args)
{
(new Program()).Run(); // static 메소드가 아니지만 델리게이트기 때문에 가능함
}
void Run()
{
int[] a = { 5, 53, 3, 7, 1 };
// 올림차순으로 소트
MySort.CompareDelegate compDelegate = AscendingCompare;
MySort.Sort(a, compDelegate);
// 내림차순으로 소트
compDelegate = DescendingCompare;
MySort.Sort(a, compDelegate);
}
int AscendingCompare(int i1, int i2) // int Delegate(int, int)
{
if (i1 == i2)
return 0;
return (i2 - i1) > 0 ? 1 : -1;
}
int DescendingCompare(int i1, int i2) // int Delegate(int, int)
{
if (i1 == i2)
return 0;
return (i1 - i2) > 0 ? 1 : -1;
}
}
- 델리게이트 필드와 속성
델리게이트는 클래스의 필드나 속성으로 사용가능하다.
class MyArea : Form
{
public MyArea()
{
this.MouseClick += delegate { MyAreaClicked(); };
}
public delegate void ClickDelegate(object sender);
public ClickDelegate MyClick; // 델리게이트 필드
//public ClickDelegate Click { get; set; } // 속성으로도 가능함.
void MyAreaClicked() // MyArea 클릭시 아래 함수가 자동으로 호출된다.
{
if (MyClick != null)
{
MyClick(this);
}
}
}
class Program
{
static MyArea area;
static void Main(string[] args)
{
area = new MyArea();
area.MyClick = Area_Click;
area.ShowDialog();
}
static void Area_Click(object sender)
{
area.Text = "MyArea 클릭!";
}
}
- 멀티캐스트 델리게이트
여러개의 메소드를 할당할 수 있는 델리게이트이며, += 연산자를 통해 추가한다.
class Program
{
static MyArea area;
static void Main(string[] args)
{
area = new MyArea();
// 복수 개의 메서드를 delegate에 할당
area.MyClick += Area_Click;
area.MyClick += AfterClick;
area.ShowDialog();
}
static void Area_Click(object sender)
{
area.Text += " MyArea 클릭! ";
}
static void AfterClick(object sender)
{
area.Text += " AfterClick 클릭! ";
}
}
- 이벤트
델리게이틔 단점을 해소하기 위해 특수한 형태로 제작했다.
- =(할당) 연산자로 기존 리스트가 사라짐
- 클래스 외부에서도 호출할 수 있다.
area = new MyArea();
area.MyClick += Area_Click;
area.MyClick += AfterClick;
area.MyClick = Area_Click; // 기존 리스트 삭제, 덮어쓰기 중
area.MyClick(null); // 델리게이트를 Area 클래스가 아닌 외부 클래스에서 호출한다
- 이벤트의 특성
- = 연산자 사용 불가, +=(이벤트 핸들러 추가)와 -=(이벤트 핸들러 삭제) 가능
- 외부 클래스에서 이벤트를 호출할 수 없다.
class MyArea : Form
{
public MyArea()
{
this.MouseClick += delegate { MyAreaClicked(); };
}
public delegate void ClickEvent(object sender);
public event ClickEvent MyClick; // 이벤트 필드
...
}
class Program
{
static MyArea area;
static void Main(string[] args)
{
area = new MyArea();
// 이벤트 핸들러 추가
area.MyClick += Area_Click;
area.MyClick += AfterClick;
// 이벤트 핸들러 삭제
area.MyClick -= Area_Click;
// Error: 이벤트 직접호출 불가
//area.MyClick(this);
area.ShowDialog();
}
...
}
- 델리게이트 vs 함수포인터
델리게이트 | 함수포인터 |
객체의 주소(객체 레퍼런스)와 메소드 주소 | 함수에 대한 주소 값 |
프로토타입이 같으면 모든 클래스의 메소드 할당 가능 | 특정 1개의 클래스 지정 |
멀티 캐스트(1개 이상의 메소드 레퍼런스 보유) | 하나의 함수 포인터 보유 |
Type Safety 보장 | Type Safety 보장 X |
// C 함수 포인터 예제
void myfunc(int x)
{
printf( "%d\n", x );
}
void main()
{
// 함수포인터 정의 및 초기화
void (*f)(int);
f = &myfunc;
f(2);
}
// C++ Pointer to member 예제
class Cls
{
public:
void myfunc(string str)
{
cout << str << endl;
}
};
void main()
{
// Pointer to member function 정의
void (Cls::*fp)(string);
fp = &Cls::myfunc;
// Cls 객체 생성 및 객체 포인터 지정
Cls obj;
Cls* pObj = &obj;
// Cls 객체에서 함수포인터 사용
(pObj->*fp)("hello");
}
8. 무명 메소드
- 무명 메소드
메소드명이 없는(미리 정의 X) 메소드이다.
// 무명 메소드 delegate(파라미터) { 실행문장; };
delegate(int p1) { Console.Write(p1); };
- 무명 메소드 사용
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.button1.Click += new System.EventHandler(this.button1_Click); // 이미 정의됨.
this.button2.Click += delegate(object s, EventArgs e) // 무명 메소드 지정
{
MessageBox.Show("버튼2 클릭");
};
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show("버튼1 클릭");
}
}
- 델리게이트 vs 무명 메소드
delegate 키워드는 델리게이트와 무명 메소드 둘다 사용된다.
// Delegate
public delegate int SumDelegate(int a, int b); // delegate 타입을 가리킨다.
SumDelegate sumDel = new SumDelegate(mySum); // delegate 객체를 생성하고 할당한다.
// 무명 메소드 (이벤트 핸들러 이용)
button1.Click += new EventHandler(delegate(object s, EventArgs a) { MessageBox.Show("OK"); });
button1.Click += (EventHandler) delegate(object s, EventArgs a) { MessageBox.Show("OK"); };
// 무명 메소드 (이미 어떤 델리게이트로 사용되는지 안다면 핸들러 생략 가능)
button1.Click += delegate(object s, EventArgs a) { MessageBox.Show("OK"); };
button1.Click += delegate { MessageBox.Show("OK"); };
델리게이트 타입을 사용하는 곳에, 무명 메소드를 사용하면 컴파일 에러가 발생한다.
Invoke()는 델리게이트의 파라미터가 몇 개인지, 리턴 값이 무엇인지를 모른다. 즉 무명 메소드가 불가능하다.
// 컴파일 에러 발생
this.Invoke(delegate {button1.Text = s;} );
// 파라미터와 리턴 값을 명시해야함.
MethodInvoker mi = new MethodInvoker(delegate() { button1.Text = s; });
this.Invoke(mi);
// 축약된 표현
this.Invoke((MethodInvoker) delegate { button1.Text = s; });
/*
MethodInvoker는 입력 파라미터가 없고, 리턴 타입이 void인 델리게이트이다.
MethodInvoker는 System.Windows.Forms 에 다음과 같이 정의되어 있다.
public delegate void MethodInvoker();
*/
9. 람다식
람다식 문법 : (입력 파라미터) => { 실행할 블럭 }; 으로 표현한다.
// 기본적인 람다식 표현
Action<string> print = str => Console.WriteLine(str);
print("Hello"); // Hello 출력
// 입력 파라미터가 없다
() => Write("NO");
// 입력 파라미터가 있다. (n1, n2)로 해도 된다!
Action<int, int> sum = (int n1, int n2) => Console.WriteLine(n1 + n2);
sum(1, 2);
람다식을 통해 delegate와 무명 메소드를 더 간략히 사용할 수 있다.
// 일반적인 델리게이트
this.button1.Click += button1_Click; // new System.EventHandler(button1_Click); 표현이 같다.
private void button1_Click(object sender, EventArgs e)
{
((Button)sender).BackColor = Color.Red;
}
// 무명 메소드
this.button1.Click += delegate(object sender, EventArgs e)
{
((Button)sender).BackColor = Color.Red;
};
// 람다식
this.button1.Click += (sender, e) => ((Button)sender).BackColor = Color.Red; // 실행 문장이 1줄이면 생략 가능
10. 익명 타입
C# 3.0 부터 클래스를 정의하지 않아도 되는 익명 타입이 생겼다.
- 익명 타입은 읽기 전용이므로 프로퍼티를 갱신할 수 없다.
- 키워드 var를 사용
// 익명타입 : new { 프로퍼티1 = 값1, 프로퍼티2 = 값2 };
var t = new { Name = "홍길동", Age = 20 };
string s = t.Name;
배열로도 생성하고 사용할 수 있따.
private void RunTest()
{
var v = new[]
{
new { Name="Lee", Age=33, Phone="02-111-1111" },
new { Name="Kim", Age=25, Phone="02-222-2222" },
new { Name="Park", Age=37, Phone="02-333-3333" },
};
// LINQ Select를 이용해 Name과 Age만 갖는 새 익명타입 객체들을 리턴.
var list = v.Where(p => p.Age >= 30).Select(p => new { p.Name, p.Age });
foreach (var a in list)
{
Debug.WriteLine(a.Name + a.Age);
}
}
11. 확장 메소드 1-2
12. partial
13. dynamic
14. await
댓글