본문 바로가기

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

[문과 코린이의 IT 기록장] C# - 클래스 (클래스 상속, 모든 클래스의 조상 Object 클래스, 메서드 Overridding, 추상 클래스 (Abstract 클래스), 인터페이스 (Interface))

반응형

[문과 코린이의 IT 기록장] C# - 클래스 (클래스 상속, 모든 클래스의 조상 Object 클래스, 메서드 Overridding, 추상 클래스 (Abstract 클래스), 인터페이스 (Interface))

[문과 코린이의 IT 기록장] C# - 클래스 (클래스 상속, 모든 클래스의 조상 Object 클래스, 메서드 Overridding, 추상 클래스 (Abstract 클래스), 인터페이스 (Interface))


 

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

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

www.inflearn.com


1. 클래스 상속 1

[ Program. cs ]

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

namespace class_0804
{
    class Program // = class Program : Object
    {
        static void Main(string[] args)
        {
            // 학생 정의
            // 학생에 필요한 속성 : 성명, 나이, 학년, 반, 번호, 키, 몸무게, 국적, 주소, 전화번호 ...
            // - 학생만이 가질 수 있는 속성 : 학년, 반, 번호, ..
            // - 사람이기 때문에 가지는 속성 : 성명, 나이, 키, 몸무게, 국적, 주소, 전화번호 ...
            // - 동물(Animal)이기 때문에 가지는 속성 : 나이, 성별, 몸무게 ...

            Student st = new Student(); // st인스턴스는 animal, human, student 속성 및 메서드 모두 사용 가능

            // Full Class Name을 출력하도록 기능이 정의되어 있음.
            Console.WriteLine(st.ToString()); // 결과 : class_0804.Student
            // ToString(), Equals()와 같은 object클래스가 제공하는 메서드 또한 모두 나옴. 왜냐하면 기본적으로 가장 상위의 클래스는 Object이기 때문.

            // -> 1. 각각의 생성자에서 실습해보기
            // 정의한 인스턴스는 자기가 상속받은 조상들의 모든 속성 및 메서드를 물려받음.
            // 조상들의 인스턴스가 모두 만들어진 후, 최종적으로 내 인스턴스가 만들어진다.

        }
    }
}

[ Animal.cs ]

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

namespace class_0804
{
    class Animal
    {
        public int Age { get; set; }
        public SexEnum Sex { get; set; }
        public int Weight { get; set; }

        // 생성자
        public Animal() // 1.
        {
            Console.WriteLine("Animal " + this.ToString());
        }


        // 메소드
        public void Sleep() { } // 동물들은 자는 행위를 함

        public void Eat() { }

        public void Walk() {  }

    }
}

[ Human.cs ]

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

namespace class_0804
{
    class Human:Animal // Human 클래스는, Animal 클래스를 상속받고 있다.
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public string CellPhone { get; set; }

        public Human() // 1.
        {
            Console.WriteLine("Human " + this.ToString());
        }

        public void Drawing() { }
        public void Study() { }

    }
}

[ Student.cs ]

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

namespace class_0804
{
    class Student:Human // Student는 Human, 그리고 그 상위인 Animal까지 상속받는다.
    {
        public int Grade { get; set; }
        public int StClass { get; set; }
        public int StId { get; set; }

        public Student() // 1.
        {
            Console.WriteLine("Student " + this.ToString());
        }


    }
}

 


2. 클래스 상속 2

[ Program.cs ]

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

namespace class_0804
{
    class Program // = class Program : Object
    {
        static void Main(string[] args)
        {
            Student st = new Student(); // st인스턴스는 animal, human, student 속성 및 메서드 모두 사용 가능

            Human h1 = st; // 1. h1이 st를 가리킬 수 있다.
            // 부모 인스턴스는, 자식 인스턴트를 가리킬 수 있지만, 해당 부모 인스턴스까지의 기능만 활용 가능하다.
            // 즉 부모가 가지고 있는 메서드 범위를 벗어나서, 자식 인스턴스를 사용할 수는 없다. 

            // Student st2 = h1; // 2. st2는 h1을 가리킬 수 없다.
            // 자식 인스턴스는, 부모 인스턴스를 가리킬 수 없다.

            Student st2 = h1 as Student; // 3. as Student : Student형으로 형 변환 시켜줌. (as 형 변환은 컴파일 타임 때 일어남. 즉 아직 코드가 만들어 지지 않았을 때, 나타남)
            // 형 변환을 해주면, 이 구문이 수행될 수 있다.
            st2.ExamRun(); // 따라서 Student클래스의 메소드 사용 가능


            Human h2 = new Human(); // 4. Student st2 = (Student)h2; // 이 형 변환 방법은 실행 에러가 남. (동적 형 변환, 즉 실행 시에 일어나기 때문에, 불가능)

            bool ret = st2 is Human; // 5. st2값이 Human입니까? ( bool값 리턴 => ret에 true값 저장 )
            bool ret2 = h2 is Student; // h2값이 Student입니까? ( ret2에 flase값 저장 )
        }
    }
}

[ Animal. cs ]

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

namespace class_0804
{
    class Animal
    {
        public int Age { get; set; }
        public SexEnum Sex { get; set; }
        public int Weight { get; set; }

        // 생성자
        public Animal() // 1.
        {
            Console.WriteLine("Animal " + this.ToString());
        }


        // 메소드
        public void Sleep() {
            Console.WriteLine("I'm sleeping now");
        } 

        public void Eat() {
            Console.WriteLine("I'm eating Banana");
        }

        public void Walk() {
            Console.WriteLine("I'm walking in the street");
        }

    }
}

[ Human.cs ]

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

namespace class_0804
{
    class Human:Animal // Human 클래스는, Animal 클래스를 상속받고 있다.
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public string CellPhone { get; set; }

        public Human() // 1.
        {
            Console.WriteLine("Human " + this.ToString());
        }

        public void Drawing() {
            Console.WriteLine("I'm drawing a picture");
        }
        public void Study() {
            Console.WriteLine("I'm studying C#");
        }

    }
}

[ Student.cs ]

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

namespace class_0804
{
    class Student:Human // Student는 Human, 그리고 그 상위인 Animal까지 상속받는다.
    {
        public int Grade { get; set; }
        public int StClass { get; set; }
        public int StId { get; set; }

        public Student() // 1.
        {
            Console.WriteLine("Student " + this.ToString());
        }


        public void ExamRun()
        {
            Console.WriteLine("시험을 보고 있습니다.");
        }


    }
}

 


3. 모든 클래스의 조상 Object 클래스

이와 같이 Object로 표시가 나오는 것은, Object 클래스가 가지고 있는 메서드

[ Program.cs ]

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

namespace class_0804
{
    class Program // = class Program : Object
    {
        static void Main(string[] args)
        {
            // Object
            Animal a = new Animal(); 
            Animal b = new Animal();
            
            Console.WriteLine(a.ToString()); // "class_0804라는 namespace 밑에 있는, Animal 클래스"라고 출력됨.
            // 프로젝트 내 namespace는 프로젝트 명과 같이 정의되어 있음. 그러나 이 둘이 반드시 일치해야 하는 것은 아님)
            // 클래스 이름의 중복을 해결하는 방안으로 제시한 것 - namespace ex. A.Student , B.Student
            // 다른 namespace를 사용하고 싶다면, using으로 가져오기

            Type type = a.GetType(); // Type형식을 반환함.
            Console.WriteLine("Type test = " + type.FullName);

            bool ret = a.Equals(b); // False출력
            // a인스턴스와 b 인스턴스는 다른 객체임.
            Console.WriteLine(ret);

            string sa = "hello";
            string sb = "hello";
            bool ret1 = sa.Equals(sb);  // True 출력
            // 인스턴스는 다르게 구별하지만, string은 자식 클래스에서 메소드가 동작하는 방법을 바꿔놓음. 이를 오버라이딩이라고 함.
            Console.WriteLine(ret1);

            int ia = 5;
            int ib = 5;
            bool iret = ia.Equals(ib); // False 출력
            Console.WriteLine(iret);
        }
    }
}

4. 메서드 Overridding

[ Program.cs ]

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

namespace class_0804
{
    class Program // = class Program : Object
    {
        static void Main(string[] args)
        {
            Student st1 = new Student();
            st1.WhoAreYou();

            Human h1 = new Human();
            h1.WhoAreYou();

            Animal a1 = new Animal();
            a1.WhoAreYou();
            // WhoAreYou()는 오버라이딩 됨. 즉, 부모가 준 메서드를 안 쓰고 싶고, 자기 자신의 메서드를 쓰고 싶은 것.
            
            Animal a2 = st1;
            Console.WriteLine("-------------------------------------------");
            a2.WhoAreYou();
            // a2는 animal 타입으로 캐스팅 되었기 때문에, WhoAreYou()는 animal의 메소드가 불려진다.
            // 만약 a2를 통해, h1 즉 Human타입의 WhoAreYou()를 사용하고 싶다면, Virtual 개념이 도입되어야 한다.
            // 가장 상위 클래스의 메소드에 virtual, 그리고 하위 클래스에 override 개념을 도입하면, 가르키는 객체의 재정의한 메소드를 사용할 수 있다.

            Console.WriteLine("-------------------------------------------");
            Console.WriteLine(a2.ToString()); // 오버라이드 되어, 해당 생성자에 맞는 값을 리턴해줌
            Console.WriteLine(h1.ToString());

            Console.WriteLine("-------------------------------------------");
            Student st2 = new Student();
            Console.WriteLine(st2.ToString());
            Console.WriteLine(st2);
            // 1. 같은 결과가 나온다. WriteLine(st2)일 경우, 클래스의 어떤 것을 출력할지 결정하지 못한다.
            // 그래서 만약 WriteLine이 값을 받으면, 해당 인스턴스의 ToString을 호출한다. 그리고 ToString이 반환된 string 값을 WriteLine이 찍기 때문에, 위의 두 줄은 같은 결과를 출력한다.
            // 2. 이는 Object 클래스에서부터 파생되는 것으로, 하위 클래스에서 new를 사용해 재정의했다면, new로 정의한 것 이후부터는 독립적인 다른 ToString으로 인식한다.
            // 3. new로 재정의한 것을 사용하려면, WriteLine(st2)가 아니라, WriteLine(st2.ToString())으로 확실하게 적어줘야 한다.

            Console.WriteLine("-------------------------------------------");
            Student a = new Student();
            a.Grade = 1;
            a.StClass = 10;
            a.StId = 35;
            a.Name = "홍길동";
            Console.WriteLine(a);
            
            
            Console.WriteLine("-------------------------------------------");
            Human h = a; // 부모가 자식을 가르켰는데, 자식이 ToString 메서드를 override했기 때문에 Student클래스의 메서드 호출.
            Console.WriteLine(h);
        }
    }
}

[ Animal.cs ]

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

namespace class_0804
{
    class Animal
    {
        public int Age { get; set; }
        public SexEnum Sex { get; set; }
        public int Weight { get; set; }


        // 메소드
        public void Sleep() {
            Console.WriteLine("I'm sleeping now");
        } 

        public void Eat() {
            Console.WriteLine("I'm eating Banana");
        }

        public void Walk() {
            Console.WriteLine("I'm walking in the street");
        }

        virtual public void WhoAreYou()
            // virtual로 선언 
            // 하위 클래스의 메소드에서는, override를 통해 재정의 가능
        {
            Console.WriteLine("I'm an Animal");
        }

        public override string ToString() 
        // Object 클래스의, ToString() 메소드 재정의
        // ToString()은 string 값을 반환함.
        {
            return "I'm an Animal";
        }
    }
}

[ Human.cs ]

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

namespace class_0804
{
    class Human:Animal // Human 클래스는, Animal 클래스를 상속받고 있다.
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public string CellPhone { get; set; }


        public void Drawing() {
            Console.WriteLine("I'm drawing a picture");
        }
        public void Study() {
            Console.WriteLine("I'm studying C#");
        }

        override public void WhoAreYou()
        // override : 조상의 메서드와 이름이 같은 것을 물려받았지만, 그것을 쓰지 않을 것이다.
        // 즉, 조상의 메서드를 재정의하여 사용할 것이라고 선언한 것
        {
            Console.WriteLine("I'm an Human");
        }

        public override string ToString()
        {
            return $"{base.ToString()} I'm an Human";
        }


        /*        public new string ToString() // 이 ToString은 상속받은 것을 재정의하는 것이 아니라, 새로운 것을 정의해 독립적으로 사용하겠다는 것
                {
                    return "I'm an Human in my own method";
                }*/
    }
}

[ Student.cs ]

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

namespace class_0804
{
    class Human:Animal // Human 클래스는, Animal 클래스를 상속받고 있다.
    {
        public string Name { get; set; }
        public string Address { get; set; }
        public string CellPhone { get; set; }


        public void Drawing() {
            Console.WriteLine("I'm drawing a picture");
        }
        public void Study() {
            Console.WriteLine("I'm studying C#");
        }

        override public void WhoAreYou()
        // override : 조상의 메서드와 이름이 같은 것을 물려받았지만, 그것을 쓰지 않을 것이다.
        // 즉, 조상의 메서드를 재정의하여 사용할 것이라고 선언한 것
        {
            Console.WriteLine("I'm an Human");
        }

        public override string ToString()
        {
            return $"{base.ToString()} I'm an Human";
        }


        /*        public new string ToString() // 이 ToString은 상속받은 것을 재정의하는 것이 아니라, 새로운 것을 정의해 독립적으로 사용하겠다는 것
                {
                    return "I'm an Human in my own method";
                }*/
    }
}

5. 추상 클래스 (Abstract 클래스)

- 추상 클래스 : 하나 이상의 추상 메서드를 포함하는 클래스

 * 특징 : 인스턴스화 될 수 없음

- 추상 메소드 : 선언부만 존재하는 것이며, 구현은 자식 클래스에서 하도록 하는 메서드

- 추상 클래스 사용 목적 : 상속 받는 클래스들이, 반드시 제공해야 하는 메서드를 정의한다.

클래스들이 상속되면서 계보를 만들어갈 때, 표준화 작업을 위해 존재하는 것이 abstract class의 역할이다.

즉 스스로가 인스턴스가 되는 것이 아니라, 설계 단계의 표준들을 정하고 있는 역할을 한다.

cf. 인터페이스 : 클래스들이 상속받을 수 있는, 클래스같이 생긴 것. 이는 100% 규격으로만 되어있고, 구현은 하나도 없다. 그러나 asbtract class는 asbtract method도 있으며, 실제 구현 method도 둘다 존재할 수 있다.


[ 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)
        {
            // figure f = new figure(); // 추상클래스 인스턴스화 불가능
            Rectangle rect = new Rectangle(10, 20); // width 10, height 20인, Rectangle 인스턴스
            Circle cir = new Circle(10); // 지름 10인, Circle 인스턴스

            Console.WriteLine($"사각형의 면적은 : {rect.Area()}");
            Console.WriteLine($"원의 면적은 : {cir.Area()}");
        }
    }
}

[ figuare.cs ]

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

namespace class_0804
{
    abstract class figure // 추상클래스 선언
    {

        // 추상 메소드는, 반드시 추상 클래스 내부에 존재해야 한다.
        abstract public Double Area(); // 도형체계를 관리
        // 사각형 : width, height 필요
        // 원 : 반지름(PhiR^2) 필요
        // Area()함수를 표준으로 하여, 어느 도형이던지 도형.Area()를 호출하면, 그 도형의 면적을 되돌려주도록 함.

    }
}

[ Rectangle.cs ]

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

namespace class_0804
{
    class Rectangle : figure
    {
        int x1, y1; // |x2-x1| = width
        int x2, y2; // |y2-y1| = height

        public double Width { get; set; }
        public double Height { get; set; }


        public Rectangle(double width, double height) // 매개변수는 소문자로, 맴버변수는 첫번째 대문자로
        {
            Width = width;
            Height = height;
        }

        /// <summary>
        /// 사각형의 면적을 반환
        /// </summary>
        /// <returns></returns>
        // Area 추상 클래스, 오버라이딩해서 실제 구현
        public override double Area()
        {
            return (Width * Height);
        }
    }
}

[ Circle.cs ]

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

namespace class_0804
{
    class Circle:figure
    {
        public double Diameter { get; set; } // 지름

        public Circle(double diameter)
        {
            Diameter = diameter; // 생성자에 지름을 강요하는 방법
        }

        /// <summary>
        /// 원의 면적을 반환합니다.
        /// </summary>
        /// <returns></returns>
        public override double Area()
        {
            return (((Diameter) / 2)*((Diameter) / 2)) * 3.141592;
        }
    }
}


6. 인터페이스 (Interface) 1

인터페이스는, 추상(abstract)메서드들로 구성되어 있으며, 즉 실제로 구현하는 부분이 하나도 없는 메서드들로 정의되어 있다.

따라서 인터페이스는 계약(Contract)이라는 말로 많이 사용하곤 한다. 즉 규칙들만 많이 정의해놨지, 어떻게 구현하는지는 상속받는 클래스들에서 알아서 결정하게 된다는 것이다.


1) 인터페이스의 정의

- 추상 메서드와 같이, 메서드 선언부만 포함하는 형식이다.

- 클래스와는 구분되며, 다중 상속이 가능하다. 즉, 인터페이스는 실존하는 것이 아니라 규칙만 정의된 것이기 때문에, 하나의 클래스는 여러 인터페이스를 상속 받을 수 있다.

 * .net framework에서는 어떤 클래스가, 자신의 부모 클래스로부터 상속을 받을 때, 하나의 부모 클래스로부터 상속을 받지 못한다.

- 인스턴스화 될 수 없다.

- 인터페이스를 상속한 클래스는, 반드시 인터페이스에서 선언된 메서드를 구현해야 한다.

- 인터페이스 선언에는, 속성도 포함될 수 있다.

 * 속성 : getter / setter라는 개념으로 확장됨. 이를 인터페이스에서 선언 가능

 

2) 인터페이스 사용 목적

: 표준화 / 규격화 / 계약 등의 의미

 

3) 인터페이스 응용

- 인터페이스와 다형성 

 ex. abstract class를 상속받아 구현하면서, 자신의 메소드를 스스로 구현할 수 있는 성질

- 인터페이스를 이용한 콜백 구현

- 느슨한 연결 (Loosely Coupling)

 

cf) 인터페이스 이름 규칙 : 인터페이스 명 앞에는 반드시 대문자 I를 붙인다. ex. I + 명사, I + ~able

인터페이스 생성


구성

 

[ 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)
        {
            // figure f = new figure(); // 추상클래스 인스턴스화 불가능
            Rectangle rect = new Rectangle(10, 20); // width 10, height 20인, Rectangle 인스턴스
            Circle cir = new Circle(10); // 지름 10인, Circle 인스턴스

            // 부모는 자식을 가리킬 수 있음
            figure f1 = rect;
            figure f2 = cir;

            IFiguarable if1 = rect;
            IFiguarable if2 = cir;

            PrintRectangleArea(rect);
            PrintCircleArea(cir);

            // 느슨한 연결(Loosely Coupling)의 사례
            PrintFigure(rect);
            PrintFigure(cir);
            PrintFigure(f1);
            PrintFigure(if1);

        }

        static void PrintRectangleArea(Rectangle rect) // static함수로 되어야 함.
        {
            Console.WriteLine($"사각형의 면적 : {rect.Area()}");
        }

        static void PrintCircleArea(Circle cir)
        {
            Console.WriteLine($"원의 면적 : {cir.Area()}");
        }

        // 이 함수는, IFigurable만 만족하면 되는 것이지, 어떤 특정 클래스에 대한 종속성이 낮다. (Rectangle, Circle 모두 가능) 이를 느슨한 연결(Loosely Coupling)이라고 한다. 즉 이는 설계할 적에, 굉장히 유연성 및 확장성을 가지게끔 구조를 가지고 있는 것이다.
        // 위 두 함수는, Rectangle, Circle 각각 특정 클래스에 대한 종속성이 굉장히 높다. 이를 Tightly Coupling이라고 한다.
        static void PrintFigure(IFiguarable obj)
        {
            Console.WriteLine($"{obj.GetType().FullName} 면적 : {obj.Area()}");
        }

    }
}

[ IFiguare.cs ] - 인터페이스

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

namespace class_0804
{
    interface IFiguarable // IFiguare보다 IFigurable로 정의. ("~ 하니?" 하는 형식)
    {
        // 인터페이스 구현 방식 : 반환 형식 + 메소드명 만 정의
        Double Area();

        // IFigurable을 구현한 모든 클래스들은, 그 인터페이스에서 정의하고 있는 메서드들을 모두 가지고 있다는 것
    }
}

[ figuare.cs ]

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

namespace class_0804
{
    abstract class figure:IFiguarable
    {
        // abstract class에서는 공통적으로 모두 적용될 수 있는 메소드들은 아에 구현을 해버린다. 
        // 이를 상속받는 모든 클래스들이, 이를 구현하지 않고 가져다쓰게끔 해주는 역할을 한다.
        
        // 그러나 interface는 이런 것들이 없다
        // 즉, 단지 규격만 정의해준다. 그것을 구현하는 클래스는, 모든 메서드 및 프로퍼티들을 반드시 가지고 있을 것이다.
        
        abstract public Double Area(); // abstract method 선언

        public override string ToString() // 이와 같이 완전히 구현된 method 존재 가능.
        {
            return base.ToString();
        }

    }
}

[ Circle.cs ]

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

namespace class_0804
{
    class Circle:figure
    {
        public double Diameter { get; set; } // 지름

        public Circle(double diameter)
        {
            Diameter = diameter; // 생성자에 지름을 강요하는 방법
        }

        /// <summary>
        /// 원의 면적을 반환합니다.
        /// </summary>
        /// <returns></returns>
        public override double Area()
        {
            return (((Diameter) / 2)*((Diameter) / 2)) * 3.141592;
        }
    }
}

[ Rectangle.cs ]

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

namespace class_0804
{
    class Rectangle : figure
    {
        int x1, y1; // |x2-x1| = width
        int x2, y2; // |y2-y1| = height

        public double Width { get; set; }
        public double Height { get; set; }


        public Rectangle(double width, double height) // 매개변수는 소문자로, 맴버변수는 첫번째 대문자로
        {
            Width = width;
            Height = height;
        }

        /// <summary>
        /// 사각형의 면적을 반환
        /// </summary>
        /// <returns></returns>
        // Area 추상 클래스, 오버라이딩해서 실제 구현
        public override double Area()
        {
            return (Width * Height);
        }
    }
}

7. 인터페이스 (Interface) 2 - IEnumerable 인터페이스

* .Net Framework에서 제공하는 인터페이스는 굉장히 많음. 그 중 가장 대표적인 몇 가지 인터페이스 존재. 그 중 하나

- IEnumerable : 어떤 배열이 있을 때, 그 배열의 요소를 하나씩 뽑아오도록 설계한 인터페이스. 

a. array Class

 : 가장 대표적으로 IEnumerable를 구현한 것이, array이다. 이를 포함한 모든 컬랙션들은 IEnumerable을 구현하고 있다.

b. string Class


1) Ienumerable 클래스

public interface Ienumerable
{
	IEnumerator Getenumerator();
    // Getenumerator()메소드는 IEnumerator타입으로 반환한다.
}

public interface Ienumerator // 특정 타입에 대해서만 움직이는 것이 아니라, 다음이 뭐다라는 규칙만 있으면 이동하도록 구성되어 있다.
{
	object Current{ get; } // object를 반환하는 Current 속성(프로퍼티) 정의 
  	bool MoveNext(); // 메소드 정의 (Current시점부터 다음 요소로 이동하라)
        void Reset(); // 메소드 정의 (이전단계로 가는 것 X. 처음으로 와서 다시 읽도록 하는 메서드)
}

2) 실습

[ 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)
        {
            Inter1 i1 = new Inter1();
            i1.Run();
        }
    }
}

[ Inter1.cs ]

using System;
using System.Collections; // Ienumerator는 System.Collections에 포함되어 있음.
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace class_0804
{
    class Inter1
    {
        private void TestBasic()
        {
            int[] intArray = new int[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
            // array [] 자체가, Ienumerable을 상속하고 있기 때문에, GetEnumerator() 사용 가능

            IEnumerator enumerator = intArray.GetEnumerator();
            // 이제 enumerator를 가져왔기 때문에, 이를 사용해 for문을 돌면서 출력이 가능하다.

            // 1. 기존 방식
            for (int i = 0; i < intArray.Length; i++)
            {
                Console.WriteLine(intArray[i]);
            }

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

            // 2. 인터페이스 사용한 새로운 방식 - 위와 같은 결과 도출
            while (enumerator.MoveNext()) // (bool형. fasle를 return하면 더이상 자료가 없다는 것)
            {
                Console.WriteLine(enumerator.Current);
            }

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

            // 3. foreach - 2. 를 줄여서 쓰는 방법
            foreach (int i in intArray)
            // ienumable을 구현하고 있는 객체 intArray로부터, 내부적으로 반복해달라. 그리고 그 값을 i에 줘라.
            {
                Console.WriteLine(i);
            }
        }

        public void Run()
        {
            // 4. Student들을 배열로 갖는다면?
            TestStudent();
        }

        private void TestStudent()
        {
            Student[] st = new Student[] { new Student("홍길동", 89, 1), new Student("황진이", 87, 2) };
            IEnumerator enumerator = st.GetEnumerator(); // 인스턴스 st, 즉 Student 클래스는, IEnumerator 인터페이스의 하위 클래스이기 때문에, 이렇게 호출할 수 있으며, GetEnumerator를 사용할 수 있다.
            while (enumerator.MoveNext())
            {
                Console.WriteLine(enumerator.Current); 
                // enumerator객체를 그냥 WriteLine내에 넣었기 때문에, 자동으로 ToString()호출.
                // Student에 재정의된 것이 있으니 그것으로 호출
            }
        }

        class Student
        {
            public string Name { get; set; }
            public int Score { get; set; }
            public int Id { get; set; }

            public Student(string name, int score, int id)
            {
                Name = name;
                Score = score;
                Id = id;
            }

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

    }
}

8. 인터페이스 (Interface) 2 - IComparable 인터페이스

- IComparable

: 객체비교하기 위한 인터페이스

: 두 객체를 비교해서 즉,

 Student a CompareTo(Student b) // Student a와 Student b를 비교

 * a가 작은 경우, 음수 반환

 * a와 b가 같은 경우, 0을 반환

 * a가 큰 경우, 양수 반환 

 

- 배열 및 컬랙션 등을 정렬하는데 효과적인 인터페이스

 

[ 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)
        {
            Inter1 i1 = new Inter1();
            i1.Run();
        }
    }
}

[ Inter1.cs ]

using System;
using System.Collections; // Ienumerator는 System.Collections에 포함되어 있음.
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace class_0804
{
    class Inter1
    {
        internal void Run()
        {
            // 2.
            /*int[] intArray = new[] { 5, 4, 3, 2, 1 };
            Array.Sort(intArray);
            // 사실 해당 객체에서 iComparable을 구현했기 때문에, Sort()가 동작한다.

            foreach (int i in intArray) Console.WriteLine(i);*/


            // 2. 
            Student[] st = new Student[]
            {
                new Student("이가나", 79, 3),
                new Student("김나라", 82, 1)
            };

            // 이름으로 비교(Compare To)해서 return값을 받을 것
            Array.Sort(st);
            
            foreach(Student s in st)
            {
                Console.WriteLine(s);
            }
        }
    }


    class Student: IComparable // Icomparable 인터페이스를 구현 (상속 = 구현)
    {
        public string Name { get; set; }
        public int Score { get; set; }
        public int Id { get; set; }
        

        public Student(string name, int score, int id)
        {
            Name = name;
            Score = score;
            Id = id;
        }

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

        public int CompareTo(object obj) // Student가 들어와서 비교
        {
            Student st = obj as Student; // 모든 클래스 조상을 Student로 형변환.
            // 1. 이름으로 비교
            // int ret = this.Name.CompareTo(st.Name); // 현재 객체의 Name과, 받아온 obj(st).Name을 비교한다.
            // return ret;

            // 2. 성적으로 비교
            // return Score - st.Score; // 내 점수가 높으면 양수, 같으면 0, 작으면 음수 (오름차순으로 낮은 순부터 나타남)

            // 3. id로 비교
            return Id - st.Id;
        }
    }

}

9. 인터페이스 (Interface) 3 - IDisposable 인터페이스

- IDisposable : 객체의 자원을 해제하는 규격

 

즉 C#에서 다 사용한 메모리를 제거해주는 역할을 하는 인터페이스이다.

그런데, C#에는 Garbage Collector(가비지 컬렉터)가, 힙 메모리 영역에서 더 이상 사용하지 않는 객체들을 소거해주는 역할을 한다. 따라서 자동 소거되기 때문에, 개발자는 소거하는 일 자체는 신경쓰지 않아도 된다.

 

그럼에도 불구하고 IDisposable이 필요한 이유가 있다.

1. Garbage Collector는 관리되지 않는 리소스들은 인식하지 못한다.

2. Garbage Collector는 개발자가 동작을 지시하는 것이 아니기 때문에, 어느 시점에 메모리 해제가 일어나는지 알 수 없다.

3. Garbage Collector가 자주 발생하게 되면, 오버헤드로 인한 비용이 증가하게 된다.

4. 즉 적절한 메모리 처리를 해주지 못하면, 메모리 out현상이 발생할 수 있다. ex. 다수의 이미지 파일을 open했다가 버리는 경우

 

IDisposable은 중요 데이터와 메모리를 얼른 쓰고 돌려주어야 할 때, 다른 프로세스와의 교착상태를 방지하고자 할 때, 메모리 낭비를 방지하고자 할 때, 유용하게 사용할 수 있다.

 

[ 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)
        {
            Inter1 i1 = new Inter1();
            i1.Run();
        }
    }
}

[ Inter1.cs ]

using System;
using System.Collections; // Ienumerator는 System.Collections에 포함되어 있음.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace class_0804
{
    class Inter1
    {
        internal void Run()
        {
            Student[] st = new Student[]
            {
                new Student("이가나", 79, 3),
                new Student("김나라", 82, 1)
            };
            Array.Sort(st);
            
            foreach(Student s in st)
            {
                Console.WriteLine(s);
            }

            // StreamReader사용법 1.
            // .NET에서 제공하는 클래스들
            FileStream stream = new FileStream(@"d:aaa.txt",FileMode.Open);
            // using의 역할 : 객체를 어디부터 어디까지 사용할 것인지 명시하는 역할 { } 이후부터는 사용 불가능.
            using(StreamReader reader = new StreamReader(stream))
            {
                string buffer;
                while ((buffer = reader.ReadLine()) != null) 
                {

                }
            }// 이 끝에 오면 using 내부에 reader가 자동으로 dispose를 호출한다.
            // using문 내에서, reader변수와 관련된 모든 것이 생겨났다가, using문이 끝날 때 모두 사라져버림. 이런식의 관리를 수행해주는 것이 중요함.
        }
    }


    class Student: IComparable, IDisposable // 이와 같이 인터페이스는 여러개를 구현할 수 있음.
        // IDisposable 인터페이스를 연결해주면, 객체의 메모리 사용 끝 부분을 인지하게 되며, 자동으로 객체 사용이 완료되었을 때, 제거해주는 역할을 한다.
    {
        public string Name { get; set; }
        public int Score { get; set; }
        public int Id { get; set; }


        public Student(string name, int score, int id)
        {
            Name = name;
            Score = score;
            Id = id;
        }

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

        public int CompareTo(object obj) 
        {
            Student st = obj as Student; 
            return Id - st.Id;
        }


        ~Student() // 소멸자
        {
            Dispose(false);
        }

        bool disposed; // 초기값 : false (해제가 안되있는 상황)

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this); // 가비지 컬렉터에게 Dispose가 되었기 때문에, 청소를 해달라는 것.
        }

        // StreamReader사용법 2.
        StreamReader reader; // 맴버변수로 reader를 정의

        private void aaa()
        {
            FileStream stream = new FileStream(@"d:aaa.txt", FileMode.Open);
            reader = new StreamReader(stream);
            // 2. reader를 다 썼다면, 반환해야 함. 즉 내 클래스가 disposed될 때, reader도 닫혀야함.
            string buffer;
            while ((buffer = reader.ReadLine()) != null)
            {

            }
        }
        public void Dispose(bool disposing)
        {
            if (disposed) return; // 이미 disposed 되었냐
            // disposing 중이냐
            if (disposing) // dispose()를 불렀을 때
            {
                // C#에서 사용하는 IDisposable을 구현한 객체들 정리 (IDisposable 객체 정리)

                // 2. 이 때 자원을 닫아주고 reader.Dispose()를 명시적으로 불러준다.
                reader.Close();
                reader.Dispose();
                // 사용했던 자원들을 깔끔히 정리할 때, Disposable을 구현한 객체들을 썼을 때는, 명시적으로 Disposed 메소드에다 Dispose를 호출해서 다 없애버릴 필요가 있다.그러기 위해서는 IDisposable을 구현해야 한다.
            }
            else // 소멸자가 왔을 때.
            {
                // .NET Framework에서 관리되지 않는 자원들 정리 (소멸자로 객체 정리)
            }
            disposed = true; // 정리가 완료되었으므로 true반환 (해제 완료)
        }
        
    }

}

 


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