본문 바로가기

C#

C# 캡슐화, 상속(접근제한자, 프로퍼티, as, is, Object, this, base)

반응형

캡슐화 (Encapsulation)

객체안의 변수나 함수에 아무나 함부로 접근해서 변경하면 프로그램에 지장을 주는 경우가 많아지므로 그렇게 하지 못하게 하기 위해 내부 멤버를 숨겨야 함. 이렇게 하는 것이 캡슐화.

접근제한자

private : 클래스 내부에서만 접근 가능

protected : 내부에서 접근 및 파생 클래스에서만 접근 가능

public : 내부, 파생클래스, 외부에서도 접근 가능

internal : 동일 어셈블리 내에서는 public에 준한 접근 가능, 다른 어셈블리에서는 접근 불가

internal protected : 동일 어셈블리 내에서 정의된 파생클래스까지만 접근 가능

* 어셈블리는 아직 모르니 패스. (보통 private, public 으로 대부분 구현)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApp2
{
    class Circle
    {
        double pi = 3.14;
        public double GetPi()
        {
            return pi;
        }
        public void SetPi(double value)
        {
            pi = value;
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            //생략
        }
    }
    
}
 


Cricle 클래스에서는 pi라는 멤버변수를 은닉했다.

그러나 무조건 멤버변수를 은닉하는 것이 좋은 코드는 아니다.

멤버 변수 자체가 클래스의 고유 특성을 반영하고 있다면 외부에서 그 변수에 접근할 필요가 있기 때문.

접근 =  읽기/쓰기 즉, get/set

읽기만 가능하게 하려면 set 메서드를 없애면 됨.

get/set 을 쓸꺼면 멤버변수를 private으로 쓰는게 편하지 왜 get/set을 구현할까?

=> 멤버변수에 접근하는 부분이 코드 사방에 퍼져있으면 유지보수가 어려움. get/set하는 부분을 만들면 그부분만 수정하면 됨.


* 프로퍼티 : C#에서 get/set메서드에 대해 편리한 구문. ( != 속성(attribute)은 필드/멤버변수에 해당 )

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApp2
{
    class Circle
    {
        double pi = 3.14;
        //프로퍼티 이용
        public double Pi
        {
            get { return pi; }
            set { pi = value; }
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
            Circle c = new Circle();
            c.Pi = 3.141592// set으로 사용
            double piValue = c.Pi; // get으로 사용
        }
    }
    
}
 


기존의 set/get을 위와같은 구문으로 쓰고 Main에서 보이는 것처럼 사용.

* 컴파일러는 프로퍼티를 보고 기존의 get/set 으로 변환해서 컴파일함.


상속(Inheritance)

공통적인 특징을 정의하는 부모클래스를 이용하고 자식클래스에서 부모클래스의 기능을 물려받는 식의 처리

예를들면 Notebook, Desktop, Netbook이 공통적으로 컴퓨터라는 특징이 있기때문에 컴퓨터라는 부모클래스를 만들고 추가되는 기능은 자식클래스에서 구현하는 것.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApp2
{
    public class Computer
    {
        bool powerOn;
        public void Boot() { }
        public void Shutdown() { }
        public void Reset() { }
    }
    public class Notebook : Computer
    {
        bool fingerScan;
        public bool HasFingerScanDevice() { return fingerScan; }
    }
    public class Desktop : Computer
    {
 
    }
    class Program
    {
        static void Main(string[] args)
        {
          //생략
        }
    }
    
}
 


사용방법은 자바의 extends와 다르게 그냥 콜론(:) 을 뒤에서 씀.

* 마찬가지로 다중 상속 불가.

powerOn이 부모클래스에서 private으로 접근제한을 뒀기때문에 자식인 Notebook에서 접근 불가

protected로 제한을 뒀으면 자식클래스에서 접근 가능

[ sealed 예약어 ]

- sealed 예약어를 클래스 앞에 붙으면 상속을 의도적으로 막을 수 있음. ex) string 타입


클래스에서 암시적 형변환, 명시적 형변환 (casting)

부모클래스 변수에 자식클래스 인스턴스 대입 가능

1
2
3
4
5
Notebook noteBook = new Notebook();
 
Computer pc1 = noteBook // 암시적 형변환 가능
pc1.Boot();
pc1.Shutdown();


반대로 부모클래스의 인스턴스를 자식 클래스 변수에 대입은 불가.

강제로 명시적 형변환을 사용할 수 있으나 실행하면 오류 발생. (컴파일은  가능)

(자식에 더 많은 멤버가 있는데 할당한 메모리에 그것들을 반영하고 있지 않을 것이다. 그런상태에서 자식클래스에만 있는 메서드를 사용하려하면 당연히 불가하니까 안됨)

대신 의도하는 경우가 있음

1
2
3
4
5
6
Notebook noteBook = new Notebook();
 
Computer pc1 = noteBook // 암시적 형변환
 
Notebook note2 = (Notebook)pc1; // 다시 본래타입으로 명시적 
note2.CloseLid();



as, is 연산자 *****

as는 참조형 변수에 대해서만 적용가능, 참조형 타입으로의 체크만 가능

1
2
3
4
5
6
7
Computer pc = new Computer();
Notebook notebook = pc as Notebook; // 형변환 가능한지 판단하고 변환하는 연산자.
// 변환이 가능하면 인스턴스 값을 반환하고 불가하면 null 
if(notebook != null)
{
    notebook.CloseLid();
}


pc(Computer)가 Notebook으로 형변환 가능하니? / 아니 / null리턴

즉, if문 실행 안됨.

(강제 형변환해서 에러를 출력하는건 내부적으로 부하가 커서 이렇게 하는게 좋음)

is 는 형변환에 대한 가능성 여부만 묻고 결과로 true/false만 리턴

1
2
3
4
5
6
7
8
9
10
int n = 5;
if( n is string )
{
    Console.WriteLine("변수 n 은 string 타입");
}
string txt = "text";
if( txt is int )
{
    Console.WriteLine("변수 txt는 int 타입");
}


둘다 실행 안됨.

* is 연산자는 참조형식뿐만아니라 값형식에도 사용 가능.

// as / is 연산자는 형변환된 인스턴스가 필요하면 as, 필요없으면 is를 사용하면 됨.


Object는 모든 클래스의 부모클래스. 따로 명시하지않아도 자동으로 Object를 상속받음.

그래서 Object의 메서드를 사용가능하고 오버라이딩으로 변환도 가능하다.

ToString, GetType, Equals, GetHashCode 메서드를 사용가능

* 따로 공부해야함. 객체간의 비교(Equals, HashCode 충돌등)


클래스 내부 코드에서 객체 자신을 가리킬 수 있는 방법

this 키워드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
 
namespace ConsoleApp2
{
    class Book
    {
        string title;
        decimal isbn13;
        string author;
        public Book(string title) : this(title, 0)
        {
 
        }
        public Book(string title, decimal isbn13) : this(title,isbn13,string.Empty)
        {
 
        }
        public Book(string title,decimal isbn13, string author)
        {
            this.title = title;
            this.isbn13 = isbn13;
            this.author = author;
        }
        public Book() : this(string.Empty, 0string.Empty)
        {
 
        }
    }
    class Program
    {
        static void Main(string[] args)
        {
          //생략
        }
    }
    
}


: this 를 통해서 또 다른 생성자를 호출하는 구문을 사용해 초기화 관련 코드를 하나의 메서드내에서 처리하게함.

* 정적메서드에서는 this 사용불가. (당연하다)

this 의 이해는 메서드나 생성자를 호출할 때 자기 자신을 컴파일러가 매개변수로 넣어주는 것이다.

public void Close(){} 라는 메서드는 컴파일러가 public void Close(Book this){} 이렇게 넣어주는 것.


base

this 예약어가 클래스의 인스턴스 자체를 가리키는 것과 달리 base는 부모클래스를 명시적으로 가리키는데 사용된다.

this와 마찬가지로 base키워드가 생략된 것이나 다름 없다.

부모의 메서드를 그냥 Shutdown(); 이라고 쓰면 자동으로 base.Shutdown();으로 변경해서 부모에게 상속받은 메서드를 호출하게 되는 것.

반응형