이전에 C#에서 가비지 컬렉션이 어떤 방식으로 메모리를 할당하고 해제 하는지에 대해서 포스팅 하였다.

해당 포스팅은 아래 링크를 참고하자.


C, C++에서 메모리 할당과 해제 원리 : http://hijuworld.tistory.com/28

C#/닷넷 에서 가비지 컬렉션의 메모리 할당 및 해제 기본 원리 : http://hijuworld.tistory.com/32


이번에는 가비지 컬렉션이 동작할때 세대별로 관리를 하는 방식에 대해서 알아보자.

힙영역에 할당된 메모리들은 0세대, 1세대, 2세대 세가지로 구분된다.

가장 처음 할당되는 메모리들은 0세대이며 가비지 컬렉션이 한번이 이루어 질때마다 한세대씩 증가한다.

가비지 컬렉션은 0세대 가비지 컬렉션 부터 발생하며 0세대 가비지 컬렉션은 0세대 메모리들만을 메모리해제 한다. 남은 메모리들은 1세대씩 증가한다.

0세대 가비지 컬렉션이 이루어 졌는데도 메모리가 부족하다 판단되면 1세대 가비지 컬렉션이 발생한다.

그러면 0세대 메모리와 1세대 메모리 모두 사용하지 않는 메모리는 해제된다. 그리고 한세대씩 증가하게 된다.

0세대와 1세대 가비지 컬렉션이 발생하였는데도 메모리가 부족하면 2세대 가비지 컬렉션이 발생한다.


글로 이해하면 어려울 수 있으니 아래 그림을 참고하자.

0세대 가비지 컬렉션의 원리이다.



아래는 1세대와 2세대 가비지 컬렉션 동작 원리이다.



이러한 방식으로 동작하는 이유는 최근 생성된 객체는 금방 사용하지 않을 가능성이 높고 오래된 객체일수록 계속 사용할 가능성이 높은 이유이다.


2세대 가비지 컬렉션은 상당히 큰 오버해드가 될 수 있다.


가비지 컬렉션이 똑똑하기는 하지만 완벽하지는 않다. 항상 주의해서 사용해야 한다.


Google에 C# 메모리라고 검색해도 C#에 메모리에 관한 이슈가 많이 검색이 된다.


한번 프로그램을 다 만들고 나서 이후에 관련 메모리 이슈를 해결하려면 쉽지 않기 때문에 프로그램을 만들면서 서 주의를 해야 한다. 특히나 24시간 돌아가는 프로그램들에 특히 주의가 필요하다.


다음으로 메모리 사이즈가 큰 객체에 대해서는 어떻게 가비지컬렉션이 일어나는지 알아보자.


용량이 큰 객체에 메모리 할당 해제 방식  : http://hijuworld.tistory.com/46

Posted by 꿈만은공돌
,

델리게이트란? 델리게이트와 함수포인터 : http://hijuworld.tistory.com/39


델리게이트(delegate) 체인은 하나의 델리게이트 안에 여러개의 함수를 연결하여 연쇄적으로 호출하는 방식이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
delegate void testDele();
static void Main(string[] args)
{
            
    void print1()
    {
        Console.WriteLine("print1");
    }
    void print2()
    {
        Console.WriteLine("print2");
    }
    void print3()
    {
        Console.WriteLine("print3");
    }
    testDele dele = new testDele(print1);
    dele += new testDele(print2);
    dele += new testDele(print3);
    dele();
}
cs


- 출력 -

print1

print2

print3


위에서 보듯이 print1함수와 print2, print3 함수들을 델리게이트 testDele에 +=로 추가를 하고 dele()로 호출을 하니 넣은 순서대로 3개 함수가 모두 호출되는 것을 볼 수 있다.

Posted by 꿈만은공돌
,


델리게이트(delegate)는 C#에서 함수포인터와 비슷한 역활을 담당한다.


우선 함수포인터에 대해서 알아보자.


함수포인터는 함수의 주소값을 저장하는 역활을 한다.


1
2
3
4
5
6
7
8
int (*p)(int,int);
 
int add(int a, int b){
    return a+b;
}
= add;
p(3,2); // 5 리턴
 
cs


위와 같이 p가 함수포인터이다. 함수의 이름이 곧 함수의 주소값이기 때문에 p = add로 함수의 주소값을 함수포인터 p에 저장하였다.


함수 포인터와 같이 델리게이트(delegate)역시 함수를 대리호출 할 수 있다.


C#에서 함수포인터가 없고 델리게이트란 개념을 새로 만든 것은 포인터대신 참조자인 ref를 사용하는 것과 같은 이유이다.


주소값을 직접 저장하고 다루면 위험하기 때문이다.


사용자에게 주소값을 직접 다루지 않아도 포인터와 같은 역활을 하는 개념을 만든 것이다.


아래와 같은 형식으로 선언하면 된다.



delegate 반환형식 델리게이트이름 (매개변수 목록);


1
2
3
4
5
6
7
8
delegate int TestDele(int a,int b);
 
int add(int a, int b) {
    return a+b;
}
TestDele dele = new TestDele(add);
dele(3,2); //5 리턴
 

cs


델리게이트(delegate) 체인은 하나의 델리게이트 안에 여러개의 함수를 연결하여 연쇄적으로 호출하는 방식이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
delegate void testDele();
static void Main(string[] args)
{
            
    void print1()
    {
        Console.WriteLine("print1");
    }
    void print2()
    {
        Console.WriteLine("print2");
    }
    void print3()
    {
        Console.WriteLine("print3");
    }
    testDele dele = new testDele(print1);
    dele += new testDele(print2);
    dele += new testDele(print3);
    dele();
}
cs


- 출력 -

print1

print2

print3


위에서 보듯이 print1함수와 print2, print3 함수들을 델리게이트 testDele에 +=로 추가를 하고 dele()로 호출을 하니 넣은 순서대로 3개 함수가 모두 호출되는 것을 볼 수 있다.


Posted by 꿈만은공돌
,


C++과 마찬가지로 C#/.NET에도 연산자 오버로딩을 지원한다.


연산자 오버로딩은 해당 언어 자체에서 제공하고 있는 연산자에 대하여 그 의미를 다시 부여하는 것을 말한다.


즉 +나 -, * 등과 같은 연산자는 원래의 의미인 4칙연산이 있지만 이것을 재정의하여 사용하게 해주는 것이다.


연산자 오버로딩은 자주 사용하는 문법은 아니지만 잘만 사용하면 문법자체가 간결해질 수 있다.


아래 코드를 보면 +와 - 연산자에 대해서 오버로딩 함수를 정의한 것을 볼 수 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Point
{
    public int X { get; set; }
    public int Y { get; set; }
    public int Z { get; set; }
    public Point(int x,int y,int z)
    {
        this.X = x;
        this.Y = y;
        this.Z = z;
    }
    public static Point operator +(Point p1, Point p2)
    {
        return new Point(p1.X + p2.X, p1.Y + p2.Y, p1.Z + p2.Z);            
    }
    public static Point operator -(Point p1, Point p2)
    {
        return new Point(p1.X - p2.X, p1.Y - p2.Y, p1.Z - p2.Z);
    }
    public String toString()
    {
        return "x: " + X + ", y: " + Y + ", z: " + Z;
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        Point p1 = new Point(5,4,11);
        Point p2 = new Point(1,3,3);
        Point addPoint = p1 + p2;
        Point subPoint = p1 - p2;
 
        Console.WriteLine(addPoint.toString());
        Console.WriteLine(subPoint.toString());
    }
}
cs





아래는 MSDN에 나와있는 연산자 오버로딩이 가능한 연산자이다. 

연산자

오버로드 가능성

+, -, !, ~, ++, --, true, false

이 단항 연산자는 오버로드할 수 있습니다.

+, -, *, /, %, &, | , ^, <<, >>

이 이항 연산자는 오버로드할 수 있습니다.

==, !=, <, >, <=, >=

비교 연산자는 오버로드할 수 있습니다. 단, 이 표의 아래에 있는 참고 사항을 참조하십시오.

&&, ||

조건 논리 연산자는 오버로드할 수 없지만, 오버로드 가능한 & | 를 사용하여 계산할 수 있습니다.

[]

배열 인덱싱 연산자는 오버로드할 수 없지만 인덱서를 정의할 수 있습니다.

(T)x

캐스트 연산자는 오버로드할 수 없지만 새로운 변환 연산자를 정의할 수 있습니다(explicit  implicit 참조).

+=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=

할당 연산자는 오버로드할 수 없지만 다른 방법으로 계산할 수 있습니다. 예를 들어, += 연산자는 오버로드할 수 있는 + 연산자를 사용하여 계산할 수 있습니다.

=, ., ?:, ??, ->, =>, f(x), as,checked, unchecked, default,delegate, is, new, sizeof, typeof

이 연산자는 오버로드할 수 없습니다.

자료출처 : https://msdn.microsoft.com/ko-kr/library/8edha89s(v=vs.120).aspx

Posted by 꿈만은공돌
,

C#/닷넷에서 자주 사용되는 get, set 키워드에 대해서 알아보자.

아주 편리한 키워드 이다.


우선 아래 코드를 보자


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class People
{
    public String name;
    public int age;
}
 
class Program
{
    static void Main(string[] args)
    {
        People kim = new People();
        kim.name = "kim";
        kim.age = 10;
    }
}
cs


name과 age 변수에 대해서 public으로 설정하여 어디서든 접근이 가능하다.

하지만 이렇게 하면 정보은닉 원칙에 어긋나기 때문에 private 바꾸는 것이 좋다.


그러면 아래와 같이 get과 set 함수들을 만들어 사용할 수 있다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class People
{
    private String name;
    private int age;
    public String getName() {
        if (name == null)
            return "Error";
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        People kim = new People();
        kim.setName("test");
        kim.setAge(10);
        Console.WriteLine(kim.getName() + " " + kim.getAge());    
    }
}
cs


위와 같은 방식이 이전 java나 C++에서 사용하던 방식들이다.



하지만 C#에서는 좀더 편리한 get, set 키워드를 제공한다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class People
{
    private String name;
    private int age;
    public String Name
    {
        get {
            if (name == null)
                return "Error";
            return name;
        }
        set {
            name = value;
        }
    }
    public int Age
    {
        get {
            return age;
        }
        set {
            age = value;
        }
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        People kim = new People();
        kim.Name = "test";
        kim.Age = 10;
        Console.WriteLine(kim.Name + " " + kim.Age);
    }
}
cs



위의 코드와 같이 private으로 변수를 선언하고 get,set용 프로퍼티를 만들수도 있지만

아래의 코드와 같이 변수 선언을 생략할 수도 있다. (C# 3.0 이후부터 지원)

코드가 좀더 간결해진다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class People
{
    public String Name
    {
        get {
            if (Name == null)
                return "Error";
            return Name;
        }
        set {
            Name = value;
        }
    }
    public int Age
    {
        get;set;
    }
}
 
class Program
{
    static void Main(string[] args)
    {
        People kim = new People();
        kim.Name = "test";
        kim.Age = 10;
        Console.WriteLine(kim.Name + " " + kim.Age);
    }
}
cs



Posted by 꿈만은공돌
,

C#, 닷넷에서 readonly 키워드와 const 키워드는 비슷하지만 다른 키워드 이다.

두 키워드의 차이점 및 공통점에 대해서 알아보자. 


 const 키워드는 대부분에 프로그래밍 언어에서 지원하는 기본적인 문법입니다. 변수를 선언할때 변수 타입앞이나 뒤에 명시하여 해당 변수를 상수화 합니다. 그래서 선언할때 변수값을 지정하면 이 변수는 이제 상수가 되어 값을 읽을 수는 있지만 수정이 불가능 합니다. 이런 불편한 상수를 왜 사용할까 하지만 많은 장점이 존재 합니다. 


 예를들어 원에 넓이를 구하는 문제에서 파이값을 3.14로 해서 파이값 X 반지름 X 반지름 해서 넓이를 구하는 문제에서 파이값은 항상 같기 때문에 변수에 넣어두고 사용하면 됩니다. 하지만 실수로 파이값에 다른 숫자나 변수를 대입하여 값이 변경되면 에러가 발생하게 됩니다. 그래서 파이변수를 선언할 때 const 키워드를 넣어서 상수화 합니다. 그러면 절대 변경될 일이 없기때문에 안정적인 프로그램이 됩니다. 혹시라도 값을 대입하는 코드를 작성하면 컴파일 단계에서 에러가 발생하기 때문에 쉽게 버그를 수정할 수 있습니다. 

 그리고 파라미터로 객체의 주소값을 받으면 해당 객체를 수정하지 못하게 파라미터 선언에 const 키워드를 붙이면 해당 함수에서 파라미터로 넘어온 값을 수정할 수 없게 됩니다. const 키워드를 사용해도 되는 곳이라면 최대한 많이 사용하는 것이 안정적인 코드를 작성하는 방법 입니다. 



1. 공통점


두 키워드 모두 한번 값이 정해지면 수정을 할 수 없다.



아래 두 예제를 보자.

두 예제 모두 클래스에 string 변수를 선언해서 const 키워드를 사용하였다. 해당 test 변수는 상수가 되어서 선언한 곳 이외에서는 수정이 될 수 없다.

혹시라도 수정하려고하면 컴파일 에러가 발생하게 된다.


- const 예제

class TEST
{
	private const string test = "test1234";
	public string getTest()
	{
		return test;
	}
}

class Program
{
	static void Main(string[] args)	
	{
		TEST t1 = new TEST();
		Console.WriteLine("t1 : " + t1.getTest());
	}
}


- readonly 예제

class TEST2
{
	private const string test = "test1234";
	public string getTest()
	{
		return test;
	}
}

class Program
{
	static void Main(string[] args)	
	{
		TEST2 t2 = new TEST();
		Console.WriteLine("t2 : " + t2.getTest());
	}
}



2. 차이점


const키워드와 readonly 키워드의 가장 큰 차이점은 const와는 다르게 readonly 는 생성자에서 값을 초기화 할 수 있다. 아래 예제를 보자. 생성자를 통해 readonly 값을 수정하는 것을 볼 수 있습니다.


- readonly 예제

class TEST
{
	public readonly string test = "start";
	public TEST()
	{
		test = "abc";
	}
	public TEST(string s1)
	{
		test = s1;
	}
}
class Program
{
	static void Main(string[] args)
	{
		TEST t1 = new TEST();
		TEST t2 = new TEST("def");
		Console.WriteLine("t1 : " + t1.test);
		Console.WriteLine("t2 : " + t2.test);
	}
}


test는 readonly 선언을 하였고 초기화를 하였지만 생성자인 TEST()와 TEST(string s1)에서 다시 초기화를 하고있다.

const 선언을 하면 에러가 발생하게 된다.


그리고 const는 선언과 동시에 static이 컴파일러에 의해 자동으로 붙게된다. 정적 필드가 되는것이다. 하지만 readonly의 경우 그렇지 않기 때문에 선언적으로 static 키워드를 붙여주면 정적 필드로 사용이 가능하다.


확장성이 좋고 생성자에 따른 값도 변경할 수 있다. 되도록 const 키워드 이외에 readonly 키워드를 사용하는 것이 좋다.


주의할점은 아래코드와 같이 함수에 지역변수에는 readonly 키워드를 사용할 수 없다.







Posted by 꿈만은공돌
,

C와 C++에서 직접 new 연산자와 malloc 함수를 이용해서 힙(heap)영역에 동적 메모리 할당을 받고 delete 연산자와 free 함수를 이용해서 동적 메모리를 해제하게 된다.


C, C++에서 메모리 할당과 해제 원리 : http://hijuworld.tistory.com/28


하지만 C#과 JAVA 에서는 가비지 컬렉터(garbage collecter)에 의해서 메모리 할당과 메모리 해제가 이루어진다.


사용하지 않는 메모리를 가비지 컬렉션(garbage collection)이 해제 시킨다.


우선 간단하게 메모리를 할당하는 방식에 대해서 알아 보자.




1. C#에서 객체 생성시 메모리 할당 방식

new 키워드를 통해 객체를 생성하게 되면 힙(Heap) 메모리 영역에 객체가 할당된다. C언어와 C++과 다른점은 메모리 첫영역부터 차례대로 할당하게 되고 할당이 가능한 부분의 첫 주소에 포인터로 가르키게 된다. 다음에 또 객체를 생성하면 할당 가능한 메모리의 포인터 영역부터 메모리를 할당하고 다시 해당 포인터는 할당된 사이즈 만큼 증가하게 됩니다.


C, C++과 다르게 이렇게 하면 메모리 할당이 가능한 영역을 찾는 과정이 없기 때문에 속도가 훨씬 빠르다.

아래 그림을 보면 쉽게 이해가 갈것이다.








2. C#에서 Garbage Collector에 의한 메모리 해제 방식

Test a = new TEST();

a = null;

위와 같은 코드를 만나면 처음에 할당한 변수 a가 가지고 있는 객체는 더이상 사용하지 않기 때문에 메모리 해제의 대상이 됩니다.

힙영역에 메모리가 계속 쌓이다 보면 특정 시점에서 Garbage collector에 의해 사용되지 않는 메모리(아래 그림에서 B, D)는 해제하게 된다.

Root Reference 메모리 영역에서 사용하는 메모리 영역을 관리하게 되고(아래 그림의 A와 C) 객체에서 할당한 객체도 존재(아래 그림에서 E)한다. Root Reference를 탐색하여 A와 C를 찾고 다시 A와 C를 검색하여 E를 찾아낸다.

그러면 그외에 메모리 영역인 B와 D는 사용하지 않는 메모리 영역으로보고 메모리를 해제한다.

그리고 남은 A, C, E를 다시 재배치 한다.

그리고 다음 객체를 할당할 메모리의 포인터의 위치도 재배치 한다.




여기까지가 간단하게 설명한 메모리 할당 및 해제 원리이다.

사실 가비지 컬렉션에 방식은 이외에도 상당히 복잡하다.


1. 세대별 가비지 컬렉션 방식 : http://hijuworld.tistory.com/41


2. 용량이 큰 객체에 메모리 할당 해제 방식 : http://hijuworld.tistory.com/46


3. 가비지 컬렉션의 모드에 따른 방식


따라서 이 3가지 방식에 대해서 다음 포스팅에서 설명하겠다.




참고 사이트 및 서적 

- c# GC.Collect() 정리 : http://helloit.tistory.com/40

- 유경상의 닷넷 블로그 : http://www.simpleisbest.net/post/2011/04/01/Review-NET-Garbage-Collection.aspx

- MSDN : https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals

- 뇌를 자극하는 C# 4.0(한빛미디어, 박상현 지음)





Posted by 꿈만은공돌
,


프로젝트를 할때 함수별 또는 클래스 별로 단위 테스트를 진행하고 문제가 있는지 검증을 해보면 좀더 완성도 높은 프로그램을 만들 수 있다.


Microsoft Visual Studio 2017에서 이런 단위 테스트를 어떻게 하는지 알아보자.


클래스를 만들어 그에 속한 함수들을 검증해보는 단위 테스트 프로젝트를 만들어 보자.


1. 파일 -> 새로 만들기 -> 프로젝트 선택




2. Visual C# -> .NET Standard -> 클래스 라이브러리(.NET Standard) -> 라이브러리 이름 지정 -> 확인 클릭






3. 클래스의 이름을 원하는 이름으로 변경하기




4. 아래와 같은 확인창이 뜨면 예(Y) 클릭 




5. 라이브러리에 클래스와 코드를 작성 -> Ctrl + s 로 저장



- 코드 -

using System;

namespace AddLib
{
    public class AddClass
    {
		public double double_add(double a, double b)
		{
			return a + b;
		}
		public int int_add(int a, int b)
		{
			return a * b; //테스트를 위해 오타 작성
		}
	}
}



이제 위에서 작성한 코드를 테스트 하는 단위 테스트 프로젝트를 만들자


1. 파일 추가 -> 추가 -> 새 프로젝트




2. visual C# -> NET Core -> xUnit 테스트 프로젝트 선택 -> 프로젝트 이름 입력 -> 확인 클릭 




3. 단위 테스트 프로젝트에 이전에 작성한 클래스를 추가한다. 

솔루션 탐색기 -> 단위 테스트 프로젝트에 종속성 -> 참조 추가




4. 이전에 테스트용으로 만든 프로젝트 추가




5. 단위테스트 프로젝트에 테스트 코드 작성



- 코드 -


using System;
using Xunit;
using AddLib; //추가
namespace XUnitTestProject1
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
			double a = 3;
			double b = 4;
			double expected = 7;
			var calc = new AddClass();
			var actual = calc.double_add(a, b);
			Assert.Equal(expected, actual);
		}
		[Fact]
		public void Test2()
		{
			int a = 3;
			int b = 4;
			int expected = 7;
			var calc = new AddClass();
			var actual = calc.int_add(a, b);
			Assert.Equal(expected, actual);
		}
	}
}


6. 단위 테스트 시작

테스트 -> 창 -> 테스트 탐색기




7. 테스트 탐색기 -> 아래 그림과 같이 테스트 실행




8. 아래와 같이 테스트 결과 확인

예상한대로 한개의 에러가 발생하였다.



참고 : 크로스 플랫폼 개발을 위한 C# 7과 닷넷 코어 2.0(에이콘) 서적



Posted by 꿈만은공돌
,


C#에 컬렉션 중 하나인 Dictionary 이다.

아래 예제는 인자 추가(Add()) 및 삭제(RemoveAt()) 관한 내용이다.

using System.Collections.Generic;

var dict = new Dictionary<int, String>();

dict.Add(1, "one");
dict.Add(2, "two");
dict.Add(3, "three");
Console.WriteLine("----------1----------");
foreach (KeyValuePair<int, string> item in dict)
{
    Console.WriteLine(item.Key + " " + item.Value);
}

Console.WriteLine("----------2----------");
dict.Remove(2); //key값이 2인 두번째 인자 삭제
foreach (KeyValuePair<int, string> item in dict)
{
    Console.WriteLine(item.Key + " " + item.Value);
}


Posted by 꿈만은공돌
,

C#에 컬렉션 중 하나인 리스트(List) 이다.


아래 예제는 인자 추가(Add()) 및 삭제(RemoveAt()), 삽입(insert())에 관한 내용이다.


using System.Collections.Generic

var list = new List<string>(); // 선언
 
//Add() : 추가
list.Add("Test1");
list.Add("Test2");
list.Add("Test3");
Console.WriteLine("----------1----------");
foreach (string str in list)
{
    Console.WriteLine(str);
}
 
//RemoveAt() : 제거
list.RemoveAt(1); //두번째 인자인 TEST2 제거
Console.WriteLine("----------2----------");
foreach (string str in list)
{
    Console.WriteLine(str);
}
 
//Insert() : 삽입
list.Insert(1, "TEST4"); // 두번째 인자에 TEST4 삽입
Console.WriteLine("----------3----------");
foreach (string str in list)
{
    Console.WriteLine(str);
} 







Posted by 꿈만은공돌
,