String 클래스의 경우 sealed 선언이 되어 있어 상속이 불가능하다.


selaed 키워드에 대해선 아래 링크 참고하자.


- C# 상속금지 키워드 sealed 키워드 : http://hijuworld.tistory.com/44


그래서 String 클래스를 상속받아 새로운 메서드를 추가하여 사용하고 싶어도 불가능하다.

아래 처럼 에러가 발생한다.




하지만 C# 3.0에서 확장 메서드 기능을 사용하면 String에 메서드를 추가한 것 같이 사용이 가능하다.


아래 코드를 보자.


1
2
3
4
5
6
7
8
9
10
using System.Text.RegularExpressions;
 
static class PlusString
{
    public static bool IsValidPhone(this string input)
    {
        Regex regex = new Regex(@"^01[01678]-[0-9]{4}-[0-9]{4}$");
        return regex.IsMatch(input);
    }
}
cs




static으로 선언한 class에 static 변수를 선언하였다. 매개변수를 this string 을 받도록 하였다.

this는 컴파일러에게 이 메서드가 string타입을 확장한 메서드라는 것을 알려주는 역활을 한다. 


그리고 메서드의 내용은 정규표현식을 사용하여 input이 전화번호인지 검사하는 코드이다.
정규표현식을 검사하는 Regex class 에 대해서는 아래 링크를 참고하자.

- C# 정규식을 사용하여 패턴 매칭(Regex, IsMatch) : http://hijuworld.tistory.com/22



이제 해당 확장메서드를 사용해보자.


아래코드는 그 예시이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Program
{        
    static void Main(string[] args)
    {
        String test = "010-9559-4444";
        if (test.IsValidPhone())
            Console.WriteLine("일치");
        else
            Console.WriteLine("불일치");
 
        test = "012-9559-4444";
        if (test.IsValidPhone())
            Console.WriteLine("일치");
        else
            Console.WriteLine("불일치");
    }
}
cs


실제로 String 클래스의 메서드처럼 사용하면 된다.



참고자료 : 크로스 플랫폼 개발을 위한 C# 7과 닷넷 코어 2.0 ( 에어콘 출판사) 7장



Posted by 꿈만은공돌
,

class 선언앞에 sealed 키워드를 붙이면 해당 클래스의 상속을 막을 수 있다.



Test2에서 Test 클래스를 상속하려 했지만 sealed 때문에 에러가 발생하는 것을 볼 수 있다.

C#에서 대표적으로 string 클래스가 sealed 클래스이다.

따라서 string 클래스는 상속을 할 수 없다.


그리고 함수선언시에 sealed 키워드를 붙이면 상속할때 해당 함수를 오버라이딩을 할 수 없다.


sealed 클래스에 대해서 메서드를 추가하고싶다면 아래 링크를 참고하자.


C#/.NET 확장 메서드 기능, String에 메서드 추가 하기 : http://hijuworld.tistory.com/45

Posted by 꿈만은공돌
,

C#에서 구조체(struct)와 클래스(class)가 어떻게 다른지 알아보자.


사용하는데 있어선 차이를 알기 힘들다. 일반적으론 구조체는 함수가 없고 변수들만 저장 가능하고 클래스는 변수이외에 함수도 포함시킬 수 있다고 알고 있다. 하지만 구조체에도 함수를 포함 시킬 수 있다. 그렇다면 무슨차이가 있는지 보도록 하자. 


아래 두개의 코드를 보자.

-구조체 코드-


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct Point
{
    public int X;
    public int Y;
    public int Z;
    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 String toString()
    {
        return "x: " + X + ", y: " + Y + ", z: " + Z;
    }
}
cs





-클래스 코드-


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Point
{
    public int X;
    public int Y;
    public int Z;
    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 String toString()
    {
        return "x: " + X + ", y: " + Y + ", z: " + Z;
    }
}
cs


코드상으론 선언을 하는 부분을 제외하면 아무런 차이가 없다.

클래스와 마찬가지로 구조체도 필드로 변수와 함수들을 가질 수 있으며 생성자 역시 가질 수 있다.


하지만 큰 차이 2가지가 존재한다.

1. 구조체(struct)는 상속을 할 수 없다.

2. 클래스(class) 객체는 힙(heap)에 할당되지만 구조체(struct) 객체는 스택(stack)에 할당된다.


두 가지 차이중 2번이 핵심이다. 

스택의 경우 사용할 수 있는 메모리 크기가 작고 한정적인 반면 힙은 많은 메모리크기를 가질 수 있다.

하지만 스택의 경우 가비지컬랙션에 의해 관리되지 않기 때문에 성능상 많은 장점이 있다.


실제 메모리 할당이 어떻게 되는지 아래 실험으로 보자.

우선 Main문 안에 아래와 같이 객체 1000개를 차례로 할당 받는 코드를 작성해보자.

Sleep(100)은 100ms 즉 0.1초간 딜레이는 거는 역할을 한다.


1
2
3
4
5
6
7
8
9
static void Main(string[] args)
{
    Point []p = new Point[1000];
    for (int i=0; i< 1000; i++)
    {
        p[i] = new Point(i, i + 1, i + 2);
        System.Threading.Thread.Sleep(100);
    }
}
cs

 


그리고 디버깅 모드로 실행시킨 후에 메모리 사용량을 보도록 하자. Visual studio 2017에서는 쉽게 메모리 사용양을 확인할 수가 있다. 중간중간 원하는 지점에서 메모리를 표시해줍니다. 개인적인 생각으론 C#이 가비지컬렉션이 완벽하지 않아서인지 메모리이슈가 많아서 해당기능을 강력하게 지원해주는 것 같다.


아래 구조체(struct)를 객체로 만들때에는 아래와 같이 힙 영역에 차이가 없는 것을 볼 수 있다. 



아래 클래스(class)의 경우 시간이 증가함에 따라 객체의 갯수와 힙 메모리의 크기역시 증가하는 것을 볼 수 있다. 




하지만 구조체로 선언을 했어도 객체를 힙영역에 메모리를 할당할 때가 있다. 


1. 모든 필드의 합이 16byte를 넘는 경우이다.

2. 구조체안에 클래스 타입을 필드로 가질 경우이다.


C#에서는 위의 두경우와 상속이 필요한 경우가 아닌 경우라면 구조체로 선언해서 스택(stack) 메모리 영역을 사용하기를 권장한다. 스택은 기본적으로 가비지 컬렉션의 대상이 아니다. 선언한 함수가 종료되면 그때그때 해제가 되기때문에 heap영역을 사용하는 것보다 좋은점이 많다고 생각된다. 가비지 컬랙션이 자동으로 메모리를 해제시켜주긴 하지만 많은 오버해드가 발생할 수가 있다. 가비지 컬랙션에 동작원리를 알면 이해가 갈 것이다. 

되도록 16바이트가 넘지 않는다면 구조체를 사용하는 버릇을 들이는 것이 좋다.


C, C++에서 메모리 할당과 해제 원리에 관한 포스팅 입니다. http://hijuworld.tistory.com/28


번외로 C++에서는 C#과 다르게 클래스는 public 선언을 하지 않으면 private이 되지만 구조체는 기본적으로 public 으로 된다.

Posted by 꿈만은공돌
,

이벤트를 이해하기 위해선 델리게이트(delegate)를 우선 알아야 한다.

델리게이트에 대해서는 아래 링크를 참조하자.

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


이벤트를 발생시키는 것은 델리게이트를 정의해서 메서드를 간접 호출하는 방식이다. 이러면 메서드 호출에 유연성을 가질 수 있기 때문이다.


처음에는 이벤트를 이해하는게 조금 어려울 수 있다.

우선 예제를 보자.

GUI라는 클래스에 버튼을 누르면 이벤트가 발생하게 하는 코드이다.


1
2
3
4
5
6
7
8
9
10
class GUI
{
    public event EventHandler OkButtonClickHandler;
    public void OkButtonClick()
    {
        if ( OkButtonClickHandler != null ) {
            OkButtonClickHandler(this, EventArgs.Empty);
        }
    }
}
cs

event EventHandler 핸들러_이름; 

위와같은 형식으로 핸들러를 선언한다.

그리고 특정 조건에서 해당 이벤트를 호출한다.


public delegate void EventHandler(object sender, EventArgs e);

위는 미리 정의된 델리게이트 원형을 표시한 것이다.

따라서 OkButtonClickHandler(this, EventArgs.Empty); 와 같은 코드로 이벤트를 발생시킨다.


이제 GUI에 객체를 만들고 이벤트 핸드러에 함수를 등록하고 이벤트를 호출시키는 코드를 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Program
{
    static void Main(string[] args)
    {
        GUI gui = new GUI();
        gui.OkButtonClickHandler += Gui_OkButtonClickHandler; //핸들러에 등록
        //...
        gui.OkButtonClick(); //버튼클릭
        gui.OkButtonClick(); //버튼클릭
        gui.OkButtonClick(); //버튼클릭
    }
    private static void Gui_OkButtonClickHandler(object sender, EventArgs e)
    {
        Console.WriteLine("ok button click ");        
    }
}
cs




+= 연산자를 사용하여 핸들러에 Gui_OkButtonClickHandler() 함수를 정의하고 추가하였다.

반대로 -=연산자를 사용하면 핸들러에 등록한 함수를 제외 시킬 수 있다.

그리고나서 OkButtonClick() 함수를 호출해서 이벤트를 3번 발생시켰다.


- 실행 결과 -

ok button click

ok button click

ok button click




핸들러에 함수를 추가를 할때 visual studio 2017에서 자동으로 함수를 만들어주는 기능이 있다.

아래와 같이 핸들러에 += 를 입력하면 탭키를 누르면 자동삽입을 해준다고 뜬다.



그러면 아래와 같이 함수가 만들어지고 해당 함수가 핸들러에 삽입되는 코드가 자동으로 작성된다.





그리고 핸들러 선언을 public으로 선언 했는데 정보은닉을 위해 private으로 선언도 가능하다.

아래 코드가 그 예시이다. add, remove 키워드로 핸들러에 함수를 추가 및 제거 가능하다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class GUI
{
    private EventHandler _okButtonClickHandler;
    public event EventHandler OkButtonClickHandler {
        add
        {
            _okButtonClickHandler += value;
        }
        remove
        {
            _okButtonClickHandler -= value;
        }
    }
    public void OkButtonClick()
    {
        if (_okButtonClickHandler != null )
            _okButtonClickHandler(this, EventArgs.Empty);
    }
}
cs





Posted by 꿈만은공돌
,

이전에 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# 7.0에서 out 키워드를 좀더 사용하기 쉽게 변경되었다.

기존엔 변수선언을 하고 인자를 전달해야 했지만 C# 7.0 부터는 선언과 동시에 값을 전달 할 수 있도록 변경되었다.


아래 예제를 보자.

기존에 out 키워드를 사용하는 방식이다.

class TtestClass
{
	public void outTest(out int a)
	{
		a = 10;
	}
}
class Program
{
	static void Main(string[] args)
	{
		TtestClass t = new TtestClass();
		int b; //선언을 먼저 해주어야 한다.
		t.outTest(out b);
		Console.WriteLine("b : " + b);
	}
}	


아래는 C# 7.0에서 변경된 방식이다.

class TtestClass
{
	public void outTest(out int a)
	{
		a = 10;
	}
}
class Program
{
	static void Main(string[] args)
	{
		TtestClass t = new TtestClass();
		t.outTest(out int b); //선언과 동시에 인자 전달
		Console.WriteLine("b : " + b);
	}
}


Posted by 꿈만은공돌
,