C#

클래스

Barbarian developer 2024. 10. 30.

클래스 선언과 객체의 생성

using System;

namespace BasicClass
{
    class Cat
    {
        public string Name;
        public string color;

        public void Meow()
        {
            Console.WriteLine($"{Name} : 야옹");
        }
    }

    class MainApp
    {
        static void Main( string[] args )
        {
            Cat kitty = new Cat();
            kitty.color = "하얀색";
            kitty.Name = "키티";
            kitty.Meow();
            Console.WriteLine($"{kitty.Name} : {kitty.color}");

            Cat nero = new Cat();
            nero.color = "검은색";
            nero.Name = "네로";
            nero.Meow();
            Console.WriteLine($"{nero.Name} : {nero.color}");
        }
    }
}

 

<실행결과>

 

생성자와 종료자

c# 에서는 종료자를 굳이 안써도 가비지 컬렉터가 알아서 수거해갑니다.

쓸데 없이 쓰지 않도록 합니다.

 

정적필드와 메소드

using System;

class Global
{
    public static int Count = 0;
}

class ClassA
{
    public ClassA()
    {
        Global.Count++;
    }
}

class ClassB
{
    public ClassB()
    {
        Global.Count++;
    }
}

class MainApp()
{
    static void Main()
    {
        Console.WriteLine($"Global.Count : {Global.Count}");

        new ClassA();
        new ClassA();
        new ClassB();
        new ClassB();

        Console.WriteLine($"Global.Count : {Global.Count}");
    }
}

 

static으로 수식한 필드는 프로그램에서 하나밖에 존재하지 않습니다.

그러므로, static을 수식하는 필드는 인스턴스를 만들지 않고 클래스 이름을 통해 필드에 바로 접촉 할 수 있습니다.

 

객체 복사하기: 얕은 복수와 깊은 복사

                                                     

using System;

namespace Deepcopy
{
    class Myclass
    {
        public int MyField1;
        public int MyField2;

        public Myclass DeepCopy()
        {
            Myclass newCopy = new Myclass();
            newCopy.MyField1 = this.MyField1;
            newCopy.MyField2 = this.MyField2;

            return newCopy;
        }
    }
    class MainApp
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Shallow Copy");

            {
                Myclass source = new Myclass();
                source.MyField1 = 10;
                source.MyField2 = 20;

                Myclass target = source;
                target.MyField2 = 30;

                Console.WriteLine($"{source.MyField1} {source.MyField2}");
                Console.WriteLine($"{target.MyField1} {target.MyField2}");
            }

            Console.WriteLine("DeepCopy");

            {
                Myclass source = new Myclass();
                source.MyField1 = 10;
                source.MyField2 = 20;

                Myclass target = source.DeepCopy();
                target.MyField2 = 30;

                Console.WriteLine($"{source.MyField1} {source.MyField2}");
                Console.WriteLine($"{target.MyField1} {target.MyField2}");
            }
        }
    }
}

 

 

<실행결과>

 

  • 개념을 최대한 간단하게 말하자면, 다른 인스턴스일지라도 같은 heep영역을 공유하기 때문에 값을 변경하면 함께 변경됩니다.
  • 깊은 복사는 heep영역을 새로 만들고 거기서 클래스를 복사해와서 사용하고 버리기 때문에, 다른 인스턴스과 값을 공유하지 않습니다.

this 키워드

using System;

namespace This
{
    class Employee
    {
        private string Name;
        private string Position;

        public void SetName(string Name)
        {
            this.Name = Name;
        }

        public string GetName()
        {
            return Name;
        }

        public void SetPosition(string Position)
        {
            this.Position = Position;
        }

        public string GetPosition()
        {
            return this.Position;
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Employee pooh = new Employee();
            pooh.SetName("Pooh");
            pooh.SetPosition("Waiter");
            Console.WriteLine($"{pooh.GetName()} {pooh.GetPosition()}");

            Employee tigger = new Employee();
            tigger.SetName("Tigger");
            tigger.SetPosition("Cleaner");
            Console.WriteLine($"{tigger.GetName()} {tigger.GetPosition()}");
        }
    }
}

 

<실행결과>

 

this는 객체가 자신을 지칭할 때 사용되는 키워드입니다. 

객체 외부에서는 객체의 필드나 메소에 접근할 때 객체의 이름을 사용한다면, 객체 내부에서는 필드나 메소드에 접근할 때 this를 사용합니다.

 

this() 생성자

using System;

namespace ThisConstructor
{
    class MyClass
    {
        int a, b, c;

        public MyClass()
        {
            this.a = 5425;
            Console.WriteLine("MyClass()");
        }

        public MyClass(int b) : this()
        {
            this.b = b;
            Console.WriteLine($"MyClass({b})");
        }

        public MyClass(int b, int c) : this(b)
        {
            this.c = c;
            Console.WriteLine($"MyClass({b}. {c})");
        }

        public void PrintFields()
        {
            Console.WriteLine($"a:{a}, b:{b}, c:{c}");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            MyClass a = new MyClass();
            a.PrintFields();
            Console.WriteLine();

            MyClass b = new MyClass(1);
            b.PrintFields();
            Console.WriteLine();

            MyClass c = new MyClass(10, 20);
            c.PrintFields();
        }
    }
}

 

<실행결과>

 

 

상속으로 코드 재활용하기

using System;

namespace Inheritance
{
    class Base
    {
        protected string Name;
        public Base(string Name)
        {
            this.Name = Name;
            Console.WriteLine($"{this.Name}.Base()");
        }

        ~Base()
        {
            Console.WriteLine($"{this.Name}.~Base()");
        }

        public void BaseMethod()
        {
            Console.WriteLine($"{Name}.BaseMethod()");
        }
    }
    class Derived : Base
    {
        public Derived(string Name) : base(Name)
        {
            Console.WriteLine($"{this.Name}.Derived()");
        }


        ~Derived()
        {
            Console.WriteLine($"{this.Name}.~Derived()");
        }


        public void DerivedMethod()
        {
            Console.WriteLine($"{Name}.DerivedMethod()");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Base a = new Base("a");
            a.BaseMethod();

            Derived b = new Derived("b");
            b.BaseMethod();
            b.DerivedMethod();
        }
    }
}

 

<실행결과>

 

해당 코드를 통해 저는 '상속'개념에 대해 이해했습니다.

 

Base class를 상속하기 위해 Derived : Base를 사용해줍니다.

 

그리고 나서 MainApp에서 Derived 클래스의 public 함수를 호출하면, Base의 메소드를 모두 불러와서 사용합니다.

 

Base의 메소드를 모두 물려받아서 사용 하는 것입니다.

 

 기반 클래스와 파생 클래스 사이의 형식 변환

 

using System;

namespace TypeCasting
{
    // Mammal 클래스는 기본 클래스이며, Nurse 메서드를 포함하고 있습니다.
    class Mammal
    {
        // Nurse 메서드: "Nurse()"라는 텍스트를 출력합니다.
        public void Nurse()
        {
            Console.WriteLine("Nurse()");
        }
    }

    // Dog 클래스는 Mammal 클래스를 상속받아 Bark 메서드를 추가로 제공합니다.
    class Dog : Mammal
    {
        // Bark 메서드: "Bark()"라는 텍스트를 출력합니다.
        public void Bark()
        {
            Console.WriteLine("Bark()");
        }
    }

    // Cat 클래스는 Mammal 클래스를 상속받아 Meow 메서드를 추가로 제공합니다.
    class Cat : Mammal
    {
        // Meow 메서드: "Meow()"라는 텍스트를 출력합니다.
        public void Meow()
        {
            Console.WriteLine("Meow()");
        }
    }

    class MainApp
    {
        static void Main(string[] args) 
        {
            // Mammal 타입의 참조 변수 mammal을 선언하고 Dog 객체로 초기화
            // mammal은 실제로 Dog 객체를 참조하지만, 현재는 Mammal 타입으로 다룹니다.
            Mammal mammal = new Dog();
            Dog dog;

            // 'mammal'이 Dog 타입인지 확인
            if (mammal is Dog)
            {
                // mammal이 Dog 타입인 것이 확인되었으므로, (Dog)mammal로 캐스팅
                dog = (Dog)mammal;
                
                // Dog 타입으로 캐스팅된 dog 객체의 Bark 메서드를 호출
                dog.Bark(); // "Bark()" 출력
            }

            // Mammal 타입의 참조 변수 mammal2를 선언하고 Cat 객체로 초기화
            Mammal mammal2 = new Cat();

            // 'mammal2'을 Cat 타입으로 안전하게 캐스팅하여 'cat'에 저장
            // 'as' 키워드를 사용하면 캐스팅이 실패할 경우 null을 반환
            Cat cat = mammal2 as Cat;

            // cat이 null이 아닌 경우, 즉 캐스팅이 성공한 경우 Meow 메서드를 호출
            if (cat != null)
                cat.Meow(); // "Meow()" 출력

            // 'mammal'을 Cat 타입으로 캐스팅하여 cat2에 저장 (실제로는 Dog 객체이므로 실패)
            Cat cat2 = mammal as Cat;

            // cat2가 null이 아닌 경우 Meow 메서드 호출
            // 그러나 'mammal'은 Dog 객체이므로 'cat2'는 null이 됩니다.
            if (cat2 != null)
                cat2.Meow();
            else
                // 'cat2'가 null이므로 "cat2 is not a Cat" 출력
                Console.WriteLine("cat2 is not a Cat");
        }
    }
}

 

<실행결과>

 

is 또는 as를 통해 형변환을 하지 않으면, Nurse의 메소드가 호출됩니다.

 

오버라이딩과 다형성

using System;

namespace Overriding
{
    class ArmorSuite
    {
        public virtual void Initialize()
        {
            Console.WriteLine("Armored");
        }
    }

    class IronMan : ArmorSuite
    {
        public override void Initialize()
        {
            base.Initialize();
            Console.WriteLine("Repulsor Rays Armed");
        }
    }

    class WarMachine : ArmorSuite
    {
        public override void Initialize()
        {
            base.Initialize();
            Console.WriteLine("Double-Barrel Cannons Armed");
            Console.WriteLine("Micro-Rocket Launcher Armed");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Creating ArmorSuite...");
            ArmorSuite armorsuite = new ArmorSuite();
            armorsuite.Initialize();

            Console.WriteLine("\nCreating IronMan...");
            ArmorSuite ironman = new IronMan();
            ironman.Initialize();

            Console.WriteLine("\nCreating WarMaching...");
            ArmorSuite warmachine = new WarMachine();
            warmachine.Initialize();
        }
    }
}

 

<실행결과>

 

  • virtual라는 키워드를 이용하여 이 메소드를 오버라이딩 할 것이라는 것을 선언해야 합니다.
  • 다음 사용할 메소드 함수에 override라는 키워드와 해당 클래스를 상속 한다는 선언을 하면, 부모 클래스를 상속하는 오버라이딩 메소드 함수를 생성할 수 있습니다.

메소드 숨기기

 

using System;
using System.Runtime.InteropServices.Marshalling;
using System.Security.Cryptography.X509Certificates;

namespace MehtodHiding
{
    class Base
    {
        public void MyMethod()
        {
            Console.WriteLine("Base.MyMethod()");
        }
    }

    class Dereived : Base
    {
        public new void MyMethod()
        {
            Console.WriteLine("Derived.MyMethod()");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Base baseObj = new Base();
            baseObj.MyMethod();

            Dereived derivedOjg = new Dereived();
            derivedOjg.MyMethod();

            Base baseOrDerived = new Dereived();
            baseOrDerived.MyMethod();
        }
    }
}

 

<실행결과>

 

  • Dereived 클래스의 메소드 함수를 만들때, new 키워드를 사용하면, 부모 클래스의 메서드를 숨기고 독립적인 메소드로 사용 가능합니다.

오버라이딩 봉인하기

클래스를 봉인하는 것처럼 메소드도 오버라이딩 되지 않도록 봉인이 가능합니다.

 예를 들어 다음과 같이 Base클래스와 Derived 클래스가 있다고 하면, Derived의 sealMe()만 봉인 가능합니다.

 

읽기 전용 필드

읽기 전용 필드는 이름에서 알 수 있듯이, 읽기만 가능한 필드를 말합니다.

즉, 클래스나 구조체의 멤버로만 존재할 수 있으며, 생성자 안에서 한 번 값을 지정하면, 그 후로는 값을 변경할 수 없는 것이 특징입니다.

usint System;

namespace ReadonlyFields
{
    class Configuration
    {
        private readonly int min;
        private readonly int max;

        public Configuration(int v1, int v2)
        {
            min = v1;
            min = v2;
        }

        public void ChangeMax(int newMax)
        {
            max = newMax;
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Configuration c = new Configuration(100, 10);
        }
    }
}

 

  • 실행결과는 빌드 실패입니다.
  • readonly 라는 키워드를 사용한 이상, 해당 메소드는 봉인되어, 생성할 때를 제외하고 는 값을 변경 할 수 없습니다.
  • 생성할 때를 제외하고 다른 곳에서 값을 변경하려 한다면, 컴파일이 되지 않습니다.
  • 즉, 객체의 상수화와 같다고 생각하면 될 것 같습니다.

 

중첩 클래스

 중첩 클래스는 클래스 안에 선언되어 있는 클래스를 말합니다.

중첩 클래스를 이용하는 이유는 크게 두가지가 있습니다.

 

1. 클래스 외부에 공개하고 싶지 않은 형식을 만들고자 할때,

2. 현재 클래스의 일부분 처럼 표현할 수 있는 클래스를 만들고자 할때.

 

위의 예제와 같이 중첩 클래스는 다른 클래스의 private에 자유롭게 접근이 가능하다는 점에서 은닉성을 무너뜨리는 단점이 있지만, 더 유연한 표현력을 선사 할수 있는 장점이 있다고 합니다만, 이걸 어떻게 써야할지는 아직 모르겠네요.

 

분할 클래스

분할 클래스란 여러번에 나눠서 구현하는 클래스를 말합니다. 

특별한 기능이 있다라기보다는 클래스의 구현이 길어질 경우 여러 파일에 나눠서 구현 할 수 있도록 하는 

프로그래머의 소스코드 관리의 편의를 제공하는 정도입니다.

 

키워드는 클래스 앞에 partial이라고 선언합니다.

 

확장 메소드

기존 클래스의 기능을 확장하는 기법입니다. 파생클래스를 만든 뒤 필드나 메소드를 추가하는 상속과는 엄연히 다릅니다.

메소드를 선언할 때, static 한정자로 수식을 해야합니다.

그리고, 첫번째 매개변수는 반드시 this 키워드와 함께 확장하려는 클래스(형색)의 인스턴스여야 합니다.

즉, string이나 int같은 수정할 수 없는 클래스를 수정할 때 사용되는 기능입니다.

using System;
using MyExtension;

namespace MyExtension
{
    public static class IntegerExtension
    {
        public static int Square(this int myInt)
        {
            return myInt * myInt;
        }

        public static int Power(this int myInt, int exponent)
        {
            int result = myInt;
            for (int i = 1; i < exponent; i++)
                result = result * myInt;

            return result;
        }
    }
}

namespace ExtensionMethod
{
    class MainApp
    {
        static void Main(string[] args)
        {
            Console.WriteLine($"{3}^2 : {3.Square()}");
            Console.WriteLine($"{3}^{4} : {3.Power(4)}");
            Console.WriteLine($"{2}^{10} : {2.Power(10)}");
        }
    }
}

 

<실행결과>

 

구조체

구조체는 클래스와 상당 부분 비슷합니다.

 

using System;

namespace Structure
{
    struct Point3D
    {
        public int X;
        public int Y;
        public int Z;

        public Point3D(int X, int Y, int Z)
        {
            this.X = X;
            this.Y = Y;
            this.Z = Z;
        }

        public override string ToString()
        {
            return string.Format($"{X}, {Y}, {Z}");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Point3D p3d1;	//선언만으로 인스턴스 생성
            p3d1.X = 10;
            p3d1.Y = 20;
            p3d1.Z = 40;

            Console.WriteLine(p3d1.ToString());

            Point3D p3d2 = new Point3D(100, 200, 300);	//생성자를 이용한 인스턴스 생성
            Point3D p3d3 = p3d2;	//인스턴스를 다른 인스턴스에 할당하여 깊은 복사.
            p3d3.Z = 400;

            Console.WriteLine(p3d2.ToString());
            Console.WriteLine(p3d3.ToString());
        }
    }
}

 

<실행결과>

 

 

튜플

튜플은 여러 필드를 담을 수 있는 구조체입니다.

튜플은 응용 프로그랩 전체에서 사용할 형식을 선언할 때가 아닌, 즉석에서 사용할 복합 데이터 형식을 선언할 때 적합합니다.

튜플은 구조체이므로 값 형식입니다.

var tuple = (123,789);

 

using System;

namespace Tuple
{
    class Mainapp
    {
        static void Main(string[] args)
        {
            //명명되지 않은 튜플
            var a = ("슈퍼맨", 9999);
            Console.WriteLine($"{a.Item1}, {a.Item2}");

            //명명된 튜플
            var b = (Name: "바바리안", Age: 17);
            Console.WriteLine($"{b.Name}, {b.Age}");

            //분해
            var (name, age) = b; //(var name, var age) = b;
            Console.WriteLine($"{name} {age}");

            //분해2
            var (name2, age2) = ("박문수", 34);
            Console.WriteLine($"{name}, {age}");

            //명명된 튜플 = 명명되지 않은 튜플
            b = a;
            Console.WriteLine($"{b.Name} {b.Age}");
        }
    }
}

 

<실행 결과>

 

튜플을 switch에 활용

using System;
using System.Threading.Tasks.Dataflow;

namespace PosisionalPattern
{
    class MainApp
    { 
        private static double GetDiscountRate(object client)
        {
            return client switch
            {
                ("학생", int n) when n < 18 => 0.2,
                ("학생", _) => 0.1,
                ("일반", int n) when n < 18 => 0.1,
                ("일반", _) => 0.05,
                _ => 0,
            };
        }

        static void Main(string[] args)
        {
            var alice = (JoinBlock: "학생", age: 17);
            var bob = (job: "학생", age: 23);
            var charlie = (job: "일반", age: 15);
            var dave = (job: "일반", age: 21);

            Console.WriteLine($"alice : {GetDiscountRate(alice)}");
            Console.WriteLine($"bob : {GetDiscountRate(bob)}");
            Console.WriteLine($"charlie : {GetDiscountRate(charlie)}");
            Console.WriteLine($"dave : {GetDiscountRate(dave)}");
        }
    }
}

 

<실행결과>

 

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

프로퍼티  (3) 2024.11.01
인터페이스와 추상클래스  (0) 2024.10.31
메소드로 코드 간추리기  (0) 2024.10.30
코드의 흐름 제어하기  (1) 2024.10.30
데이터를 가공하는 연산자  (2) 2024.10.29

댓글