본문 바로가기

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

[문과 코린이의 IT 기록장] C# - 기타 주요 내용들 (NULL 관련 연산자, 타입 - Nullable (?), Null 병합 연산자 (??), Null 조건 연산자, Var 예약어 / Dynamic / 익명 타입, 익명메서드, 확장메서드)

반응형

[문과 코린이의 IT 기록장] C# - 기타 주요 내용들 (NULL 관련 연산자, 타입 - Nullable (?), Null 병합 연산자 (??), Null 조건 연산자, Var 예약어 / Dynamic / 익명 타입, 익명메서드, 확장메서드)

[문과 코린이의 IT 기록장] C# - 기타 주요 내용들 (NULL 관련 연산자, 타입 - Nullable (?), Null 병합 연산자 (??), Null 조건 연산자, Var 예약어 / Dynamic  / 익명 타입, 익명메서드, 확장메서드)

 


 

 

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

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

www.inflearn.com


1. NULL 관련 연산자, 타입 - Nullable (?)

1) Nullable ?

- C# 2.0 System.Nullable<T>로 정의된, 구조체를 의미한다.

- Nullable은 값 형식의 초기값이, 모두 0(false)으로 채워지는 문제를 해결하기 위해 생겨났다.

 : 참조형식의 초기값은, Null로 초기화한다. 즉, 참조형식에는 값이 실존O / 실존X에 대해 구분이 가능하다.

 : 그러나 값 형식에는, 無라는 개념이 존재하지 않는다. int a = 0;이더라도 이는 값이 0으로 존재하는 것이다.

 : 이 Null과 관련된 부분은 DB와 연동할 때 굉장히 중요하게 고려되어야 할 부분이다.

 ex. 학생의 성(Sex) 정보를 입력하는 레코드가 있을 때, 남성/여성 정보를 입력하지 않았다면, 이것을 Null로 처리해야 한다. 그런데, bool로 남성/여성을 설정했다면, default값으로 어느 하나 값이 설정될 것이다. 즉, 본인은 성(Sex)정보를 입력하지 않았지만, 잘못 수집된 결과로 처리된다는 것이다. 이러한 문제를 해결하기 위해 나타난 것이, Nullable이다.

enum Sex{man, woman} // man으로 자동 초기화 (문제 발생)
enum Sex {man, woman, notDefined} // (애매모호) 미정 enum필드를 하나 더 만들어서, 3가지 status로 운영하는 방식
// 성 정보에 notdefined를 가지는 것은 조금 애매모호함.
// => 이를 해결하기 위해 nullable 등장

- 정리하자면, Nullable로 선언할 경우, 값 형식임에도 불구하고 null값을 가질 수 있게 된다는 것이다. 

- 이는 구조체로 정의되며, 제공되는 프로퍼티는 HasValue / Value가 존재한다.


2) Nullable 형식

Nullable<int> intValue = 10;

// Nullable<T>에서 T로 대입
int target = intValue.Value; // 즉, int변수에, Nullable<int>를 대입하려면 .Value값을 넣어주면 됨

// T에서 Nullable<T>로 대입
intValue = target; // Nullable<int>에다가는, int 변수를 넣으면 들어감

// Nullable<T>에 null값 대입
double? temp = null; // Nullable<double> = double?, 여기에 null값 대입 가능
Console.Write(temp.HasValue); // 출력결과 : False

3) Nullable 예시

[ Program.cs ]

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

namespace End_0811
{
    class Program
    {
        static void Main(string[] args)
        {
            EndSub es = new EndSub();
            es.Run();
        }
    }
}

[ EndSub.cs ]

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

namespace End_0811
{
    class EndSub
    {
        public void Run()
        {
            Member m = new Member();

            // 1. bool 타입 사용
            Console.WriteLine(m.GetMarried1 ? "기혼" : "미혼"); // 초기화 : False로 됨.

            // 2. Nullable<bool> 타입 사용
            // Console.WriteLine(m.GetMarried.HasValue == true ? "기혼" : "미혼"); 
            // 즉 아래와 같다.
            if (m.GetMarried2.HasValue)
            {
                Console.WriteLine(m.GetMarried2 == true ? "기혼" : "미혼");
            }
            else
            {
                Console.WriteLine("정보없음"); // 이것이 출력되어야 함.
            }

            // 3. T? = Nullable<T>
            int? aa = null;
            aa = 10;
            int? bb = aa;
            int c = 10;
            // c = bb; // bb가 nullable이기 때문에, 이러한 대입은 불가능하다. 
            c = bb.Value; // 그러나 nullable값에 Value를 해주면, 이러한 대입은 가능하다.

        }
    }

    class Member
    {
        /* bool getmMarried;  // 기본 값 : false
         public bool GetMarried // GetMarried의 속성을 가져와보면, 기혼자임에도 불구하고 설정을 안했을 경우, 미혼으로 가져가질 수 도 있음.
         {
             get { return GetMarried;  }
             set { GetMarried = value; }
         }*/

        // 1. bool 타입 사용 (값 형식의 문제점)
        public bool GetMarried1 { get; set; } // 위와 같음.

        // 2. Nullable<bool> 타입 사용 
        // public Nullable<bool> GetMarried2 { get; set; }  // boolean타입의 Nullable을 선언 (True / False / Null)을 가지게 됨.
        // 프로퍼티 선언에서도, Nullable<bool>로 해서, Null을 반환 가능하다.
        // [ 약어 ] : bool? = Nullable<bool>
        public bool? GetMarried2 { get; set; }

    }
}

 


2. NULL 관련 연산자, 타입 - Null 병합 연산자 (??)

1) Null 병합연산자 ??

- C#2.0에서 발표된 내용

- 삼항 연산자를 조금 더 간소화시켰다고 생각하면 됨.

피연산자1 ?? 피연산자2
 // 참조형식에서, 
 // 피연산자1이 null이 아니라면 그값을 그대로 반환하고(피연산자1을 반환하고), 
 // null이 아니라면, 피연산자 2의 값을 반환한다.

2) Null 병합연산자 예시 

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

namespace End_0811
{
    class EndSub
    {
        public void Run()
        {
            string name = null;
            // 1. 삼항연산자
            Console.WriteLine(name == null ? "(null)" : name);
            // 2. null병합 연산자
            Console.WriteLine(name??"(null)");
        }
    }
}

 


3. NULL 관련 연산자, 타입 - Null 조건 연산자

1) Null 조건 연산자

- C# 6.0에서 나온 것으로, 참조변수의 값이 null이라면 그대로 null을 반환하고, null이 아니라면 지정된 맴버를 호출한다.

- null조건 연산자는 단독으로 사용할 수 없고, 반드시 해당 참조형 변수의 맴버를 접근하거나, 배열 인덱스와 같은 부가적인 접근을 필요로 한다. 

List<int> list = GetList(); // null을 반환했다면? - int형 List인 list(참조변수)에 null값 저장. 
// Count는 List의 맴버변수
Console.Write(list.Count); // 예외발생
Console.Write(list?.Count); // null 반환 (참조형 변수에 직접적으로 사용)
// 즉, 참조형 변수가 어떤 클래스의 맴버에 접근하려고 한다던가, Array 등의 index의 element에 접근할 경우 null이면 예외가 발생하는데 그럴 때 사용

// C# 6.0 컴파일러는, 다음과 같은 코드로 변환 처리
Console.Write(list != null ? new int?(list.count) : null);
// list가 null이 아니라면(값이 존재한다면), new int?(list.count) -> nullable 타입으로 반환해줌
// list가 null이라면, null반환.

* nullable은 데이터타입 뒤에 ?가 붙으며, null 조건 연산자는 참조형 변수 뒤에 ?가 붙는다.

 

List<int> list = null;

for(int i=0; i<list.Count ; i++) // list.Count에서 바로 에러 발생.
{
	Console.Write(list[i]);
}

// 1 ) 해결방안 1
if(list != null) // 고전적 방법. list가 null이 아닐때만 코드가 작동하도록
{
	for(int i=0 ; i<list.Count ; i++)
    {
    	Console.Write(list[i]);
    }
}

// 2 ) 해결방안 2
for(int i=0; list!=null && i<list.Count; i++) // list가 null이 아니면서, list.Count보다 작을 때 작동시키기 (코드 지저분함)
{
  	Console.Write(list[i]);
}

// 3 ) 해결방안 3
for(int? i=0; i<list?.Count; i++) // int형 nullable로 선언해 받음. 조건문 또한 list?.Count로. (null이어도, null로 작동시킴)
{
  	Console.Write(list[i]);
}
string[] lines = null; // string 배열, lines
string firstElement = lines?[0]; // lines의 인덱스에 접근. null이 나옴.

List<int> list = null;

// 1)
int count = list?.Count; // 예외 발생

// 2)
int? count = list?.Count // Nullable int 반환

// 3)
list?.Add(5) // list가 null이기 때문에, Add()메서드가 호출되지 않는다.

2) Null 조건연산자 예시 

 

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

namespace End_0811
{
    class EndSub
    {
        public void Run()
        {
            List<int> list1 = new List<int>(); // Count=0인 List Array를 포인팅하고 있음.
            List<int> list2 = null; // 변수지만 메모리를 전혀 포인팅하고 있지 않음.

            Console.WriteLine(list1.Count);

            // list1이 null이면 null반환 (Count가 안불러짐)
            Console.WriteLine(list1?.Count); // = (list1 != null ? new list1?(list1.count) : null)

            // try-catch : 예외가 발생할지 안할지 모를 경우 사용하는 구문
            // try-catch문이 많이 사용되는 부분 : List에서 null을 다루는 부분 / File IO , Directory Info 
            try
            {
                // 1. 처리되지 않은 예외 발생 - list2에접근했지만, 아무것도 가르키고 있지 않기 때문에, exception으로 가버림
                //Console.WriteLine(list2.Count);

                // 2. 
                int? ret = list2?.Count; // int nullable 타입인 ret변수로 받음 
                Console.WriteLine(ret == null ? "(null)" : ret.ToString());
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message); // [출력] 개체 참조가 개체의 인스턴스로 설정되지 않았습니다.
            }
        }
    }
}

 


4. Var 예약어 / Dynamic  / 익명 타입

* 편리하긴 하지만, 프로그래밍에서 굉장히 유익하다고 볼 수는 없는 내용들.

* 익명 타입이 람다식으로 발전하게 됨


1) Var 예약어

- C#3.0 컴파일러부터 타입 추론(type inference) 기능이 추가됨. 

 * 타입 추론 예시 :  컴파일러가 return되는 값이 int이기 때문에, int로 받아야겠다라는 추론을 하게 됨.

- 메서드에 지역변수를 선언할 때, 타입을 신경쓰기 힘들 경우, 타입에 관계없이 var 예약어를 사용 가능하다.

void Func()
{
    int i = 5;
    var j = 6; 
    Console.WriteLine(i.GetType().FullName); // GetType() : object 메소드
    Console.WriteLine(j.GetType().FullName); 
    // 둘 다 System.Int32출력.
}

2) dynamic 예약어

- C# 4.0 실행시에, 타입에 상황에 맞게 정해지고 초기화됨

 * CLR(정적 환경)에서는 dynamic을 쓸 일이 거의 없음

- 실행시까지도 타입이 정해지지 않으며, 즉 미정의 변수가 존재하는 것. 그래서 우항이 결정이 되면, 그제서야 dynamic변수가 초기화가 되면서 준비된다.

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

namespace End_0811
{
    class EndSub
    {
        public void Run()
        {
            dynamic a = 1;
            // Console.WriteLine(a.GetType()); 
            // 동적 변수이기 때문에, a가 무엇인지 결정되지 않으며, 따라서 GetType()이 나타나지 않는다. 
            // 그러나 특정 시점에서 어떤 타입이었는지, 컴파일 한 후에는 나올 수 있다. 그렇지만 계속 변경될 수 있다.
            // 즉, 동적 변수는, 고정된 Type이 없다는 의미이며, GetType()이 갖는 의미가 없다.
        }
    }
}

3) 익명 타입

- C# 3.0부터 나왔으며, 타입에도 이름을 지정하지 않는 방식을 지원하는 것이다.

void Func()
{
	var p = new {Count=10, Title="Test"};
    // new 이후에 타입이 나와야 하지만, 타입이 나오지 않고, 익명(이름이 없음) 형태로 씀.
    // 그 타입의 맴버들을 보면, 추론을 할 수 있으며, 초기화도 가능하다.
    // 어떤 클래스를 가져다가, 클래스에 값을 넣을 적에, 꼭 그 클래스를 사용해 넣지 않고, 익명타입으로 넣을 수 있음.
    // 익명타입을 쓰는 이유 : 딱 한번 쓰고 말 것이기 때문에, 즉흥적으로 타입을 만들고 사용하고 싶을 경우 사용
    Console.Write(p.Title + ": " + p.Count); // test: 10 출력
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace End_0811
{
    class EndSub
    {
        public void Run()
        {
            var st = new Student(); // Student 클래스의 인스턴스 st 생성
            st.Name = "홍길동";
            st.Score = 10;

            var st2 = new Student { Name = "홍길동", Score = 90 }; // 초기화까지 한번에 진행
            // 컴파일 타임 때 자동으로 Student 타입으로 확정되서 진행됨.

            // 클래스를 만들어서 번거롭게 하지 않도록, 익명 타입 설정,
            // 익명 타입은 특정 타입이 없기 때문에, 타입으로 값을 받아들일수는 없다.
            var st3 = new { Name = "황진이", Score = 60 };
            Console.WriteLine(st3.Name);  // 황진이
            Console.WriteLine(st3.Score); // 60
        }
    }

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

 


5. 익명메서드, 확장메서드

1) 익명메서드 

- C# 2.0에서 간편한 표기법으로, 델리게이터에 전달되는 메서드가, 일회성으로만 필요할 때, 편의상 사용하는 기능이다.

delegate int? MyDivide(int a, int b);
// delegate : 함수를 가리키는 타입을 선언하는 것
// 이 함수는 MyDivde타입을 가지고 있고, int nullable을 반환해주고 있으며, 매개변수는 int a, b

static void Main()
{
	myDivide myFunc = delegate(int a, int b)
    {
    	if(b==0) return null;
        return a/b;
    } // 함수이자 delegate가 됨
    // 원래는 private int? AA(int a, int b){} 이렇게 되어야 함. 그러나 이름을 가지지 않을 경우, 위와 같이 구성
    
    Console.Write(myFunc(10,2)); // 5
    Console.Write(myFunc(10,0)); // null
}

[ 예제 ]

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

namespace End_0811
{
    class EndSub
    {
        delegate int? MyDivide(int a, int b);

        public void Run()
        {
            // 1. 익명메서드
            // 이 함수는, 만들어서 쓸 정도는 아니며, 이 기능은 여기서 쓰고 더 이상 쓸 일이 없다.
            MyDivide md = delegate (int a, int b)
            {
                return a / b;
            };

            Console.WriteLine(md(6, 3)); // 2반환

            // 이후에 이 익명 메서드가, 람다식으로 변환됨. 즉 익명의 메서드를 함축해서 표현하는 방법임.
            // 람다식 : delegate(){}는 익명의 메서드를 만들 경우 항상 나오는 것들임. 이것에 착안해서, 만든 것으로
            // MyDivide md = (a,b) => a / b; 이런 식으로 위의 코드를 쓸 수 있음.
            // .NetFramework에서 제공하는 delegate들이 있는데, 이것들을 람다식과 연결해서 쓸 수 있으며, collection을 람다식에 적용시킬 수 있음. 
        }

    }
}

 

2) 확장메서드 (extension method)

- C# 3.0에서 나오는 개념으로, 기존 클래스의 내부 구조를 전혀 바꾸지 않고, 마치 새로운 인스턴스 메서드를 정의하는 것처럼 추가할 수 있는데, 이를 확장 메서드라고 한다.

 

[ 조건 ]

- static 클래스에서 정의되어야 한다.

- 확장메서드 또한 반드시 static이어야 한다.

- 확장하려는 타입은 this예약어와 함께 명시해야 한다.

static class MyClass
{
	public static int GetWordCount(this string txt)
    {
    	return txt.Split(' ').Length;
    }
}

class Program
{
	static void Main()
    {
    	string text = "Hello, World!";
        Console.Write(text.GetWordCount());
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace End_0811
{
    static class MyClass
    {
        // 2. 확장 메서드
        // 확장하고자 하는 메서드 : GetWordCount / 확장하고자 하는 타입 : string / this : 키워드
        public static int GetWordCount(this string txt)
        {
            // 아래와 같은 의미. (풀어씀)
            /*string[] tmp = txt.Split(' ');
            return tmp.Length;*/

            return txt.Split(' ').Length;
            // txt를 공백을 기준으로 split한 후, 몇개의 단어로 되어 있는지 반환하는 것
            // 이러한 메서드는 기존 string 타입에서는 제공하지 않음.
            // 이 GetWordCount라는 메서드를 string 타입에 메서드로 집어넣어 확장시키고 싶음. (확장메서드)

            // [ 함수 동작 방법 ]

        }

        public static int? IsDigit(this string txt)
        {
            int tmp;
            if (int.TryParse(txt, out tmp)) // True이면 tmp에 바뀐 값을 넣음. 아니면 false를 return.
            {
                return tmp; // 숫자이면, 해당 숫자 값 출력
            }
            return null; // 숫자가 아니면 null 출력 (즉 값이 출력되지 않음)
        }
    }


    class EndSub
    {
        public void Run()
        {
            string kk = "Hello World!";
            Console.WriteLine(kk.GetWordCount()); // 2 출력
            // GetWordCount() : 확장 메서드

            string ii = "1133";
            // [ 문제점 ] 숫자값인가 문자값인가? 하는 함수
            Console.WriteLine(ii.IsDigit()); // 1133 출력

        }
    }
}

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