본문 바로가기

문과 코린이의, [C#] 기록/C# 이론

[문과 코린이의 IT 기록장] C# - 클래스 2 (구조체 , 제네릭 (Generic), Delegate / Event)

반응형

[문과 코린이의 IT 기록장] C# - 클래스 2 (구조체 , 제네릭 (Generic), Delegate / Event)

[문과 코린이의 IT 기록장] C# - 클래스 2 (구조체 ,  제네릭 (Generic), Delegate / Event)


 

 

C# 프로그래밍 기초 - 인프런 | 강의

본 강좌는 C# 문법 위주로 구성되어있지 않습니다. 클래스를 이해하고 만드는 요령 위주로 구성되어 있습니다. 기초 문법도 다루지만 많은 예제를 가지고 진행하기 때문에 프로그램 실전 작성

www.inflearn.com


1. 구조체 (Struct)

1) 참조 형식(클래스)과 값 형식(구조체)에 대한 이해

- Heap Memory / Stack Memory

값 형식 (Stack에서 사용) - 구조체의 대표 원리
참조 형식 (Class에서 사용) - 클래스의 대표 원리

 

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

namespace class_0804
{
    class Program 
    {
        static void Main(string[] args)
        {
            // Value Type의 특성
            // - 동작방법 
            /*int a = 1;
            int b = 2;

            Console.WriteLine($"1. a = {a} b = {b}");
            int c = ValueTypeTest(a, b);
            Console.WriteLine($"2. a = {a} b = {b}");*/


            Console.WriteLine("클래스 : 참조 형식");
            Student1 st1 = new Student1();
            st1.Name = "Lee";
            Console.WriteLine(st1.Name);
            ReferenceTypeTest(st1);
            Console.WriteLine(st1.Name);

            Console.WriteLine("-----------------------------------");

            Console.WriteLine("구조체 : 값 형식");
            Student2 st2 = new Student2();
            st2.Name = "Lee";
            Console.WriteLine(st2.Name);
            ReferenceTypeTest(st2);
            Console.WriteLine(st2.Name);
        }



        static int ValueTypeTest(int a, int b) // 호출할 때 값을, copy해 준 것. (참조가 X)
        {
            Console.WriteLine($"3. a = {a} b = {b}");
            a++;
            b++;
            Console.WriteLine($"4. a = {a} b = {b}");
            return a + b;
        }

        static void ReferenceTypeTest(Student1 a)
        {
            a.Name = "Kim";
        }

        static void ReferenceTypeTest(Student2 a)
        {
            a.Name = "Kim";
        }

    } 

    /// <summary>
    /// 참조 형식으로 학생 클래스입니다.
    /// </summary>
    class Student1
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    struct Student2
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

}


2) 구조체 정의

- 클래스 정의와 유사하다. 

- class 예약어를 struct 예약어대체한다.

 

3) 구조체가 클래스와 다른점

- 인스턴스 생성new로 해도 되고, 안 해도 된다.

- 참조 형식이 아니라 값 형식이다.

- 기본 생성자명시적으로 정의 불가능하다.

- 매개변수를 갖는 생성자를 정의해도, 기본 생성자가 C# 컴파일러에 의해 자동 포함된다.

- 매개변수를 받는 생성자의 경우, 반드시 해당 코드 내에서, 구조체의 모든 필드에 값을 할당해야 한다.

 

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

namespace class_0804
{
    class Program
    {
        static void Main(string[] args)
        {
            Student st = new Student();
            Student st1 = st; // 메모리 자체가 통째로 복사되는, 깊은 복사가 이루어짐.
            Student st2; // new를 쓰지 않고 생성 가능 (값 형식 변수이기 때문)

            st.Name = "Lee";
            st1.Name = "Kim";
            //st2.Name = "Kim";

            Console.WriteLine(st.Name);
            Console.WriteLine(st1.Name); // 깊은 복사이기 때문에, 새로운 메모리 공간에 st1이 만들어진 것. 따라서, 둘은 다른 값을 가짐

            AAA(st);
        }


        static void AAA(Student st) // 깊은 복사 발생. (스택에서 발생) - 구조체는 참조형식 X. (매개변수만 참조형식으로 힙에 존재)
        {
        }


        /// <summary>
        /// 참조 형식으로 학생 클래스입니다.
        /// </summary>
        struct Student
        {
            public string Name { get; set; }
            public int Age { get; set; }

            // 1. Sturct에 대해, C컴파일러가 알아서, 매개변수가 없는 생성자를 만들어준다.
            // public Student() { }; // 구조체는 명시적으로 기본 생성사를 정의하는 것이 불가능하다.

            // 2. 매개변수를 갖는 생성자의 경우, 반드시 해당 코드 내에 구조체의 모든 필드의 값을 할당해야 한다.
            public Student(string name, int age)
            {
                Name = name;
                Age = age;
            }
        }

    }
}

 


2. 제네릭 (Generic)

- OOP에서 Object를 처리하는데 발생하는 문제점들을 개선하기 위한 하나의 방법


1) Generic 탄생 동기

- ArrayList를 사용할 때, boxing, unboxing의 문제와, 코드 중복의 문제를 해결하기 위해, Generic이라는 개념이 나타났다.

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

namespace class_0804
{
    class generic
    {

        public void Run()
        {
            ArrayList list = new ArrayList();
            list.Add(4); // boxing 발생
            int a = (int)list[0]; // unboxing 발생

            PrintMe(3);
            PrintMe("String");
        }

        // [문제] 여러 종류의 Type이 존재할 때, 이렇게 함수를 다 나눠서 코드를 작성해야 할 것인가?
        // - 코드 중복이 굉장히 많이 발생
        private void PrintMe(int val)
        {
            Console.WriteLine($"val = {val}");
        }
        private void PrintMe(string val)
        {
            Console.WriteLine($"val = {val}");
        }

      
    }
}

 


2) Generic 사용법 : ArrayList -> List<T>

a. 프로젝트 1 - [ Generic.cs ]

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

namespace class_0804
{
    class generic
    {
        public void Run()
        {
            // [해결방법] List 컬랙션 사용
            // 1. int 사용
            // List<T> list = new List<T>(); // T : 타입 변수명 = Type Parametor (Generic)
            List<int> list = new List<int>(); // int ArrayList 생성 : 이 ArrayList에는 int만 들어온다. 다른 것이 들어오면 error.

            list.Add(1); // 가능 O
            // list.Add("string"); // 불가능

            int a = list[0]; // (int)로 캐스팅 해줄 필요 없음. 즉, unboxing이 필요 없음. (Type을 int로 설정했기 때문)

	// 2. 클래스 사용
            // 다른 프로젝트의 클래스를, 각각 클래스는 어셈블리 파일(.exe, .dll)로 구성되어 있으며, 이들 간에 서로가 보일려면 public이라는 접근 제한자를 쓸 수 밖에 없음. 
            // 참조로, Student namespace를 가져온 후, StudentC 사용
            List<StudentC> list1 = new List<StudentC>();
            StudentC st = new StudentC(); // st 인스턴스 생성
            list1.Add(st);
            //list1.Add(1); // 타입 불일치. 불가능.
        }
    }
}

b. 프로젝트 2 - [StudentC.cs]

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

namespace Student
{

    // public class Student = internal class Student
    // ( internal이란? : 동일한 어셈블리 내에서는 public과 같이 동작됨. 그러나 다른 어셈블리에서 바라봤을 때는 보이지 않는다. )
    
    // 다른 프로젝트에서 해당 클래스를 가져다 쓰기 위해서는, public을 넣어줘야 함.
    public class StudentC:IComparable // IComparable : 두 객체를 비교하기 위한 인터페이스 (배열 및 컬랙션을 정렬하는데 효과적인 인터페이스)
    {
        public string Name { get; set; }
        public int StudentSex { get; set; }
        public int Score { get; set; }

        public int CompareTo(object obj)
        {
            StudentC st1 = obj as StudentC;
            // return Score - st1.Score;
            return Name.CompareTo(st1.Name);
        }

        public override string ToString()
        {
            return $"{Name}[{StudentSex}][{Score}]";
        }
    }
}

 


3) Generic class & Generic method

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

namespace class_0804
{
    class generic
    {
        public void Run()
        {
            // 1. Generic Class < T >
            PrintAny<int> p1 = new PrintAny<int>(); // PrintAny형의 인스턴스 p를 생성 (내부 T는 모두 int형으로 변환)
            p1.Print(3);

            PrintAny<double> p2 = new PrintAny<double>(); // PrintAny 인스턴스 p2를 생성 (내부 T는 모두 double형으로 변환)
            p2.Print(3.141592);

            PrintAny<StudentC> p3 = new PrintAny<StudentC>();
            StudentC st = new StudentC();
            st.Name = "홍길동";
            p3.Print(st);

            // 2. Generic Method
            PrintAny1 p4 = new PrintAny1();
            p4.Print<int>(15);
            p4.Print(10); // <int>를 지정해주지 않아도, 타입 추정 가능.

            // 3. Generic Class < T, U >
            PrintAny2<int, string> p5 = new PrintAny2<int, string>();
            p5.Print(33, "string");

        }
    }


    // [문제] 수많은 기본 타입, class들이 존재함. 그런데, 이렇게 type별로 print해야하는 클래스를 모두 만드는 것은 어려움
    class PrintInteger
    {
        public void Print(int val)
        {
            Console.WriteLine($"val = {val}"); 
        }
    }

    // [해결방법] Generic Class<T>로 해결
    class PrintAny<T> // Generic Class : T는 어떤 type이던, 컴파일 타임 때 지정된 type으로 전부 다 변경됨.
    {
        public void Print(T val) // T는 타입변수. 따라서 이렇게 선언 가능
        {
            Console.WriteLine($"val = {val}");
            // Console.WriteLine($"val = {val + 1}"); 
            // 이는 불가능. 특정 타입에 관계된 연산 처리와 같은 것을 Generic이라고 쓸 수 없음. 즉, 범용으로 커버하는데 사용될 수 없음. 
        }
    }

    // [해결방법] Generic Method로 해결
    class PrintAny1
    {
        public void Print<T>(T val) // Generic Method
        {
            Console.WriteLine($"val = {val}");
        }
    }

    // Generic Class <T, U>
    class PrintAny2<T, U> 
    {
        public void Print(T val1, U val2) 
        {
            Console.WriteLine($"val1 = {val1}, val2 = {val2}");
        }
    }

    // T와 U가 IComparable이라는 인터페이스를 구현하지 않은 타입이라면 받아들이지 못한다. ex. :IComparable을 하지 않은 클래스
    class PrintAny3<T, U> where T:IComparable where U:IComparable
    {
        public void Print(T val1, U val2)
        {
            Console.WriteLine($"val1 = {val1}, val2 = {val2}");
        }
    }
}

 


3. Delegate / Event

1) Delegate

- Method를 값으로 갖는 타입

int a = 3; // 3이라는 value를 가지는 타입 - value 타입
Student st = new Student(); // Student 객체를 갖는 타입 - reference 타입
delegate void MyDelegate(int a); // 함수를 값으로 갖는 타입 - delegate 타입

- delegate가 붙으면 method가 아니라 MyDelegate가 Student, int 처럼 type으로 선언된다.

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

namespace class_0804
{
    class Dele
    {

        // 2.
        delegate void MyDelegate(int val); // delegate type 맴버변수로 선언
        // 3.
        delegate int MyDelegate1(int val);

        public void Run()
        {
            int a = 3;
            Console.WriteLine(a);

            StudentC st = new StudentC();
            st.Name = "홍길동";
            Console.WriteLine(st);

            // 1. 그냥 메소드로 실행
            DelegateTest(3); // 정상적으로 호출됨

            // [ C언어에서 함수 포인터와 개념이 유사 ]

            // 2. delegate 사용. 
            // 클래스를 선언하듯이, MyDelegate타입을 쓸 수 있게 됨
            MyDelegate d = new MyDelegate(DelegateTest); // d변수는, 함수를 포인팅하는(가리키는) 변수가 됨.
            // 즉 하나의 메소드가, 매개변수로서 들어올 수 있도록 돕는다.
            d(4); // d가 DelegateTest를 가르킴. 즉, 이 변수 이름을 부른다 = 함수 호출

            // 3. 
            // MyDelegate d1 = new MyDelegate(DelegateTest1);
            // 위는 불가능. 왜냐하면 MyDelegate라는 타입은, 반환형식은 void, 파라미터는 int 하나를 가르키는 함수에 대해서만 가르킬 수 있다는 것. 이를 해결하기 위해서는 또 다른 delegate를 선언해야 함.
            MyDelegate1 d1 = new MyDelegate1(DelegateTest1);

        }

        private void DelegateTest(int myVal)
        {
            Console.WriteLine($"DelegateTest() called {myVal}");
        }

        private int DelegateTest1(int myVal)
        {
            return myVal + 1;
        }

    }
}

[ Windows Forms App 에서 Delegate 예시 ]

[ Form1.cs ]

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();
        }

        // 이 버튼이 눌러지면, 이벤트가 발생하는데, 그 이벤트(delegate)에다가 button1_Click메소드를 만들고 이 메소드 명을 넣어준 것.
        // 컴퓨터 뒤에서는 delegate 행위가 수행되고 있는 것 (개발자 입장에서는 보이지 않음)
        // 즉 button 클래스에는 delegate가 있음. 이것이 button1_Click과 연결되게 된 것
        // 다시말해 button(return type : void, 매개변수 sender, e)의 delegate에 button1_Click메소드를 할당해준 것.
        private void button1_Click(object sender, EventArgs e)
        // [ object sender, EventArgs e를 사용한다는 것 중요 ]
        {
            textBox1.Text = "버튼이 눌러졌어요";
        }
        // delegate로 연결되어 callback이 이루어지도록 만드는 개념 = Event Driven 방식
    }
}

Click Event Handler : 변수명 - button_1_Click


2) Delegate를 이용한 ConsoleMenu 만들기

 

- CallBack 

ex )

[ Program.cs ]

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

namespace class_0804
{
    class Program
    {
        static void Main(string[] args)
        {
            Dele d = new Dele();
            d.Run();
        }
    }
}

[ Dele.cs ]

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

namespace class_0804
{
    class Dele
    {
        ConsoleMenu Menu;

        public Dele()
        {
            Menu = new ConsoleMenu();
        }


        public void Run()
        {
            // 1. Menu1 : "1" or "Menu1" 입력 -> Delegate 호출
            // cf) menu는 1번을 눌렀을 때 무엇을 해야할지 아무것도 모른다. 그러나 그것에 등록된 Delegate를 호출해준다. 즉 Menu에 보편성이 생김.
            // 2. Menu2
            // ...
            // q : 종료
            // 입력 : n

            CreateMenu();

            // 무한루프 돌면서 메뉴를 보여주는 프로그램 - q를 누르면 죽어버림.
            for (; ; )
            {
                Menu.Show();
                Menu.Read();
            }
        }

        private void CreateMenu()
        {
           /* MenuItem item = new MenuItem("1","Menu_Title1",Menu_1_Callback);
            Menu.MenuList.Add(item);
            item = new MenuItem("2","Menu_Title2",Menu_2_Callback);*/

            // 줄여쓰기
            Menu.MenuList.Add(new MenuItem("1", "Menu_Title1", Menu_1_Callback)); 
            // delegate로, Menu_1_Callback이 들어가는 것
            Menu.MenuList.Add(new MenuItem("2", "Menu_Title2", Menu_2_Callback));
            Menu.MenuList.Add(new MenuItem("q", "프로그램 종료", Quit_Callback));
        }


        // [ 메뉴 1 구현 ]
        // delegate에서 구현한 시그니처를 그대로 받음.
        // = public delegate void MenuKeyPressDelegate(object sender, MenuArgs args);

        // ConsoleMenu에서는 MenuArgs가 아닌 MenuKeyPressArgs(retval)로 보냄.
        // MenuArgs는 MenuKeyPressArgs의 조상이기 때문에, 이를 가리킬 수 있다.
        private void Menu_1_Callback(object sender, MenuArgs args)
        {
            Console.WriteLine($"Menu_1_Callback 호출됨. sender={sender.ToString()} args={((MenuKeyPressArgs)args).MenuChar}");
            // MenuKeyPressArgs로 실제로는 보낸것을 MenuArgs로 받았기 때문에, 다시 자식으로 캐스팅해줌으로써 자식까지 보이도록 해줌.
        }


        // [ 메뉴 2 구현 ]
        private void Menu_2_Callback(object sender, MenuArgs args)
        {
            Console.WriteLine($"Menu_2_Callback 호출됨. sender={sender.ToString()} args={((MenuKeyPressArgs)args).MenuChar}");
        }

        // [ 프로그램 종료 구현 ]
        private void Quit_Callback(object sender, MenuArgs args)
        {
            Environment.Exit(0); // 프로그램 빠져나가기
            Console.WriteLine($"Quit_Callback 호출됨. sender={sender.ToString()} args={((MenuKeyPressArgs)args).MenuChar}");
            Console.WriteLine("Bye");
        }
    }
}

[ MenuItem.cs ]

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

namespace class_0804
{
    class MenuItem
    {
        public delegate void MenuKeyPressDelegate(object sender, MenuArgs args);
        // MenuKeyPressDelegate가 불러지면, callback함수로 날라감. 즉 호출한 클래스에 있는 함수를 부르게 됨. 이 때, 해당 함수에게 seneder(누가 불렀는지)와, MenuArgs 클래스의 args매개변수 전달.

        // sender : Windows에서는 이벤트 핸들러가 불러줄 적에 사용하는 파라미터가 정형화되어 있음. 이러한 원리로 날라간다는 것을 설명하기 위해 이렇게 작성한 것.


        public string MenuChar { get; set; } // 1, 2, 3, ... , q
        public string MenuTitle { get; set; } // Menu1, Menu2, Menu3, ...

        public MenuKeyPressDelegate KeyPressDelegate { get; set; } // 특정 메뉴를 눌렀을 때, 불러올 함수 => func(sender, args)


        // 생성자 단축키 : ctor + tab + tab
        public MenuItem(string menu_char, string menu_title, MenuKeyPressDelegate dele)
        {
            MenuChar = menu_char;
            MenuTitle = menu_title;
            KeyPressDelegate = dele;
        }

        public MenuItem():this(null, null, null){ }
    }
}

[ ConsoleMenu.cs ]

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

namespace class_0804
{
    class ConsoleMenu
    {
        public List<MenuItem> MenuList { get; set; }
        // (List 컬랙션을 활용) MenuItem을 계속 가질 수 있는 Array형태의 컬랙션
        // MenuList각각에는 MenuItem이 들어감

        public ConsoleMenu()
        {
            MenuList = new List<MenuItem>(); // 컬랙션 생성
        }

        // 호출한 부분의, MenuItem의 menu들을 보여줌
        public void Show()
        {
            foreach(MenuItem item in MenuList)
            {
                Console.WriteLine($"{item.MenuChar}.{item.MenuTitle}");
            }
            Console.WriteLine();
        }

        // menu 선택
        public void Read()
        {
            Console.Write("메뉴 선택 : ");
            string retVal = Console.ReadLine(); // 1, 2, ... ,q
            // 이 값들이 MenuList중 하나에 속해있을 것. 

            foreach (MenuItem item in MenuList)
            {
                // 1. MenuItem에서 MenuChar의 값 == 입력한 값
                // 2. MenuItem에서 KeyPressDelegate값이 null이 아닐 때 (Delegate 값을 가지고 있는가)
                if (item.MenuChar == retVal && item.KeyPressDelegate != null)
                {
                    item.KeyPressDelegate(this, new MenuKeyPressArgs(retVal)); 
                    // delegate 호출
                }
            }
        }

    }
}

[ MenuArgs.cs ]

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

namespace class_0804
{
    class MenuArgs
    {
    }

    // Menu를 선택할 적에, 파리미터로 전달될 형식을 모델링하는 것.
    class MenuKeyPressArgs : MenuArgs
    {
        public string MenuChar { get; set; } // string으로 입력한 값을 돌려주겠다.

        public MenuKeyPressArgs(string menu_char)
        {
            MenuChar = menu_char;
        }
    }
}


3) EventHandler를 사용한 ConsoleMenu 프로그램 만들기

- Windows Forms 프로그램에서는 전부 다 EventHandler를 쓰게 됨.

- delegate(low level)보다 EventHandler(high level)를 사용하는 것이 더 효율적임.

 

[ Program.cs ]

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

namespace class_0804
{
    class Program
    {
        static void Main(string[] args)
        {
            Dele d = new Dele();
            d.Run();
        }
    }
}

[ Dele.cs ]

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

namespace class_0804
{
    class Dele
    {
        ConsoleMenu Menu;

        public Dele()
        {
            Menu = new ConsoleMenu();
        }


        public void Run()
        {
            CreateMenu();

            for (; ; )
            {
                Menu.Show();
                Menu.Read();
            }
        }

        private void CreateMenu()
        {
            MenuItem item = new MenuItem("1", "Menu_Title1");
            item.MenuKeyPressEventHandler += Menu_1_Callback;
            Menu.MenuList.Add(item);

            item = new MenuItem("2", "Menu_Title2");
            item.MenuKeyPressEventHandler += Menu_2_Callback;
            Menu.MenuList.Add(item);

            item = new MenuItem("q", "프로그램 종료");
            item.MenuKeyPressEventHandler += Quit_Callback;
            Menu.MenuList.Add(item);
        }


        private void Menu_1_Callback(object sender, EventArgs args)
        {
            Console.WriteLine($"Menu_1_Callback 호출됨. sender={sender.ToString()} args={((MenuKeyPressArgs)args).MenuChar}");
        }


        private void Menu_2_Callback(object sender, EventArgs args)
        {
            Console.WriteLine($"Menu_2_Callback 호출됨. sender={sender.ToString()} args={((MenuKeyPressArgs)args).MenuChar}");
        }

        private void Quit_Callback(object sender, EventArgs args)
        {
            Environment.Exit(0); // 프로그램 빠져나가기
            Console.WriteLine($"Quit_Callback 호출됨. sender={sender.ToString()} args={((MenuKeyPressArgs)args).MenuChar}");
            Console.WriteLine("Bye");
        }
    }
}

[ MenuItem.cs ]

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

namespace class_0804
{
    class MenuItem
    {
        // [ Delegate 사용 X ]
        // public delegate void MenuKeyPressDelegate(object sender, EventArgs args);

        // [ EventHandler 등록 ]
        public event EventHandler MenuKeyPressEventHandler; 
        // EventHandler 변수인, MenuKeyPressEventHandler
        // EventHandler는 이미 delegate를 모두 가지고 있음.

        public string MenuChar { get; set; } // 1, 2, 3, ... , q
        public string MenuTitle { get; set; } // Menu1, Menu2, Menu3, ...

        // [ Delegate 사용 X ]
        //public MenuKeyPressDelegate KeyPressDelegate { get; set; } 

        public MenuItem(string menu_char, string menu_title)
        {
            MenuChar = menu_char;
            MenuTitle = menu_title;
        }

        public MenuItem():this(null, null){ }

        public void CallEvent(object sender, string args)
        {
            // EventHandler는 자신이 선언된 쪽에서 처리가 되어야 함. 다른 클래스에서 처리하는 것은 허락하지 않음.
            // 따라서, ConsoleMenu.cs에서 사용할 수 없고, 여기서 사용해야 함.

            if (MenuKeyPressEventHandler != null)
            {
                MenuKeyPressEventHandler(sender, new MenuKeyPressArgs(args));
            }
        }
    }
}

[ ConsoleMenu.cs ]

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

namespace class_0804
{
    class ConsoleMenu
    {
        public List<MenuItem> MenuList { get; set; }

        public ConsoleMenu()
        {
            MenuList = new List<MenuItem>();
        }

        // 호출한 부분의, MenuItem의 menu들을 보여줌
        public void Show()
        {
            foreach(MenuItem item in MenuList)
            {
                Console.WriteLine($"{item.MenuChar}.{item.MenuTitle}");
            }
            Console.WriteLine();
        }

        // menu 선택
        public void Read()
        {
            Console.Write("메뉴 선택 : ");
            string retVal = Console.ReadLine(); // 1, 2, ... ,q

            foreach (MenuItem item in MenuList)
            {
                if (item.MenuChar == retVal)
                {
                    item.CallEvent(this, retVal);
                }
            }
        }

    }
}

[ MenuArgs.cs ]

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

namespace class_0804
{
/*    class MenuArgs
    {
    }*/

    class MenuKeyPressArgs : EventArgs 
     //.Net Framework에서는 EventArgs라는 것을 지원해줌. MenuArgs를 따로 지정할 필요 X
     // EventArgs를 상속하여, 여러 다양한 종류의 파라미터를 받을 수 있음.
    {
        public string MenuChar { get; set; } // string으로 입력한 값을 돌려주겠다.

        public MenuKeyPressArgs(string menu_char)
        {
            MenuChar = menu_char;
        }
    }
}

 


* 유의사항
- 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다.
- 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다.
- 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :)
반응형