C#

[C#] 객체지향 프로그래밍 : 은닉성, 상속성, 다형성

sunlight-dby 2025. 4. 20. 00:44

 

'이것이 C#이다 개정판'의 chapter 7 정리한 글입니다.

 


 

접근 한정자 (Access Modifier) : 은닉성

접근 한정자 설명
public     클래스의 내부/외부 모든 곳에서 접근할 수 있습니다.
protected     클래스의 외부에서는 접근할 수 없지만, 파생 클래스에서는 접근이 가능합니다.
private     클래스 내부에서만 접근할 수 있습니다. 파생 클래스에서도 접근이 불가능합니다.
internal     같은 어셈블리어에 있는 코드에서만 public으로 접근할 수 있습니다.
    다른 어셈블리에 있는 코드에서는 private과 같은 수준의 접근성을 가집니다.
protected internal     같은 어셈블리에 있는 코드에서만 protected로 접근할 수 있습니다.
    다른 어셈블리에 있는 코드에서는 private과 같은 수준의 접근성을  가집니다.
private protected     같은 어셈블리에 있는 클래스에서 상속받은 클래스 내부에서만 접근이 가능합니다.

 

※ 접근 한정자로 수식하지 않은 클래스의 멤버는 무조건 private으로 접근 수준이 자동 지정됩니다.
    이 말인즉슨, 클래스 내의 멤버는 일단 감추고 나중에 공개할지를 결정하는 것이 순서라는 뜻입니다.

 

[예시]

using System;

namespace AccessModifier
{
    class WaterHeater
    {
        protected int temperature;

        public void SetTemperature(int temperature)  // Code A
        {
            if (temperature < -5 || temperature > 42)
            {
                throw new Exception("Out of temperature range");
            }

            this.temperature = temperature;  // Code B
        }

        internal void TurnOnWater()
        {
            Console.WriteLine($"Turn on water : {temperature}");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            try
            {
                WaterHeater heater = new WaterHeater();
                heater.SetTemperature(20);
                heater.TurnOnWater();

                heater.SetTemperature(-2);
                heater.TurnOnWater();

                heater.SetTemperature(50);  // Code C
                heater.TurnOnWater();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }
}

실행 결과

  • Code A
    • SetTemperature() 메소드는 -5 ~ 42 사이의 값만 받아들이고, 이 범위를 벗어난 값에 대해서는 예외를 발생시킵니다.
  • Code B
    • temperature 필드는 protected로 수식되었으므로 외부에서 직접 접근할 수 없습니다. 이렇게 public 메소드를 통해 접근해야 합니다.
  • Code C
    • 해당 행에서 예외가 발생하며, 다음 행인 heater.TurnOnWater();는 실행되지 않고 아래의 catch 블록으로 실행 위치가 이동합니다.

상속

객체지향 프로그래밍에서는 물려받는 클래스(파생 클래스(Derived Class) 또는 자식 클래스)가 유산을 물려줄 클래스(기반 클래스(Base Class) 또는 부모 클래스)를 지정합니다.

class 기반_클래스
{
    // 멤버 선언
}

class 파생_클래스 : 기반_클래스
{
    // 아무 멤버를 선언하지 않아도 기반 클래스의 모든 것을 물려받아 갖게 됩니다.
    // 단, private으로 선언된 멤버는 예외입니다.
}

 

파생 클래스는 자신만의 고유한 멤버 외에도 기반 클래스로부터 물려받은 멤버를 갖고 있습니다.

이것은 파생클래스가 기반 클래스 위에 새로운 멤버를 얹어 만든 것이기 때문입니다.

 

파생 클래스는 객체를 생성할 때 내부적으로 기반 클래스의 생성자를 호출한 후에 자신의 생성자를 호출하고, 객체가 소멸될 때는 반대의 순서로(파생 클래스 → 기반 클래스) 종료자를 호출합니다.


base 키워드

base 키워드는 파생 클래스의 인스턴스를 생성할 때 호출되는 기반 클래스의 생성자에 매개변수를 전달하기 위한 키워드입니다.

 

this 키워드가 '자기 자신'을 가리킨다면, base는 '기반 클래스'를 가리킵니다.

base 키워드를 통해 기반 클래스의 멤버에 접근할 수 있습니다.

 

[예시]

class Base
{
    protected string Name;
    
    public Base(string Name)  // A
    {
        this.Name = Name;
    }
}

class Derived : Base
{
    public Derived(string Name) : base(Name)  // A : Base(string Name) 호출
    {
        Console.WriteLine("{0}.Derived()", this.Name);
    }
}

sealed 한정자

sealed 한정자를 이용하면, 기반 클래스의 작성자는 의도하지 않은 상속이나 파생 클래스의 구현을 막기 위해 상속이 불가능하도록 클래스를 선언할 수 있습니다.

 

sealed 한정자로 클래스를 수식하면, 해당 클래스는 '상속 봉인'이 되어 봉인 클래스가 됩니다.
해당 클래스로부터 상속받으려는 시도가 컴파일러부터 발견됐을 때, 컴파일 에러가 발생합니다.

sealed class Base
{
    // ...
}

class Derived : Base
{
    // ...
}

봉인 클래스를 상속받으려할 때 발생되는 컴파일 에러


is 연산자 & as 연산자

is 연산자

  • 역할
    • 객체가 특정 타입과 호환되는지(즉, 해당 타입이거나 그 하위 타입인지) 검사합니다.
    • 결과는 bool 타입(true 또는 false)으로 반환됩니다.
  • 문법
obj is Type
  • 특징
    • 타입 검사만 수행하며, 변환된 값을 반환하지 않습니다.
    • C# 7.0부터는 패턴 매칭 기능이 추가되어, 검사와 동시에 변수 선언이 가능합니다.
  • 예시
object obj = "Hello";

is (obj is string)
{
    Console.WriteLine("obj는 문자열입니다.");
}

// C# 7.0 이상 : 패턴 매칭
if (obj is string s)
{
    Console.WriteLine($"obj는 문자열이고, 값은 {s}");
}

as 연산자

  • 역할
    • 객체를 지정한 타입으로 안전하게 변환하려고 시도합니다.
    • 변환에 실패하면 예외를 발생시키지 않고 null을 반환합니다.
      • 형식 변환 연산자는 변환에 실패하는 경우 예외를 발생시킵니다.
  • 문법
var result = obj as Type;
  • 특징
    • 참조형 타입 또는 널 허용형(Nullable) 타입에만 사용할 수 있습니다.
      • 값 형식의 객체는 기존의 형식 변환 연산자를 사용해야 합니다.
    • 변환에 실패해도 예외가 발생하지 않으므로, 변환 성공 여부를 null 체크로 판단해야 합니다.
  • 예시
object obj = "Hello";

// 안전한 형 변환 시도
string str = obj as string;

if (str != null)
{
    Console.WriteLine($"변환 성공 : {str}");
}
else
{
    Console.WriteLine($"변환 실패");
}

 

 


객체의 형식 변환 테스트 예제 프로그램

using System;

namespace TypeCasting
{
    class Mammal
    {
        public void Nurse()
        {
            Console.WriteLine("Nurse()");
        }
    }

    class Dog : Mammal
    {
        public void Bark()
        {
            Console.WriteLine("Bark()");
        }
    }

    class Cat : Mammal
    {
        public void Meow()
        {
            Console.WriteLine("Meow()");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Mammal mammal = new Dog();
            Dog dog;

            if (mammal is Dog)  // is 연산자를 사용하여 타입 검사 수행
            {
                dog = (Dog)mammal;  // 명시적 형 변환(casting)으로 Mammal 타입의 mammal 객체를 Dog 타입으로 변환합니다. 
                                    // 이전에 is 연산자를 통해 mammal이 Dog 타입임을 확인했기 때문에 안전합니다.
                dog.Bark();
            }

            Mammal mammal2 = new Cat();

            Cat cat = mammal2 as Cat;  // as 연산자를 사용하여 형 변환
            if (cat != null)
            {
                cat.Meow();
            }

            Cat cat2 = mammal as Cat;  // as 연산자를 사용하여 mammal 객체를 Cat 타입으로 변환 시도합니다. 
                                       // 만약 변환이 불가능하면 null이 반환됩니다.

            if (cat2 != null)
            {
                cat2.Meow();
            }
            else
            {
                Console.WriteLine("cat2 is not a Cat");
            }
        }
    }
}

실행 결과

 

cat2 객체는 Cat 타입으로 변환을 시도하는 mammal 객체를 복사하여 생성하려고 합니다.

하지만 mammal이 실제로 참조하는 객체는 Dog 타입의 인스턴스입니다.

Dog와 Cat 클래스는 서로 형제 관계에만 있고, 서로 직접적인 상속 관계가 없기 때문에 타입 변환이 불가능합니다.

 

따라서 mammal이 실제로 Dog 객체를 가리키고 있기 때문에 Cat으로 변환할 수 없으며,

as 연산자의 실패로 null이 반환되어 cat2는 null이 됩니다.


오버라이딩(Overriding)과 다형성(Polymorphism)

객체지향 프로그래밍에서 다형성은 객체가 여러 형태를 가질 수 있음을 의미합니다.

다형성은 하위 형식 다형성(Subtype Polymorphism)의 준말로, 자신으로부터 상속받아 만들어진 파생 클래스를 통해 다형성을 실현한다는 것입니다.

 

다형성을 실현하기 위해서는 virtualoverride가 필요합니다.

 

[예제]

using System;

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

    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 armoursuite = new ArmorSuite();
            armoursuite.Initialize();

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

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

실행 결과

 

private으로 선언한 메소드의 오버라이딩

private으로 선언된 멤버는 파생 클래스에서 보이지 않습니다. 같은 이름, 같은 형식, 같은 매개변수를 이용하여 선언했다 하더라도 컴파일러는 기반 클래스의 메소드를 재정의한다고 생각하지 않는 것입니다.

 

private으로 선언한 메소드는 오버라이딩이 불가하고, 컴파일러는 이를 전혀 없었던 메소드를 선언한다고 간주하게 됩니다.


메소드 숨기기 (Method Hiding)

'메소드 숨기기'는 파생 클래스에서 기본 클래스에 이미 정의된 메소드와 동일한 이름과 시그니처를 가진 메소드를 새로 정의하는 것을 의미합니다. 이때, 기본 클래스의 메소드는 오버라이딩되는 것이 아니라 숨겨지게 됩니다.

 

  • 역할
    • 기본 클래스(Base class)에 정의된 메소드를 파생 클래스(Derived class)에서 동일한 이름과 시그니처로 새롭게 정의하여 메소드를 숨깁니다.
    • 기본 클래스의 메소드를 오버라이딩하지 않고, 파생 클래스에서 독립된 새 메소드를 구현할 때 사용됩니다.
    • 참조 변수의 타입에 따라 호출되는 메소드가 달라져, 정적 바인딩(컴파일 시점에 결정)되는 동작을 구현할 수 있습니다.
  • 문법
    • 파생 클래스에서 같은 이름의 메소드를 정의할 때, new 키워드를 사용하여 숨긴다는 것을 명시합니다.
  • 예시
using System;

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

    class Derived : Base
    {
        public new void MyMethod()  // new 키워드를 사용하여 메소드 숨기기
        {
            Console.WriteLine("Derived.MyMethod()");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Base baseObj = new Base();           // 기본 클래스 메소드 호출
            baseObj.MyMethod();

            Derived derivedObj = new Derived();  // 파생 클래스에서 숨긴 메소드 호출
            derivedObj.MyMethod();

            Base baseOrDerived = new Derived();  // Code A
            baseOrDerived.MyMethod();
        }
    }
}

실행 결과

 

Code A

Base baseOrDerived = new Derived();
baseOrDerived.MyMethod();

 

baseOrDerived는 Base 타입 변수이지만, 실제 인스턴스는 Derived 객체입니다.

메소드 숨기기는 정적 바인딩이기 때문에, 참조 타입에 따라 호출 메소드가 결정됩니다.

그렇기에 실제 인스턴스가 Derived 객체이지만, Base에 따라 호출 메소드가 결정되어 Base.MyMethod()가 출력되게 됩니다.

 

더 자세히 설명한다면,

변수 baseOrDerived의 컴파일 시 타입은 Base입니다.

하지만 실제 런타임 시 객체의 타입은 Derived입니다.

즉, baseOrDerived는 Base 타입으로 선언되었지만, Derived 객체를 참조합니다.

 

해당 구문에서 MyMethod()는 Base 클래스에 정의된 일반 메소드입니다.

Derived 클래스에서 MyMethod()를 new 키워드로 메소드를 숨겼지만, 이 메소드는 오버라이딩된 메소드가 아닙니다.

C# 컴파일러는 메소드 호출 시점에 참조 변수 타입을 기준으로 호출할 메소드를 결정합니다.

 

즉, 컴파일러는 baeOrDerived가 Base 타입임을 알고, Base 클래스에 있는 MyMethod()를 호출하도록 코드에 바인딩합니다.

이것이 바로 '정적 바인딩'입니다.


정적 바인딩 (Static Binding)

정적 바인딩은 메소드 호출 시점에 어떤 메소드를 호출할지 컴파일 시점에 결정하는 것을 말합니다.

즉, 컴파일러가 참조 변수의 타입을 보고 호출할 메소드를 고정합니다.

 

[특징]

  • 일반 메소드는 기본적으로 정적 바인딩됩니다.
  • virtual 키워드가 없는 메소드는 정적 바인딩되어, 참조 변수의 타입에 따라 호출 메소드가 결정됩니다.
  • new 키워드를 사용하여 메소드를 숨기는 경우에도 정적 바인딩이 적용됩니다.

 

※ 반대 개념인 '동적 바인딩'은 런타임 시점에 실제 객체의 타입에 따라 호출 메소드를 결정합니다.


동적 바인딩 (Dynamic Binding)

위의 예시 코드에서 동적 바인딩이 아닌 이유는 virtual 키워드가 붙은 메소드가 없었기 때문입니다.

동적 바인딩은 virtual 키워드가 붙은 메소드에서만 동작합니다.

 

[특징]

  • 호출할 메소드가 컴파일 시점이 아니라 프로그램 실행 중 실체 타입에 따라 결정됩니다.
  • 기본 클래스에서 virtual 키워드로 선언된 메소드와 파생 클래스에서 override로 재정의된 메소드에 적용됩니다.
  • 메소드 호출이 참조 변수의 타입이 아니라, 실제 객체의 타입에 의해 결정됩니다.
  • 내부적으로는 가상 함수 테이블(Virtual Method Table, VMT)을 통해 런타임에 호출할 메소드를 찾는 동적 디스패치(dynamic dispatch) 방식을 사용합니다.

동적 디스패치는 프로그램 실행 중(런타임)에 호출할 메소드를 결정하는 메커니즘입니다.
    즉, 객체의 실제 타입에 따라 어떤 메소드를 호출할지 동적으로 선택하는 과정입니다.
    이 과정은 메소드 호출이 실제 객체 타입에 맞게 동작하도록 보장하기 위해 필요하고, 이를 통해 다형성을 지원합니다.


오버라이딩 봉인 : sealed 키워드

클래스를 상속이 안 되도록 봉인하는 것처럼 메소드도 오버라이딩되지 않도록 sealed 키워드를 이용해서 봉인할 수 있습니다.

모든 메소드를 봉인할 수 있는 것은 아니고, virtual로 선언되 가상 메소드를 오버라이딩한 버전의 메소드만 가능합니다.

 

봉인한 메소드를 오버라이딩하면 봉인 클래스를 상속할 때처럼 컴파일 에러가 발생합니다.

 

[예제]

using System;

class Base
{
    public virtual void SealMe() { }
}

class Derived : Base
{
    public sealed override void SealMe() { }
}

class WantToOverride : Derived
{
    public override void SealMe() { }    // 컴파일 에러 발생
}

class MainApp
{
    static void Main(string[] args)
    {

    }
}

봉인 메소드를 오버라이딩하려할 때 발생되는 컴파일 에러


읽기 전용 필드

읽기 전용 필드는 읽기만 가능한 필드를 말합니다.

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

 

읽기 전용 필드는 readonly 키워드를 이용해 선언할 수 있습니다.

class Configuration
{
    private readonly int min;
    private readonly int max;
    
    public Configuration(int v1, int v2)
    {
        min = v1;
        min = v2;
    }
}

 

읽기 전용 필드는 생성자 안에서만 초기화가 가능합니다.

만약 생성자가 아닌 다른 메소드에서 min, max와 같은 읽기 전용 필드를 수정하려는 시도가 발생하면 컴파일 에러가 일어납니다.

 

[예제]

using System;

namespace ReadonlyFields
{
    class Configuration
    {
        // readonly를 이용해서 읽기 전용 필드를 선언합니다.
        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);
        }
    }
}

읽기 전용 필드의 값을 생성자가 아닌 다른 곳에서 수정하려 할 때 발생하는 컴파일 에러


중첩 클래스 (Nested Class)

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

class OuterClass
{
    class NestedClass
    {
        // ...
    }
}

 

중첩 클래스를 선언하는 문법은 매우 간단합니다. 객체를 생성하거나 객체의 메소드를 호출하는 방법도 보통의 클래스와 다르지 않습니다.

 

중첩 클래스가 다른 클래스의 한 가지 다른 점이라면,
private 멤버를 포함한 자신이 소속된 클래스의 멤버에 자유롭게 접근할 수 있다는 사실입니다.

 

[중첩 클래스를 사용하는 이유]

  • 클래스 외부에 공개하고 싶지 않은 형식을 만들고자 할 때 사용합니다.
    • 외부에서 직접 접근하지 못하도록 캡슐화(encapsulation)를 강화할 수 있습니다.
    • 특정 기능이나 데이터를 다루는 보조 클래스를 외부에 공개하지 않고 내부 구현 세부사항으로 숨길 수 있습니다.
    • API 설계 시 불필요한 클래스 노출을 막아 사용자의 혼란을 줄이고, 유지보수를 쉽게 할 수 있습니다.
  • 현재 클래스의 일부분처럼 표현할 수 있는 클래스를 만들고자 할 때 사용합니다.
    • 외부 클래스와 논리적으로 밀접하게 연관된 개념 또는 기능을 표현할 때 유용합니다.
    • 외부 클래스의 일부 역할이나 세부 구현을 중첩 클래스로 분리하면, 코드의 구조가 명확해지고 가독성이 향상됩니다.
    • 마치 외부 클래스의 '내부 구성 요소'처럼 보여, 설계 의도를 직관적으로 알 수 있게 해줍니다.

[예제]

 

using System;
using System.Collections.Generic;

namespace NestedClass
{
    class Configruation
    {
        List<ItemValue> listConfig = new List<ItemValue>();
        public void SetConfig(string item, string value)
        {
            ItemValue iv = new ItemValue();
            iv.SetValue(this, item, value);
        }
        public string GetConfig(string item)
        {
            foreach (ItemValue iv in listConfig)
            {
                if (iv.GetItem() == item)
                    return iv.GetValue();
            }
            return null;
        }

        private class ItemValue    // Code A
        {
            public string item;
            public string value;
            public void SetValue(Configruation config, string item, string value)
            {
                this.item = item;
                this.value = value;

                bool found = false;
                
                for (int i = 0; i < config.listConfig.Count; i++)  // Code B
                {
                    if (config.listConfig[i].item == item)
                    {
                        config.listConfig[i] = this;
                        found = true;
                        break;
                    }
                }

                if (found == false)
                    config.listConfig.Add(this);
            }

            public string GetItem()
            { return item; }
            public string GetValue()
            { return value; }
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            Configruation config = new Configruation();

            config.SetConfig("Version", "V 5.0");
            config.SetConfig("Size", "655.324 KB");

            Console.WriteLine(config.GetConfig("Version"));
            Console.WriteLine(config.GetConfig("Size"));

            config.SetConfig("Version", "V 5.0.1");

            Console.WriteLine(config.GetConfig("Version"));
        }
    }
}

실행 결과

Code A

Configuration 클래스 안에 선언된 중첩 클래스입니다.
private으로 선언했기 때문에 Configuration 클래스 밖에서는 보이지 않습니다.

 

Code B

중첩 클래스는 상위 클래스의 멤버에 자유롭게 접근할 수 있습니다.


분할 클래스 (Partial Class)

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

분할 클래스는 클래스의 구현이 길어질 경우 여러 파일에 나눠서 구현할 수 있게 함으로써 소스 코드 관리의 편의를 제공합니다.

 

분할 클래슨느 다음과 같이 partial 키워드를 이용해서 작성합니다. 단, 클래스 이름은 동일해야 합니다.

pratial class MyClass
{
    public void Method1() { }
    public void Method2() { }
}

partial class MyClass
{
    public void Method3() { }
    public void Method() { }
}

// ...

MyClass obj = new MyClass();

obj.Method1();
obj.Method2();
obj.Method3();
obj.Method4();

 

이 코드에서 MyClass는 두 번에 걸쳐 정의되고 있습니다.

첫 번째 정의에서는 Method1()과 Method2() 메소드만 정의하고, 두 번째 정의에서는 MEthod3()과 Method4()를 정의합니다.

 

C# 컴파일러는 이렇게 분할 구현된 코드를 하나의 MyClass로 묶어 컴파일합니다.

그렇기에 클래스를 몇 개로 나눠 분할 구현했는지에 대해서는 신경쓰지 않아도 됩니다. 결국은 하나의 클래스로 컴파일되기 때문입니다.

 

[예제]

using System;

namespace PartialClass
{
    partial class MyClass
    {
        public void Method1()
        {
            Console.WriteLine("Method1");
        }

        public void Method2()
        {
            Console.WriteLine("Method2");
        }
    }

    partial class MyClass
    {
        public void Method3()
        {
            Console.WriteLine("Method3");
        }

        public void Method4()
        {
            Console.WriteLine("Method4");
        }
    }

    class MainApp
    {
        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            obj.Method1();
            obj.Method2();
            obj.Method3();
            obj.Method4();
        }
    }
}

실행 결과


확장 메소드 (Extension Method)

확장 메소드는 기존 클래스의 기능을 확장하는 기법입니다.

기반 클래스를 물려받아 파생 클래스를 만든 뒤 여기에 필드나 메소드를 추가하는 상속과는 다릅니다.

 

확장 메소드를 선언하는 방법은 메소드를 선언하되, static 한정자로 수식해야 합니다.

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

그 뒤에 따라오는 매개변수 목록은 실제로 확장 메소드를 호출할 때 입력되는 매개변수입니다.

 

[사용하는 이유]

  • 기존 클래스나 인터페이스에 새로운 메소드를 추가하고 싶을 때 사용합니다.
    • 기존에 작성된 클래스, 인터페이스, 특히 외부 라이브러리나 닷넷 프레임워크의 클래스를 수정하지 않고도 새로운 기능을 손쉽게 추가할 수 있습니다.
    • 원래 클래스 소스 코드에 접근할 수 없거나 수정이 불가능할 때 매우 유용합니다.
  • 코드의 가독성과 유지보수성 향상을 위해 사용합니다. 
    • 확장 메소드를 사용하면, 기존 클래스의 인스턴스 메소드처럼 호출할 수 있어 코드가 더 직관적이고 깔끔해집니다.
    • 예를 들어, 문자열에 새로운 기능을 추가할 때 'string.DoSomething()' 형태로 사용할 수 있습니다.
    • 별도의 유틸리티 클래스에서 정적 메소드를 호출하는 것보다 훨씬 자연스러운 문법입니다.
  • 인터페이스에 메소드 추가 효과를 위해 사용합니다.
    • 인터페이스는 직접 구현 없이 메소드를 추가하기 어렵지만, 인터페이스 타입을 위한 확장 메소드를 만들면 인터페이스에 새로운 기능을 간접적으로 추가하는 효과를 얻습니다.
    • 따라서 여러 구현체가 있는 인터페이스에 공통 기능을 추가할 때 유용합니다.
  • 기존 코드 변경 없이 기능 확장 가능하여 사용합니다.
    • 확장 메소드는 기존 코드(클래스, 인터페이스)를 변경하지 않고도 새로운 메소드를 추가할 수 있어, 안전성과 호환성을 유지할 수 있습니다.

 

[예제]

using System;
using System.Runtime.CompilerServices;
using MyExtension;  // 확장 메소드를 담는 클래스의 네임스페이스를 사용합니다.

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

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

            return result;
        }

        public static string Append(this string str, string input)  // Code C
        {
            str += input;

            return str;
        }
    }
}

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

            string hello = "Hello";
            Console.WriteLine(hello.Append(", World!"));
        }
    }
}

실행 결과

 

Code A : this int my int

int 타입에 대해 정의된 확장 메소드입니다.

 

Code B : this int myInt, int exponent

int 타입에 대한 확장 메소드로, this int myInt는 확장 대상 타입과 변수명을 나타냅니다..

 

Code C : this string str, string input

string 타입에 대해 정의된 확장 메소드로, this string str은 확장 대상 타입과 변수명을 나타냅니다.

 

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

[C#] 인터페이스  (0) 2025.04.24
[C#] 구조체, 변경 불가능 객체, 튜플  (0) 2025.04.22
[C#] 객체지향 프로그래밍 : 객체, 클래스  (0) 2025.04.15
[C#] 메소드  (0) 2025.04.11
[C#] Switch, 반복문, 점프문  (0) 2025.04.10