Public > Internal > Protected > Private
public으로 선언된다면 어느 곳에서든 자유롭게 사용될 수 있다. 아무런 제한을 받지 않고 원하는 곳에서 사용 가능하다.
private로 선언되면 약간의 제한을 갖게 된다.
예를 들어 어떤 클래스에 변수나 메서드가 private로 선언된 경우 해당 클래스에서만 접근이 가능하다.
internal로 선언된다면 해당 프로젝트에서 Public처럼 사용 가능하다.
자신의 프로젝트에서 내부적으로 사용된다는 뜻이다. 이는 외부에서 사용될 가능성이 있는 프로젝트에서 유용하게 사용된다. 접근제한자를 선언하지 않았다면, 기본값은 internal이다.
protected로 선언된다면 상속받은 자식의 클래스에서만 사용 가능한 제한을 갖는다.
System 하위에 있음
byte, short, int, long 등이 있으며, 정수 값을 저장한다.
byte만이 signed 타입을 가지고 다른 정수형 타입들은 unsigned 타입을 가진다.
s와 u로 구분한다.
정수를 저장한다. 1바이트(8비트)의 크기를 가지며 기본값은 0이다.
값의 범위 - 0~255
부호가 있는 정수이다. 1바이트의 크기를 가지며 기본값은 0이다.
값의 범위 - -128~127
정수를 저장한다. 2바이트의 크기를 가지며 기본값은 0이다.
값의 범위 - -32,768 ~ 32,767
부호가 없는 정수를 저장한다. 2바이트의 크기를 가지며 기본값은 0이다.
값의 범위 - 0 ~ 65,535
정수를 저장한다. 4바이트의 크기를 가지며 기본값은 0이다.
값의 범위 - -2,147,483,648 ~ 2,147,483,647
부호가 없는 정수를 저장한다. 4바이트의 크기를 가지며 기본값은 0이다.
값의 범위 - 0 ~ 4,294,967,295
정수를 저장한다. 8바이트의 크기를 가지며 기본값은 0L이다.
부호가 없는 정수를 가진다. 8바이트의 크기를 가지며 기본값은 0L이다.
float, double, decimal이 있으며, 실수 값을 저장한다.
실수를 저장한다. 4바이트의 크기를 가지며 기본값은 0.0F이다.
실수를 저장한다. 8바이트의 크기를 가지며 기본값은 0.0D이다.
실수를 저장한다. 16바이트의 크기를 가지며 기본값은 0.0M이다.
char, string이 있으며, 문자 또는 문자열을 저장한다.
char은 단일 문자를 저장한다. (진짜로 문자 하나만 가능)
char은 string과 다르게 '값' 으로 ''를 사용한다.
string은 문자열을 저장한다.
char과는 다르게 "값"으로 " "를 사용한다.
bool이 있다.
참(true)와 거짓(false) 값만을 저장 가능하다.
System 하위에 있음
ArrayList는 배열(Array)과 매우 유사하다.
배열(Array)처럼 저장위치(Index)로 값에 접근이 가능하다.
ArrayList는 Array와 다르게 생성시 크기를 지정해주지 않는다.
생성 후 추가 또는 삭제하는 값에 따라 크기가 자동으로 변화한다.
using System.Collections;
public void ArrayList_Test(){
ArrayList arr = new ArrayList();
arr.Add(1);
arr.Add("String");
arr.Add(true);
arr.Add(new Person(1,2));
// 원하는 인덱스에 값을 넣는다.
arr.Insert(0, 3);
// 특정 요소를 리스트에서 제거함
arr.Remove(1);
// 삭제하고자 하는 값의 인덱스를 지정하여 값을 삭제
// 인덱스를 벗어날 경우 Exception
arr.RemoveAt(arr.Count - 1);
// ArrayList 내부 데이터 전체 삭제
arr.Clear();
// Array 내부에 지정된 값이 있는지를 확인한다.
// 반환타입은 bool
bool result = arr.Contains(1);
arr.Add(1);
arr.Add("String");
arr.Add(true);
arr.Add(new Person(1,2));
// ArrayList의 크기
int size = arr.Count;
// ArrayList에 특정 값이 몇번 인덱스에 있는지 확인
// 값이 존재하지 않다면 -1을 반환함
int idx = arr.IndexOf(1);
var value = arr[0];
// 특정 인덱스의 값 수정
arr[0] = 10;
// 모든 타입에 대한 값을 가질 수 있기 때문에 var 타입으로 값을 받아야된다.
foreach(var item in arr){
Console.WriteLine(item);
}
}
HashTable은 다른 컬렉션들과 다르게 키와 값으로 구성되어 있다.
ArrayList와 다르게 저장위치(Index)로 저장하는 것이 아닌 키로 값을 찾는다.
중복되는 키가 존재할 수 없다.
using System.Collections;
public void HashTable_Test(){
Hashtable ht = new Hashtable();
// Key, Value
ht.Add(6, 6);
ht.Add(6.6M, 6.6M);
ht.Add(true, false);
// 명시적으로 형변환 해줘야됨
// [] 안에 들어가는 값은 키값임
int value01 = (int)ht[6];
decimal value02 = (decimal)ht[6.6M];
// 해당 키가 없을 경우 예외발생
//bool value03 = (bool)ht[false];
// 잘못된 형변환으로 예외발생
//bool value04 = (bool)ht[6];
//var타입으로 데이터를 받으면 됨
var vluae05 = ht[6];
// 키를 이용하여 해당 데이터 삭제
ht.Remove(6);
// 전체 데이터 삭제
ht.Clear();
int size = ht.Count;
// 특정 키를 가진 데이터가 있는지 확인
bool findKey = ht.ContainsKey(6);
// 특정 값을 가진 데이터가 있는지 확인
bool findValue = ht.ContainsKey(6);
}
Queue도 마찬가지로 모든 타입의 변수를 담을 수 있다.
넣은 순서 그대로 데이터를 꺼내온다.
using System.Collections;
public void Queue_Test(){
Queue que = new Queue();
que.Enqueue(1);
que.Enqueue("2");
que.Enqueue(true);
// Queue에서 데이터 하나를 받아오고 Queue에서 삭제한다.
// 가장 먼저 집어넣었던 데이터 1이 data에 들어감
var data = que.Dequeue();
Console.WriteLine(data);
// Queue에 들어있는 데이터의 갯수
int size = que.Count;
// 특정 값이 Queue에 존재하는지 확인
bool isContain = que.Contains("1");
// 가장 앞에 있는 데이터인 "2"가 TopData에 들어감
var TopData = que.Peek();
Console.WriteLine(TopData);
que.Clear();
}
Stack은 Queue와는 다르게 가장 먼저 들어간 데이터가 가장 마지막에 나온다.
Stack또한 모든 타입의 변수를 담을 수 있다.
using System.Collections;
public void Stack_Test(){
Stack s = new Stack();
s.Push(1);
s.Push("2");
// 가장 위에 있는 "2"를 반환하고 Stack에서 제거한다.
var data = s.Pop();
// Stack에 들어있는 데이터의 갯수
int size = s.Count;
// 특정 값에 Stack에 들어있는지 확인
bool isContain = s.Contains(4);
s.Clear();
}
추상 클래스를 만들고 상속받는 클래스에서 필수적으로 구현해야됨
추상 메서드를 정의한 경우에는 override 키워드와 함께 해당 메서드와 같은 이름의 메서드를 하나 만들어 구현한다.
추상 클래스는 지역변수와 몸체를 가진 메서드를 가질 수 있다.
public abstract class AbstractA{
public int a;
public abstract void AbstractMethod();
public void test(){}
}
public class AbstractB : AbstractA {
public override void AbstractMethod(){
Console.WriteLine("구현!");
}
}
as 연산자와 is 연산자는 객체를 캐스팅 할 때 사용된다.
as 연산자는 캐스팅에 성공하면 캐스팅 결과를 리턴하고 캐스팅에 실패하면 null값을 리턴한다.
캐스팅은 자식 클래스를 부모 클래스로 변경하는 것이다.
부모는 자식을 품을 수 있다~
class Parent{}
class Child : Parent {
public void talk(){
Console.WriteLine("아아");
}
}
public class Keyword{
public void ASNull(){
Parent p = new Parent();
Child c = new Child();
c = p as Child;
// 실행 안됨 false
if(c != null){
c.talk();
}
}
public void ASNotNull(){
Parent p = new Parent();
Child c = new Child();
p = c as Parent;
if(p != null){
Console.WriteLine("Casting Success");
}
}
}
is 키워드는 as와 다르게 캐스팅 결과를 반환하지 않고 캐스팅이 가능하다면 true, 불가능하다면 false를 리턴한다.
public class IS {
public void IS_Test(){
Parent p = new Parent();
Child c = new Child();
if(p is Child){
Console.WriteLine("P를 C로 형변환 가능? X");
} else if (c is Parent){
Console.WriteLine("c를 Parent로 형변환 성공");
}
}
}
자바로 따지면 super 키워드와 같음
public class Parent_Class{
public void baseMethod(){
Console.WriteLine("Parent");
}
}
public class Base_Test : Parent_Class{
public void baseMethod(){
base.baseMethod();
Console.WriteLine("Child");
}
}
// Output
Parent
Child
연산이나 변환에 의해 값이 오버플로, 언더플로 검사를 컴파일러 단에서 걸러주도록 하는 키워드이다.
연산이나 변환에 의해 값이 오버플로, 언더플로가 생긴다 하더라도 컴파일러에게 "내가 의도한 것이니 무시해라" 라고 하는 키워드이다.
여기에서 말하는 오버플로는 데이터 타입이 표현할 수 있는 최대값을 넘어버려서 최소값이 되는 경우를 말한다.
언더플로는 표현할 수 있는 최소값을 넘어버려서 최대값이 되는 경우를 말한다.
Byte의 경우 0~255까지의 값을 표현할 수 있는데 오버플로가 생기면 값이 0이 되고 언더플로가 되면 값이 255가 된다.
public class CheckedAndUnCheckedTest{
public void Checked_Test(){
byte overflowByte = 255;
byte underflowByte = 0;
// 예외발생
checked {
overflowByte++;
underflowByte--;
}
Console.WriteLine(overflowByte);
Console.WriteLine(underflowByte);
}
public void UnChecked_Test(){
byte overflowByte = 255;
byte underflowByte = 0;
// 예외발생 X
// overflowByte에는 0이 들어가고
// underflowByte에는 255값이 들어감
unchecked {
overflowByte++;
underflowByte--;
}
Console.WriteLine(overflowByte);
Console.WriteLine(underflowByte);
}
}
delegate란 대리자를 의미한다.
delegate는 한번의 이벤트에 여러 메서드를 동시에 호출할 수 있다.
delegate는 메서드 자체를 인자로 넘겨주는 형식이다. (함수가 아닌 형식이다.)
delegate를 사용하는 이유는 코드의 유연성과 재사용성을 높이고, 관점지향적으로 개발을 하기 위해서이다.
나중에 추가 예정
나중에 추가 예정
나중에 추가 예정
나중에 추가 예정
나중에 추가 예정
goto 구문은 코드의 흐름을 특정 레이블로 이동하는 기능을 한다.
C#에서 레이블은 콜론(:) 기호를 레이블 이름 뒤에 붙여 만든다. 이렇게 만든 레이블 코드는 평상시에는 주석처럼 아무 의미 없는 코드로 사용하지만, goto 구문 뒤에 레이블을 지정하면 해당 레이블로 이동하는 기능을 한다.
Label은 함수가 아닌 코드에 로직에 포함되는 것이다.
그렇기 때문에 아래 코드에서 chapter에 2를 넣게 되면 2, 3, END가 출력된다.
public class GoTOTest{
public void Test(){
Console.WriteLine("Test");
Console.WriteLine("0, 1, 2 중 하나 입력 : _\b");
int chapter = Convert.ToInt32(Console.ReadLine());
switch (chapter){
case 1:
goto Label1;
break;
case 2:
goto Label2;
break;
case 3 :
goto Label3;
break;
default :
goto END;
break;
}
Label1 :
Console.WriteLine("1");
Label2 :
Console.WriteLine("2");
Label3 :
Console.WriteLine("3");
END:
Console.WriteLine("END");
}
}
매개변수 한정자 - void Method(int a)가 있을 때 매개변수 앞에 붙이는 키워드들을 매개변수 한정자라고 부른다. void Method(한정자 int a) 이런 식으로 데이터 타입 앞쪽에 위치하게 된다.
in 키워드는 매개변수 한정자에 해당한다.
in 키워드를 사용하면 해당 매개변수가 값이 아닌 참조로 전달이 된다. 또한, 해당 매개변수는 값을 수정할 수 없게 된다.
매개변수가 call by value로 넘어가게 되면 값이 복사되어 메모리에 위치하게 되는데 이렇게 되면 복사 비용이 들게 된다.
이러한 비용을 줄이기 위해서 in을 사용하게 된다.
public class InTest{
public void test(in int a){
// 에러
// a = 20;
Console.WriteLine(a);
}
}
out은 in과 다르게 처음에 매개변수를 전달할 때에 초기화가 되어있지 않아도 되며, 메서드 내부에서 무조건 값을 1회 이상 수정해줘야된다.
public class OutTest{
public void test(out int a){
a = 20;
Console.WriteLine(a);
}
}
ref를 사용하면 매개변수를 Call By Value가 아닌 Call By Reference로 전달하게 되어 메서드 내부에서 값을 변경하게 되면 이 메서드를 호출한 메서드에서 가지고있는 값에도 영향이 간다.
public class RefTest(){
public void test(){
int a = 10;
RefParamTest(ref a);
Console.WriteLine(a);
}
public void RefParamTest(ref int a){
a++;
}
}
우선 Lock을 걸지않고 코드를 비동기로 실행하는 코드를 보자
public class ThreadTest{
public void test(){
Data data = new Data();
Thread t1 = new Thread(MyFunc);
Thread t2 = new Thread(MyFunc);
t1.Start(data);
t2.Start(data);
t1.Join();
t2.Join();
}
private static void MyFunc(object o){
Data data = o as Data;
if(data != null){
for(int i = 0; i < 10; i++){
data.Increase();
Console.WriteLine(data.num);
}
}
}
}
public class Data{
public int num = 0;
public void Increase(){
this.num++;
Thread.Sleep(5);
}
}
이렇게 실행하게 되면 1에서 20까지 순차적으로 출력하지 않는다.
public class Data{
public int num = 0;
private readonly object obj = new object();
public void Increase(){
lock (obj) {
this.num++;
Thread.Sleep(5);
}
}
}
이렇게 특정 객체를 하나 만들고 여기에 락을 걸어서 순차적으로 Data객체의 num에 접근할 수 있도록 설정한다.
결과는 1~20까지 순차적으로 출력된다.
partial 키워드를 사용하여 partial class를 정의할 수 있다.
partial class는 클래스를 정의할 때 한군데가 아닌, 복수의 장소에서 class를 정의할 수 있도록 지원하고 있다.
public partial class PartialClass01 {
public int a;
public partial void test();
}
public partial class PartialClass01 {
// 하나로 합쳐질 것이기 때문에 같은 변수를 또 선언하는 것은 안됨
// public int a;
public int b;
public partial void test(){
Console.WriteLine();
}
}
partial을 사용하면 같은 이름의 클래스를 여러개 만들 수 있다. 이렇게 하면 다수의 개발자가 한 클래스를 만들고 단위 기능으로 적당히 역할분담을 할 수 있다.
partial class들은 컴파일러가 컴파일 시점에 하나의 클래스로 합쳐준다.
하지만 partial Method는 조금 다르다.
다수의 개발자가 한 클래스를 개발할 때, partial class의 한 부분에서는 함수선언만 하고, 다른 부분에서 함수 정의를 할 수 있게된다. 하나로 합쳐지는 것이 아니다.
나중에 추가 예정
public class Parent {
public void test(){
}
}
public class Child : Parent {
public new void test() {
}
}
부모 클래스의 메서드를 그대로 두고, 자식 클래스에서 완전히 다른 동작을 정의할 때 사용한다.
public class Parent {
public virtual void test(){
Console.WriteLine("asd");
}
}
public class Child : Parent {
public override void test() {
Console.WriteLine("www");
}
}
부모 클래스의 메서드를 재정의하여 동작을 변경하고, 다형성을 활용해야 할 때 사용한다.
Const와 같이 값을 변경하지 못하도록 설정하는 키워드이다.
다만 const는 변수 선언 시점에 값이 들어가야 된다.
public const int MaxValue = 100;
또한 const는 암묵적으로 static이다.
하지만 readOnly는 변수 선언 시점에 값이 들어가지 않아도 된다.
선언 시점 혹은 생성자에서 초기화가 가능하다.
객체지향 프로그래밍 언어에서는 추상클래스나 상속을 이용해 코드를 좀 더 간결하고 재활용 가능하게 사용한다.
하지만, sealed는 상속을 제한하는 클래스이다. 더이상 상속이 불가능 하다는 뜻이다.
sealed는 클래스 뿐만 아니라 속성에도 사용이 가능하다.
public sealed class SealedClassTest{
}
// 상속이 불가능하기 때문에 오류
public class ChildClass : SealedClassTest {
}
아래는 재정의한 메서드를 더이상 재정의 할 수 없게 하는 것이다.
public class SealedClassTest{
public virtual void test(){
}
}
public class ChildClass : SealedClassTest {
public sealed override void test(){
base.test();
}
}
struct는 구조체이다.
구조체 형식은 데이터와 관련 기능을 캡슐화할 수 있는 값 형식이다. struct는 class와 유사하게 변수 선언, 함수 선언 등이 가능하지만 몇가지 차이점이 있다.
struct는 상속이 불가능하며, 값 타입이다.
public struct StructData{
int a;
string str;
public void test(){
Console.WriteLine("ww");
}
}
값 타입이라는 것은 객체를 만들고 매개변수로 넘겨준 이후, 값을 변경해도 특정 메서드를 호출한 메서드에서의 struct 객체에는 값 변경이 반영 되지 않는다.
virtual 키워드를 붙인 메서드는 자식 클래스에서 이 메서드를 재정의 할 수 있도록 허용하겠다는 의미이다.
public class VirtualMethodClass{
public virtual void test(){
}
}
public class ChildOverrideMethodClass : VirtualMethodClass {
public override void test(){
}
}
특정 변수에대한 get, set을 지정하고 내부에 조건을 걸 수 있다.
set에서 받는 값은 value이다.
public class TestClass{
public void test(){
GetterSetterTestClass gs = new GetterSetterTestClass();
gs.a = 10;
Console.WriteLine(gs.a);
}
}
public class GetterSetterTestClass{
private int _a;
public int a{
get {return _a;}
set {_a = value;}
}
}
C#에서는 변수 이름을 지을때 카멜 표기법(camelCase) 또는 파스칼 표기법(PascalCase)을 따른다.
파스칼 표기법을 따른다. record의 프로퍼티는 public이므로 파스칼 케이스로 작성한다.
Interface는 이름 앞에 "I"를 붙이고, enum은 이름 앞에 "E"를 붙인다.
또한, 파스칼 표기법을 따른다.
파스칼 표기법을 따른다.
private 혹은 internal과 같은 액세스 한정자는 카멜 표기법을 따르고, 프로퍼티의 경우 이름 앞에 "_"를 붙인다.
메서드의 매개변수는 카멜 표기법을 따른다.
const를 사용하는 경우 대문자 스네이크 표기법을 따른다.
Ex) SOME_CONSTANT
전처리기는 컴파일 시점에 코드를 변형하거나 컴파일 프로세스 자체를 제어하는 데 사용된다. 스크립트들을 효율적으로 관리하는 데 상당히 좋다.
"#" 키워드로 시작하며, 한 라인에 한 개의 전처리기 명령만을 사용 가능하다.
#region testRegion
public class TestClass {
}
public interface ITestInterface{
}
#endregion
dll이란 동적 링크 라이브러리로, 코드와 데이터의 재사용과 배포를 위해 사용되는 바이너리 파일이다.
메타 데이터란, 해당 dll이 제공하는 클래스, 메서드, 속성, 이벤트 등의 정의와 시그니처를 포함하는 정보이다.
동적 링크는 컴파일 시점에는 라이브러리가 실행 파일의 일부분이 되지 않는다. 즉, 함수가 직접 복사되는건 아니고, 함수의 위치정보만 가지고 그 함수를 호출할 수 있게 된다.
dll의 장점은 필요할 때에만 라이브러리를 호출하기 때문에, 실행 파일에 모든 함수가 복사되는 것이 아니므로, 디스크 용량을 적게 차지하게 된다.
그리고 한 번의 dll 작성으로 여러 프로그램에서 이를 돌려쓸 수 있기 때문에 생산성이 높다.
뷰와 모델을 하나로 묶어 연결하는 것을 데이터 바인딩이라고 부른다.
두개의 데이터 소스를 함께 연결하고 동기화 상태를 유지하는 일반적인 기술이다.