Visual Studio C# 콘솔 앱 프로젝트 이름 지정(예: FileAndDirectoryManagement) 그리고 "만들기"를 클릭해서 프로젝트를 만들자. (여기서 Console.APP은 NET Core를 사용해야 하는데, Net Core는 NET 5.0 이후 Version이 통일되어서, NET 5.0 이후 Version을 쓰면 된다. NET 7.0을 쓰도록 하자.)

< 파일과 디렉터리에 필요한 네임스페이스 >

파일과 디렉터리를 다루기 위해, System.IO 네임스페이스가 필요하다. Program.cs 파일 맨 위에 다음 코드를 추가하자.

using System.IO;

 

< 텍스트 파일 쓰기 >

텍스트 파일에 쓰는 것은 일반적으로 로그 기록이나 설정 파일을 만들 때 사용된다.
C#에서 StreamWriter 클래스를 사용하여 텍스트 파일에 쓸 수 있다.

< StreamWriter의 장점 >

StreamWriter는 텍스트 파일에 쓰기를 매우 간단하게 만들어준다. 몇 줄의 코드만으로 파일에 텍스트를 쓸 수 있고, 복잡한 설정 없이도 기본적인 작업을 수행할 수 있다.버퍼링을 사용하여 쓰기 작업을 효율적으로 수행할 수 있다. 즉, 작은 데이터 조각들을 메모리에 모아 놓고 한 번에 디스크에 쓰는 방식으로 입출력 성능을 향상시킨다. 예를 들어서 마트에서 쇼핑을 할 때, 고르자마자 계산대로 간다면 시간이 많이 걸린다. 대신, 여러 생필품이나 살 것들을 카트에 담아서 한번에 계산대로 가져가면 훨씬 효율적이다.StreamWriter도 이와 비슷한 방식으로 작동한다. 데이터를 메모리에 모아놓고 한 번에 파일에 쓰는 방식으로 입출력 성능을 향상시킨다. StreamWriter를 사용하면 쉽게 라인 브레이크를 추가할 수 있고, 다양한 인코딩 옵션을 설정할 수 있다. 이를 통해 다양한 텍스트 파일 포맷과 문자셋을 처리하는 데 유용하다. 또한, StreamWriter는 텍스트 파일에 쓸 때 텍스트를 추가하는 기능을 제공한다. 이를 통해 기존의 파일 내용을 덮어쓰지 않고 계속해서 데이터를 추가할 수 있다. 이 기능은 로그 기록과 같이 지속적으로 데이터가 쌓이는 경우에 매우 유용하다.  마치, 일기장에 일기를 쓰는 것과 같다.

string filePath = "example.txt"; // 파일 경로를 변수로 저장.

// StreamWriter를 사용하여 텍스트 파일에 쓸 수 있다.
using (StreamWriter writer = new StreamWriter(filePath))
{
    writer.WriteLine("안녕하세요!"); // 한 줄 작성
    writer.WriteLine("액션 빔!"); // 또 다른 줄
}

 

< 텍스트 파일 읽기 >

텍스트 파일을 읽는 것은 설정을 불러오거나 로그를 확인할 때 유용합니다.

StreamReader 클래스를 사용하여 텍스트 파일을 읽을 수 있다.

// StreamReader로 텍스트 파일을 열어서 읽기
using (StreamReader reader = new StreamReader(filePath))
{
    string line; // 각 줄을 저장할 변수를 선언
    
    // 파일의 끝에 도달할 때까지 각 줄을 읽는다.
    while ((line = reader.ReadLine()) != null)
    {
        Console.WriteLine(line); // 콘솔에 각 줄을 출력한다.
    }
}

 

이제 이 두개를 통해서, 메모장을 만들어서, 내용을 적고, 저장하는 것을 할 수 있다.

using System;
using System.IO;

namespace NoteTakingApp
{
    class Program
    {
        static void Main(string[] args)
        {
            //저장할 파일 이름
            Console.WriteLine("저장할 파일 이름을 입력하세요 (예: mynotes.txt):");
            string fileName = Console.ReadLine();

            //메모 내용을 입력 받는다.
            Console.WriteLine("메모할 내용을 입력하세요. (입력을 끝내려면 'END'를 입력하세요):");
            string line;
            using (StreamWriter writer = new StreamWriter(fileName))
            {
                while ((line = Console.ReadLine()) != null)
                {
                    // END를 입력하면 루프를 종료
                    if (line.ToUpper() == "END")
                    {
                        break;
                    }
                    // 입력한 텍스트를 파일에 기록
                    writer.WriteLine(line);
                }
            }

            // 파일 저장이 완료되었음을 알림.
            string fullPath = Path.GetFullPath(fileName);
            Console.WriteLine($"메모가 '{fullPath}'에 저장되었습니다!");
        }
    }
}

이렇게 사용자 입력을 받고, 내용을 저장하는 코드를 작성 후, GetFullPath를 통해, 경로를 알려주게 하면 어디에 저장되었는지 알 수 있다. 밑과 같이 경로를 말해준다.

Console에 입력한 것들이 저장되는 것을 볼 수 있다.

< 이진 파일 다루기 >

이진 파일은 텍스트 형식이 아닌 데이터를 저장한다. 예를 들어, 이미지, 오디오, 동영상 파일이 있다.

< 이진 파일 쓰기 >

BinaryWriter를 사용하여 이진 파일에 쓸 수 있다.

string binaryFilePath = "example.bin"; // 이진 파일의 경로를 변수로 저장.

// BinaryWriter로 이진 파일에 쓸 수 있다.
using (BinaryWriter writer = new BinaryWriter(File.Open(binaryFilePath, FileMode.Create)))
{
    writer.Write(123); // 정수
    writer.Write("이진 파일 예제"); // 문자열
}

 

< 이진 파일 읽기 >

BinaryReader를 사용하여 이진 파일을 읽을 수 있다.

// BinaryReader로 이진 파일을 열어서 읽기.
using (BinaryReader reader = new BinaryReader(File.Open(binaryFilePath, FileMode.Open)))
{
    int number = reader.ReadInt32(); // 정수
    string text = reader.ReadString(); // 문자열

    Console.WriteLine($"Number: {number}, Text: {text}"); // 콘솔에 출력
}

이것을 통해서, 사용자의 텍스트와 이미지 파일을 입력 받아서, 저장경로에 저장할 수 있는 코드를 적용 해볼 수 있다.

Visual Studio에서 "새 프로젝트"를 선택하고, "Windows Forms App (.NET)"을 선택하여 프로젝트를 생성해보자.프로젝트 이름을 FilePreviewApp으로 설정한다.

프로젝트가 생성되면, Form1 디자인 뷰에서 다음 컨트롤들을 폼에 드래그해서 추가한다.(추가할려면 도구 상자가 필요하다. 상단에 보기 탭에서 도구 상자를 클릭하도록 하자.

이후, TextBox을 검색해서 드로그앤 드랍 해서  추가한 다음, txtMessage로 설정한다.

Button을 검색해서 이름을 btnBrowse로 설정하고, Text 속성을 "Browse..."로 설정한다.

Button을 하나 더 추가해서 이름을 btnSave로 설정하고, Text 속성을 "Save"로 설정한다.


PictureBox를 검색해서 이름을 pictureBox로 설정한다.

 

위와 같이 만들면 대략 이런 모습이 완성된다.

 

이후, Form1.cs에서 파일저장 로직을 작성하고, Form1.Desinger.cs에서 속성을 설정해준다.

// FilePreviewApp/Form1.cs
using System;
using System.Drawing;
using System.IO;
using System.Windows.Forms;

namespace FilePreviewApp
{
    public partial class Form1 : Form
    {
        private string selectedFilePath; // 선택된 파일 경로를 저장할 변수

        public Form1()
        {
            InitializeComponent();
        }

        private void btnBrowse_Click(object sender, EventArgs e)
        {
            OpenFileDialog openFileDialog = new OpenFileDialog();
            if (openFileDialog.ShowDialog() == DialogResult.OK)
            {
                selectedFilePath = openFileDialog.FileName;

                // 이미지 파일인 경우
                if (selectedFilePath.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) ||
                    selectedFilePath.EndsWith(".png", StringComparison.OrdinalIgnoreCase) ||
                    selectedFilePath.EndsWith(".bmp", StringComparison.OrdinalIgnoreCase))
                {
                    pictureBox.Image = Image.FromFile(selectedFilePath);
                    pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
                }
                // 텍스트 파일인 경우
                else if (selectedFilePath.EndsWith(".txt", StringComparison.OrdinalIgnoreCase))
                {
                    txtMessage.Text = File.ReadAllText(selectedFilePath);
                }
            }
        }

        private void btnSave_Click(object sender, EventArgs e)
        {
            if (string.IsNullOrEmpty(selectedFilePath) || string.IsNullOrEmpty(txtMessage.Text))
            {
                MessageBox.Show("메시지와 파일을 선택해주세요.");
                return;
            }

            string destinationDirectory = @"C:\Users\dlem7\source\repos\FileAndDirectoryManagement";
            string destinationFilePath = Path.Combine(destinationDirectory, Path.GetFileName(selectedFilePath));

            try
            {
                // 파일 복사
                File.Copy(selectedFilePath, destinationFilePath, true);

                // 메세지 저장
                string messageFilePath = Path.Combine(destinationDirectory, "message.txt");
                File.WriteAllText(messageFilePath, txtMessage.Text);

                MessageBox.Show("메세지와 파일이 저장되었습니다.");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"오류 발생: {ex.Message}");
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            btnBrowse.Click += btnBrowse_Click;
            btnSave.Click += btnSave_Click;
        }
    }
}
// FilePreviewApp/Form1.Designer.cs
namespace FilePreviewApp
{
    partial class Form1
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            btnBrowse = new Button();
            btnSave = new Button();
            txtMessage = new TextBox();
            pictureBox = new PictureBox();
            ((System.ComponentModel.ISupportInitialize)(pictureBox)).BeginInit();
            SuspendLayout();
            // 
            // btnBrowse
            // 
            btnBrowse.Location = new Point(171, 41);
            btnBrowse.Name = "btnBrowse";
            btnBrowse.Size = new Size(75, 23);
            btnBrowse.TabIndex = 0;
            btnBrowse.Text = "Browse...";
            btnBrowse.UseVisualStyleBackColor = true;
            btnBrowse.Click += btnBrowse_Click;
            // 
            // btnSave
            // 
            btnSave.Location = new Point(461, 389);
            btnSave.Name = "btnSave";
            btnSave.Size = new Size(75, 23);
            btnSave.TabIndex = 1;
            btnSave.Text = "Save";
            btnSave.UseVisualStyleBackColor = true;
            btnSave.Click += btnSave_Click;
            // 
            // txtMessage
            // 
            txtMessage.Location = new Point(171, 12);
            txtMessage.Name = "txtMessage";
            txtMessage.Size = new Size(365, 23);
            txtMessage.TabIndex = 2;
            // 
            // pictureBox
            // 
            pictureBox.Location = new Point(171, 64);
            pictureBox.Name = "pictureBox";
            pictureBox.Size = new Size(365, 319);
            pictureBox.TabIndex = 3;
            pictureBox.TabStop = false;
            // 
            // Form1
            // 
            AutoScaleDimensions = new SizeF(7F, 15F);
            AutoScaleMode = AutoScaleMode.Font;
            ClientSize = new Size(800, 450);
            Controls.Add(pictureBox);
            Controls.Add(txtMessage);
            Controls.Add(btnSave);
            Controls.Add(btnBrowse);
            Name = "Form1";
            Text = "Form1";
            Load += Form1_Load;
            ((System.ComponentModel.ISupportInitialize)(pictureBox)).EndInit();
            ResumeLayout(false);
            PerformLayout();
        }

        private Button btnBrowse;
        private Button btnSave;
        private TextBox txtMessage;
        private PictureBox pictureBox;
    }
}

 

이후 실행하면, 아래와 같이 Form1이 실행된다. Save를 누르면, 저장된 경로에 이미지와 텍스트가 저장되는걸 볼 수 있다. Form1에서 저장 경로를 변경하려면 destinationDirectory 변수의 값을 원하는 경로로 설정하면 된다.

 

 

Reference : 이것이 C#이다 3판

https://product.kyobobook.co.kr/detail/S000201856223

 

이것이 C#이다 | 박상현 - 교보문고

이것이 C#이다 | 전문가로 레벨업!C# 프로그래밍 완전 정복이 책은 C#의 기본, 고급 문법과 더불어 기초 클래스 라이브러리까지 다루고 있다. 총 22개 장으로, 앞서 배운 내용을 활용하면서 단계별

product.kyobobook.co.kr

 

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

TCP 클라이언트/실습  (0) 2023.06.19
전기제어 PLC(Programmable Logic Controller)  (0) 2023.06.19
dynamic  (0) 2023.06.18
일반화와 제네릭  (0) 2023.06.17
대리자와 이벤트  (0) 2023.06.17

< dynamic 이란? >

dynamic은 데이터 형식 중 하나로서, 보통 변수를 선언할 때, .int,string,double과 같은 데이터 Type을 지정하는데,

dynamic을 사용하면 컴파일 시점이 아닌, 실행 시점에 변수의 데이터 형식이 결정된다.

int나 string과 같은 형식은 상자의 크기가 정해진 상자라면, dynamic은  크기가 변하는 상자와 같다. 필요에 따라 원하는 물건을 담을 수 있다. 

< dynamic을 이용해서 변수 사용하기 >

using System;

namespace Dynamic
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic Box = "초코비";
            Console.WriteLine($"Box 안에는 {Box}가 있어요 dynamic 타입은 실행 시점에서 결정되니까 string 이겠죠. 답 : {Box.GetType()}");

            Box = 2000;
            Console.WriteLine($"Box 안에는 {Box}원이 있어요. dynamic 타입은 int 타입 이겠죠.{Box.GetType()}");

            Box = new System.Collections.Generic.List<string> {"초코비","액션가면"};
            Console.WriteLine($"Box 안에는 {string.Join(" ", Box)} of type {Box.GetType()}");
        }
    }
}

< COM 객체 >

dynamic은 COM 객체를 다룰 때 특히 유용하다. 예를 들어, Excel을 제어하는 경우에 사용할 수 있다.

우선 Visual Studio에서 "도구" > "NuGet 패키지 관리자" > "패키지 관리자 콘솔"을 선택해서.
콘솔에 다음 명령어를 입력하여 Excel 라이브러리를 설치해도 된다.

Install-Package Microsoft.Office.Interop.Excel

 

혹은, 아래와 같이 참조를 오른쪽 클릭 후, COM 탭에서 Microsofte Excel 16.0 Object Libaray를 찾은 후 확인을 눌러도 된다.

이제 아래와 같이 코드를 작성하고, 실행을 하면,

using System;
using Excel = Microsoft.Office.Interop.Excel;

namespace MagicalComInteraction
{
    class Program
    {
        static void Main(string[] args)
        {
            dynamic excelApp = new Excel.Application();
            excelApp.Workbooks.Add();
            excelApp.Visible = true;

            dynamic worksheet = excelApp.ActiveSheet;
            worksheet.Cells[1, 1].Value = "반갑습니다";
            worksheet.Cells[1, 2].Value = "저는";
            worksheet.Cells[1, 3].Value = "윤태영";
            worksheet.Cells[1, 4].Value = "입니다.";
            worksheet.Cells[1, 5].Value = "나이는";
            worksheet.Cells[1, 6].Value = "90살";
            worksheet.Cells[1, 7].Value = "최고에요!";
        }
    }
}

Excel 창이 열리면서 설정한 값이 셀 텍스트에 보이게 된다.

< Python과 상호 운용성 >

C# 코드 안에서 Python 코드를 호출하는 데에도 dynamic이 유용하다. 이를 위해 Pythonnet라는 라이브러리를 사용할 수 있다. 우선 Pythonnet 라이브러리를 추가하거나.

dotnet add package Python.Runtime

혹시나 실행 시 안된다면, NuGet 패키지관리에서 pythonnet을 설치하면 해결 된다.

using System;
using Python.Runtime;

namespace PythonBridge
{
    class Program
    {
        static void Main(string[] args)
        {
            using (Py.GIL())
            {
                dynamic np = Py.Import("numpy");
                dynamic array = np.array(new double[] { 1.2, 3.4, 5.6 });
                Console.WriteLine($"Sum of array: {np.sum(array)}");
            }
        }
    }
}

위 코드는 Python의 numpy 라이브러리를 불러와서 배열의 합을 계산한다.

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

전기제어 PLC(Programmable Logic Controller)  (0) 2023.06.19
C# 파일과 디렉터리  (0) 2023.06.19
일반화와 제네릭  (0) 2023.06.17
대리자와 이벤트  (0) 2023.06.17
리플렉션,애트리뷰트  (0) 2023.06.17

"일반화"는 주로 특정한 사례나 타입에 국한되지 않고, 보다 넓은 범위의 사례나 타입들을 처리할 수 있도록 코드를 작성하는 것을 의미한다. 이것은 C#에서는 제네릭을 사용해서 일반화라는 개념을 구현할 수 있다.

 

제네릭은 타입을 매개변수로 받아서, 하나의 코드로 여러 타입을 처리할 수 있게 해준다.

 

예를 들어 정수 리스트,문자열 리스트,사용자 정의 객체 리스트 등을 만들 때 각각의 리스트를 위한 별도의 클래스를 작성하는 대신, 제네릭을 사용해서 일반화된 리스트 클래스를 만들 수 있다.

< 제네릭을 사용하지 않은 경우 >

정수와 문자열을 저장하는 데 각각 다른 클래스를 만들어야 한다.

public class IntegerBox
{
    private int item;

    public void SetItem(int item)
    {
        this.item = item;
    }

    public int GetItem()
    {
        return item;
    }
}

public class StringBox
{
    private string item;

    public void SetItem(string item)
    {
        this.item = item;
    }

    public string GetItem()
    {
        return item;
    }
}

< 제네릭을 사용한 경우 >

제네릭을 사용하면 하나의 클래스로 여러 타입을 처리할 수 있다.

public class Box<T>
{
    private T item;

    public void SetItem(T item)
    {
        this.item = item;
    }

    public T GetItem()
    {
        return item;
    }
}

< 사용해보기 >

using System;

namespace GenericBoxExample
{
    class Program
    {
        // 제네릭 클래스 Box 정의
        public class Box<T>
        {
            private T item;

            public void SetItem(T item)
            {
                this.item = item;
            }

            public T GetItem()
            {
                return item;
            }
        }

        static void Main(string[] args)
        {
            // 정수 저장 BOX
            Box<int> intBox = new Box<int>();
            intBox.SetItem(123);
            Console.WriteLine($"정수 상자에 담긴 내용 => {intBox.GetItem()}");

            // 문자열을 저장하는 Box
            Box<string> stringBox = new Box<string>();
            stringBox.SetItem("태영");
            Console.WriteLine($"문자열 상자에 담긴 내용 => {stringBox.GetItem()} ");

            // 사용자 정의를 저장하는 BOx
            Box<Person> personBox = new Box<Person>();
           personBox.SetItem(new Person { Name = "윤태영", Age = 90 });
            Console.WriteLine($"저는 {personBox.GetItem().Name} 이며, {personBox.GetItem().Age}살 입니다. ");


        }

        //사용자 정의 클래스 Person
        public class Person
        {
            public string Name { get; set; }
            public int Age { get; set; }

        }
    }
}

 

 

Reference: https://www.tutorialsteacher.com/csharp/csharp-generics

 

C# Generics

C# Generics Generic means the general form, not specific. In C#, generic means not specific to a particular data type. C# allows you to define generic classes, interfaces, abstract classes, fields, methods, static methods, properties, events, delegates, an

www.tutorialsteacher.com

 

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

C# 파일과 디렉터리  (0) 2023.06.19
dynamic  (0) 2023.06.18
대리자와 이벤트  (0) 2023.06.17
리플렉션,애트리뷰트  (0) 2023.06.17
LINQ  (0) 2023.06.16

< 대리자(Delegate) 란? >

대리자는 메서드를 가리키는 타입이다. 그래서 대리자를 통해 메서드를 호출할 수 있다.

병사가 장교에게 전화를 했는데, 장교가 아닌 다른 부사관이 받아서, 부사관이 장교에게 ~사항을 전해달라고 부탁 해서, 그 부사관이 장교에게 전달 사항을 주면, 장교가 다시 병사에게 가서, 전달 사항에 대해 처리를 해주는 상황과 비슷하다. 
예를 들어, "더하기"라는 메서드를 호출하고 싶다면, 대리자를 사용해서 "더하기" 메서드를 가리키고, 대리자를 통해 그 메서드를 실행할 수 있다.

< 대리자 선언, 사용하기 >

ConsoleApp 프로젝트를 만들어서 대리자 선언 및 사용하는 코드

using System;

namespace DelegatesExample
{

	class Program
    {
    	// 대리자를 선언한다. 이 대리자는 두 정수를 받아서, 정수를 반환하는 메서드를 참조할 수 있다.
        public delegate int Calculate(int x, int y);
        
        // 대리자가 참조할 수 있는 형태의 메서드를 만든다.
        public static int Add(int a, int b)
        {
        
        	return a + b;
        }
        
        static void Main(string[] args)
        {
        	// 대리자 인스턴스를 생성하고, Add 메서드를 참조하도록 한다.
            Calculate calculate = Add;
            
            // 대리자 인스턴스를 통해 Add 메서드를 호출한다.
            int result = calculate(3, 4);
            Console.WriteLine($"3 + 4 = {result}");
        }
     }
    }

코드를 실행하면, 아래와 같이, 정상적으로 작동된다.

[일반화 된 대리자 사용하기 ]

Func와 Action은 C#에서 기본적으로 제공하는 일반화된 대리자다. Func는 반환값이 있는 메서드를, Action은 반환값이 없는 메서드를 참조한다.

using System;

namespace DelegatesExample
{

    class Program
    {
        // 대리자를 선언한다. 이 대리자는 두 정수를 받아서, 정수를 반환하는 메서드를 참조할 수 있다.
        public delegate string Calculate(int x, string y);

        // 대리자가 참조할 수 있는 형태의 메서드를 만든다.
        public static string Add(int a, string b)
        {
            return $"{a} + {b}";
        }

        static void Main(string[] args)
        {
            // Func 대리자를 사용하는 예시 
            Func<int, string, string> addFunc = Add;
            string resultFunc = addFunc(5, "감자");
            Console.WriteLine($"5 + 감자 = {resultFunc}");

            // 사용자 정의 대리자를 사용하는 예시
            Calculate calculate = Add;
            string resultCalculate = calculate(5, "감자");
            Console.WriteLine($"5 + 감자 = {resultCalculate}");
        
        }
    }
}

using System;

namespace DelegatesExample
{

    class Program
    {
      
        delegate void MyDelegate(string message);

        static void Main()
        {
            Action<string> myAction = SayHello;
            myAction("Hello with Action!");

            Func<int, int, int> myFunc = Sum;
            int result = myFunc(3, 4);
            Console.WriteLine("Sum with Func: " + result);
        }

        static void SayHello(string message)
        {
            Console.WriteLine(message);
        }

        static int Sum(int a, int b)
        {
            return a + b;
        }

    }
}

< 대리자 체인 >

여러 메서드를 하나의 대리자에 연결하여 한 번에 호출할 수 있다. 이를 대리자 체인이라고 한다.

using System;

namespace DelegatesExample
{

    class Program
    {
      
        delegate void MyDelegate(string message);

        static void Main()
        {
            Action<string> myAction = SayHello;
            myAction += SayGoodbye;

            myAction("태영님");
        }

        static void SayHello(string name)
        {
            Console.WriteLine($"안녕하세요, {name}!");
        }

        static void SayGoodbye(string name)
        {
            Console.WriteLine($"조심히 가십시오, {name}!");
        }

    }
}

< 익명 메서드 >

대리자에 이름이 없는 메서드를 직접 할당할 수도 있다.

using System;

namespace DelegatesExample
{

    class Program
    {

        delegate void MyDelegate(string message);

        static void Main()
        {
            Action<string> myAction = delegate (string message)
            {
                Console.WriteLine(message);
            };

            myAction("반갑습니다!!!");
        }
    }
}

< 이벤트 >

이벤트(Events)
이벤트는 어떤 사건이 발생했을 때 해당 사건을 처리하는 메서드를 실행하는 데 사용된다. 이벤트를 선언할 때는 event 키워드를 사용한다.
.NET Core 또는 .NET 5~6 프로젝트를 만들어서
Program.cs에 아래와 같이 코드를 작성하고 실행해보면, Button 클릭 확인 문구가 출력되는걸 볼 수 있다. 

이 Event 키워드와, 대리자를 사용해서

using System;

namespace EventExample
{
    public class Alarm
    {
        // 대리자 타입 선언
        public delegate void AlarmEventHandler(string message);

        // 이벤트 선언
        public event AlarmEventHandler AlarmEvent;

        // 메서드를 호출하여 알람 이벤트를 발생
        public void RaiseAlarm(string message)
        {
            // 이벤트가 null이 아닌 경우에만 이벤트를 발생시키기
            AlarmEvent?.Invoke(message);
        }
    }

    class Program
    {
        static void Main()
        {
            // Alarm 클래스의 인스턴스를 생성
            Alarm alarm = new Alarm();

            // 알람 이벤트에 대한 처리 로직을 익명 메서드로 등록
            alarm.AlarmEvent += delegate (string message)
            {
                Console.WriteLine($"알람: {message}");
            };

            // 알람 이벤트 발생
            alarm.RaiseAlarm("불이야!");
        }
    }
}

이렇게 작성도 가능하다.

 

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

dynamic  (0) 2023.06.18
일반화와 제네릭  (0) 2023.06.17
리플렉션,애트리뷰트  (0) 2023.06.17
LINQ  (0) 2023.06.16
Indexers  (0) 2023.06.16

< 리플렉션(Reflection)이란? >

리플렉션은 프로그램이 실행 중에 자기 자신의 구조를 들여다보고, 조작하는 기능이다. 마치 바코드 스캐너가 상품의 정보를 읽는 것과 같다.
Object.GetType() 이 메서드를 사용하면, 객체의 타입 정보를 얻을 수 있다.  과일 상자에 있는 과일이 어떤 종류인지 확인하는 것과 비슷하다.

var number = 5;
Type type = number.GetType();
Console.WriteLine(type); // 출력: System.Int32

 

< Type 클래스>
Type 클래스는 C#의 리플렉션 기능과 밀접한 관련이 있는데, 이 클래스를 사용하면 타입에 대한 상세한 정보를 조회하고 조작할 수 있다. 타입은 클래스, 인터페이스, 열거형, 구조체 등과 같은 프로그래밍 요소를 의미한다.
아래는  StringBuilder라는 클래스의 인스턴스를 리플렉션을 통해 동적으로 생성하는 코드다.

Type myType = typeof(StringBuilder);
object myInstance = Activator.CreateInstance(myType);

 

Type myType = typeof(StringBuilder);
typeof 키워드를 사용하여 StringBuilder 클래스의 Type 객체를 가져온다. StringBuilder는 문자열을 조작할 때 사용되는 C#의 기본 클래스 중 하나다.
myType 변수에는 이제 StringBuilder 클래스에 대한 메타데이터 정보가 담긴 Type 객체가 저장된다.

Type은 C#이 제공하는 클래스와 메서드가 있고, 그걸 typeof를 사용하면, Type안에 있는 기능을 가져올 수 있다.


object myInstance = Activator.CreateInstance(myType);
Activator.CreateInstance 메서드를 사용하여, 앞서 가져온 Type 객체(myType)를 기반으로 새 인스턴스를 생성한다.
이렇게 생성된 인스턴스는 object 형식의 myInstance 변수에 저장된다. 이 경우, myInstance는 StringBuilder 클래스의 새 인스턴스를 참조하게 된다.
주목할 점은, 일반적으로 new StringBuilder()와 같은 구문을 사용하여 인스턴스를 생성하는 대신, 리플렉션을 사용하여 동적으로 인스턴스를 생성한다는 것이다. 이 기법은 특정 타입이 런타임에 결정될 때 유용하며, 런타임에 객체를 생성하고 조작할 수 있다.

 

스마트폰을 사려고 하는데, 일반적으로 직원에게 "아이폰13 모델을 보고싶네요"라고 할 것이다. 이때, 직원은 아이폰13을 가져와서 고객에게 주는데, 이 상황이 일반적으로 객체를 생성하는 방법과 비슷하다. 즉, new iPhone13()처럼 코드로 작성해서 바로 객체를 만드는 것이다.

하지만, 이번에는 "아이폰"이라는 것에 대한 정보가 적힌 카드를 가지고 있다. 그런데 이 카드에는 모델 번호가 적혀있지 않다. 그래서 가게 직원에게 이 카드를 보여주며 "이 정보를 가지고 있는 스마트폰을 하나 주세요"라고 말한다. 직원은 카드를 보고, 해당 정보에 맞는 스마트폰을 찾아서 준다.

이 상황이 바로 Activator.CreateInstance(myType)를 사용하는 상황과 비슷하다. 이 메서드를 통해, 우리가 가지고 있는 Type 객체(정보가 적힌 카드)를 사용하여 새로운 객체(스마트폰)를 생성하는 것이다.

MethodInfo appendStringMethod = myType.GetMethod("Append");
appendStringMethod.Invoke(myInstance, new object[] { "Hello, Reflection!" });


이 코드는 StringBuilder 클래스의 Append 메서드를 동적으로 찾아 호출하는 것을 보여준다. 이렇게 리플렉션을 사용하면, 컴파일 시간이 아닌 런타임에 객체와 해당 타입의 멤버들을 다룰 수 있다.

 

예를 들자면 로봇이 명령에 따라 다양한 종류의 샌드위치를 만들 수 있는 것과 비슷하다.
MethodInfo appendStringMethod = myType.GetMethod("Append");

이 코드는 마치 로봇에게 말하는 것과 비슷하다. "로봇아, 너가 샌드위치를 만드는 데 사용할 수 있는 'Append'라는 기능(메서드)이 있는지 확인해줘." 라고 말하는 것이다. 로봇은 내부 메뉴얼을 확인하고 'Append'라는 기능을 찾아준다. 여기서 myType은 로봇의 메뉴얼이고, appendStringMethod는 'Append'라는 기능에 대한 정보를 가지고 있게 되는 것이다.

appendStringMethod.Invoke(myInstance, new object[] { "Hello, Reflection!" });
이제 로봇에게 샌드위치를 만들도록 명령할 시간이다. 이 코드는 마치 "로봇아, 방금 찾아준 'Append' 기능을 사용해서, 'Hello, Reflection!' 이라는 재료를 넣어 샌드위치를 만들어줘." 라고 말하는 것과 비슷하다. 여기서 myInstance는 로봇이 만들고 있는 샌드위치(즉, StringBuilder 객체)이고, "Hello, Reflection!"은 샌드위치에 넣을 재료다.

리플렉션을 사용하면, 컴파일 시간에 정해진 것이 아니라 실행 중에 어떤 기능을 사용할지, 어떤 객체에 적용할지 결정할 수 있다. 이는 마치 로봇에게 동적으로 다양한 샌드위치를 만들도록 지시하는 것과 비슷하다.

 

< 애트리뷰트(Attribute)란? >

애트리뷰트는 코드에 추가 정보를 제공하는 데 사용된다. 상품에 붙어있는 라벨과 비슷하다. 예를 들어, 어떤 메서드가 작업을 수행하는 데 오랜 시간이 걸린다고 알리고 싶을 때 사용할 수 있다.

[애트리뷰트 작성하기]

애트리뷰트를 사용하려면 대괄호([]) 안에 애트리뷰트 이름을 적고, 해당 코드 요소 앞에 붙인다. 라벨을 만들어 상품에 붙이는 것처럼, 애트리뷰트도 만들어서 코드에 붙일 수 있는 것이다.

[AttributeUsage(AttributeTargets.Method)]
public class LongRunningAttribute : Attribute
{
    public string Message { get; }

    public LongRunningAttribute(string message)
    {
        Message = message;
    }
}

 

[애트리뷰트 사용하기]
애트리뷰트를 사용하려면 대괄호([]) 안에 애트리뷰트 이름을 적고, 해당 코드 요소 앞에 붙인다.

[LongRunning("This method takes a long time.")]
public void TimeConsumingMethod()
{
    // ...
}

< 실습 >

Visual Studio를 실행해서. "새 프로젝트 만들기"를 클릭 후, "C# Console App"을 선택하고, "다음"을 클릭한다.
프로젝트 이름을 입력하고, "만들기"를 클릭한다.


Program.cs 파일을 아래와 같이 수정해보자.

using System;
using System.Reflection;
using System.Text;

namespace ReflectionAndAttributesDemo
{
    [AttributeUsage(AttributeTargets.Method)]
    public class LongRunningAttribute : Attribute
    {
        public string Message { get; }

        public LongRunningAttribute(string message)
        {
            Message = message;
        }
    }

    public class Program
    {
        [LongRunning("This method takes a long time.")]
        public static void TimeConsumingMethod()
        {
      
        }

        public static void Main(string[] args)
        {
            // Reflection
            var myNumber = 5;
            Type myType = myNumber.GetType();
            Console.WriteLine($"Type of myNumber: {myType}");

            Type stringBuilderType = typeof(StringBuilder);
            object myStringBuilder = Activator.CreateInstance(stringBuilderType);
            Console.WriteLine($"Type of myStringBuilder: {myStringBuilder.GetType()}");

            // Using Attribute
            MethodInfo methodInfo = typeof(Program).GetMethod("TimeConsumingMethod");
            var attribute = (LongRunningAttribute)methodInfo.GetCustomAttribute(typeof(LongRunningAttribute));
            Console.WriteLine($"Attribute message: {attribute.Message}");
        }
    }
}

여기서 아래의 코드부터 상세히 보자.

[AttributeUsage(AttributeTargets.Method)]
public class LongRunningAttribute : Attribute
{
    public string Message { get; }

    public LongRunningAttribute(string message)
    {
        Message = message;
    }
}

 

[AttributeUsage(AttributeTargets.Method)] : 이 애트리뷰트가 메서드에만 적용될 수 있도록 제한.
public string Message { get; } : 읽기 전용 속성으로, 애트리뷰트가 전달하는 메시지를 저장한다.
public LongRunningAttribute(string message) : 생성자로, 애트리뷰트를 생성할 때 메시지를 전달받아 Message 속성에 저장한다.

[LongRunning("오랜 시간 걸립니다.")]
public static void TimeConsumingMethod()
{
   
}

[LongRunning("오랜 시간 걸립니다.")] : LongRunningAttribute 애트리뷰트를 메서드에 적용하고, 메시지를 전달한다.

public static void Main(string[] args)
{
    // Reflection
    var myNumber = 5;
    Type myType = myNumber.GetType();
    Console.WriteLine($"Type of myNumber: {myType}");
    
    Type stringBuilderType = typeof(StringBuilder);
    object myStringBuilder = Activator.CreateInstance(stringBuilderType);
    Console.WriteLine($"Type of myStringBuilder: {myStringBuilder.GetType()}");
    
    // Using Attribute
    MethodInfo methodInfo = typeof(Program).GetMethod("TimeConsumingMethod");
    var attribute = (LongRunningAttribute)methodInfo.GetCustomAttribute(typeof(LongRunningAttribute));
    Console.WriteLine($"Attribute message: {attribute.Message}");
}


Ctrl + F5를 눌러 프로그램을 실행한다.

.

 

 

 

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

일반화와 제네릭  (0) 2023.06.17
대리자와 이벤트  (0) 2023.06.17
LINQ  (0) 2023.06.16
Indexers  (0) 2023.06.16
Thread Pool  (0) 2023.06.16

< LINQ 란? >

LINQ는 Language Integrated Query의 약자로, 데이터를 쿼리하는 기능을 프로그래밍 언어에 통합한 것이다. SQL을 생각하면 되는데, SQL처럼 데이터베이스뿐만 아니라, 배열이나 리스트 같은 메모리에 있는 데이터도 다룰 수 있다.

SQL과 비슷하다고 생각하면 된다. 예를 들어, 도서관에서 책을 찾고 있을 때. 이때 LINQ가 도서관 직원이라고 생각해보자. 이용자가 “모험 소설 중 300페이지 이상인 책들을 보여주세요”라고 요청하면, LINQ(도서관 직원)가 책장을 뒤져서 원하는 책들을 가져다 줄 것이다.

< LINQ 기본 문법 >

LINQ에는 주로 두 가지 문법이 사용된다. 하나는 Query 문법이고, 다른 하나는 Method 문법이다.

[Query 문법]

var result = from item in collection
             where item > 2
             select item;

 

[Method 문법]

var result = collection.Where(item => item > 2);

 

두 문법은 동일한 결과를 내지만, 개인의 취향이나 상황에 따라 선택해 사용할 수 있다.

< 실습 >

[Visual Studio 프로젝트 생성하기]
Visual Studio를 열고, "Create a new project"를 클릭해서. "콘솔 앱(.NET Framework)"를 선택하고, "다음" 버튼을 클릭한다.(이미 .Net Core 프로젝트가 있다면.. 프로젝트를 굳이 새로 안만들어도 될 듯 하다.)


여기서부터는 Program.cs 파일을 아래와 같이 만들 것이다.

밑의 코드를 우선 보자면, 예제 데이터를 리스트로 생성한 뒤(numbers 쪽) 에 LINQ를 사용해서 숫자를 필터링 했다.

queryResult 부분에서는 numbers 리스트에서 2보다 큰 숫자들로 구성되는 쿼리다.

methodResult 부분에서도 똑같이 numbers 리스트에서 2보다 큰 숫자들이 나오게 시도 해보았다.

그리고 출력문 부분에서 foreach문을 각각 1개씩 넣어서, 2보다 큰 숫자들이 나올 수 있게 문을 만들어 놨다.

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqTutorial
{
    class Program
    {
        static void Main(string[] args)
        {
            // 예제 데이터
            List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

            // Query 문법 사용
            var queryResult = from number in numbers
                              where number > 2
                              select number;

            // Method 문법 사용
            var methodResult = numbers.Where(number => number > 2);

            // 결과 출력
            Console.WriteLine("Query 문법 결과:");
            foreach (var item in queryResult)
            {
                Console.WriteLine(item);
            }

            Console.WriteLine("Method 문법 결과:");
            foreach (var item in methodResult)
            {
                Console.WriteLine(item);
            }
        }
    }
}

이제 이걸 실행하면 아래와 같이, 원하던 대로 2보다 큰 수들이 나오는 걸 볼 수 있다.

LINQ는 Where 뿐만 아니라 Select, OrderBy, GroupBy 등 다양한 연산을 제공한다. 이것들은 데이터를 필터링하거나 정렬하고, 변환하는데 사용된다. (지금까지 보았다면 느꼈겠지만 사실 SQL과 비슷하다.)
[예시]

using System;
using System.Collections.Generic;
using System.Linq;

namespace LinqTutorial
{
    class Program
    {
        static void Main(string[] args)
        {
            // 예제 데이터
            List<int> numbers = new List<int> { 1, 2, 3, 4, 5 };

            // Query 문법 사용
            var orderedResults = from number in numbers
                                 orderby number descending
                                 select number;

            // 또는

            var orderedMethodResults = numbers.OrderByDescending(number => number);

            // 결과 출력
            Console.WriteLine("Query 문법 결과:");
            foreach (var item in orderedResults)
            {
                Console.WriteLine(item);
            }

            Console.WriteLine("Method 문법 결과:");
            foreach (var item in orderedMethodResults)
            {
                Console.WriteLine(item);
            }
        }
    }
}

numbers라는 리스트 내의 숫자들을 내림차순으로 정렬하는 쿼리를 작성해보았다.

정상적으로 작동한다.

 

< 데이터 원본에서 데이터 조회하기 >

LINQ는 데이터 원본에서 데이터를 조회할 수 있다. 예를 들어, 데이터베이스에서 데이터를 가져오는 경우가 있다. 뿐만 아니라, XML, Docs, Collection, ADO.Net DataSet, Web Service, MS SQL Server 및 다른 Data Server와 같은 다양한 종류와도 호환이 된다.

단, 데이터베이스 연결과 관련된 코드이므로 Entity Framework가 프로젝트에 설정되어 있어야한다.

 

[예제코드]

※   DbContext를 상속받도록 구현하면 아래와 같은 장점이 있다고 한다.

  • [Entity Framework와의 통합] : DbContext를 상속받음으로써 MyDbContext 클래스는 Entity Framework의 핵심 기능과 기능을 이용할 수 있다고 한다. 이는 데이터베이스와의 상호작용, 데이터베이스 쿼리 작성, 트랜잭션 관리 등 다양한 기능을 제공해준다.
  • [데이터베이스 매핑] : DbContext를 상속받은 클래스는 데이터베이스 테이블과 개체 간의 매핑을 설정할 수 있다. 예를 들어, DbSet<T> 프로퍼티를 사용하여 데이터베이스 테이블과 개체 간의 관계를 정의하고, LINQ를 사용하여 데이터베이스에서 데이터를 쿼리할 수 있다.
  • [마이그레이션 지원] : DbContext를 상속받은 클래스를 사용하면 Entity Framework의 마이그레이션 기능을 활용하여 데이터베이스 스키마를 관리할 수 있다. 데이터베이스 스키마 변경을 코드로 추적하고, 변경 사항을 데이터베이스에 적용할 수 있다.
  • [유연한 데이터베이스 연결 구성] : DbContext를 상속받는 클래스에서 OnConfiguring 메서드를 재정의하여 데이터베이스 연결 구성을 설정할 수 있다. 이를 통해 데이터베이스 공급자, 연결 문자열 등을 유연하게 구성할 수 있다.
    ※ OnConfiguring 메서드에서 optionsBuilder.UseSqlServer("YourConnectionString") 부분은 사용자의 실제 데이터베이스 연결 문자열로 수정되어야 한다.
  • [테스트 용이성] : DbContext를 상속받는 클래스는 인터페이스 분리 원칙을 준수하면서, 데이터베이스와의 상호작용을 추상화할 수 있다. 이는 유닛 테스트 등에서 데이터베이스에 대한 의존성을 분리하고, 대체할 수 있는 가짜 데이터 또는 메모리 내 데이터베이스를 사용하여 테스트할 수 있는 유연성을 제공한다.
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;

namespace EntityFrameworkExample
{
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class MyDbContext : DbContext
    {
        public DbSet<User> Users { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("YourConnectionString");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new MyDbContext())
            {
                // 새 사용자 추가
                var newUser = new User { Name = "Alice", Age = 27 };
                db.Users.Add(newUser);
                db.SaveChanges();

                // 특정 사용자 조회
                var user = db.Users.FirstOrDefault(u => u.Name == "Alice");
                if (user != null)
                {
                    Console.WriteLine($"Id: {user.Id}, Name: {user.Name}, Age: {user.Age}");
                }

                // 사용자 업데이트
                user.Age = 30;
                db.SaveChanges();

                // 사용자 삭제
                db.Users.Remove(user);
                db.SaveChanges();
            }
        }
    }
}

위 코드는 Users는 데이터베이스의 테이블과 매핑되는 프로퍼티다. User는 해당 테이블의 레코드를 나타내는 클래스로 각 컬럼에 해당하는 프로퍼티들을 가진다. ※ 테이블을 매핑하기 전에 그 테이블이 MySql인지.. Oracle인지 모르기에 

먼저 연결 문자열과 공급자를 설정 해줘야한다.

MySQL 연결 설정을 할려고 한다면,  appsettings.json 파일에서 MySQL 연결 문자열을 설정한다. appsettings.json file이 없을 수 있는데, 없다면

솔루션 탐색기에서 프로젝트를 마우스 오른쪽 버튼으로 클릭하고 "추가" → "새 항목"을 선택해서


"JSON 파일"을 검색하여 선택하고 파일 이름으로 "appsettings.json"을 입력하고 "추가" 버튼을 클릭한다.

Mysql을 쓰고 싶다면,

[appsettings.json 파일에서 MySQL 연결 문자열을 설정한다.] 

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Port=3306;Database=mydatabase;Uid=myusername;Pwd=mypassword;"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

 

appsettings.json 파일의 "DefaultConnection" 항목에서 "Uid"와 "Pwd"는 사용자 이름과 비밀번호를 나타낸다. 이렇게 "DefaultConnection"에다가 사용자 이름과 비밀번호를 넣으면 연결 시 해당 정보를 사용하여 데이터베이스에 접속할 수 있다.
예를 들어, "myusername"은 사용자 이름이고, "mypassword"는 비밀번호를 나타내는 곳인데. 실제로 사용할 때에는 이 부분을 자신의 데이터베이스 사용자 이름과 비밀번호로 변경해야 한다.
또한, Database=mydatabase; 부분은 데이터베이스의 이름을 나타낸다. 여기서 "mydatabase"는 실제 사용할 데이터베이스의 이름으로 변경되어야 한다. 데이터베이스 이름은 데이터베이스 서버에서 생성한 데이터베이스의 실제 이름으로 바꿔주어야 한다. (보통 root로 되어 있는 것으로 알고 있다.)

[Startup.cs 파일에서 DbContext에 MySQL 연결 문자열을 설정 한다]

일부 템플릿에서는 프로젝트 생성 시 자동으로 생성되지 않을 수도 있다. Startup.cs 파일이 없는 경우, 아래와 같은 추가 방식으로 직접 Startup.cs 파일을 추가하면 된다.

public void ConfigureServices(IServiceCollection services)
{
    string connectionString = Configuration.GetConnectionString("DefaultConnection");
    services.AddDbContext<MyDbContext>(options =>
        options.UseMySql(connectionString));
    // ...
}

위의 코드로 Mysql을 설정해주면 되는데, 여기서 최상위문을 써야한다고 빨간 줄이 나오면, 그것은 .Net Framwork를 5.0이하로 쓰고 있다는 것이다. 왜냐하면 C# 9.0은 5.0이상 지원을 하기 때문이다. 이것 때문에 매우 골치가 아팠다.

때문에,  https://dotnet.microsoft.com/ko-kr/download/visual-studio-sdks?cid=getdotnetsdk 이 링크에서 8.0 x86을 설치하고. cmd로 dotnet 명령어를 통해 framework가 정상 설치되는 것을 확인 했다.

이후 visual studio installer에서 다시 업데이트를 해보았지만 되지 않아서,

cs8370를 해결하고자, Docs를 자세히 찾아보니... C# 9.0은 Version 5.x로 변경했어야 했다.

확인 한 뒤, Visual Studio Installer에서 개별 설치에서 5.0 version을 다운로드 했다.

 

이후로 안되어서, Console APP보다는 ASP.NET CORE Web App Project가 SQL에 적합하다고 해서,  적합한 Project를 만들어서, 이후 StartUp.cs를 아래와 같이 변경 하고, 코드를 재수정 해주었다.

Program.cs도 재수정 해주었다.

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;

namespace EntityFrameworkExample
{
    public class User
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
    }

    public class MyDbContext : DbContext
    {
        public DbSet<User> Users { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("YourConnectionString");
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new MyDbContext())
            {
                // 새 사용자 추가
                var newUser = new User { Name = "Alice", Age = 27 };
                db.Users.Add(newUser);
                db.SaveChanges();

                // 특정 사용자 조회
                var user = db.Users.FirstOrDefault(u => u.Name == "Alice");
                if (user != null)
                {
                    Console.WriteLine($"Id: {user.Id}, Name: {user.Name}, Age: {user.Age}");
                }

                // 사용자 업데이트
                user.Age = 30;
                db.SaveChanges();

                // 사용자 삭제
                db.Users.Remove(user);
                db.SaveChanges();
            }
        }
    }
}
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.EntityFrameworkCore;
using EntityFrameworkExample;

namespace YourNamespace
{
    public class Startup
    {
        public IConfiguration Configuration { get; }

        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public void ConfigureServices(IServiceCollection services)
        {
            string connectionString = Configuration.GetConnectionString("DefaultConnection");

            services.AddDbContext<MyDbContext>(options =>
                options.UseSqlServer(connectionString));

            services.AddControllers();
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
     
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseStaticFiles();

            app.UseRouting();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}

 

 

 

연결 문자열에는 해당 데이터베이스에 연결하기 위한 필수 정보 (예: 서버, 포트, 데이터베이스 이름, 사용자 이름, 암호 등)가 포함되어야 한다.
연결 문자열을 통해 데이터베이스 유형을 확인하고, Entity Framework를 사용하여 해당 데이터베이스와 연결하여 작업할 수 있다.

 


🔗 Reference : 이것이 C#이다 3판 

https://product.kyobobook.co.kr/detail/S000201856223

 

이것이 C#이다 | 박상현 - 교보문고

이것이 C#이다 | 전문가로 레벨업!C# 프로그래밍 완전 정복이 책은 C#의 기본, 고급 문법과 더불어 기초 클래스 라이브러리까지 다루고 있다. 총 22개 장으로, 앞서 배운 내용을 활용하면서 단계별

product.kyobobook.co.kr

 

https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9

 

What's new in C# 9.0 - C# Guide

Get an overview of the new features available in C# 9.0.

learn.microsoft.com

https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/

 

Language-Integrated Query (LINQ) (C#)

Learn about Language-Integrated Queries (LINQs) and review an example of the complete query operation.

learn.microsoft.com

https://www.javatpoint.com/what-is-linq

 

What is LINQ - Javatpoint

What is LINQ with java tutorial, features, history, variables, programs, operators, oops concept, array, string, map, math, methods, examples etc.

www.javatpoint.com

 

 

 

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

대리자와 이벤트  (0) 2023.06.17
리플렉션,애트리뷰트  (0) 2023.06.17
Indexers  (0) 2023.06.16
Thread Pool  (0) 2023.06.16
Timer  (0) 2023.06.16

< Indexers 란? >

Indexers를 사용하면, 클래스나 구조체의 인스턴스를 배열처럼 쉽게 접근할 수 있다. 꼭 배열처럼 정수로 인덱스를 사용하지 않아도 된다.
Indexers는 this 키워드를 사용하여 정의한다. 보통 get 과 set 접근자를 가지며, 값을 가져오거나 설정할 수 있다.

< 실습 >

 

[Visual Studio 프로젝트 생성하기]
Visual Studio를 열고, "Create a new project"를 클릭해서. "콘솔 앱(.NET Framework)"를 선택하고, "다음" 버튼을 클릭한다.

 

프로젝트 이름을 "IndexersDemo"로 정하고, 원하는 위치에 프로젝트를 생성한다. (프로젝트 이름은 마음대로 해도 괜찮다.)

 

[클래스 생성하기]

Indexers를 적용할 샘플 클래스를 만들어봅시다. "SampleCollection"이라는 새로운 클래스를 생성한다.


프로젝트 이름을 오른쪽 클릭한 후, "클래스" > "Class"를 찾고 추가를 클릭한다.


[Indexers 코딩하기]

SampleCollection 클래스 파일에 Indexers를 추가한다.

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

namespace IndexersDemo
{
    class SampleCollection<T>
    {
        private T[] arr = new T[100];

        public T this[int i]
        {
            get => arr[i];
            set => arr[i] = value;
        }
    }
}

[Main 메서드에서 사용해보기]
이제 Program.cs 파일로 돌아가서, 만든 Indexers를 사용해본다.

using System;

namespace IndexersDemo
{
    class Program
    {
        static void Main()
        {
            var stringCollection = new SampleCollection<string>();
            stringCollection[0] = "Hello, C# !";
            Console.WriteLine(stringCollection[0]);
        }
    }
}




이제 어플리케이션을 실행해본다. Visual Studio 상단의 녹색 재생 버튼("시작")을 클릭하면. 콘솔에 "Hello, C# "라는 메시지가 출력되는 걸 볼 수 있다.


[더 간결한 문법으로 작성하기]

Indexers의 get이나 set 접근자가 단 한 줄인 경우, 표현식 본문(=>)을 사용해서 더 간단하게 쓸 수 있다.

public T this[int i]
{
    get => arr[i];
    set => arr[i] = value;
}

※ 주의사항

Indexers는 정수가 아닌 다른 타입으로도 인덱싱할 수 있다.
Indexers를 여러 개 정의하여 오버로드할 수 있다.
Indexers는 두 개 이상의 매개변수를 가질 수 있다. (예: 2차원 배열 접근).

Reference  : 이것이 C#이다 3판

https://product.kyobobook.co.kr/detail/S000201856223

 

이것이 C#이다 | 박상현 - 교보문고

이것이 C#이다 | 전문가로 레벨업!C# 프로그래밍 완전 정복이 책은 C#의 기본, 고급 문법과 더불어 기초 클래스 라이브러리까지 다루고 있다. 총 22개 장으로, 앞서 배운 내용을 활용하면서 단계별

product.kyobobook.co.kr

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

 

Indexers - C# Programming Guide

Indexers in C# allow class or struct instances to be indexed like arrays. You can set or get the indexed value without specifying a type or instance member.

learn.microsoft.com

 

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

리플렉션,애트리뷰트  (0) 2023.06.17
LINQ  (0) 2023.06.16
Thread Pool  (0) 2023.06.16
Timer  (0) 2023.06.16
일반화 프로그래밍  (0) 2023.06.15

< Thread Pool >

수영장을 떠올려보자. 수영장에는 여러 개의 수영 차선이 있다. 이제 각 차선을 스레드라고 생각하고, 수영하는 사람들을 작업이라고 생각해보자. 수영장 관리자는 사람들이 수영할 때마다 새로운 차선을 만들지 않는다. 대신 이미 있는 차선을 활용한다. 이렇게 미리 만들어진 차선을 사용하는 것이 효율적이다. 이 개념이 바로 Thread Pool이다.

< 👨‍🍳 레스토랑에서의 Thread Pool >

레스토랑을 예로 들어보자. 레스토랑에는 요리사들이 있고, 주문이 들어올 때마다 요리를 만들어야 한다. 요리사들은 스레드처럼 생각할 수 있고, 주문은 작업으로 생각할 수 있다.

레스토랑이 바빠지면 요리사들은 계속해서 요리를 만들어야 하는데. 이때 레스토랑 주인이 매번 새로운 요리사를 고용하는 것은 비효율적이다. 대신 미리 여러 요리사를 고용해두고, 주문이 들어올 때마다 그들에게 요리를 맡기는 것이 효율적이다. 이 미리 고용된 요리사들의 그룹이 Thread Pool과 비슷한 개념이다.

using System;
using System.Threading;

class Program
{
    static void Main()
    {
        Console.WriteLine("레스토랑이 문을 열었습니다. 주문을 기다리는 중...");

        // 5개의 주문이 들어왔다고 가정
        for (int i = 1; i <= 5; i++)
        {
            int orderNumber = i;
            ThreadPool.QueueUserWorkItem(o => ProcessOrder(orderNumber));
        }

        // 요리사들이 요리를 완료할 때까지 기다린다.
        Console.WriteLine("주문 처리중...");
        Thread.Sleep(5000);

        Console.WriteLine("모든 주문이 처리되었습니다!");
    }

    static void ProcessOrder(int orderNumber)
    {
        Console.WriteLine($"주문 번호 {orderNumber}를 처리하는 중...");
        Thread.Sleep(1000); // 요리하는 시간으로 가정
        Console.WriteLine($"주문 번호 {orderNumber} 처리 완료!");
    }
}


위의 코드에서는 레스토랑을 열고 5개의 주문을 처리한다. Thread Pool을 사용하여 여러 요리사들이 동시에 주문을 처리한다.

 

Reference : 이것이 C#이다

https://product.kyobobook.co.kr/detail/S000201856223

 

이것이 C#이다 | 박상현 - 교보문고

이것이 C#이다 | 전문가로 레벨업!C# 프로그래밍 완전 정복이 책은 C#의 기본, 고급 문법과 더불어 기초 클래스 라이브러리까지 다루고 있다. 총 22개 장으로, 앞서 배운 내용을 활용하면서 단계별

product.kyobobook.co.kr

 

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

LINQ  (0) 2023.06.16
Indexers  (0) 2023.06.16
Timer  (0) 2023.06.16
일반화 프로그래밍  (0) 2023.06.15
이벤트 생성/호출  (0) 2023.06.15

< Timer란 ? >

Timer는 일정한 시간 간격으로 코드를 실행할 때 사용된다. 예를 들어, 주기적으로 데이터를 새로고침하거나, 시간을 표시하는 데 사용할 수 있다.

C#에는 크게 세 가지 타입의 Timer가 있다.

  • System.Windows.Forms.Timer
  • System.Timers.Timer
  • System.Threading.Timer

 


[System.Windows.Forms.Timer]

이 Timer는 UI 스레드에서 동작한다. 따라서, UI 요소를 갱신하는데 유용하다. 하지만 오래 걸리는 작업을 처리하면 UI가 멈출 수 있다.

[매 초마다 현재 시간을 화면에 표시하는 코드]

using System;
using System.Timers;

class Program
{
    static void Main()
    {
        System.Timers.Timer myTimer = new System.Timers.Timer(2000); // 2초 간격으로 설정
        myTimer.Elapsed += OnTimedEvent; // 이벤트 핸들러 연결
        myTimer.Start(); // 타이머 시작

        Console.WriteLine("Press Enter to exit...");
        Console.ReadLine();
    }

    private static void OnTimedEvent(Object source, ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
    }
}

위 코드는 2초마다 콘솔에 현재 시간을 출력한다. Elapsed 이벤트에 메소드를 연결하여 타이머가 경과할 때마다 실행된다.


[System.Threading.Timer]

이 Timer는 스레드 풀의 스레드에서 작업을 수행한다. 주로 백그라운드 작업에 적합하다.

using System;
using System.Threading;

class Program
{
    static int count = 0;

    static void Main()
    {
        TimerCallback callback = new TimerCallback(PrintMessage);
        Timer stateTimer = new Timer(callback, null, 0, 2000); // 2초 간격으로 설정

        Console.WriteLine("Press Enter to exit...");
        Console.ReadLine();
    }

    static void PrintMessage(object state)
    {
        count++; // 카운터 증가
        Console.WriteLine($"안녕하세요. {count}번째 윤태영입니다.");
    }
}

위 코드도 2초마다 메시지를 출력한다. 하지만 이 타이머는 스레드 풀의 스레드를 사용한다.

 

※ System.Threading.Timer와 System.Timers.Timer

둘 다 주기적으로 코드를 실행하는데 사용된다. 하지만 둘 사이에는 차이점이 있다.

무거운 작업이거나 높은 정밀도(빠른 간격의 정확한 타이밍)가 필요하면 System.Threading.Timer를 사용이 권장된다.
이벤트 기반으로 작업하거나 UI와 상호작용이 필요하다면 System.Timers.Timer이 권장된다.


[System.Windows.Forms.Timer] 

WinForms 애플리케이션에서 사용되는 Timer. UI 스레드에서 실행된다.

using System;
using System.Drawing;
using System.Windows.Forms;

namespace WinFormsApp4
{
    public partial class Form1 : Form
    {
        private System.Windows.Forms.Timer timer1;
        private int counter = 0;
        private Label greetingLabel;
        private Color[] colors = { Color.Red, Color.Orange, Color.Yellow, Color.Green, Color.Blue, Color.Indigo, Color.Violet };
        
        public Form1()
        {
            InitializeComponent();

            timer1 = new System.Windows.Forms.Timer();
            timer1.Tick += TimerEventProcessor;
            timer1.Interval = 2000; // 2초
            timer1.Start();
        }

        private void TimerEventProcessor(object sender, EventArgs e)
        {
            counter++;
            this.Text = "Counter: " + counter.ToString();

            //배경색 변경
            this.BackColor = colors[counter % colors.Length];
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            
            // 라벨을 생성하고 "안녕하세요!" 출력하기.
            greetingLabel = new Label();
            greetingLabel.Text = "안녕하세요!";
            greetingLabel.Location = new Point(350, 200); // 라벨의 위치 설정
            greetingLabel.AutoSize = true;

            // 라벨을 폼에 추가.
            this.Controls.Add(greetingLabel);
        }
    }
}
namespace WinFormsApp4
{
    partial class Form1
    {
        private System.ComponentModel.IContainer components = null;

        // 폼이 종료될 때 리소스를 정리하는 메서드
        // disposing 매개변수가 true이면 관리되는 리소스를 해제
        protected override void Dispose(bool disposing)
        {
            // disposing이 true이고, components가 null이 아니라면
            // components를 해제하여 메모리를 정리
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Windows Form Designer generated code


        private void InitializeComponent()
        {
            SuspendLayout();

            // Form1 설정
            // AutoScaleDimensions -> 폼의 크기를 조절하는데 사용되는 설정값
            AutoScaleDimensions = new SizeF(7F, 15F);
            //  폰트 크기에 맞춰 조절.
            AutoScaleMode = AutoScaleMode.Font;
            ClientSize = new Size(800, 450);
            Name = "Form1";
            Text = "Form1";
            // 폼이 로드될 때 실행할 이벤트 핸들러를 설정
            Load += new System.EventHandler(this.Form1_Load);

            ResumeLayout(false);
        }

        #endregion
    }
}

2초마다 폼의 배경색을 변경할 수 있도록 해보았다.

 

 

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=77894&tab=curriculum 

 

학습 페이지

 

www.inflearn.com

 

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

Indexers  (0) 2023.06.16
Thread Pool  (0) 2023.06.16
일반화 프로그래밍  (0) 2023.06.15
이벤트 생성/호출  (0) 2023.06.15
크로스 스레드  (0) 2023.06.15

< 일반화 프로그래밍이란? >

일반화 프로그래밍이란, 메서드나 클래스를 정의할 때 특정 타입이 아닌, 타입 파라미터를 사용하여 여러 타입에 대해 동작하는 코드를 작성하는 것이다.

일반화 프로그래밍을 상자에 비유하자면, 상자 안에는 무엇이든 넣을 수 있다. 과자도 넣을 수 있고, 책도 넣을 수 있다. 일반화 프로그래밍에서의 상자가 바로 '제네릭 클래스'나 '제네릭 메서드'다.

< 일반화 프로그래밍의 장점 >

  • 타입 안전(Type Safety) : 컴파일 시점에 타입 검사를 하기 때문에 런타임 오류를 줄일 수 있다.
  • 재사용성(Reusability) : 하나의 코드로 다양한 타입에 대응할 수 있으므로 코드 중복을 줄일 수 있다.
  • 성능 향상(Performance): 박싱(Boxing)과 언박싱(Unboxing)을 줄여 메모리와 CPU 사용량을 최적화한다.

 

[제네릭 클래스 만들기] 

public class MyBox<T>
{
    private T item;

    public void Put(T item)
    {
        this.item = item;
    }

    public T TakeOut()
    {
        return item;
    }
}

 

위의 예시에서 MyBox는 제네릭 클래스다. <T>는 타입 파라미터로, 어떤 타입이든 될 수 있다.

[제네릭 클래스 사용해보기] 

MyBox<string> stringBox = new MyBox<string>();
stringBox.Put("Hello, Generics!");
Console.WriteLine(stringBox.TakeOut()); // 출력: Hello, Generics!

MyBox<int> intBox = new MyBox<int>();
intBox.Put(123);
Console.WriteLine(intBox.TakeOut()); // 출력: 123

 

MyBox를 사용하여 문자열과 정수를 담는 상자를 만들었다.

[제네릭 메서드 만들기]

public class Utils
{
    public static void Swap<T>(ref T a, ref T b)
    {
        T temp = a;
        a = b;
        b = temp;
    }
}

 

Swap 메서드는 두 개의 값을 교환한다. 여기서도 타입 파라미터 <T>를 사용한다.

[제네릭 메서드 사용해보기]

int a = 5, b = 10;
Utils.Swap(ref a, ref b);
Console.WriteLine($"a: {a}, b: {b}"); // 출력: a: 10, b: 5

string x = "hello", y = "world";
Utils.Swap(ref x, ref y);
Console.WriteLine($"x: {x}, y: {y}"); // 출력: x: world, y: hello

 

숫자 뿐만 아니라 문자열도 바꿀 수 있다.


 

Reference : 이것이 C#이다

https://product.kyobobook.co.kr/detail/S000201856223

 

이것이 C#이다 | 박상현 - 교보문고

이것이 C#이다 | 전문가로 레벨업!C# 프로그래밍 완전 정복이 책은 C#의 기본, 고급 문법과 더불어 기초 클래스 라이브러리까지 다루고 있다. 총 22개 장으로, 앞서 배운 내용을 활용하면서 단계별

product.kyobobook.co.kr

 

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

Thread Pool  (0) 2023.06.16
Timer  (0) 2023.06.16
이벤트 생성/호출  (0) 2023.06.15
크로스 스레드  (0) 2023.06.15
스레드 문제점 해결  (0) 2023.06.14

+ Recent posts