[문과 코린이의 IT 기록장] C# - 기타 주요 내용들 (NULL 관련 연산자, 타입 - Nullable (?), Null 병합 연산자 (??), Null 조건 연산자, Var 예약어 / Dynamic / 익명 타입, 익명메서드, 확장메서드)
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 출력
}
}
}
* 유의사항 - 아직 공부하고 있는 문과생 코린이가, 정리해서 남겨놓은 정리 및 필기노트입니다. - 정확하지 않거나, 틀린 점이 있을 수 있으니, 유의해서 봐주시면 감사하겠습니다. - 혹시 잘못된 점을 발견하셨다면, 댓글로 친절하게 남겨주시면 감사하겠습니다 :) |