< 이벤트란? >

이벤트(event)는 어떤 사건이 발생했을 때 통보를 받고 해당 사건에 대응하여 처리를 수행하는 프로그래밍 패턴이다. 예를 들어, 버튼 클릭, 키 입력, 타이머 만료 등과 같은 상황을 이벤트라고 할 수 있다. 이벤트는 특정 사건이 발생했을 때 델리게이트를 통해 해당 사건을 처리하는 메서드를 호출한다.

< 이벤트 생성하기 >

이벤트를 사용하기 위해서는 먼저 이벤트를 정의해야 한다.

이벤트를 정의할 때는 event 키워드를 사용한다.

public delegate void MyEventHandler(string message); // 델리게이트 정의
public event MyEventHandler MyEvent; // 이벤트 정의


< 이벤트 구독 및 처리하기 >

이벤트가 발생했을 때 실행할 메서드를 작성하고, 이 메서드를 이벤트에 연결한다. 이를 이벤트 구독(subscription)이라고 한다.

아래의 Form1과 Form2 코드가 있다.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            Form2 form2 = new Form2();
            form2.Show();
            form2.OnCount += MyCount;
        }

        int c = 0;

        //자식클래스가 보낸데이터 수신(구독)
        public void MyCount(int myCount)
        {
            c += myCount;
            label1.Text = c.ToString();
        }
        
    }
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp2
{
    public partial class Form2 : Form
    {
        public delegate void CountHandler(int addCount);
        public event CountHandler OnCount;

        public Form2()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            //이 예제에서 매개변수로 int를 전달하지만.
            //개발자가 만든Class의 객체,string도 가능
            OnCount(1);
        }
    }
}

 

[Form2 (이벤트 생성자)]

먼저 Form2에서는 델리게이트를 정의한다. 이 델리게이트는 int형 매개변수를 받는 메서드를 참조한다.

델리게이트를 사용하여 이벤트를 정의한다. 이 이벤트는 Form2의 버튼이 클릭될 때 발생시킬 것이다.

public delegate void CountHandler(int addCount);

 

button1_Click 메서드에서는 이벤트를 발생시킨다. 이 예제에서는 1이라는 값을 매개변수로 전달한다.

public event CountHandler OnCount;

[Form1 (이벤트 구독자)]
Form1에서는 Form2의 인스턴스를 생성하고, Form2의 OnCount 이벤트에 대한 구독을 설정한다. 이때 Form1 내의 MyCount 메서드가 구독자로 동작한다.

private void button1_Click(object sender, EventArgs e)
{
    Form2 form2 = new Form2();
    form2.Show();
    form2.OnCount += MyCount;
}

 

MyCount 메서드는 Form2로부터 받은 값을 c에 더하고, 이를 레이블에 표시한다. 이 메서드는 Form2의 OnCount 이벤트에 의해 호출된다.

int c = 0;

public void MyCount(int myCount)
{
    c += myCount;
    label1.Text = c.ToString();
}

 

 

이렇게 Form2에서 발생한 이벤트가 Form1에서 처리된다. Form2의 버튼을 클릭할 때마다 Form1의 레이블에 표시되는 값이 증가한다. 이벤트를 사용함으로써 두 폼 사이에 느슨한 결합(loose coupling)을 유지할 수 있고, 코드의 유지 보수가 용이해진다.


Reference : https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/events/

 

Events - C# Programming Guide

Learn about events. Events enable a class or object to notify other classes or objects when something of interest occurs.

learn.microsoft.com

C#.NET 0.5년차~3년차(파트1)

※ 블로그에 있는 모든 강의와 책의 내용은 저작권자(출판사/저자,공동저자)에게 직접 허락을 받아 작성되었습니다.

https://www.inflearn.com/course/lecture?courseSlug=%EB%8B%B7%EB%84%B7-%EC%9C%88%ED%8F%BC-1&unitId=77892&tab=curriculum 

 

학습 페이지

 

www.inflearn.com

 

'C#' 카테고리의 다른 글

Timer  (0) 2023.06.16
일반화 프로그래밍  (0) 2023.06.15
크로스 스레드  (0) 2023.06.15
스레드 문제점 해결  (0) 2023.06.14
비동기, 동기  (0) 2023.06.14

< 크로스 스레드란 ? >

크로스 스레드 문제는 멀티 스레드 환경에서 발생한다. Form 컨트롤러를 생성한 UI 스레드 외에 다른 스레드가 컨트롤러에 직접 접근할 경우 발생하는 에러다. 이를 통해 개발자에게 스레드 문제가 있음을 알려준다.

이는 디버그 모드에서만 발생하며, Thread Safety를 보장하지 않는다. 크로스 스레드가 발생하면, Program이 실행되다가, OS 상황이 안좋아지면, 프로그램에 영향을 줄 수 있다.

< Thread Safety란? >

Thread Safety는 변수, 객체 또는 다른 공유 자원이 여러 스레드로부터 동시에 접근이 이루어져도 프로그램 실행에 문제가 없음을 의미한다. 즉, 공유 자원의 상태가 예상 가능하고 일관된 상태를 유지해야 한다.

< UI-Thread 외 다른 스레드에서 Controller를 안전하게 하는 방법 >

크로스 스레드 문제를 해결하기 위해 Invoke와 Delegate 조합 사용하여 동기적으로 UI 스레드에게 해당 함수를 호출하도록 요청할 수 있다. 이렇게 하면 UI 스레드가 컨트롤러를 안전하게 업데이트한다.(비동기로 할려면 BeginInvoke를 사용해도 된다.)

또 매게 변수가 없고 Return Type이 없는 것은 new Action을 통해서도 할 수 있다.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        Thread thread2 = null;

        private void Form1_Load(object sender, EventArgs e)
        {
            try
            {
                thread2 = new Thread(new ThreadStart(SendLogic));
                thread2.IsBackground = true;
                thread2.Priority = ThreadPriority.Normal;
                thread2.Start();
            }
            catch (Exception ex)
            {
                //에러 로그기록
            }
            
        }

        private void SendLogic()
        {
            try
            {

                this.Invoke(new Action(() =>
                {
                    label1.Text = "bb";
                    label2.Text = "bb";
                }));
//MethodInvoker를 사용하는 이유 : Invoke 메서드와 MethodInvoker를 사용하면 UI 스레드에서 코드를 안전하게 실행할 수 있기 때문
                Invoke((MethodInvoker)delegate {
                    label1.Text = "cc";
                    label2.Text = "cc";

                });
            }
            catch (Exception ex)
            {
                //에러 로그기록
            }

        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            try
            {
                if (thread2.IsAlive)
                {
                    thread2.Abort();
                }
                thread2 = null;
            }
            catch (Exception ex)
            {
                //에러 로그기록
            }
        }
    }
}

private void Form1_FormClosing(object sender, FormClosingEventArgs e) :

이 부분은 Form1의 FormClosing 이벤트 핸들러다. 이 핸들러는 폼이 닫히기 직전에 호출된다. 여기서 sender는 이벤트를 발생시킨 객체를 나타내고, e는 이벤트와 관련된 추가 정보를 담고 있다.

try : 코드 블록 내에서 발생할 수 있는 예외를 처리하기 위해 try 블록 사용.

if (thread2.IsAlive) : thread2가 여전히 실행 중인지 확인. IsAlive 속성은 스레드가 아직 실행 중인 경우 true를 반환한다.

thread2.Abort() : thread2가 실행 중인 경우, Abort() 메서드를 호출하여 스레드를 중단시킨다. ※(이 메서드는 스레드를 즉시 중지하는 것이 아니라, 스레드가 중단될 수 있을 때 중단하도록 요청한다.)


thread2 = null; : thread2 참조를 null로 설정하여 더 이상 참조되지 않도록 한다. 이렇게 하면 가비지 컬렉터가 나중에 메모리를 정리할 수 있다.

catch (Exception ex) : try 블록 내의 코드가 예외를 발생시킬 경우, 이 catch 블록이 실행된다.

//에러 로그기록 : 일반적으로 이 부분에서 예외 정보를 로그 파일이나 콘솔에 기록하는 코드를 작성합니다. 이를 통해 나중에 문제를 분석하고 해결할 수 있다.

 

 

Reference : C#.NET 0.5년차~3년차(파트1)

※ 블로그에 있는 모든 강의와 책의 내용은 저작권자(출판사/저자,공동저자)에게 직접 허락을 받아 작성되었습니다.

https://www.inflearn.com/course/lecture?courseSlug=%EB%8B%B7%EB%84%B7-%EC%9C%88%ED%8F%BC-1&unitId=77892&tab=curriculum 

 

학습 페이지

 

www.inflearn.com

 

'C#' 카테고리의 다른 글

일반화 프로그래밍  (0) 2023.06.15
이벤트 생성/호출  (0) 2023.06.15
스레드 문제점 해결  (0) 2023.06.14
비동기, 동기  (0) 2023.06.14
스레드 동기화(lock,AutoResetEvent)  (0) 2023.06.13

멀티스레딩을 사용하면 한 프로세스 내에서 여러 개의 작업을 동시에 처리할 수 있다. 하지만 이런 스레드들을 잘 관리하지 않으면 리소스가 해제되지 않는 문제가 발생할 수 있다.

 

이는 시스템 자원을 불필요하게 점유하게 되어 프로그램의 성능을 저하시키거나 메모리 누수 같은 문제를 발생시킬 수 있다.

< 스레드의 자원을 제대로 해제하는 두 가지 방법 >

[Background 속성 이용]
스레드는 기본적으로 Foreground 스레드로 생성된다. 때문에 메인(UI) 스레드가 종료되어도 해당 스레드는 계속 실행된다. 하지만 Background 스레드로 설정하면 메인 스레드가 종료될 때 해당 스레드도 함께 종료되어 자원을 해제하게 된다.

 Thread.IsBackground = true;를 통해 해당 스레드를 Background 스레드로 설정할 수 있다.이렇게 하면 해당 스레드가 프로그램이 종료되는 시점에서 함께 종료되도록 한다.

[Form Closing]
Form이 종료되는 시점에 작동하는 이벤트인 Form Closing을 이용해 스레드의 자원을 해제할 수도 있다. 만약 Form이 닫히는 시점에 생성된 스레드가 아직 실행 중이라면 해당 스레드를 종료하고 자원을 해제하는 코드를 작성한다. 이를 통해 스레드가 더 이상 필요하지 않은 시점에 정확하게 스레드를 종료하고 자원을 회수할 수 있다.

< 실습 >

아래와 같이 코드를 작성하고 솔루션 빌드 후, 실행을 하고 Form1 창을 닫았다. 그러면 정말 종료가 된 것일까?

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        Thread th = null;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //쓰레드 자원할당
            th = new Thread(new ThreadStart(methodStart));
            th.Start();//쓰레드가 실행 
        }
        private void methodStart()
        { 
          while(true) 
            { 
                //1초씩 쓰레드가 멈추도록 
                Thread.Sleep(1000);

            }
        }
    }
}

위의 코드를 실행 하고, Form1 창을 닫아 보았다.

 

창을 닫으면, WinFormsApp1이 실행 종료가 된 줄 알았는데 작업관리자에 가보니, Background에서 여전히 실행 중인 것을 알 수 있다.

즉, 아래의 스레드 부분이 계속 실행이 되고 있다는 것이다.

이럴 경우 작업관리자에서 직접 프로세스를 종료를 해줘야한다. 이러한 현상이 일어나는 이유는 스레드 리소스를 해제를 안했다는 것이다.

 

이때 Thread.IsBackground = true;를 사용하게 되면, 창을 닫으면 프로세스도 같이 종료가 되는 것을 확인할 수 있다.

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {
        Thread th = null;

        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            //쓰레드 자원할당
            th = new Thread(new ThreadStart(methodStart));
            th.IsBackground = true;
            th.Start();//쓰레드가 실행 
        }
        private void methodStart()
        { 
          while(true) 
            { 
                //1초씩 쓰레드가 멈추도록 
                Thread.Sleep(1000);

            }
        }
    }
}

※ 주의 사항 Form1에서 Form2를 생성하고, Form2를 실행하도록 하고,

Form2에 th.IsBackground = true;를 설정하면, 스레드가 종료가 될 줄 알았는데, 종료가 안된다.

이유는  UIThread 즉, Program.cs에서 Form2로 변경해야지만, 삭제가 된다.

 

 이렇게 IsBackground를 사용하기 보다는 다시 new Form2를 Form1으로 원상복구 한 뒤,

Form2가 닫히면, 스레드가 종료되도록 하고 싶다면 이때 Form Closing 방법을 쓸 수 있다. 

 

Reference : C#.NET 0.5년차~3년차(파트1)

이 블로그의 모든 내용은 원작자와 출판사로부터 허락을 받아 작성되었습니다.

https://www.inflearn.com/course/lecture?courseSlug=%EB%8B%B7%EB%84%B7-%EC%9C%88%ED%8F%BC-1&unitId=77888&tab=curriculum 

 

'C#' 카테고리의 다른 글

이벤트 생성/호출  (0) 2023.06.15
크로스 스레드  (0) 2023.06.15
비동기, 동기  (0) 2023.06.14
스레드 동기화(lock,AutoResetEvent)  (0) 2023.06.13
멀티스레드  (0) 2023.06.13

< 동기(Synchronous) >

[특징]

a. 한 번에 하나의 작업만 처리한다.
b. 이전 작업이 끝나야 다음 작업이 시작된다.

[일상 예시]

상점에서 손님1이 계산을 하고 있으면, 손님2는 손님1이 계산을 끝내기를 기다려야 한다. 이처럼 한 작업이 끝나야 다음 작업이 시작되는 것을 동기 방식이라고 한다.

 


< 비동기(Asynchronous) >

[특징]
a. 여러 작업을 동시에 처리할 수 있다.
b. 작업들 간에 서로 기다리지 않고 병렬로 진행된다.

[예시]
요리사가 여러 음식을 동시에 준비하는 것과 비슷하다. 비빔면을 조리하는 동안, 짜파게티도 함께 조리할 수 있다.

 

< 장점과 단점 >

동기 :
장점: 코드가 간단하며, 이해하기가 쉽다.
단점: 리소스를 효율적으로 사용하지 못하고, 대기 시간이 길어질 수 있다. (위에서 설명한 것과 같이, 비슷한 예시로 편의점을 생각해봐도 좋다. 계산하시는 분은 원래 한 분이시니, 계산대에 사람이 줄을 서서 기다리고 있는 모습을 상상해보자.)
비동기 :
장점: 리소스를 효율적으로 활용하고, 대기 시간을 줄일 수 있다.
단점: 코드가 복잡해질 수 있고, 디버깅이 어려울 수 있다.

< C# 대표적인 비동기,동기 함수 > 

C#에서 대표적인 비동기 함수는  Async await가 있고 동기 함수는 Sync가 있다.  

비동기 개념중 중요한 개념엔 Task가 있다.

[Async await] :

여러 작업을 동시에 처리할 수 있는 방식.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main()
    {
        // 커피 만들기 시작
        var coffeeTask = MakeCoffeeAsync();

        // 사용자 입력 받기
        var userInputTask = GetUserInputAsync();

        // 커피 만들기 작업과 사용자 입력 받기 작업이 모두 완료될 때까지 기다리기.
        await Task.WhenAll(coffeeTask, userInputTask);

        // 커피가 만들어지고 사용자 입력이 완료된 후에 실행되는 코드.
        Console.WriteLine("커피가 준비되었고 사용자 입력이 완료되었습니다.");
    }

    static async Task MakeCoffeeAsync()
    {
        Console.WriteLine("Coffee making process started.");

        // 물을 끓이는 작업
        Console.WriteLine("Boiling water...");
        await Task.Delay(2000); // 2초 동안 대기 (물 끓이는 시간)

        // 커피를 내리는 작업
        Console.WriteLine("Brewing coffee...");
        await Task.Delay(2000); // 2초 동안 대기 (커피 내리는 시간)
    }

    static async Task GetUserInputAsync()
    {
        Console.WriteLine("성함 :"); // 사용자에게 이름을 입력받는 안내 메시지를 출력.
        string name = await Task.Run(() => Console.ReadLine()); // 사용자 입력을 비동기적으로 받아오기.
        Console.WriteLine($"Hello, {name}!"); // 사용자의 이름을 출력.
    }
}

 

 

[Sync]

한 번에 하나의 작업만 처리하는 방식.

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Console.WriteLine("Coffee making process started.");
        MakeCoffee();
        Console.WriteLine("Coffee is ready.");
    }

    static void MakeCoffee()
    {
        Console.WriteLine("Boiling water...");
        Thread.Sleep(2000);
        Console.WriteLine("Brewing coffee...");
        Thread.Sleep(2000); 
    }
}

 

[Task] :

async await 구문을 사용할 때 Task를 함께 사용한다.

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        // 케이크 굽기 시작 (비동기적으로)
        Task bakeCakeTask = BakeCakeAsync();

        // 파스타 만들기 시작 (비동기적으로)
        Task makePastaTask = MakePastaAsync();

        // 샐러드 만들기 시작 (비동기적으로)
        Task makeSaladTask = MakeSaladAsync();

        // 모든 요리가 완료될 때까지 기다림
        await Task.WhenAll(bakeCakeTask, makePastaTask, makeSaladTask);

        Console.WriteLine("모든 요리가 준비되었습니다!");
    }

    static async Task BakeCakeAsync()
    {
        Console.WriteLine("케이크 굽는 중...");
        await Task.Delay(5000); // 케이크 굽는데 5초 걸린다고 가정
        Console.WriteLine("케이크가 완성되었습니다!");
    }

    static async Task MakePastaAsync()
    {
        Console.WriteLine("파스타 만드는 중...");
        await Task.Delay(3000); // 파스타 만드는데 3초 걸린다고 가정
        Console.WriteLine("파스타가 완성되었습니다!");
    }

    static async Task MakeSaladAsync()
    {
        Console.WriteLine("샐러드 만드는 중...");
        await Task.Delay(2000); // 샐러드 만드는데 2초 걸린다고 가정
        Console.WriteLine("샐러드가 완성되었습니다!");
    }
}

요리사가 비빔면을 조리하면서 동시에 짜파게티를 조리하는 코드

using System;
using System.Threading.Tasks;

class Program
{
    static async Task Main(string[] args)
    {
        Task cookJjapagettiTask = CookJjapagettiAsync();
        Task cookBibimmyeonTask = CookBibimmyeonAsync();

        await Task.WhenAll(cookJjapagettiTask, cookBibimmyeonTask);

        Console.WriteLine("모든 요리가 완료되었습니다.");
    }

    static async Task CookJjapagettiAsync()
    {
        Console.WriteLine("짜파게티 요리를 시작합니다.");
        await BoilWaterAsync("짜파게티");
        Console.WriteLine("짜파게티 면을 삶습니다.");
        await Task.Delay(2000); // 면을 삶는데 시간이 걸림
        Console.WriteLine("짜파게티를 양념합니다.");
        await Task.Delay(1000); // 양념하는데 시간이 걸림
        Console.WriteLine("짜파게티 요리가 완료되었습니다.");
    }

    static async Task CookBibimmyeonAsync()
    {
        Console.WriteLine("비빔면 요리를 시작합니다.");
        await BoilWaterAsync("비빔면");
        Console.WriteLine("비빔면 면을 삶습니다.");
        await Task.Delay(2000); // 면을 삶는데 시간이 걸림
        Console.WriteLine("비빔면을 양념합니다.");
        await Task.Delay(1000); // 양념하는데 시간이 걸림
        Console.WriteLine("비빔면 요리가 완료되었습니다.");
    }

    static async Task BoilWaterAsync(string dishName)
    {
        Console.WriteLine($"{dishName}을(를) 위해 물을 끓이는 중...");
        await Task.Delay(1000); // 물 끓이는데 시간이 걸림
    }
}

 

 

 

Reference : C#.NET 0.5년차~3년차(파트1)

이 블로그의 모든 내용은 원작자와 출판사로부터 허락을 받아 작성되었습니다.

https://www.inflearn.com/course/lecture?courseSlug=%EB%8B%B7%EB%84%B7-%EC%9C%88%ED%8F%BC-1&unitId=77888&tab=curriculum 

 

학습 페이지

 

www.inflearn.com

 

'C#' 카테고리의 다른 글

크로스 스레드  (0) 2023.06.15
스레드 문제점 해결  (0) 2023.06.14
스레드 동기화(lock,AutoResetEvent)  (0) 2023.06.13
멀티스레드  (0) 2023.06.13
C# 프로퍼티, 객체 초기화  (0) 2023.06.08

스레드를 사용하다 보면, 변수를 스레드끼리 공유해서 사용하는 경우가 있다.

그럴 때, 스레드 동기화를 해주면 둘 이상의 스레드가 서로의 작업을 덮어쓰지 않고 공유 자원에 안전하게 엑세스 할 수 있는 방법이다.

출처 : C#.NET 0.5년차~3년차(파트1)

처음 a값에 변수 5를 가지고 있는데 Thread2에서 a 값을 4로 변경하고 getValue(a)를 통해서 4값을 동기화를 해주면, 4값을 얻을 수 있는데, 만약 동기화가 안되어 있다면 getValue(a)에서 4를 기대했으나, Thread1에 의해서 3으로 변경한 것이 영향을 받게 되어, 3의 값을 얻게 되버린다.

 

Thread2가 a=4;로 선언했으면 Thread2에서 getValue(a)를 했을 시, 4를 얻을 수 있는 영역을 임계영역(Critical section)이라고 한다. 임계 영역은 공유 자원에 Thread Safety하게 접근하는 영역이다.

 

a=5;를 하고

Thread2 에서 a=4;로 변경을 요청한다.

그런데 Thread2을 임계영역으로 두게 된다면 Thread1의 a=3;요청은 뒤로 미뤄지게 되고, Thread2가 독점적으로 임계영역으로 사용하게 되고, Thread2에서 getValue(a)를 하게 되면 4가 출력된다. 그리고 임계 영역이 끝나면, 공유자원에 대한 독점을 해제 함으로써, Thread1이 a를 3으로 변경하게 되는 작업을 하게 된다.

 

 

< 스레드 동기화 문법 >

[lock 키워드]

C#에서는 lock 키워드를 통해 공유 자원에 대해서 서로 작업을 스레드끼리 충돌이 안나게끔 해줄 수가 있게 된다. 

 

먼저 동기화에 사용할 객체를 정의해줘야 한다.ex) object lockObj = new object(); 

 

그 다음에 lock 키워드를 적고 나서 생성한 객체를 소괄호 안에 넣어주면 만든 object에 대해서 상호 베재 잠금을 획득하게 된다.  

< 실습 > 

아래와 같이 코드를 작성하게 되면, Thread2에서는 Thread1의 영향을 받아, Message Box에서는 3이 출력되는 상황이 벌어진다. 

이런 상황에서 서로간에 영향을 안받게 할려면 lock() 키워드를 사용하게 된다는 것이다.

 

lock 키워드 안에는 잠금에 사용할 객체를 만들어 줘야 한다.

잠금에 사용할 객체 생성
상호배제 잠금하기

여기서 While문을 통해서 이런 작업이 반복적으로 일어나도록 할 수 있다.

위와 같이 코드를 작성하면, 의도한 대로 Thread1에 영향을 받지 않고 4가 출력이 된다는 것을 확인할 수 있다. 

Form1 실행 후 4초 뒤 4가 출력되고 확인을 누르면 또 4초뒤에 반복적으로 해당 메세지가 출력된다.

< 코드의 실행 순서 >

Message.show에서 3초 후에 나타나는 줄 알았는데 Thread1의 Thread.Sleep의 시간도 같이 합해지는 이유를 몰랐었다.

이에 대해 친구에게 매우 상세하게 답변을 얻었다.

 

우선 상상을 해야한다.

 

두 명의 사람이 한 대의 컴퓨터를 쓰려고 하는 상황이다.

컴퓨터는 한 번에 한 사람만 사용할 수 있고, 각자 쓰고자 하는 프로그램이 있다고 가정해보자.

사람 1은 컴퓨터를 쓰려고 자리에 앉는다 그리고선 컴퓨터를 사용하기 시작하는데, 갑자기 2분 동안 쉬기로 결정한다.

하지만 사람1은 컴퓨터를 점유한 상태다.
사람 2는 컴퓨터를 사용하고 싶지만, 사람 1이 여전히 컴퓨터 앞에 앉아 있으므로 기다려야 하는 상황이다.
사람 1이 쉬는 것을 끝내고 컴퓨터를 떠나면, 사람 2는 컴퓨터를 사용하기 시작한다. 그러나 이제 사람 2가 컴퓨터를 사용하면서 3분 동안 쉬기로 결정한다.


이 예제에서, 사람 1은 WorkThread 메서드를, 사람 2는 Work2 메서드를, 컴퓨터는 공유 자원인 변수 a를 상징한다.

사람들이 쉬는 시간은 Thread.Sleep 메서드에 해당한다.

MessageBox.Show가 나타나는 시점은 사람 2가 컴퓨터를 사용하기 시작한 이후다.

그러나 사람 2는 사람 1이 쉬는 시간 동안 기다려야 했기 때문에, 실제로는 사람 1의 쉬는 시간 + 사람 2의 쉬는 시간 만큼 기다린 후에 MessageBox.Show가 나타난다.

즉, Work2 스레드는 WorkThread 스레드가 lock을 해제할 때까지 기다려야 하고, WorkThread는 2초 동안 쉰다.

그 후 Work2가 실행되고, 추가로 3초를 기다린 후에 MessageBox가 표시된다. 따라서 총 대기 시간은 2초 + 3초인 5초가 되는 것이다.

 

이런 lock 키워드는 파일 읽고쓰기,네트워크 전송,수신,DB작업 등과 같은 곳에 사용할 수 있다.

 

※< lock keyword 사용할 때 주의점 >

lock(obj)
{
…
  lock(obj)
  {
    …  
  }
…
}//상호배제잠금을해제

위와 같이 lock안에 동일한 객체에 잠금을 요청하게 된다면, lock을 해제 하기 전에 lock을 또 시도하기 때문에

교착상태(DeadLock) 발생할 수 있다.

(위와 같은 코드를 사용한 프로그램 멈출 가능성이 높다. try catch에

잡히지 않기에 원인도 파악하기 힘들다.)


< AutoResetEvent > 

스레드를 동기화 하는 방법 중 두 번째는 AutoResetEvent가 있다. 

 

AutoResetEvent를 사용하게 된다면 Thread간의 실행 순서를 제어할 수 있게 된다.

예를 들어 Thread1이 작업이 끝나야지만 Thread2가 동작이 되게 하게 끔을 할 수 있는 것이다.

 

Thread간의 순서를 둬야할 경우, 위의 그림과 같이 DB에 데이터를 먼저 쓰고 나서

데이터를 읽는 작업을 Thread2에서 한다거나 할 때 AutoResetEvent를 사용하면 효과적이다.

 

또 DB데이터 쓰는 작업이 계속해서 반복 될 때 오래걸리게 되면, Thread2가 오래걸리는 작업을 영향을 안받고 데이터를 읽을 수 있다. 이렇듯 서로의 작업에 영향을 받지 않고 작업을 하는데 단지 먼저 데이터가 쓰여야지만, 읽을 수 있게끔 방법을 만들 수 있다.

 

이 코드를 실행하면 autoResetEvent.WaitOne();이 신호를 받을 때 까지 기다리고 있다가, Thread.Sleep(5000);에 의해 5초를 기다리고 autoResetEvent.set();을 통해 신호가 되었다고 신호를 보내주면, 이제서야 WaitOne();이 신호를 받고, 

MessageBox.Show(""+a);를 통해 a메세지가 출력되는 것이다. 즉, Set()을 보내기 전까지 Thread2의 Set()의 윗코드가 실행된다.

 

또한

이 AutoResetEvent를 true로 바꾸게 되면, 첫번째 신호는 받은 상태로 변경된다. 즉 실행하자마자 변수가 나타나고, 이후 Sleep()등을 통해, 작동 된다.

using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;


namespace WinFormsApp1
{
    public partial class Form1 : Form
    {

        int a = 5; //공유 자원
        object lockObj = new object();

        AutoResetEvent autoResetEvent = new AutoResetEvent(true);


        Thread thread = null;
        Thread thread2 = null;
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            thread = new Thread(new ThreadStart(WorkThread));
            thread.IsBackground = true;
            thread.Priority = ThreadPriority.Normal;
            thread.Start();

            thread2 = new Thread(new ThreadStart(Work2));
            thread2.IsBackground=true;
            thread2.Priority = ThreadPriority.Normal;
            thread2.Start();
            
        }
        private void WorkThread()
        {
            while (true) //반복문 시작
            {
                autoResetEvent.WaitOne(); //신호를 받을 때 까지 대기하게 된다.

                MessageBox.Show("" + a);
            
            }
        }
        private void Work2()
        {
            while (true)
            {
                a++;
                Thread.Sleep(5000);
                autoResetEvent.Set(); //신호를 보내서, WaitOne()을 실행할 수 있게 된다.
               
            }
        }
    }
}

위와 같이 코드를 작성하게 되면, 코드를 빌드하자마자 바로 5가 출력된다. 이후, a값이 1씩 증가할 수 있는 것을 확인할 수 있다. 

 

 

 

Reference : C#.NET 0.5년차~3년차(파트1)

이 블로그의 모든 내용은 원작자와 출판사로부터 허락을 받아 작성되었습니다.

https://www.inflearn.com/course/lecture?courseSlug=%EB%8B%B7%EB%84%B7-%EC%9C%88%ED%8F%BC-1&unitId=77888&tab=curriculum 

 

학습 페이지

 

www.inflearn.com

 

'C#' 카테고리의 다른 글

스레드 문제점 해결  (0) 2023.06.14
비동기, 동기  (0) 2023.06.14
멀티스레드  (0) 2023.06.13
C# 프로퍼티, 객체 초기화  (0) 2023.06.08
MS-SQL 및 SSMS 설치 방법  (0) 2023.06.04

< 멀티 스레드 >

멀티스레드란, 하나의 프로그램 안에서 여러 개의 작업 흐름을 동시에 실행하는 것을 말한다.

컴퓨터 프로그램은 기본적으로 순차적으로 실행된다. 즉, 한 번에 하나의 작업만 수행하게 된다.

일반적인 프로그램 실행 순서

하지만 멀티스레드를 사용하면, 여러 작업을 동시에 처리할 수 있어서 프로그램이 훨씬 효율적으로 동작한다.

 

예를 들어, 웹 브라우저를 사용하면서 동영상을 시청하고 있으면, 웹 브라우저는 멀티스레드를 사용하여 동영상을 스트리밍 하는 동안에도 사용자의 입력을 받아 처리할 수가 있는 것이다.

< C# 멀티스레드 문법 >

Thread thread = null;
thread = new Thread(new ThreadStart(GetItemThread));
thread.IsBackground = true;
thread.Priority = ThreadPriority.Normal;
thread.Start();
  • Thread thread = null; : Thread 객체를 선언한다. 여기서 Thread는 새로운 작업 흐름을 나타낸다
  • thread = new Thread(new ThreadStart(GetItemThread));    새로운 Thread 객체를 생성하고, 이 Thread에서 실행할 메서드(GetItemThread)를 지정한다.
  • thread.IsBackground = true;  : 이 Thread를 백그라운드로 설정한다. 백그라운드 스레드는 프로그램이 종료될 때 자동으로 종료된다.
  • thread.Priority = ThreadPriority.Normal   : Thread가 OS 자원을 얼마나 자주 할당받을 것인지 (보통 normal, above를 자주 쓴다.)
  •  thread.Start();   thread를 시작하게 된다. 시작하게 되면 ThreadStart(GetItemThread));에서 GetItemThread 함수가 가장 먼저 실행된다. 

< 실습해 보기 >

위의 코드를 실행하면 Form1의 "AAAA테스트" MessageBox가 5초 후에 나오는 것을 확인할 수 있다.

 

만약 Thread.Sleep()을 Form_Load함수에 넣으면 어떻게 될까?

위 코드를 실행하게 되면 BBB테스트가 먼저 출력이 되고, 확인을 누르면(안 누르면 그다음으로 안 넘어간다.) AAAA테스트 메시지가 찍히고 나서 Form1창이 뜨는 것을 직접 확인할 수 있다. 

실행하면 2초후에 BBB테스트
BBB테스트 누른 후 4초후 AAAA테스트
이후 Form1창이 출력된다.

< Thread 1개 더 생성해 보기 >

이렇게 코드를 작성하게 된다면

Form1이 Load 되고 나서 Thread안에 있는 동작이 이루어지게 된다. BBB테스트가 2초 후에 출력되고, 확인을 누를 필요 없이, Form1 Load 됨과 동시에 5초 후에 AAA테스트 메시지가 나타난다. 즉, 각각의 스레드는 별개로(병렬적) 동작한다는 것을 알 수 있다. 

 

이러한 Thread는 병렬작업과 오래 걸리는 작업에 유용하게 사용된다.(예 네트워크전송, 수신, DB작업)

또한 로딩창, 로딩바를 호출해서 사용자 편의성을 증가시킬 수도 있다. 

< try catch로 감싸기 >

Thread의 함수들을 try catch로 감싸서 오류내용을 메모장 같은 곳에 기록을 할 수 있다.

 

또한 반복문을 통해서, 어떠한 작업이 반복적으로 일어나게 할 수도 있다. 그런데 While문에서 오류를 만나면 While문에서 빠져나오게 된다. 그때 계속 동작을 시키고 싶다면, try catch()로 감싸주면 While문을 빠져나가지 않고  계속 동작할 수 있도록 할 수 있다.

 

< 주의사항 >

Form2가 있다고 가정했을 때, 

Form1_Load 함수에 

Form2 form2 = new Form2();

form2.Show(); 이렇게 작성하면 form2가 개별 Thread로 동작할 것 같지만, 동작하지 않는다.

개별 스레드로 동작하지 않는 코드

만약 Form2에서 어떠한 작업, 개별적으로 실행하고자 한다면

Form2의 Thread로 작성해야 한다.

 

또한, Thread는 여러 개 만들 수 있는 것을 기억해야 한다.

 

 

Reference : C#. NET 0.5년 차~3년 차(파트 1)

이 블로그의 모든 내용은 원작자와 출판사로부터 허락을 받아 작성되었습니다.
https://www.inflearn.com/course/lecture?courseSlug=%EB%8B%B7%EB%84%B7-%EC%9C%88%ED%8F%BC-1&unitId=77886&tab=curriculum 

 

학습 페이지

 

www.inflearn.com

 

'C#' 카테고리의 다른 글

비동기, 동기  (0) 2023.06.14
스레드 동기화(lock,AutoResetEvent)  (0) 2023.06.13
C# 프로퍼티, 객체 초기화  (0) 2023.06.08
MS-SQL 및 SSMS 설치 방법  (0) 2023.06.04
C# 문법 정리 ②  (0) 2023.06.04

< 프로퍼티(Property)란? >

프로퍼티는 공개 데이터 멤버처럼 사용할 수 있지만, 실제로는 접근자(Accessor)라는 특별한 메소드들 이다.

< 프로퍼티의 구조 >

프로퍼티는 get , set Accessor로 구성된다.
get은 프로퍼티의 값을 반환하는데 사용된다. set은 새로운 값을 할당하는데 사용된다.
(C# 9 이후로는 init 액세서를 사용하여 객체 생성 시에만 값을 할당할 수 있다.)
set 또는 init에서는 value 키워드를 사용하여 할당되는 값을 정의한다.

< 백킹 필드와 프로퍼티 >

프로퍼티를 구현하는 기본 패턴 중 하나는 private 백킹 필드를 사용하여 프로퍼티 값을 설정하고 검색하는 것이다.

 

백킹 필드란?

더보기

프로퍼티(Property)는 객체의 상태를 나타내는데 사용되며, 일반적으로 메서드를 통해 값을 가져오거나 설정하는 역할을 한다. 허나 백킹 필드는 이 프로퍼티의 값을 실제로 저장하는 변수라고 할 수 있다.

프로퍼티의 get 또는 set 접근자를 사용하여 값을 가져오거나 설정할 때, 이 접근자들은 내부적으로 백킹 필드를 사용하여 작업을 수행한다. 백킹 필드는 프로퍼티의 값을 저장하고, 프로퍼티의 get 접근자는 이 값을 읽어오며, set 접근자는 이 값을 변경한다.

 

이러한 구조는 프로퍼티가 단순히 값을 반환하거나 설정하는 것 이상의 로직을 수행할 수 있게 해준다. 예를 들어, 값을 설정하기 전에 유효성 검사를 수행하거나, 프로퍼티 값이 변경될 때 이벤트를 발생시키는 등의 작업이 가능하다.

 

public class Person
{
    private string name; // 백킹 필드

    public string Name // 프로퍼티
    {
        get
        {
            return name;
        }
        set
        {
            if (!string.IsNullOrEmpty(value))
            {
                name = value;
            }
        }
    }
}

 

 

위 예제에서 name 변수는 백킹 필드이며, Name은 프로퍼티라고 할 수 있다. Name 프로퍼티를 통해 값을 설정하면, 유효성 검사를 거친 후 name 백킹 필드에 값을 저장한다. 또한, 값을 가져올 때는 name 백킹 필드에서 값을 읽어오는 것.

아래 예시에서 TimePeriod 클래스는 시간 간격을 나타낸다. 내부적으로 이 클래스는 _seconds라는 private 필드를 사용하여 시간 간격을 초 단위로 저장한다. Hours라는 read-write 프로퍼티를 통해 사용자가 시간 간격을 시간 단위로 지정할 수 있다.

public class TimePeriod
{
    private double _seconds;

    public double Hours
    {
        get { return _seconds / 3600; }
        set
        {
            if (value < 0 || value > 24)
                throw new ArgumentOutOfRangeException(nameof(value),
                      "The valid range is between 0 and 24.");

            _seconds = value * 3600;
        }
    }
}

< 표현식 본문 정의 (Expression Body Definitions) >

단일 라인의 문장으로 구성된 프로퍼티 액세서는 표현식 본문을 사용하여 구현할 수 있다.

public class Person
{
    private string _firstName;
    private string _lastName;

    public Person(string first, string last)
    {
        _firstName = first;
        _lastName = last;
    }

    public string Name => $"{_firstName} {_lastName}";
}

< 자동 구현 프로퍼티 (Auto-Implemented Properties) >

get과 set 접근자가 별도의 로직 없이 값을 할당하거나 검색하는 경우, 자동 구현 프로퍼티를 사용하여 코드를 간소화할 수 있다.

public class SaleItem
{
    public string Name { get; set; }
    public decimal Price { get; set; }
}

< 필요한 프로퍼티 (Required Properties) (C# 11 이상) >

C# 11부터는 required 키워드를 사용하여 클라이언트 코드가 프로퍼티 또는 필드를 초기화하도록 강제할 수 있다.

public class SaleItem
{
    public required string Name { get; set; }
    public required decimal Price { get; set; }
}

< 메서드, 필드, 프로퍼티의 차이점 >

  • 메서드(Method) : 코드 블록을 캡슐화하여 동작을 수행한다.
  • 필드(Field) : 클래스의 상태를 저장하는 변수.
  • 프로퍼티(Property) : 필드의 값을 조작하거나 반환하는 메서드 쌍(getter, setter)으로, 이를 통해 필드에 직접 접근하는 대신 필요한 로직을 캡슐화할 수 있다.

< 무명 형식 (Anonymous Types) >

무명 형식(Anonymous Types)은 명시적으로 타입을 정의하지 않고도 읽기 전용 프로퍼티들을 하나의 객체로 캡슐화하는 간편한 방법을 제공한다. 이 타입의 이름은 컴파일러에 의해 생성되며 소스 코드 수준에서는 사용할 수 없다. 각 프로퍼티의 타입은 컴파일러에 의해 추론된다.

 

무명 형식은 new 연산자와 객체 초기화자(object initializer)를 함께 사용하여 생성된다. 다음 예제는 Amount와 Message라는 두 개의 프로퍼티로 초기화된 무명 형식을 보여준다.

var v = new { Amount = 108, Message = "Hello" };
Console.WriteLine(v.Amount + v.Message);

무명 형식은 주로 쿼리 표현식의 select 절에서 사용된다. 이를 통해 소스 시퀀스의 각 객체에서 프로퍼티의 부분 집합을 반환할 수 있다. 무명 형식은 하나 이상의 public 읽기 전용 프로퍼티를 포함한다. 메소드나 이벤트와 같은 다른 종류의 클래스 멤버는 허용되지 않는다. 프로퍼티를 초기화하는 데 사용되는 표현식은 null, 익명 함수, 또는 포인터 타입이 될 수 없다.

var productQuery =
    from prod in products
    select new { prod.Color, prod.Price };

foreach (var v in productQuery)
{
    Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price);
}

무명 형식은 var를 사용하여 암시적으로 타입이 지정된 로컬 변수로 초기화하는 경우가 일반적이다.

무명 형식의 이름은 컴파일러만 알 수 있으므로 변수 선언에서 타입 이름을 지정할 수 없다.

var apple = new { Item = "apples", Price = 1.35 };
var onSale = apple with { Price = 0.79 };

무명 형식을 필드, 프로퍼티, 이벤트 또는 메서드의 반환 타입으로 선언할 수 없다. 무명 형식을 메서드에 인수로 전달하거나 저장해야 하는 경우, 일반적으로 명명된 struct 또는 클래스를 사용하는 것이 좋다.

무명 형식은 Equals 및 GetHashCode 메서드를 프로퍼티의 메서드를 기반으로 정의하기 때문에, 모든 프로퍼티가 같은 경우에만 같은 무명 형식의 두 인스턴스가 같다.

< 인터페이스와 추상 클래스에서의 프로퍼티 >

[ 인터페이스에서의 프로퍼티 ]

인터페이스에서 프로퍼티를 선언할 때는 구현을 제공하지 않고, get과 set 접근자만 지정한다.

 

interface IPerson {
    string Name { get; set; }
    int Age { get; set; }
}

[ 추상 클래스에서의 프로퍼티 ]

추상 클래스에서는 프로퍼티에 기본 구현을 제공할 수도 있고, 추상 프로퍼티로 선언하여 하위 클래스에서 구현하도록 할 수도 있다.

abstract class AbstractPerson {
    public abstract string Name { get; set; }
    public int Age { get; set; }
}

 

 

Reference: 

Microsoft Docs - Properties (C# Programming Guide)

https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/properties

 

Properties - C# Programming Guide

A property in C# is a member that uses accessor methods to read, write, or compute the value of a private field as if it were a public data member.

learn.microsoft.com

Microsoft Docs - Anonymous Types (C# Programming Guide)

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/anonymous-types

 

Anonymous Types

Anonymous types in C# encapsulate a set of read-only properties in an object without having to explicitly define a type. The compiler generates a name.

learn.microsoft.com

Microsoft Docs - Interfaces (C# Programming Guide)

https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/types/interfaces

 

Interfaces - define behavior for multiple types

An interface in C# contains definitions for a group of related functionalities that a non-abstract class or a struct must implement. It specifies the members and their signatures for a type that implements the interface.

learn.microsoft.com

 

'C#' 카테고리의 다른 글

스레드 동기화(lock,AutoResetEvent)  (0) 2023.06.13
멀티스레드  (0) 2023.06.13
MS-SQL 및 SSMS 설치 방법  (0) 2023.06.04
C# 문법 정리 ②  (0) 2023.06.04
C# 문법 정리  (0) 2023.06.03

Google에  "mssql express"를 검색한다. 

 

클릭 후, 스크롤을 조금 내리면 아래와 같은 express Download now가 있는데, 누른다.

다운로드가 다 된 후 실행을 누르면 아래와 같은 창이 뜬다.

기본 이후, 동의를 누르면, 설치가 진행된다(시간 다수 소요). 이후 설치가 완료 되면 SSMS 설치를 누른다. 

아래 링크로 이동하게 되는데, SSMS를 다운로드 한다.

아래의 설치버튼을 눌러 SSMS 설치 진행 한다.

 

'C#' 카테고리의 다른 글

멀티스레드  (0) 2023.06.13
C# 프로퍼티, 객체 초기화  (0) 2023.06.08
C# 문법 정리 ②  (0) 2023.06.04
C# 문법 정리  (0) 2023.06.03
C#으로 프로그램 만들기  (0) 2023.06.02

Reference : C# 입문부터 안드로이드, 윈도우 앱(UWP) 동시에 만들기 Xamarin Forms(자마린 폼즈) + Maui(마우이)

https://www.inflearn.com/course/lecture?courseSlug=c-sharp-%EC%9E%90%EB%A7%88%EB%A6%B0-%ED%8F%BC%EC%A6%88&unitId=84981&tab=curriculum 

 

학습 페이지

 

www.inflearn.com

 

< 클래스 생성자 오버로딩 >

클래스 생성자:

        클래스를 생성할때 호출되는 메소드로 클래스 이름과 같다.

        생성자는 다른 메소드 처럼 반환값을 가질 수가 없다.

오버로딩:

        함수의 이름은 같고 매개변수를 다르게 하여 개발하는 방식

public class Rectangle
{
    public int Width { get; set; }
    public int Height { get; set; }

    // Default constructor
    public Rectangle() 
    {
        Width = 0;
        Height = 0;
    }

    // Constructor overloading
    public Rectangle(int side) 
    {
        Width = Height = side;
    }

    public Rectangle(int width, int height)
    {
        Width = width;
        Height = height;
    }
}

이 것을 다른 .cs에 있다고 가정 했을 때,

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ConsoleApp2
{   

           class Program
        {
            static void Main()
            {
                Rectangle r1 = new Rectangle();
                Console.WriteLine($"Width: {r1.Width}, Height: {r1.Height}");

                Rectangle r2 = new Rectangle(5);
                Console.WriteLine($"Width: {r2.Width}, Height: {r2.Height}");

                Rectangle r3 = new Rectangle(4, 7);
                Console.WriteLine($"Width: {r3.Width}, Height: {r3.Height}");
            }
        }
    }

.Height,.Width로 가져올 수 있다.

< 상속과 Protected >

C#에서 상속을 사용하면 한 클래스가 다른 클래스의 속성과 메소드를 상속 받을 수 있다. protected 접근 한정자는 해당 클래스 및 상속받은 클래스에서만 접근이 가능하게 한다.

 

상속: 
       다른 클래스에 정의된 멤버를 불려 받을 수 있는 기능
        상속해주는 클래스  - 부모클래스
        상속 받는 클래스  - 자식클래스

public class Animal
{
    protected int legs = 4;  // protected 변수

    public void Eat()
    {
        Console.WriteLine("Eating...");
    }
}

public class Dog : Animal  // Animal 클래스를 상속 받음
{
    public void Bark()
    {
        Console.WriteLine($"Barking with {legs} legs...");  // protected 변수 사용
    }
}
class Program
{
    static void Main()
    {
        Dog dog = new Dog();
        dog.Eat();
        dog.Bark();
    }
}

< Sealed 클래스와 메소드 오버라이딩 >

C#에서 'sealed' 키워드는 클래스를 봉인하여 다른 클래스가 해당 클래스를 상속받는 것을 막는다.

즉, 클래스명 앞에 sealed를 붙이면 상속을 못 하고 인스턴스화를 해서만 사용할 수 있다.

오버라이딩 (Overriding) :
오버로딩과 비슷하지만 오버로딩은 클래스내에서 동일한 이름으로 매개변수를
           다르게 하는 방식이고 오버라이딩은 클래스의 상속관계에서 메소드를 재정의 하는 방법

sealed class SealedClass
{
    public int x;
    public int y;
}

class Program
{
    static void Main()
    {
        SealedClass sc = new SealedClass();
        sc.x = 10;
        sc.y = 20;
        Console.WriteLine($"x = {sc.x}, y = {sc.y}");
    }
}

메소드 오버라이딩

C#에서 메소드 오버라이딩은 상속받은 메소드를 자식 클래스에서 재정의하는 것을 의미한다. 이를 위해서는 기본 클래스의 메소드에 virtual 키워드를 사용하고, 파생 클래스에서는 override 키워드를 사용해야 한다.

public class Animal
{
    public virtual void sound()
    {
        Console.WriteLine("This is the sound of an animal");
    }
}

public class Dog : Animal
{
    public override void sound()
    {
        Console.WriteLine("This is the sound of a dog");
    }
}
class Program
{
    static void Main()
    {
        Animal myAnimal = new Animal();
        Animal myDog = new Dog();

        myAnimal.sound();
        myDog.sound();
    }
}

< Partial 클래스 >

파샬 partial class :
           두개 이상의 파일이 클래스를 나뉘어서 개밝하는 방식
           컴파일시 자동으로 결합이 되고, 코드가 길어질 경우 관리를 수월하게 할 수가 있고,
           하나의 클래스를 여러명에서 동시에 작성할 수 있다. 
partial class 클래스명
            {
                  //코드
            }

// File1.cs
public partial class PartialClass
{
    public void Method1()
    {
        Console.WriteLine("Method 1");
    }
}
// File2.cs
public partial class PartialClass
{
    public void Method2()
    {
        Console.WriteLine("Method 2");
    }
}
class Program
{
    static void Main()
    {
        PartialClass myClass = new PartialClass();
        myClass.Method1();
        myClass.Method2();
    }
}

< 추상 클래스 >

C#에서 추상 클래스(abstract class)는 인스턴스를 만들 수 없고, 하나 이상의 추상 메서드(정의되지 않은 메서드, 즉 프로토타입만 있는 메서드)를 가질 수 있는 클래스를 말한다. 이 클래스는 반드시 상속을 통해 사용되며, 상속받은 클래스에서 추상 메서드를 구현(override)해야 한다.

abstract 추상클래스:

       상속을 해주기 위한 클래스

abstract class 추상클래스

{

     abstract public void message();  //상속받은 클래스에서 기능 구현

}

class 자식클래스 : 추상클래스

{

     public override void message()

    {

  Console.WriteLine(“코드구현”);

    }

}

예를 들어서 동물소리를 추상메서드로 사용한다고 가정해보자.

public abstract class Animal
{
    public abstract void animalSound(); // 추상 메서드

    public void sleep() 
    {
        Console.WriteLine("Zzz");
    }
}

이젠 돼지가 Animal을 오버라이드하여 구현하면 된다.

public class Pig : Animal
{
    public override void animalSound() // 추상 메서드를 오버라이드하여 구현
    {
        Console.WriteLine("The pig says: wee wee");
    }
}
class Program
{
    static void Main()
    {
        Pig myPig = new Pig(); 
        myPig.animalSound();  
        myPig.sleep();  
    }
}

< 정적 Static 클래스 >

static 정적 클래스 : 
                       인스턴스를 만들어서 사용할 수 없다.
                       생성자를 포함할 수 없다.
                       어디서든 접근할 수 있다.

static class 클래스명
{
	static public string name;
}
   public static class MyMathClass
    {
        public static int Square(int num)
        {
            return num * num;
        }
    }
    static void Main()
        {
            int result = MyMathClass.Square(5); // 인스턴스 생성 없이 사용
            Console.WriteLine(result);
        }

< 인터페이스와 네임스페이스 >

interface 인터페이스:
    메소드 선언은 있지만, 기능을 구현하는 코드는 없다. 다중 상속을 위해 존재한다.

    public interface 인터페이스명
     {
void interfaceVoid();
     }
     public class 클래스명 : 인터페이스명
                            {
                                  public void interfaceVoid()
                                 {
   //코드
                                  } 
      }

네임스페이스 namespace : 
유효범위를 제공하는 선언,모든 식별자가 고유하도록 보장한다.

namespace ShapeNamespace
{
    public interface IShape
    {
        double GetArea();
    }

    public class Rectangle : IShape
    {
        public double Length { get; set; }
        public double Width { get; set; }

        public double GetArea()
        {
            return Length * Width;
        }
    }
}

파일을 별도로 분리하고 싶으면, Rectangle을 다른 파일에다가 옮겨도 똑같은 값이 나온다.

    class Program
    {
        static void Main()
        {
            Rectangle rectangle = new Rectangle();
            rectangle.Length = 5.0;
            rectangle.Width = 3.0;
            Console.WriteLine(rectangle.GetArea());
        }
    }

< 대리자와 익명 메소드 >


대리자 delegate:
매개변수와 반환형식을 갖는 메소드를 캡슐화 하며, 대리자는 참조하는 메소드의 메모리 주소를 가진다.
메소드를 보관하는 공간이고 필요할 때 이 공간을 가져와서 함수를 실행한다.

public delegate void MyDelegate(string msg);

class Program
{
    public static void Hello(string strMessage)
    {
        Console.WriteLine("Hello, " + strMessage);
    }

    public static void Goodbye(string strMessage)
    {
        Console.WriteLine("Goodbye, " + strMessage);
    }

    static void Main()
    {
        MyDelegate del = Hello;
        del("Alice");
        del = Goodbye;
        del("Bob");
    }
}

익명메소드: 
  별도의 메소드를 만들지 않고 불필요한 오버헤드를 줄일 수가 있다.
 재사용할 필요가 없을 경우 사용한다.

public delegate void MyDelegate(string msg);

class Program
{
    static void Main()
    {
        MyDelegate del = delegate(string name)
        {
            Console.WriteLine($"Hello, {name}");
        };
        
        del("Charlie");
    }
}

< 이벤트 핸들러 >

C#에서 이벤트는 특정한 일이 일어날 때마다 호출되는 방법을 제공한다. 이벤트는 대리자를 사용하여 정의된다.

public delegate string MyDel(string str);

class EventProgram
{
    event MyDel MyEvent;

    public EventProgram()
    {
        this.MyEvent += new MyDel(this.WelcomeUser);
    }

    public string WelcomeUser(string username)
    {
        return "Welcome " + username;
    }

    static void Main(string[] args)
    {
        EventProgram obj1 = new EventProgram();
        string result = obj1.MyEvent("Tutorials Point");
        Console.WriteLine(result);
    }
}

< 문자열 다루기 >

class Program
{
    static void Main()
    {
        string txt = "Hello World";
        Console.WriteLine("Length: " + txt.Length); // 문자열 길이 확인
        Console.WriteLine("Upper: " + txt.ToUpper()); // 대문자로 변환
        Console.WriteLine("Lower: " + txt.ToLower()); // 소문자로 변환
        Console.WriteLine("Contains: " + txt.Contains("World")); // 특정 문자열 포함 여부 확인
        Console.WriteLine("Index: " + txt.IndexOf("World")); // 특정 문자열의 위치 찾기
    }
}

'C#' 카테고리의 다른 글

C# 프로퍼티, 객체 초기화  (0) 2023.06.08
MS-SQL 및 SSMS 설치 방법  (0) 2023.06.04
C# 문법 정리  (0) 2023.06.03
C#으로 프로그램 만들기  (0) 2023.06.02
비주얼 스튜디오 설치 및 환경설정 (For Windows)  (0) 2023.06.02

Reference : C# 입문부터 안드로이드, 윈도우 앱(UWP) 동시에 만들기 Xamarin Forms(자마린 폼즈) + Maui(마우이)

https://www.inflearn.com/course/lecture?courseSlug=c-sharp-%EC%9E%90%EB%A7%88%EB%A6%B0-%ED%8F%BC%EC%A6%88&unitId=84981&tab=curriculum 

 

학습 페이지

 

www.inflearn.com

< 콘솔에 출력해보기 >

C#에서 가장 기본적인 출력 방법은 'Console.WriteLine()'과 'Console.Write()' 메소드를 사용하는 것이다.

  • 'Console.WriteLine()' : 출력 후 줄바꿈이 일어난다.
  • 'Console.Write()' : 줄바꿈 없이 출력한다.
Console.WriteLine("Hello, World!");
Console.Write("Hello, C#");

위 코드를 실행하면, 다음과 같은 출력이 콘솔에 나타난다.

Hello, World!
Hello, C#

여기서 "Hello,World!" 다음 새로운 줄에서 "Hello,C#"가 출력되는 것을 볼 수 있다.

< 콘솔에 입력받기 >

C#에서 사용자로부터 입력을 받기 위해서는 'Console.ReadLine()' 메소드를 사용한다.

Console.WriteLine("Enter your name:");
string name = Console.ReadLine();
Console.WriteLine($"Hello, {name}!");

위 코드를 실행하면 콘솔이 사용자로부터 이름을 입력받고, 입력받은 이름을 사용하여 메세지를 출력한다.

< 데이터 타입 알아보기 >

int number = 10;
double pi = 3.141592;
char character = 'A';
bool isTrue = true;
string name = "C#";

< 데이터타입 사용방법과 변수 상수 선언 > 

C#에서의 변수와 상수 선언

C#에서 변수는 '변수타입 변수명 = 초기값;' 형태로 선언한다. 변수는 값이 변경될 수 있다.

상수는 'const 상수타입 상수명 = 초기값;'의 형태로 선언한다. 상수는 한번 선언되면 값을 변경할 수 없다.

   string name = "홍길동";
                name = "1";
                name = "0.5"; //string은 숫자가 아닌 문자열 이다.
                char letter = 'A'; //char는 ''로 한다. ""로 할 시, 오류
                int price = 100;
                // price = 0.5; //소숫점은 넣을 수 없다.
                double b = 0.05;
                decimal sale = Convert.ToDecimal(1.5); //decimal은 Convert.ToDecimal()로 형변환을 알려주고 넣거나
                decimal sale_price;
                sale_price = price - (price * sale); //선언 하고 난 뒤 결과 값을 받을 수 있다.
                Console.WriteLine("할인된 가격은 {0}", sale_price);
                Console.ReadLine();
                const string str = "홍길동"; //const는 상수로서 변경 불가능 하다.

< Object Var Dynamic 타입과 연산자 > 

C#은 Object, Var, Dynamic이라는 특별한 타입을 가지고 있다.

  • Object: C#의 모든 타입은 Object 타입에서 파생되므로, 어떤 타입의 데이터도 저장할 수 있다.
  • Var: 컴파일 타임에 타입이 결정되며, 선언 시 초기화를 해주어야 한다.
  • Dynamic: 런타임에 타입이 결정되며, 선언 시 초기화를 하지 않아도 된다.

이들 타입과 함께 하는 사용하는 연산자에는 사칙연산자(+,-,*,/),비교연산자(==,!=,<,>,<=,>=)등이 있다.

 object aa = 11;
                aa = "감자";
                int a = Convert.ToInt32(aa); //object에서는 다른 형으로 재할당 시, Convert.Toint32(aa);로 해야 한다. 
                object aaa = "a";
                Console.WriteLine("aa값은: {0}", aa);
                var bb = 22;
                //bb = "b"; //var type은 처음에 숫자가 들어가면, 그 다음에 문자로 바꿀 수 없다. 
                Console.WriteLine("bb값은: {0}", bb);
                dynamic cc = 33;
                cc = "감자";//형을 바꿔서 넣을 수 있다. 
                dynamic ccc = "c"; 
                cc = "한글";
                int c = cc;//실행되는 시점에서 검사를 하기에, 실행하는 시점에서만 오류인지 아닌지 알 수 있다.
                Console.WriteLine("cc값은: {0}", cc);
                Console.ReadLine();

                //연산자 
                int a = 10;
                int b = 5;
                int c = a + b;
                Console.WriteLine("a+b={0}", c);
                Console.WriteLine("a+b={0}", a + b);

                Console.WriteLine("a+b={0}", a - b);

                Console.WriteLine("a*b={0}", a * b);

                Console.WriteLine("a/b={0}", a / b);

                Console.ReadLine();

                int d;
                d = 5 / 2;
                Console.WriteLine("d의 값은? {0}", d);

                double e;
                e = 5 / 2;
                Console.WriteLine("e의 값은? {0}", e);

                double f;
                f = 5.0 / 2;
                Console.WriteLine("f의 값은? {0}", f);

                float g;
                g = 5f / 2f;
                Console.WriteLine("g의 값은? {0}", g);
                Console.ReadLine();

< 증감연산자,산술연산자 > 

C#에서는 증감연산자와 산술연산자를 지원한다.

증감연산자 : 

  • ++ : 값을 1 증가 시킨다.
  • -- : 값을 1 감소시킨다.

산술연산자 :

  • + : 더하기 연산
  • - : 빼기 연산
  •  * : 곱하기 연산
  • / : 나누기 연산
  • % : 나머지 연산
  /*
                 연산자 종류
                증감연산자     ++,--
                산술연산자     +,-,*,/,%
                 */
                int a = 1;
                int b = 2;
                int c;
                c = ++a;
                Console.WriteLine("선행연산자 a의 값 {0} c의 값{1}", a, c);  //a의 값 2 c의 값 2 
                a = 1;
                c = a++;
                Console.WriteLine("선행연산자 a의 값 {0} c의 값{1}", a, c); //a의 값 2 c의 값 1
                a = 1;
                c = --a;
                Console.WriteLine("선행연산자 a의 값 {0} c의 값 {1}", a, c); //a의 값 0 c의 값 0
                c = a--;
                Console.WriteLine("선행연산자 a의 값 {0} c의 값 {1}", a, c); //a의 값 -1 c의 값 0 

                Console.WriteLine("나머지값 {0}", 5 % 2); //나머지 값 1

< IF문 관계연산자 논리연산자 >

C#에서는 if문,관계연산자,논리연산자를 사용하여 조건에 따라 코드를 실행할 수 있다.

 

if문 :

  • if문은 조건식이 참일 때 해당 코드 블록을 실행한다.

관계연산자:

  • ==:같다.
  • != : 다르다.
  • < : 작다
  • > : 크다.
  • <= : 작거나 같다.
  • >= : 크거나 같다.

논리 연산자 :

&& : 논리 AND 연산, 모든 조건이 참일 때, 참을 반환한다.

|| : 논리 OR 연산, 조건 중 하나 이상이 참일 때 참을 반환한다.

! : 논리 NOT 연산 , 조건의 반대를 반환한다.

 /*
                 관계 연산자 <,>,==,!=,<=,>=
                 논리 연산자 &&,||,!
                */
                int a = 3;
                if (a == 1)
                {
                    Console.WriteLine("1과 같음");
                }
                else if (a >= 1)
                {
                    Console.WriteLine("1보다 크거나 같음");
                }
                else if (a < 1)
                {
                    Console.WriteLine("1보다 작음");
                }
                else
                {
                    Console.WriteLine("해당없음");
                }
                int b, c;
                b = 100;
                c = 200;
                if (b == 100 && c == 300)
                {
                    Console.WriteLine("1");
                }
                else if (b == 100 || c == 300)
                {
                    Console.WriteLine("1");
                }
                else if (b > 100 || c == 300)
                {
                    Console.WriteLine("3");
                }
                else if (b != 100 || c != 300)
                {
                    Console.WriteLine("4");
                }

< 형변환 암시적 명시적 변환 >

C#에서의 데이터 타입간 암시적 변환과 명시적 변환을 사용할 수 있다.

  • 암시적 변환: 데이터 손실이 없는 경우, 컴파일러가 자동으로 변환을 수행한다.
  • 명시적 변환: 데이터 손실이 있는 경우, 개발자가 명시적으로 변환을 지시해야 한다.
 /*
                 타입       자동 변환되는 유형
                 byte       short,ushort,int,unit,long,ulong,float,double,decimal
                 sbyte      short,int,long,float,double,decimal
                 short      int,long,float,double,decimal
                 ushort     int,unit,long,ulong,float,double,decimal
                 int        long,float,double,decimal
                 uint       long,ulong,float,double,decimal
                 long       float,double,decimal
                 ulong      float,double,decimal
                 float      double
                 char       ushort,int,unit,long,ulong,float,double,decimal
                 var        암시적 변환 타입
                 */
                char a1 = 'a';
                char a2 = 'A';
                Console.WriteLine("a1의 값:" + a1);
                Console.WriteLine("a2의 값:" + a2);

                ushort change_a1, change_a2;
                change_a1 = a1;
                change_a2 = a2;
                Console.WriteLine("change_a1의 값:" + change_a1);
                Console.WriteLine("change_a2의 값:" + change_a2);

                byte ch_byte_a1, ch_byte_a2;
                ch_byte_a1 = (byte)a1;
                ch_byte_a2 = Convert.ToByte(a2);
                Console.WriteLine("ch_byte_a1의 값:" + ch_byte_a1);
                Console.WriteLine("ch_byte_a2의 값:" + ch_byte_a2);

< 소수점 자리수 조절, 문자열 코드 >

C#에서는 소수점 자리수 조절을 위해 'Math.Round()', 문자열 조절을 위해 'string.Format()'을 사용할 수 있다.

double number = 3.141592;

number = Math.Round(number, 2); // 소수점 아래 두 자리까지 반올림, number는 3.14

string str = string.Format("{0:N2}", number); // 숫자를 문자열로 변환하며 소수점 아래 두 자리까지 표현, str은 "3.14"

< For문 배열 메소드 사용 방법 >

C#에서 'for'문을 사용하여 반복 작업을 처리하고, 배열을 사용하여 어러 값을 저장 하며, 메소드를 사용하여 작업을 재사용할 수 있다.

int[] array = new int[5] {1, 2, 3, 4, 5};

for(int i = 0; i < array.Length; i++)
{
    array[i] = MultiplyByTwo(array[i]);
    Console.WriteLine(array[i]);
}

int MultiplyByTwo(int number)
{
    return number * 2;
}

< 전역변수 지역변수 열거형 Enum >

C#에서 전역변수는 클래스 내 어디서는 접근 가능한 변수이며, 지역변수는 특정 블록(메소드,if문 등) 내에서만 사용하능한 변수이다. 열거형(Enum)은 여러 개의 상수를 하나의 타입으로 묶어놓은 것이다.

using System;

namespace ConsoleApp2
{
    internal class Program
    {
        static int globalVar = 10; // 전역 변수

        enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat }; // 열거형

        static void Main(string[] args)
        {
            int localVar = 20; // 지역 변수
            Console.WriteLine(globalVar);
            Console.WriteLine(localVar);

            Days day = Days.Mon;
            Console.WriteLine(day);
        }
    }
}

< 구조체 Struct >

C#에서 구조체(Struct)는 관련된 데이터를 하나의 복합 데이터 타입으로 그룹화하는 방법을 제공한다.

 struct Point
        {
            public int x, y;

            public Point(int x, int y)
            {
                this.x = x;
                this.y = y;
            }

            public void DisplayPoint()
            {
                Console.WriteLine($"Point: ({x},{y})");
            }
        }

        static void Main()
        {
            Point point = new Point(3, 4);
            point.DisplayPoint();
        }

< Switch문 >

C#에서 'switch'문을 사용하면 어러 가지 경우에 따른 작업을 처리할 수 있다.

static void Main()
{
    int number = 2;
    
    switch(number)
    {
        case 1:
            Console.WriteLine("Number is 1");
            break;
        case 2:
            Console.WriteLine("Number is 2");
            break;
        default:
            Console.WriteLine("Number is not 1 or 2");
            break;
    }
}

< While문 Do While문 >

C#에서 'while'문과 'do while'문을 사용하여 조건에 따라 반복 작업을 처리할 수 있다.

static void Main()
{
    int number = 1;

    while(number <= 5)
    {
        Console.WriteLine(number);
        number++;
    }

    number = 1;
    
    do
    {
        Console.WriteLine(number);
        number++;
    }
    while(number <= 5);
}

< Foreach문 >

C#에서 foreach문을 사용하여 컬렉션의 모든 요소를 순회할 수 있다..

static void Main()
{
    int[] numbers = new int[5] {1, 2, 3, 4, 5};

    foreach(int number in numbers)
    {
        Console.WriteLine(number);
    }
}

< 클래스 Class 필드 속성 >

C#에서 클래스(Class)는 객체를 생성하는 템플릿이며, 필드는 클래스 내의 변수를, 속성은 클래스 내의 메소드를 정의하는 데 사용된다.

class Program
{
    public class Car
    {
        private string color; // 필드

        public string Color  // 속성
        {
            get { return color; }
            set { color = value; }
        }
    }

    static void Main()
    {
        Car car = new Car();
        car.Color = "Red";
        Console.WriteLine(car.Color);
    }
}

< 클래스 정적 Static 상수 Const >

C#에서 클래스 내의 멤버를 'static'으로 선언하면, 그 멤버는 클래스의 인스턴스가 아닌 클래스 자체에 속하게 된다. 'const'는 상수를 선언하는 데 사용되며, 선언과 동시에 초기화 되어야 하고, 그 값을 변경할 수 없다.

class Program
{
    public class Circle
    {
        public const double PI = 3.14; // 상수
        public static int count = 0; // 정적 필드

        public Circle()
        {
            count++;
        }
    }

    static void Main()
    {
        Console.WriteLine($"PI: {Circle.PI}");
        
        Circle c1 = new Circle();
        Circle c2 = new Circle();
        Console.WriteLine($"Created {Circle.count} circles");
    }
}

< 클래스 메소드 정적 메소드 참조형 Out >

C#에서 클래스 메소드는 특정 클래스의 인스턴스에 연결되어 작동하며, 정적 메소드는 클래스 자체에 연결되어 작동한다. out 키워드는 메소드에서 여러 값을 반환할 때 사용된다.

class Program
{
    public class Calculator
    {
        public static void Multiply(int a, int b, out int result)
        {
            result = a * b;
        }
    }

    static void Main()
    {
        int result;
        Calculator.Multiply(3, 4, out result);
        Console.WriteLine(result);
    }
}

< 속성 정의 >

C#에서 속성(Property)은 클래스, 구조체, 인터페이스의 멤버로, 외부에서 접근할 수 있는 값이다. get과 set 접근자를 사용하여 속성 값을 읽거나 변경할 수 있다.

class Program
{
    public class Student
    {
        private string name;

        public string Name
        {
            get { return name; }
            set { name = value; }
        }
    }

    static void Main()
    {
        Student student = new Student();
        student.Name = "John Doe";
        Console.WriteLine(student.Name);
    }
}

 

'C#' 카테고리의 다른 글

MS-SQL 및 SSMS 설치 방법  (0) 2023.06.04
C# 문법 정리 ②  (0) 2023.06.04
C#으로 프로그램 만들기  (0) 2023.06.02
비주얼 스튜디오 설치 및 환경설정 (For Windows)  (0) 2023.06.02
C#의 등장의 역사  (0) 2023.06.02

+ Recent posts