프로그램을 바라보는 관점
- 절차적 프로그래밍 : 코드의 순차적인 실행
 - 객체지향 프로그래밍 : 객체간의 상호 작용
 
클래스는 객체를 만들기 위한 청사진(설계도)

인스턴스(Instance)
string a = "123"; string b = "Hello";
- 클래스 : string
 - 객체 : a, b
 - 여기서 a와 b는 실제로 데이터를 담을 수 있는 실제 객체로 string의 실체/인스턴스라 불린다.
 
class 키워드 이용
class 클래스 이름
{
	// 데이터와 메소드
}
class 구성
// Cat의 청사진 //
class Cat  
{
	// 속성 = 클래스의 변수 = 필드(Field)
	public string Name;
   public string Color;
   
   // 기능 = 메소드
   public void Meow()
   {
   	Console.WriteLine("{0} : 야옹", Name);
   }
}
// 멤버(Member) : 필드와 메소드를 비롯해 프로퍼티, 이벤트 등 클래스 내에 선언된 요소들
// Cat의 실체(인스턴스) //
Cat kitty = new Cat();  // kitty 객체 생성
kitty.Color = "하얀색";
kitty.Name = "키티";
kitty.Meow();
Console.WriteLine("{0} : {1}", kitty.Name, kitty.Color);
Cat nero = new Cat()  // nero 객체 생성
kitty.Color = "검은색";
kitty.Name = "네로";
kitty.Meow();
Console.WriteLine("{0} : {1}", nero.Name, nero.Color);
.연산자 사용Cat kitty; // null
/* kitty는 참조로써 힙에 생성한 객체를 가리키는데
객체 생성 없이 위 코드만 실행할 경우 생성된 객체가 없으므로 null이 된다. */
// 객체 생성
Cat kitty = new Cat() // new 연산자 + 생성자
예제 프로그램
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}");
        }
    }
}

// 생성자 선언 //
class Cat
{
	// 생성자 버전 1
	public Cat()  // 한정자 + 생성자
    {
    	Name = "";
        Color = "";
    }
    
    // 생성자 버전 2
    public Cat( string _Name, string _Color) // 객체 생성시 이름과 색을 입력받아 초기화
    {
    	Name = _Name;
        Color = _Color;
    }
    
    public string Name;
    public string Color;
    
    //...
 }
 
 // 생성자 호출 및 사용 //
 
 // 생성자 버전 1 호출
 Cat kitty = new Cat(); // Cat()
 kitty.Name = "키티";
 kitty.color = "하얀색";
 
 
 // 생성자 버전 2 호출
 Cat nabi = new Cat("나비", "갈색");  // Cat(string _Name, string _Color)
~로 선언
class 클래스 이름
{
	~클래스 이름()  // 종료자 
   {
   	//
   }
   
   // 필드
   // 메소드
}
종료자는 CLR의 가비지 컬렉터에게 맡기고 되도록 사용하지 않는 것이 좋다.
예제 프로그램
using System;
namespace Constructor
{
    class Cat
    {
        public Cat()
        {
            Name = "";
            Color = "";
        }
        public Cat(string _Name, string _Color)
        {
            Name = _Name;
            Color = _Color;
        }
        ~Cat()
        {
            Console.WriteLine($"{Name} : 잘가");
        }
        public string Name;
        public string Color;
        public void Meow()
        {
            Console.WriteLine($"{Name} : 야옹");
        }
    }
    class MainApp
    {
        static void Main(string[] args)
        {
            Cat kitty = new Cat("키티", "하얀색");
            kitty.Meow();
            Console.WriteLine($"{kitty.Name} : {kitty.Color}");
            Cat nero = new Cat("네로", "검은색");
            nero.Meow();
            Console.WriteLine($"{nero.Name} : {nero.Color}");
        }
    }
}

☞ 객체의 소멸을 담당하는 가비지 컬렉터는 쓰레기의 양이 어느 선을 넘어야만 동작하는데
이것이 언제 동작할지는 누구도 알 수 없다. 따라서 위 프로그램 출력 결과는 매 실행 시마다 달라질 수 있다.
// 가비지 컬렉터가 동작할 경우 실행 결과
키티 : 야옹
키티 : 하얀색
네로 : 야옹
네로 : 검은색
네로 : 잘가
키티 : 잘가
static

예제 프로그램
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}");
   }
}
 
클래스 자체에 소속되는 메소드
클래스의 인스턴스를 생성하지 않아도 호출이 가능한 메소드
// 정적 메소드 //
class MyClass
{
	public static void StaticMethod() // static 사용
   {
   	    // ...
   }
}
//...
MyClass.StaticMethod();  // 인스턴스 만들지 않고도 바로 호출 가능
// 인스턴스 메소드 //
class MyClass
{
	public void StaticMethod() // static 사용X
   {
   	    // ...
   }
}
//...
MyClass obj = new MyClass(); // 인스턴스 생성
obj.InstanceMethod(); // 인스턴스를 만들어야 호출 가능

클래스는 태생이 참조 형식으로,
힙 영역에 객체를 할당하고 스택에 있는 참조가 힙 영역에 할당된 메모리를 가리킨다.

객체를 복사할 때 실제 객체가 아닌 스택에 있는 참조만 살짝 복사하는 것

힙에 보관되어 있는 내용을 복사해 받아 별도의 힙 공간에 객체를 보관하는 것
C#에는 이와 같은 일을 자동으로 해주는 구문이 없어 직접 수행 코드를 만들어야 함
class MyClass
{
   public int MyField1;
   public int MyField2;
   // 객체를 힙에 새로 할당해서 그곳에 자신의 멤버를 일일이 복사해 넣는다.
   public MyClass DeepCopy()
   {
   	MyClass newCopy = new MyClass();
       newCopy.MyField1 = this.Myfield1;  // this= MyClass
       newCopy.MyField2 = this.Myfield2;
       return newCopy;
   }
}
예제 프로그램
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; // 참조를 복사하므로 MyField2값이 변경된다.
               Console.WriteLine($"source : {source.MyField1} {source.MyField2}");
               Console.WriteLine($"target : {target.MyField1} {target.MyField2}");
           }
           Console.WriteLine("Deep Copy");
           {
               MyClass source = new MyClass();
               source.MyField1 = 10;
               source.MyField2 = 20;
               MyClass target = source.DeepCopy();
               target.MyField2 = 30; // 별도의 힙 공간에 복사본을 만드므로 target의 MyField2값만 변경된다.
               Console.WriteLine($"source : {source.MyField1} {source.MyField2}");
               Console.WriteLine($"target : {target.MyField1} {target.MyField2}");
           }
       }
   }
}
 
ICloneable.Clone() 메소드
- 클래스가 구현해야 하는 메소드 목록
 - 깊은 복사기능을 가질 클래스가 .NET의 다른 유틸리티 클래슨 다른 프로그래머가 작성한 코드와 호환되도록 하고 싶다면 ICloneable을 상속하는 것이 좋다.
 - Clone() 메소드 하나만 갖고 있다.
 class MyClass : ICloneable { public int MyField1; public int MyField2; public MyClass Clone() { MyClass newCopy = new MyClass(); newCopy.MyField1 = this.MyField1; newCopy.MyField2 = this.MyField2; return newCopy; } }
객체가 자신을 지칭할 때 사용하는 키워드
객체 외부에서는 객체의 필드나 메소드에 접근할 때 객체의 이름(변수 또는 식별자)을 사용한다면, 객체 내부에서는 자신의 필드나 메소드에 접근할 때 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($"Employee pooh - name :{pooh.GetName()}, position : {pooh.GetPosition()}");
           Employee tigger = new Employee();
           tigger.SetName("Tigger");
           tigger.SetPosition("Cleaner");
           Console.WriteLine($"Employee tigger - name:{tigger.GetName()}, position : {tigger.GetPosition()}");
       }
   }
}


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 AccessModifier
{
    class WaterHeater
    {
        protected int temperature;
        public void SetTemperature(int temperature)
        {
            if (temperature < -5 || temperature > 42)
            {
                throw new Exception("Out of temperature range");
            }
            this.temperature = temperature;
            // 이 필드는 protected로 수식되었으므로 외부에서 직접 접근할 수 없다. 이렇게 public 메소드를 통해 접근해야 한다.
        }
        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); // 예외가 발생하며, 실행되지 않고 catch블록으로 실행 위치가 이동한다.
                heater.TurnOnWater();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }            
        }
    }
}

상속 : 다른 클래스로부터 코드를 물려받는 것
상속 대상 : 클래스의 멤버(필드, 메소드, 프로퍼티 등등)
새로 선언하는 클래스 이름 뒤에 콜론(:)+기반 클래스 이름 표기하여 상속
물려주는 클래스 : 기반/부모 클래스, 물려받는 클래스 : 파생/자식 클래스
class 기반 클래스
{
	// 멤버 선언
}
class 파생 클래스 : 기반 클래스
{
	// 아무 멤버를 선언하지 않아도 기반 클래스의 모든 것을 물려받아 갖게 됨
   // 단, private으로 선언된 멤버는 예외
}
파생 클래스 = 자신만의 고유한 멤버 + 기반 클래스로부터 물려받은 멤버
파생 클래스 생성 과정
Base()
 Derived()
~Derived()
~Base()base : 기반 클래스의 생성자
// 기반 클래스 메서드 접근 //
class Base
{
	public void BaseMethod()
   {/*···*/}
}
class Derived : Base
{
	public void DerivedMethod()
   {
   	base.BaseMethod(); // base 키워드를 통해 기반 클래스인 Base 클래스에 속한 메서드에 접근할 수 있다.
   }
 }
 
 // 기반 클래스 필드 초기화 //
 
 class Base
 {
 	 protected string Name;
     public Base(string Name)
     {
   	    this.Name = Name;
     }
  }
  
  class Derived : Base
  {
      public Derived(string Name) : base(Name)  // Base(string Name) 호출해 Name 필드 초기화
      {
          Console.WriteLine("{0}.Derived()", this.Name);
      }
  }
예제 프로그램
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  // 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();
       }
   }
}

sealed한정자 : 상속이 불가능하도록 클래스 선언
아래와 같이sealed한정자로 클래스를 수식하면, 이 클래스는 '상속 봉인'이 되어 이로부터 상속받으려는 시도가 컴파일러로부터 발견됐을 때 에러 메시지가 출력된다.sealed class Base { // } class Derived : Base // 컴파일 에러 발생 { // }
기반 클래스와 파생 클래스 사이에서는 족보를 오르내리는 형식 변환이 가능하다.
파생 클래스의 인스턴스는 기반 클래스의 인스턴스로서도 사용할 수 있다.

// is 연산자 // 
Mammal mammal = new Dog();
Dog    dog;
if (mammal is Dog) // mammal 객체가 Dog 형식임을 확인
{
	dog = (Dog)mammal;  // 안전하게 형식 변환
    dog.Bark();
 }
// as 연산자 //
Mammal mammal2 = new Cat();
Cat cat = mammal2 as Cat; // mammal2의 Cat 형식 변환 
if (cat != null)  // mammal2가 Cat 형식 변환에 실패했다면 cat은 null이 됨
{
	cat.Meow();
}
as연산자는 참조 형식에 대해서만 사용이 가능하므로 값 형식의 객체는 기존의 형식 변환 연산자를 사용해야 함
예제 프로그램
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)
           {
               dog = (Dog)mammal;
               dog.Bark();
           }
           Mammal mammal2 = new Cat();
           Cat cat = mammal2 as Cat;
           if (cat != null)
               cat.Meow();
           Cat cat2 = mammal as Cat;
           if (cat2 != null)
               cat2.Meow();
           else
               Console.WriteLine("cat2 is not a Cat");
       }
   }
}

다형성(Polymorphism) : 객체가 여러 형태를 가질 수 있음을 의미
오버라이딩(Overriding)
기반 클래스에서 선언된 메소드를 자식 클래스에서 재정의하는 것
기반 클래스에서 오버라이딩할 메소드를 미리 virtual로 한정 (미리 준비)
// 기반 클래스 //
class ArmorSuite
{
    public virtual void Initialize()
    {
        Console.WriteLine("Armored");
    }
}
파생 클래스는 virtual 메소드를 override 한정자를 이용하여 재선언(재정의)
// 파생 클래스 //
class IronMan : ArmorSuite // ArmorSuite 상속
{
    public override void Initialize()
    {
        base.Initialize();  // ArmorSuite로부터 장갑 물려받기
        console.WriteLine("Repulsor Rays Armed");
    }
}
class WarMachine : ArmorSuite // ArmorSuite 상속
{
    public override void Initialize()
    {
        base.Initialize();  // ArmorSuite로부터 장갑 물려받기
        console.WriteLine("Double-Barrel Cannons Armed");
        console.WriteLine("Micro-Rocket Launcher Armed");
    }
}
private으로 선언한 메소드는 오버라이딩할 수 없다.
예제 프로그램
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();
       }
   }
}

CLR에게 기반 클래스에서 구현된 버전의 메소드를 감추고 파생 클래스에서 구현된 버전만 보여주는 것
파생 클래스 버전의 메소드를 new 한정자로 수식함으로써 숨길 수 있다. (생성자 호출 시 사용하는 new 연산자와는 완전 다르다)
예제 프로그램
using System;
namespace MethodHiding
{
   class Base
   {
       public void MyMethod()
       {
           Console.WriteLine("Base.MyMethod()");
       }
   }
   class Derived : Base
   {
       public new void MyMethod()
       {
       	   // Base.MyMethod()를 감추고 Derived 클래스에서 구현된 MyMethod()만 노출
           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();
           baseOrDerived.MyMethod();
       }
   }
}

클래스를 (상속이 안 되도록) 봉인하는 것처럼 메소드도 (오버라이딩되지 않도록) sealed 키워드를 이용해 봉인할 수 있다.
단 virtual로 선언된 가상 메소드를 오버라이딩한 버전의 메소드만 가능하다.
예제 프로그램
using System;
class  Base
{
   public virtual void SealMe()  // virtual 선언
   {
   }
 }
class Derived : Base
{
   public sealed override void SealMe() // 이 메서드만 봉인 가능
   {
   }
 }
class WantToOverride : Derived
{
   public override void SealMe()  // 오버라이딩 불가
   {
   }
 }
class MainApp
{
   static void Main(string[] args)
   {            
   }
 }

읽기만 가능한 필드
클래스나 구조체의 멤버로만 존재할 수 있으며 생성자 안에서 한 번 값을 지정하면, 그 후로는 값을 변경할 수 없는 것 (읽기 전용 필드는 생성자 안에서만 초기화가 가능)
readonly 키워드를 이용해 선언
예제 프로그램
using System;
namespace ReadonlyFields
{
   class Configuration
   {
   	// redonly를 이용해 읽기 전용 필드 선언
       readonly int min;
       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);
       }
   }
}
 
private 멤버에도 접근할 수 있음class OuterClass
{
	private int OuterMember;
   
   class NestedClass;
   {
   	public void DoSomething()
       {
       	OuterClass outer = new OuterClass();
           outer.OuterMember = 10;  // private 멤버에 접근하여 값을 할당하거나 읽을 수 있다.
       }
   }
}using System;
using System.Collections.Generic;
namespace NestedClass
{
    class Configuration
    {
        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 "";
        }
        private class ItemValue // 중첩 클래스, private으로 선언하여 Configuration 클래스 밖에서는 보이지 않는다.
        {
            private string item;
            private string value;
			// 중첩 클래스는 상위 클래스의 멤버에 자유롭게 접근할 수 있음
            public void SetValue(Configuration config, string item, string value)
            {
                this.item  = item;
                this.value = value;
                bool found = false;
                for (int i = 0; i < config.listConfig.Count; i++)
                {
                    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)
        {
            Configuration config = new Configuration();
            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"));
        }
    }
}

여러 번에 나눠서 구현하는 클래스
선언 : partial + class + 클래스이름 
// 클래스 이름은 동일해야 한다.
partial class MyClass
{
	public void Method1(){}
    public void Method2(){}
}
partial class MyClass
{
	public void Method3(){}
    public void Method4(){}
}
// ...
MyClass obj = new MyClass();
// C# 컴파일러는 위와 같이 분할 구현된 코드를 하나의 MyClass로 묶어 컴파일한다.
// 외와 같이 선언된 MyClass를 사용할 때는 그냥 하나의 클래스인 것처럼 사용하면 됨
obj.Method1();
obj.Method2();
obj.Method3();
obj.Method4();
예제 프로그램
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();
       }
   }
}
 
기존 클래스의 기능을 확장하는 기법으로 상속과는 다르다.
선언
static 한정자로 수식this + 확장하고자 하는 클래스/형식의 인스턴스namespace 네임스페이스이름
{
	public static class 클래스이름
   {
  	    public static 반환_형식 메소드이름( this 대상형식 식별자, 매개변수_목록 )
        {
          //
        }
    }
}
예제 프로그램
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()}"); // 3 x 3 = 3^2
           // Power가 원래 int형식의 메소드였던 것처럼 사용할 수 있다.
           Console.WriteLine($"3^4 : {3.Power(4)}"); // 3 x 3^3 = 3^4
           Console.WriteLine($"2^10 : {2.Power(10)}"); // 2 x 2^9 = 2^10
       }
   }
}

비타민 퀴즈
string 클래스에 문자열 매개변수를 입력받아 기존의 문자열 뒤에 붙여 반환하는 Append() 확장 메소드를 추가해보세요. 이 확장 메소드의 사용 예는 다음과 같습니다.string hello = "Hello"; Console.WriteLine(hello.Append(", World!")); // "Hello, World!" 출력// Answer code using System; using MyExtension; namespace MyExtension { public static class StringExtension { public static string String(this string myString) { return myString; } public static string Append(this string myString, string extension) { string first = myString; string result = first + extension; return result; } } } namespace ExtensionMethod { class MainApp { static void Main(string[] args) { string hello = "Hello"; Console.WriteLine($"string hello : {hello}"); Console.WriteLine($"Append : {hello.Append(", World!")}"); } } }
struct 구조체이름
{
	// 필드
   public int MyField1
   public int MyField2
   
   // 메소드
   public void MyMethod()
   {
   	// ...
   }
}public으로 선언해서 사용하는 경우가 많다.
=를 통해 모든 필드가 그대로 복사된다.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()  // System.Object 형식의 ToString() 메소드를 오버라이딩
        {
            return string.Format($"{X}, {Y}, {Z}");
        }
    }
    class MainApp
    {
        static void Main(string[] args)
        {
            Point3D p3d1; // 선언만으로 인스턴스 생성 (new 연산자 필요 X)
            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());
        }
    }
}

구조체는 readonly를 통해 모든 필드와 프로퍼티의 값을 수정할 수 없는, 변경불가능 구조체로 선언할 수 있다. (클래스는 불가능)
선언
readonly struct 구조체이름
{
}
readonly로 선언된 구조체 안에서 readonly로 선언되지 않은 필드와 프로퍼티를 만들거나 readonly로 선언된 필드를 수정하려하면 컴파일 에러가 발생한다.
// readonly로 선언되지 않은 필드와 프로퍼티
readonly struct ImmutableStruct
{
    public readonly int ImmutableField; // OK
    public int MutableField; // 컴파일 에러!
}
// readonly로 선언된 필드 수정 시도
readonly struct ImmutableStruct
{
    public readonly int ImmutableField; // OK
    public ImmutableStruct(int initValue)
    {
        immutableField = initValue; // 생성자에서만 초기화 가능
    }
}
// ...
class SomeClass
{
    public void SomeMethod()
    {
        ImmutableStruct is = new Immutable(123);
        is.immutableField = 456; // 컴파일 에러!
    }
}
readonly로 선언한 메소드도 구조체에서만 선언할 수 있다.
using System;
namespace ReadonlyMothod
{
   struct ACSetting
   {
       public double currentInCelsius; // 현재 온도(°C)
       public double target; // 희망 온도
	   // readonly로 한정한 메소드에서 객체의 필드를 바꾸려 들면 컴파일 에러가 발생함 
       public readonly double GetFahrenheit()
       {
           target = currentInCelsius * 1.8 + 32; // 화씨(°F) 계산 결과를 target에 저장
           return target; // target 반환
       }
   }
   class MainApp
   {
       static void Main(string[] args)
       {
           ACSetting acs;
           acs.currentInCelsius = 25;
           acs.target = 25;
           Console.WriteLine($"{acs.GetFahrenheit()}");
           Console.WriteLine($"{acs.target}");
       }
   }
}
var tuple = (Item1, Item2)var를 이용하여 선언Item1, Item2...ItemN라는 필드에 값을 담는다. (이는 튜플이 System.ValueTuple 구조체를 기반으로 만들어지기 때문이다.)필드명:의 꼴로 필드의 이름을 지정한 튜플_를 이용하면 된다. 튜플 분해는 여러 변수를 단번에 생성하고 초기화할 수 있게 해준다.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($"{name2}, {age2}");
            // 할당 | 명명된 튜플 = 명명되지 않은 튜플
            b = a; 
            Console.WriteLine($"{b.Name}, {b.Age}");
        }
    }
}
패턴 매칭(Positional Pattern Matching)
튜플의 분해자(Deconstructor)를 구현하고 있는 객체를 분해한 결과를 switch문이나 switch식의 분기 조건에 활용할 수 있다.
식별자나 데이터 형식이 아닌 분해된 요소의 위치에 따라 값이 일치하는지 판단

예제 프로그램
using System;
namespace PosisionalPattern
{
 class MainApp
 {
     private static double GetDiscountRate(object client)
     {
         return client switch  // 패턴 매칭
         {
             ("학생", int n) when n < 18 => 0.2,  // 학생 & 18세 미만
             ("학생", _) => 0.1,  // 학생 & 18세 이상
             ("일반", int n) when n < 18 => 0.1,  // 일반 & 18세 미만
             ("일반", _) => 0.05, // 일반 & 18세 이상
             _ => 0,
         };
     }
     static void Main(string[] args)
     {
         var alice   = (job: "학생", 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)}");
     }
 }
}

클래스와 객체, 인스턴스는 서로 어떤 점이 다른가요?
: 컴퓨터는 세상의 모든 것을 코드 안에 표현하는데 이때 코드로 표현 된 세상의 모든 사물을 객체라고 한다. 이 객체는 클래스라는 청사진/설계도에 의해 만들어지며 이러한 청사진을 기반으로 실제 데이터를 담을 수 있는 실제 객체가 만들어진다고해서 이러한 실체를 인스턴스(Instance)라고 부른다.
클래스가 자동차 설계도라면 객체와 인스턴스는 생산된 실제 자동차인 셈이다.
다음 코드에서 오류를 찾고, 오류의 원인을 설명하세요.
class A
{
}
class B : A
{
}
class C
{
   public static void Main()
   {
       A a = new A();
       B b = new B();
       A c = new B();
       B d = new A();  // error
   }
}
 C#에서는 암시적으로 자식 클래스의 형식을 통해 부모 클래스를 개체화하는 것이 불가능하다. 이를 다운캐스팅이라고 부른다.
B d = new A();에서 A는 B의 정보를 모른다.
this 키워드와 base 키워드에 대해 설명하세요.
구조체에 대한 다음 설명 중 틀린 것을 모두 찾으세요. → ②, ③
① struct 키워드를 이용하여 선언한다.
② 복사할 때 얕은 복사가 이루어진다. → 깊은 복사
③ 참조 형식이다. → 값 형식으로 스택에 메모리가 쌓인다
④ 메소드를 가질 수 있다.
다음 코드를 컴파일 및 실행이 가능하도록 수정하세요.
using System;
namespace ReadonlyMothod
{
   struct ACSetting
   {
       public double currentInCelsius; // 현재 온도(°C)
       public double target; // 희망 온도
       public readonly double GetFahrenheit()
       {
           target = currentInCelsius * 1.8 + 32;
           // error : 읽기 전용이므로 값이 바뀌지 않게 아래 코드를 바꾸면 된다.
           return target; // → return currentInCelsius * 1.8 + 32;
       }
   }
   class MainApp
   {
       static void Main(string[] args)
       {
           ACSetting acs;
           acs.currentInCelsius = 25;
           acs.target = 25;
           Console.WriteLine($"{acs.GetFahrenheit()}");
           Console.WriteLine($"{acs.target}");
       }
   }
}
다형성은 무엇이며, 오버라이딩과 무슨 관계가 있는지 설명하세요.
: 다형성은 객체가 여러 형태를 가질 수 있음을 의미하는 것으로 본래 하위 형식 다형성의 준말이다. 자신으로부터 상속받아 만들어진 파생 클래스를 통해 다형성을 실현한다는 것이다. 오버라이딩은 상속받은 자식 클래스에서 부모 클래스의 메소드를 재정의 하는 것으로 다형성을 실현하는 방법이라 할 수 있다.
다음 코드에서 switch 식을 제거하고 switch 문으로 동일한 기능을 작성하세요.
private static double GetDiscountRate(object client)
{
   switch (client)
   {
   
   	case("학생", int n) when n <18:
           return 0.2;
       case("학생", _):
           return 0.1;
       case ("일반", int n) when n < 18:
           return 0.1;
       case ("일반", _):
           return 0.05;
       default:
           return 0;
     
   };
}