1. 

실행결과

Start TestAsync가 불리고 3초 뒤에 EndTestAsync가 불리고 바로 while start가 불렸다. 만약 TestAsync에서 매우 긴 시간 동안 대기가 걸리면 그 이후 코드들이 실행되지 않게 된다.

2.

실행결과

await를 사용하여 대기하지 않고 바로 다음 코드가 실행되도록 할 수 있다.

 

3. await 활용방법

https://docs.microsoft.com/ko-kr/dotnet/csharp/programming-guide/concepts/async/

 

C#의 비동기 프로그래밍

async, await 및 Task를 사용하여 비동기 프로그래밍을 지원하는 C# 언어에 대해 간략히 설명합니다.

docs.microsoft.com

eggs, eggs, toast는 각각 연관이 없는 동작이다. 그렇기에 await키워드를 통하여 동시에 작업이 가능하도록 할수 있다. 이 사진에 문제점은 3가지 작업 중 toast가 끝난 뒤에 juice작업과 "Breakfast is read!"가 출력되도록 되어있다. toast가 가장 먼저 끝난다는 전제가 있다는 것이다. (우리는 어떤 작업이 먼저 끝날 줄 모른다.)

 

whenAny

3가지 작업을 List로 만들어 while문을 통해 끝난 작업들을 표시해주는 방법이 있다.

반응형

Cpaacity를 먼저 설정했을 때와 그냥 사용했을 때에 차이점을 테스트해봤다.

첫 번째 테스트

리스트에 같은 수의 데이터를 넣고 지우 고를 반복했을 때 걸리는 시간 체크하기. 여기 포인트는 미리 정해놓은 Cpaacity의 크기를 넘지 않을때이다.

 

테스트 코드

	long lPre = System.GC.GetTotalMemory(false);
        int tick1 = Environment.TickCount;
        List<int> list = null;
        if (bCapacity)
            list = new List<int>(10000000);
        else
            list = new List<int>();

        for (int k = 0; k < 10; k++)
        {
            for (int i = 0; i < nCount; i++)
            {
                for (int j = 0; j < nCount; j++)
                {
                    list.Add(i + j);
                }
            }
        }
        int tick2 = Environment.TickCount;
        long lAfter = System.GC.GetTotalMemory(false);

        Debug.LogError(string.Format("Capacity Use : {0} | list Count : {1} / Tick : {2}/ Memory : {3}", 
        			bCapacity, list.Count, tick2 - tick1, lAfter - lPre));

1. Capacity을 사용 안 했을 때.

list Count : 10000000 / Tick : 172/ Memory : 134279168

2. Capacity을 사용했을 때.

 list Count : 10000000 / Tick : 109/ Memory : 40001536


테스트 결과

Capacity를 크기를 미리 설정한 케이스가 시간이나 메모리가 더 적게 발생하는 걸 알 수 있다. 

 


 

두 번째 테스트

재 할당될 때 List의 Capacity변화를 테스트해봤다.

 

테스트 코드

        List<int> list = new List<int>();
        Debug.LogError(list.Capacity);
        list.AddRange(new int[4] { 1, 2, 3, 4 });
        Debug.LogError(list.Capacity);
        list.Add(1);
        Debug.LogError(list.Capacity);

처음에 4개의 데이터를 넣고 그 범위를 초과할 때 Capacity를 로그 찍어봤다.

 

테스트 결과

데이터의 수는 4개에서 5개로 1개 증가했지만 현재 사이즈의 2배만큼 Capacity를 할당된 것을 알 수 있다.

 


오늘 테스트 최종 결과

List에 사용하는 최대 크기를 안다면 Capacity를 미리 설정해두는 것이 좋다. List는 현재 가지고 있는 Count수가 초과될 때 기존의 데이터를 복사한 후 다시 2배 사이즈로 재할당하게 된다(기존의 있던 데이터는 가비지 해제 대상으로 잡히게 된다.) 만약 할당된 Capacity를 현재 데이터의 수만큼 변경하고 싶으면 TrimExcess 을 이용한다.

 

 

반응형

'프로그래밍언어 > C#' 카테고리의 다른 글

C#) Async, Await  (0) 2022.08.15
C#) Enum.Parse  (0) 2020.04.17
C#) String null 검사,체크 (널처리)  (0) 2020.04.16
C#) 전처리기 지시자  (0) 2020.04.12
C#) 동적바인딩 / 정적바인딩  (0) 2020.04.08

Enum.Parse메서드는 문자열 enum인스턴스로 변환한다. 이 메서드는 enum형식과 멤버 이름을 담은 문자열을 받는다.

이런 enum이 있을 때 

"dog"라는 string으로 Enum.Parse를 사용하여 Animal의 enum을 받아올 수 있다. 

 

스태틱 함수 Parse(Type, String)을 사용하면 enum을 받아 올 수있다.

반환 값이 object이기에 캐스팅하면 원하는 타입으로 값을 얻어 올 수 있다. 

 

enum으로 선언되어있지 않은 값을 부르면 에러를 뱉는다.

 

반응형

빈 문자열

빈 문자열은 길이가 0인 문자열이다. 문자열을 초기화 하거나 값을 비워줄때 빈 문자열로 만든다. 이 빈 문자열을 만드는 방법들이 몇가지 있다.  

1. 리터럴을 이용

2. 정적 string.Empty필드를 이용

 

빈 문자열을 만들었으니 실제로 값이 비워져있는지 체크해보자

결과는 모두 true

 

 

널 문자열

string은 참조 형식이므로null처리가 가능하다.

널처리된 sEmpty를 체크해보면

순서대로 true, false, NullReferenceException

반응형

전처리기 지시자

코드의 특정영역에 대한 추가적인 정보를 컴파일러에게 제공한다. 가장 흔히쓰이는 전처리기 지시자는 조건부 컴파일 지시자들이다. 

 if debug와 endif안에 있는 코드가 실행된다. 당연히 맨위 #define을 뺀다면 실행되지 않는다.

적용방법 :

1. 소스코드(클래스별로) 맨위에 #define을 선언한다.

2. 프로젝트 조건부 컴파일 기호에 define으로 사용할 기호를 넣으면 된다. 

 

조건절을 통한 사용도 가능하다.

 

단점

1. 디버깅이 힘들다.

=> 컴파일러 전단계에서 동작하므로 경고나 에러메세지 없이 코드의 흐름을 보기에 불편하다.

장점(언제쓰나)

1. 한 프로젝트로 다른 빌드버전 대응이 가능하다. 

반응형

바인딩이란?

형식이나 멤버, 연산을 구체적으로 결정하는 과정을 말한다. 변수를 예로 들면, 변수를 구성하는 식별자, 자료형 속성, 하나 이상의 주소, 자료 값에 구체적인 값으로 확정하는 것을 말한다. 이런 바인딩을 하는 시점에 따라 동적 바인딩, 정적 바인딩으로 나뉜다.

 

동적 바인딩

실행 시점에 진행되는 바인딩이다. 동적 바인딩은 컴파일 시점에서 어떤 함수나 멤버, 연산이 존재한다는 점을 프로그래머 자신을 알고 있지만 컴파일러는 알지 못하는 경우에 유용하다.

dynamic 키워드를 이용해 선언한다. 프로그래머는 d의 Quack()라는 함수가 있다고 가정하고 코드를 작성한다. 이렇게 하면 적으로 d와 Quack()은 바인딩된다.

 

정적 바인딩

 먼저 컴파일러는 p의 GetObj() 이름의 매개변수 없는 메서드를 찾는다. 그런 메서드가 없으면 컴파일러는 선택적 매개변수를 받는 메서드, 그다음에는 Progoram의 기반 클래스에 있는 메서드 여기에도 없으면 확장 메서드까지 찾는다. 없으면 컴파일 오류를 뱉는다. 정적 바인딩은 컴파일러가 바인딩을 과정을 수행하며 이미 선언되어 컴파일러가 확실하게 파악된 형식만 가능하게 되는 것이다. 

반응형

암묵적/명시적 널 가능 변환

T에서 T?로의 변환은 암묵적이고 T?에서 T로의 변환은 명시적이다.

명시적 캐스팅은 null가능 객체의 value속성을 조회하는 것에 직접 대응된다. 따라서 만일 HashValue가 거짓이면 에러가 발생한다. 

 


널 가능 값의 박싱과 언박싱

T?가 박싱된 경우, 힙에 있는 박싱된 값은 T?가 아니라 T를 담는다. 이러한 최적화가 가능한 것은, 박싱된 값은 이미 널을 표현할 수 있는 참조 형식이기 때문이다.

int?가 아닌 int가 담긴다.

c#은 널 가능 형식을 as연산자로 언박싱하는 연산도 허용한다. (캐스팅 실패 시 null)

반응형

순차열 합성

요소들의 계산은 최대로 지연되다가, MoveNext() 호출해서 비로소 계산된다. 


Nullable구조체

컴파일러는 T를 가벼운 불변이 구조체인  System.Nullable<T>로 바꾸어 컴파일한다. 이 구조체에는 두가지 필드가 존재한다. Value와 HashValue라는 두 필드만 있다. 

아시다시피 C#에서 정수,구조체 등은 Value타입들은 null을 가질 수 없다. 하지만 이런 값타입도 값이 할당되지 않은 상태를 표현하고자 하려할때 Nullable구조체를 사용한다.

컴파일러는 위쪽 코드를 아래와 같이 바꾸어서 컴파일한다.

 

HashValue가 거짓인 널 가능 객체의 value를 조회하려 하면 InvalidOperationException이 발생한다. GetValueOrDefault()메서드 HashValue가 참이 면 value를 돌려주고 그렇지 않으면 new T()또는 지정된 커스텀 기본값을 돌려준다.

(T?의 기본값은 NULL)

반응형

반복자

foreach문은 열거자의 값들을 사용하는 소비자라면, iterator는 열거자의 생산자의 해당한다.

다음은 피보나치 수열의 수들을 돌려주는 반복자 메서드를 이용하여 반복자에 대해서 살펴보자

yield return 문은 "이 열거자에 원하셨던 다음 요소가 여기 있으니 받으세요"라고 말하는것과 같다.

함수가 실행된 후 내부상태가 유지 되지않는 return과 다르게 yield return은 내부 상태를 유지하며 다음에 호출차가 다시 열거를 수행하면 열거자는 다음 요소를 돌려주게 된다. 

반복자 의미론

반복자는 하나 이상의 yield문이 있는 메서드나 속성, 인덱서이다. 그렇기 때문에 IEnumerable열거 가능 인터페이스와 IEnumerator열거자 인터페이스와 호환되어야 한다. 

 

반응형

열거자와 반복자

값들의 순차열으 반복하는 읽기 전용 커서이다. 구체적으로

System.Collections.IEnumerator

System.Collections.Generic.IEnumerator<T>

두가지 인터페이스 하나를 구현하는 객체이다

 

Foreach문은 열거 가능객체를 반복한다.

열거 가능 객체는 순차열의 논리적 표현이다. 다음 조건 하나를 만족하는 객체는 열거 가능객체로 간주된다.

  1. IEnumerator IEnumerable<T> 구현한다.

  2. 열거자를 돌려주는, GetEnumerator라는 메서드가 있다.

 

foreach문으로 훑는 예이다.

foreach없이 저수준으로 읽는 방법이다. 

열거자가 IDisposable 구현하는 경우, foreach문은 using문으로도 작용한다. , 반복이 끝나면 열거자 객체가 암묵적으로 처분된다.

반응형

Using문

클래스 중에는 파일 핸들이나그래픽 핸들, 데이터베이스 연결 같은 비관리 자원들을 캡슐화하는 것이 많다. 그런 자원들을 정리하기 위한, System.IDisposable인터페이스의 Dispose라는 메서드가 있다.

이번 포스트에 정리할 using은 콘솔창 맨위에 사용하는 지시문이 아니라 문장 형태의 using문이다.

using문을 사용하여 해당 리소스 범위를 벗어나게 되면 자동으로 리소스를 해제하여 관리하도록 하는 것이다.

이렇게 using문의 { }괄호를 벗어날때 Dispose가 된다.

 

다른 방법 try블록과 finally블록에서 Dispose를 호출한다.

 

반응형

Try문

try문은 오류 처리 또는 마무리코드를 위한 코드 블록을 지정한다. try블록 다음에 catch블록이나 finally블록이 올 수 있다. try블록 안에서 오류가 발생하면 catch블록이 실행한다. 

마무리 작업을 수행하기 위한 finally블록은 실행이 try블록을 벗어나면, 또는 catch블록이 존재하고 실제로 실행된 경우에는 catch문블록을 벗어나면 실행된다. 즉, finally블록은 오류가 발생하든 발생하지 않든 항상 실행된다.

어떻게 사용될지 예제를 보자.

위 예제를 실행하면 다음과 같은 오류를 뱉는다.

프로그램은 강제종료된다. 위와 같은 에러는 catch로 처리해 줄 수 있다.

실행결과 =>

실행순서는 이렇게 된다.

1. 함수에서 예외가 발생

2. catch블록으로 이동

3. catch블록의 실행이 성공적이면 try다음 문장으로 이동 (finally블록이 있다면 그것이 먼저 실행) 그렇지 않다면 실행은 함수를 호출한 곳으로 돌아간다(finally블록이 있다면 그것이 먼저 실행) 그런다음 같은 처리가 반복.

 

 


 

Catch 절

블록을 잡고자 하는 예외 형식을 괄호 쌍 안에 지정한다. 

그러한 예외 형식은 반드시 System.Exception클래스이거나 System.Exception의 파생 클래스이어야 한다.

 


finally 블록

finally블록은 항상 실행된다. 예외가 던져지든 아니든, 그리고 try블록이 끝까지 실행되든 아니든 이 블록은 항상 실행된다. 그래서 흔히 마무리 코드에서 쓰인다.

반응형

대리자 대 인터페이스

대리자로 풀 수 있는 문제는 인터페이스로도 해결이 가능하다.

 

 

그럼 어떨 때 대리자를 쓰는 게 더 효율적일까?

내가 보는 책에서는 3가지 정도의 예시를 보여주고 있다.

  1. 인터페이스가 메서드를 하나만 정의할 때

  2. 다중 캐스팅 능력이 필요하다.

  3. 여러 종류의 메서드를 인터페이스를 여러 번 구현해야 한다.

하나씩 살펴보자.

 

1. 인터페이스가 메서드를 하나만 정의할 때 & 3. 여러 종류의 메서드를 인터페이스를 여러 번 구현해야 한다

위 예시처럼  ITransformer인터페이스는 메서드를 하나만 가지고 있다. 이럴 경우 왜 델리게이트 더 좋냐면, 인터페이스 같은 경우 하나의 클래스에서는 하나의 메서드만 넘길 수 있는 반면에 델리게이트로 구현하게 되면 다른 메서드(int로 반환하는 메서드) 면 어떠한 메서드도 넘길 수 있기 때문이다.

델리게이트로 하면  Square 1도 되고~ Square2되지만

 

인터페이스로는

각각 클래스에 인터페이스를 상속받아넘겨야 한다.

 

2. 다중 캐스팅 능력이 필요하다.

하나의 대리자 인스턴스가 여러 개의 메서드를 지칭할 수 있는 델리게이트 기능이 필요하다면. 예를 들어 

예시가 너무 구린데 암튼 이런 식으로 쓸 거라면 델리게이트 고고.

 

 

반응형

대리자

대리자는 어떤 메서드를 호출하는 방법을 담은 객체이다. 대리자 형식은 그 형식의 인스턴스, 즉 대리자 인스턴스가 호출할 수 있는 종류의 메서드를 정의한다. 

 


 

대리자를 이용한 플러그인 메서드 작성

대리자 변수에서 메서드를 배정하는 연산은 실행 시점에서 일어난다. 따라서 대리자는 플러그인 메서드를 구현하기에 좋은 수단이다. 다음 예제를 보자. 

 

위 예제처럼 Square의 메서드를 매개변수로 넘길수도 있다.

 

 


대리자 다중 캐스트

모든 대리자 인스턴스에는 다중 캐스트 능력이 있다. 무슨 말이냐 하면, 하나의 대리자 인스턴스가 하나의 대상 메서드가 아니라 여러 개의 대상 메서드를 지칭할 수 있다는 것

 


 

표준 Func 대리자와 Action 대리자

Func 

리턴 타입이 있는경우

Func <T>은리턴 타입이 Generic폼 내에, 즉 템플릿 파라미터 안에 존재한다. T는 리턴 값의 타입을 가리킨다. 경우 Func <T, TResult>, 입력이 2개인 경우 Func<T1, T2, TResult> 를 사용한다.

 

Func<T, T2,  TResult>은 어떻게 쓸까?

TResult는 out키워드가 있는데 밑에 설명 그대로 반환하는 메서드를 캡슐화한다고 한다.  그럼 대충 눈치가 챌 수 있다. 

 

 

Action 

리턴 값이 없는 경우

System네임스페이스에서 제공되고, 파라미터의 수에 따라 0개부터 16개까지 파라미터를 받아들이는 delegate가 있다.

Action <T> delegate, 2개인 Action <T1, T2> delegate....

 

 

 

 

 

 

반응형

제네릭 형식의 파생

제네릭 형식도 제네릭이 아닌 클래스처럼 파생할 수 있다. 제네릭 클래스를 기반으로 삼아서 파생 클래스를 정의할 떄, 다음처럼 기반 클래스의 형식 매개변수를 옆린 채로 남겨두는 것이 가능함.

물론 이런식으로 구체적인 형식으로 매개변수를 제한 할 수도 있다.

또한 파생 형식에서 새로운 형식 매개변수를 도입할 수도 있다.

이렇게 사용하면 된다.

 

 

 


 

자신을 참조하는 제네릭 형식

파생 형식이 기반 형식의 형식 매개변수를 닫을 때, 파생 형식 자신을 형식 인수로 지정하는 것이 가능하다.

밑에 처럼 사용하면 되겠쥬?

 

반응형

'프로그래밍언어 > C#' 카테고리의 다른 글

2020311[C#] 대리자, 인터페이스 차이점  (0) 2020.03.11
2020310[C#] 대리자(delegate)  (0) 2020.03.10
20200225[C#] 제네릭 제약_2  (0) 2020.02.25
20200224[C#] 제네릭 제약_1  (0) 2020.02.24
20200220[C#] 제네릭  (0) 2020.02.20

형식 매개변수에 제약조건을 지정함으로써, 형식 매개변수에 대입할 수 있는 형식 인수들을 좀 더 제한하는 것이 가능하다.

4. 구조체 제약 조건

반드시 T가 값 형식(널 기능이 아닌)이어야 함을 뜻한다. 구조체 제약 조건의 좋은 예는 System.Nullable<T>구조체이다. 

구조체 제약 조건


2. 매개변수 없는 생성자 제약 조건

T에 반드시 매개변수 없는 공용 생성자가 있어야한다. 이를 만족하는 T에 대해서는 new()를 호출할 수 있다.


2. 적나라한 형식 제약 조건

형식 매개변수가 다른 형식 매개변수와 같은 형식이거나 그 파생 형식이어야 함을 뜻한다. 

반응형

형식 매개변수에 제약조건을 지정함으로써, 형식 매개변수에 대입할 수 있는 형식 인수들을 좀 더 제한하는 것이 가능하다. 

1. 참조 형식 제약 조건

사용 형태

제약조건은 참조 형식으로 설정했기 때문에 int는 에러를 발생한다.


2. 인터페이스 제약 조건

매개변수가 반드시 주어진 인터페이스를 구현해야 한다. 이러한 제약 조건들을 만족하는 형식 매개변수의 인스턴스는 해당 클래스나 인터페이스로 암묵적으로 변환된다. 

namespace System의 CompareTo

CompareTo메서드는 만일 this가 other보다 크면 양수를 돌려준다. 

어떤 형식도 인수로 받을 수 있다. 


3. 기반 클래스 제약 조건

형식 매개변수가 반드시 주어진 클래스 또는 그 클래스의 파생 클래스이어야 한다. 

Parent만 가능하다.


 

반응형

제네릭

여러 형식들에서 재사용할 수 있는 코드를 작성하기 위한 메커니즘이 두 가지 있는, 하나는 상속이고 또 하나는 제네릭이다. 이 둘은 개별적인 메커니즘으로 상속은 기반 형식을 이용해서 재사용성을 표현하는 반면, 제네릭은 '자리표'에 해당하는 형식들은 담은 '템플릿'을 통해서 재사용성을 표현한다. 상속과 비교할 때, 제네릭을 사용하면 형식 안전성이 증가하고 캐스팅과 박싱이 줄어든다.  

제네릭 형식

형식 매개변수(type parameter)들을 선언한다. 형식 매개변수는 제네릭 형식이 실제로 쓰일 때 해당 코드가 제공한 실제 형식들이 대신할 자리를 표시하는 '자리표에'해당한다. 말이 어렵지 어떻게 사용하는지 보면 쉽게 이해할 수 있다.

간단하게 스택을 제네릭 형식으로 만든 예제이다. 이렇게 만드면 타입을 상관없이 스택에 담을 수 있게 된다. 

적용방법

이렇게 타입별로 클래스를 인스턴싱한 후 원하는 타입으로 사용할 수 있다. 이렇게 하면 위에서 나온 재사용이 가능해진 것이다. 

Object형식으로 만들면?

모든 타입을 포함하는 상위 클래스인 Object형식으로 스택을 만들면 가능하지만 이 제네릭과는 조금 다르다. object로 담을 수 있기 때문에

이런 식으로 여러 타입의 값들을 같은 배열에 담을 수 있다.(이렇게 사용하지는 않을 것 같음) 하지만 이것 자체가 박싱이며, 값들을 꺼낼 때도 하향 캐스팅이 필요하다. 박싱과 하향 캐스팅 둘 다 컴파일 시점에서 형식 점검이 일어나지 않기 때문에 실수할 가능성이 높다. 

지금은 아무 에러가 없지만 프로그램을 실행하면 컴파일 에러가 난다.

제네릭 스택은 인스턴스를 만들때 한번 설정한 타입으로 컴파일 시점에서 점검이 이루어지기 때문에 안전하게 사용이 가능한 것이다.

반응형

C#의 모든 형식은 실행 시점에서 System.Type의 인스턴스로 표현이 된다. 두 가지 방법으로 Type객체를 얻을 수 있다.

  1. 인스턴스에 대해 GetType을 호출한다.

  2. 형식 이름에 대해 Typeof연산자를 호출한다.

이 두가지의 큰 차이점은 GetType은 실행 시점에서 평가되는 반면에 typeof는 컴파일 시점에서 정적으로 평가된다.(제네릭 형식은 JIT(just-in-time compilation)컴파일러가 결정한다.)

 

1. GetType 

GetType을 실행해 보면 현재 인스턴스의 타입을 가져오고 있다. (자칫 클래스 이름을 가져온다고 생각할 수 있지만 타입이다 타입)

 

그러면 아래 실행결과는 어떤값을 보내줄까?

int형인 x의 타입이니 Int32를 보내준다.

 

Name을 사용하면 타입을 string값으로 받을 수 있다. 

 


 

1. typeof

형식에 대한 System.Type개체를 얻는데 사용된다. 

이렇게 타입형식을 참조할 수 있다.

 

 

이 두개의 차이점은?

이 두개의 실행결과를 예상해 보자. 정답은 위에서부터 각각 true, false, true, true, false다.

첫 번째는 타입을 비교하고 두 번째는 인스턴스를 비교하고 있다. child로 같은 타입이기 때문에 true이다.

두 번째는 Object.Equals를 이용해 인스턴스가 같은지를 비교하고 있다. new를 이용해 새로운 생성자 인스턴스를 리턴해주고 있기 때문에 c와 c2는 같지 않다. 

세 번째는 c와 c2모두 같은 타입이고 typeof은 타입을 반환하기 때문에 '==' 비교는 true를 출력하고 다섯 번째는 다른 타입과 비교를 하고 있어 false를 출력한다.

반응형

'프로그래밍언어 > C#' 카테고리의 다른 글

20200224[C#] 제네릭 제약_1  (0) 2020.02.24
20200220[C#] 제네릭  (0) 2020.02.20
20200218[C#] Object형식, 박싱 언박싱  (0) 2020.02.18
20200217[C#] 생성자와 상속  (0) 2020.02.17
20200214[C#] 상속된 멤버 숨기기  (0) 2020.02.14

Object형식

Object형식은 모든 형식의 궁극적인 기반 클래스이다. 그 어떤 형식도 Object로 상향 캐스팅할 수 있다. 범용적으로 쓰이는 Stack구조를 살펴보자

        class Stack
        {
            public int Position;
            object[] array = new object[10];
            public void Push(object o)
            {
                array[Position++] = o;
            }
            public object Pop()
            {
                return array[Position--];
            }

        }

        static void Main(string[] args)
        {
            Stack stack = new Stack();
            stack.Push("Apple");
            string answer = (string)stack.Pop(); //하향 캐스팅
        }

그 어떤 형식의 인스턴스라도 push와 pop이 가능하다. 하지만 Object형식이기 때문에 Pop을 할 때는 명시적 캐스팅이 필요하다. 또, object는 하나의 클래스이며, 따라서 참조 형식이다. 그렇긴 하지만 int 같은 값 형식과 object사이의 캐스팅도 가능하다. c#의 이러한 특징을 형식 통합이라고 부른다.  

Object(참조) 형식도 int(값) 형식으로 변환이 가능하다.


박싱과 언박싱

위에서 살짝 언급한 내용으로 값 형식 인스턴스를 참조 형식 인스턴스로 변환하는 것을 박싱이다. 그럼 그 반대는? 언박싱이겠지라고 생각하는데 맞기는 하는데 한 가지 조건이 있다. 바로, 박싱 과정을 거친 객체를 되돌리는 것이 언박싱이다.

간단한 예제로 살펴보자

1.박싱

int x = 10;
object obj = x; //int 박싱

 

1.언박싱

int x = 10;
object obj = x; //int 박싱

int y = (int)obj; //int 언박싱

 

박싱은 그냥 하면 되고, 언박싱은 명시적 캐스팅이 필요하다. 이렇게 쉽게 참조 타입과 값 타입 사이에서 형식 변환을 쉽게 할 수 있다는 것은 매우 편리하다. 모든 함수와 자료구조 타입을 objct형식으로 만들어놓으면 캐스팅만 잘 사용한다면 코드 중복 없이 짧고 쉽게 설계가 가능할 것 같아 보인다. (이 세상에서 분명 공짜는 없다. 박싱과 언박싱에서는 단점이 있다. 다음에 설명 )

하지만, 기억해야 할 것은 objct는 참조 형식이고, int는 값 형식이다. 


박싱과 언박싱의 복사 의미론

 

박싱(값 -> 참조)은 값 형식 인스턴스를 새 객체로 복사하고, 언박싱(참조 -> 값)은 객체의 내용을 다시 값 형식 인스턴스로 복사한다. 말로 풀었을 때 무슨 소리인지 모르겠다. 이 과정은 어떤 과정인지 한 가지 예를 보고 살펴보자.

int i = 5;
object boxed = i;
i = 3;
Console.WriteLine(boxed);
Console.WriteLine(i);
Console.WriteLine(boxed);

  

먼저 출력 결과를 먼저 알려주면 위에서부터 5,3,5이다. 답을 맞혔다면 저 말을 이해가 된 것이다.  boxed와 i는 서로 다른 인스턴스 즉, "i는 boxed라는 새로운 인스턴스를 만들고 자신의 값을 복사했다."라는 말이다. 그래서 i의 값을 바꿔도 boxed의 값은 변경되지 않는다.

반응형

생성자와 상속

자식 클래스에서 부모클래스로 접근은 가능하지만 자식클래스에서 부모클래스의 생성자는 자동으로 상속되지 않는다.

    class Parent
    {
        public int X;
        public Parent() { }
        public Parent(int X)
        {
            this.X = X;
        }
    }
    class Child : Parent
    {

    }
    class Program
    {
        static void Main(string[] args)
        {
            Child child = new Child(123); //컴파일에러
            Console.WriteLine(child.X);
        }
    }

자식클래스는 자신이 노출하고자 하는 생성자들을 반드시 '다시 정의' 해야한다.

    class Parent
    {
        public int X;
        public Parent() { }
        public Parent(int X)
        {
            this.X = X;
        }
    }
    class Child : Parent
    {
        public Child(int X) : base(X) { } //base 키워드를 사용하여 상속
    }
    class Program
    {
        static void Main(string[] args)
        {
            Child child = new Child(123);
            Console.WriteLine(child.X);
        }
    }

 

만약 base키워드를 쓰지 않고 매개변수가 없는 생성자를 호출하면 부모클래스의 매개변수가 없는 생성자가 실행된다.

    class Parent
    {
        public int X;
        public Parent() { }
        public Parent(int X)
        {
            this.X = X;
        }
    }
    class Child : Parent
    {
        public int y;
    }
    class Program
    {
        static void Main(string[] args)
        {
            Child child = new Child();
            Console.WriteLine(child.X);
        }
    }

부모클래스에 접근 가능한 기본생성자가 하나도 없으면 자식클래스는 생성자에서 반드시 base클래스를 사용해야한다.

반응형

상속된 멤버 숨기기

클래스 상속을 하다보면, 부모클래스와 자식클래스에 동일한 멤버가 존재할 수 있다. (근데 되도록 이름을 다르게 지정하는게 좋다고 판단된다. 아무래도 헷갈릴수 있기 때문에)

        public class A
        {
            public int Counter = 1;
        }

        public class B : A
        {
            public int Counter = 2;
        }

똑똑한 비쥬얼스튜디오는 'new' 키워드를 이용하라고 제시한다. 

여기서 사용하는 'new'는 new연산자와 다르다.

new 키워드를 쓰나 안쓰나 똑같이 b.Counter는 b의 Counter = 2를 출력한다. 'new' 단지 컴파일러가 멤버 숨기기에 관한 경고를 내지 않게 만들고, 다른 프로그래머들에게 의도적인 것임을 알려주는 수단이라고 할 수 있다.


new와 override

        public class BaseClass
        {
            public virtual void Foo()
            {
                Console.WriteLine("BaseClass");
            }
        }

        public class Overrider : BaseClass
        {
            public override void Foo()
            {
                Console.WriteLine("Overrider.Foo");
            }
        }

        public class Hider : BaseClass
        {
            public new void Foo()
            {
                Console.WriteLine("Hider.Foo");
            }
        }
        static void Main(string[] args)
        {
            Overrider over = new Overrider();
            BaseClass b1 = over; //상향캐스팅
            over.Foo(); // Overrider.Foo
            b1.Foo();   // Overrider.Foo

            Hider h = new Hider();
            BaseClass b2 = h;
            h.Foo(); //Hider.Foo
            b2.Foo(); //Hider.Foo
        }

출력

부모클래스와 자식클래스의 함수명이 같게되면 'new'수정를 쓴 함수를 숨겨준다. 

반응형

캐스팅

상향캐스팅

    public class Asset
    {

    }
    public class Stock : Asset
    {

    }

    class Program
    {
        static void Main(string[] args)
        {
            Stock stock = new Stock();
            Asset asset = stock;
            
        }
    }

 상향 캐스팅 연산은 파생 클래스 참조로부터 기반 클래스 참조를 생성한다.

stock 와 asset가 가리키는 객체는 같다. (stock == asset는 true

    public class Foo
    {
        public static Foo Instance = new Foo();
        public static int X = 3;
        Foo() { Console.WriteLine(X); }
    }
    public class Asset
    {
        public int nInt;
    }
    public class Stock : Asset
    {
        public float fFloat;
    }

    class Program
    {
        static void Main(string[] args)
        {
            Stock stock = new Stock();
            Asset asset = stock; //상향 캐스팅
            Console.WriteLine(asset.fFloat); //컴파일 오류

        }
    }

stock형식의 객체를 가리키긴 하지만 변수 자체의 형식은 Asset이기 때문에 fFloat변수접근이 불가능하다.

하향캐스팅

하향캐스팅 역시 변하는 것은 참조 뿐이다. 하향은 실행시점에서 실패 할 수도 있기 때문에 명시적으로 캐스팅이 필요하다.

 public class Foo
    {
        public static Foo Instance = new Foo();
        public static int X = 3;
        Foo() { Console.WriteLine(X); }
    }
    public class Asset
    {
        public int nInt;
    }
    public class Stock : Asset
    {
        public float fFloat;
    }

    class Program
    {
        static void Main(string[] args)
        {
            Stock stock = new Stock();
            Asset asset = stock; //상향 캐스팅

            Stock a = (Stock)asset; //하향 캐스팅

            Console.WriteLine(a.fFloat); 
            Console.WriteLine(a.nInt);

            Console.WriteLine(asset == stock); // true
            Console.WriteLine(asset == a); // true
            Console.WriteLine(a == stock); // true



        }
    }

 


as 연산자

as 연산자도 하향 캐스팅을 수행한다. 위와 다른점은 실패할 경우 null로 평가된다.

    class Program
    {
        static void Main(string[] args)
        {
            Asset a = new Asset();
            Stock s1 = (Stock)a;
            Stock s2 = a as Stock; // 예외를 발생하지 않음
        }
    }

 

반응형

소멸자

가비지 컬렉터가 돌기전에 실행된다.


nameof연산자

임의의 기호(형식, 멤버, 변수 등)의 이름에 해당하는 문자열을 돌려준다.

출력 "count"

그냥 해당 문자열을 직접 지정하는 것에 비한 이 연산자의 장점은 정적 형식 점검이 일어난다. 참조 같은 형식으로 해당 기호의 이름을 바꾸면 그에 대한 모든 참조의 이름도 바뀐다. (컴파일 시점에 평가되며 런타임의 영향을 주지 않는다.)

출력 결과

nameof언제사용할까? 

1. ToString보다 빠르다.

enum MyEnum { ... FooBar = 7 ... }

Console.WriteLine(MyEnum.FooBar.ToString());

> "FooBar"

ToString()은 런타임에 실행되고 nameof는 컴파일 시 실행되기때문에 상대적으로 ToString()이 느리다.

Console.WriteLine(nameof(MyEnum.FooBar))

> "FooBar"

2. 예외상황이 최신화 상태로 유지된다.

public string DoSomething(string input) 
{
    if(input == null) 
    {
        throw new ArgumentNullException(nameof(input));
    }
    ...

input매개 변수의 이름이 바뀌어도 속성이나 파라미터의 이름을 얻기 때문에 

 

https://stackoverflow.com/questions/31695900/what-is-the-purpose-of-nameof

반응형

객체 초기치 대 선택적 매개변수

읽기전용으로 만들기에는 좋음 하지만 각 선택적 매개변수의 기본값이 호출 지점에 들어박힌다는 단점

 Bunny bunny = new Bunny("Rabit", true, false); 

(매개변수 bool변수의 기본값은 false)

 

식 본문 속성

C# 읽기전용 필드를 이런식으로 사용이 가능하다.

위 그림은 읽기전용 필드로 설정되어 '2'을 넣을 수 없다.

 

인덱서의 구현

이렇게도 접근자를 제한해서 자신클래스 아니면 접근하지 set 못하게 할 수 있다.

 

정적생성자

발생조건 : 1. 형식을 인스턴스화한다. 2. 형식의 정적 멤버에 접근한다.

초기화 순서 : 정적 생성자가 호출되기 전에 실행된다. 형식에 정적 생성자가 없으면 정적 필드들은 해당 형식이 쓰이기 직전에 초기화 될 수도 있고, 런타임의 판단에 따라서는 그보다 전의 임의의 시점에 초기화될 수도 있다.

예제2개를 보고 결과값을 예상해보자

정적필드들은 선언된 순서대로 초기화된다. X는 0으로 Y는 3으로 초기화된다.

만약 순서를 바꾼다면? 모두 3으로 초기화 된다.

결과는 0과 3을 출력한다.

반응형

namespace안에 using 지시자를 중첩할 수 있다. 한 이름 공간 선언 안에서 using 지시자로 도입한 이름들은 그 이름공간 선언

global

식 본문 메서드

결과는 4

 

생성자의 중복적재

반응형

공통 언어 런타임 CLR

 마이크로 소프트 이니셔티브에서 제공하는 가상 머신의 구성요소이다. 프로그램 코드를 위한 실행 환경을 정의하는 마이크로소프트의 공통 언어 기반(CLI)표준의 기능이다. C#이나 VB닷넷과 같은 언어로 프로그래밍하며, 해당 언어의 컴파일러가 소스 코드를 공통 중간 언어(IL) 코드로 변환한다. 

CLR은 관리되는 코드(managed code)를 실행하기 위한 런타임이다. C#은 여러 관리되는 언어중 하나인데, 관리되는 언어로 작성한 소스코드를 컴파일 하면 관리되는 코드가 생성된다. 관리되는 코드를 실행 파일또는 라이브러리(.dll)형태로 만들고 그것을 형식 정보, 즉 메타자료와 함께 하나의 패키지로 묶은 것을 어셈블리라고 한다. 어셈블리를 적재 할 때 CLR은 IL코드를 해당 컴퓨터(x86 등) 고유의 기계어 코드로 변환한다. 이러한 변환을  담당하는 것이 CLR의 JIT(Just - in - time)컴파일러이다. 어셈블리는 원래의 원본 언어의 구성을 거의 그대로 유지하기 때문에, 코드를 조사하기 쉽고 심지어 동적으로 생성하기도 쉽다.

위캐백과

 

 

1.이니셔티브 : 우선권, 주도권, 스스로 상황 판단을하고, 남들이 움직이기 전에 먼저 움직일 수 있는 능력

2.CLI : Command-Line user Interface또는 Character User Interface

반응형

같은 클래스로 인스턴스를 new로 생성하게 되면  

1번을 제외하고 2~4번은 false

참조형 변수의 주소값을 비교하는 object.ReferenceEquals

즉, 관리 힙에 할당된 객체의 참조 주소 값을 비교해 '같음 여부'를 판단하기 때문에, object.ReferenceEquals '값 형식'의 인스턴스에 사용해서는 안됨

값형식을 넣으면 오류는 안나지만 false를 반환

object.ReferenceEquals의 값을 넣게되면 매개변수타입이 object형식이라 박싱과정이 거친다. 그러면 힙메모리에 값이 할당되게 된다. 결국 스택에 있는 서로다른 주소값이 생기기 떄문에 항상 false를 반환하게된다.

 

 

 

반응형

abstract :

상속 제한 : sealed키워드와 반대로 무조건 상속해서 쓰라는 의미 

메서드 오버 라이딩 제한 : 이 메서드는 반드시 오브 라이딩해달라는 의미

 

as :

객체를 캐스팅 할 때 사용되는 연산자로, 캐스팅에 성공하면 캐스트 결과를 리턴하고 실패하면 null을 리턴

 

is :

캐스팅이 가능하면 true, 아니면 false를 리턴

 

base :

해당 키워드를 사용하는 부모 클래스를 가리키는 것

 

const :

상수 필드 또는 지역 상수를 선언할 때 사용한다. 변수가 아니며, 한번 값이 할당되면 이후 변경이 불가능

 

readonly :

성수를 선언할 때 사용, 선언 시 선언할 때 값을 할당하지 않아도 됨, const와 다르게 생성자에서 한번 더 값 변경이 가능하다.

 

continue :

반복문에서 continue키워드 선언 이후 부분은 넘기고 다음 반복문부터 시작하도록 한다.

 

decimal :

고정 소수점 방식이며 연산 속도가 빠르고 수의 정확성이 높은 데신 큰 수를 저장할 때 메모리를 많이 먹게 된다. 16byte

 

double:

부동소수점 방식이며 decimal보다 정확성은 떨어지지만 작은 메모리 공간에 큰 소수를 저장이 가능하다.

 

event :

public이어도 자신이 선언되어 있는 클래스의 외부에서는 호출이 불가능, 객체의 상태 변화나 사건의 발생을 알리는 용도로 사용

 

delegate:

메서드 참조를 포함하고 있는 영역

 

size of :

지정된 형식의 변수에서 사용하는 바이트 수를 반환한다.

 

반응형

C#에서 제공하는 Collections List와 LinkedList를 찾아 보게되었다. 사실 List는 사용하기가 편해서 워낙 많이 쓰이는 구조중 하나이다. List보다는 LinkedList를 좀 더 알기 위해 찾아봤다.

List

Add해서 데이터를 넣으면 입력한 중복이 가능하며, 순서대로 들어가게된다. Remove로는 해당 데이터를 삭제가 가능하다.

List.AddRange

AddRange를 통해 한꺼번에 입력이 가능하다. 다른 int형 리스트를 넣을 수 있다는 말

 

LinkedList

이중연결리스트형태를 가지고 있다. 리스트와 다르게 앞뒤 레퍼런스를 가지고 있으며, 앞(AddFirst), 뒤(AddLast)에 추가 할 수가 있다. 

물론 단순 1~4를 출력 하고싶으면 AddAfter( node , 3 ) value(3)값을 넣어도된다. 

노드 접근

다음 노드(node.Next.value) 이전 노드 (node.Previous.value)

이런식으로 특정 노드로 부 앞뒤로 데이터로 접근이 가능하다.

 

반응형

+ Recent posts