이전 버전의 C#문법은 제네릭(C# 2.0), LINQ(C# 3.0), dynamic(C# 4.0), 비동기 호출(C# 5.0)과 같이 중요 기능이 추가 되었던 반면 C#6.0에서는 코드량을 줄이기 위한 '간편 표기법'이 추가됐다.
11.1 자동 구현 속성의 초기화 구문 추가
c# 3.0에서 자동 구현 속성(Auto-implemented Properties)을 사용한 경우 초깃값을 부여하려면 별도 생성자등의 메서드를 이용해 코드를 추가해야만 했다.
class Person
{
public string Name { get; set; }
public Person()
{
this.Name = "iron";
}
}
C# 6.0에서는 '자동 구현 속성 초기화(Initializers for auto-properties)' 구문을 사용해 다음과 같이 직접 기본값을 지정 할 수 있다.
class Person
{
public string Name { get; set; } = "iron";
}
c# 6.0에서 컴파일 시 내부적으로 다음과 같이 자동 변경되어 컴파일 된다.
class Person
{
private string [Name_유일한 식별자] = "iron";
public string Name
{
get { return [Name_유일한 식별자]; }
set { [Name_유일한 식별자] = value; }
}
}
설정자 메서드를 제거해 읽기 전용 속성으로 만들 수도 있다.
class Person
{
public string Name { get; } = "iron";
}
상기 코드 빌드 시 내부적으로 읽기 전용 속성의 필드로 변경해 컴파일 한다.(c#6.0)
class Person
{
private readonly string [Name_유일한 식별자] = "iron";
public string Name
{
get { return [Name_유일한 식별자]; }
}
}
숨겨진 필드에 readonly 예약어가 부여되므로 자연스럽게 생성자에서 값을 변경하는 것까지도 허용된다.
class Person
{
private readonly string [Name_유일한 식별자] = "iron";
public string Name
{
get { return [Name_유일한 식별자]; }
}
public Person()
{
Name = "Jason";
}
}
11.2 표현식을 이용한 메서드, 속성 및 인덱서 정의
메서드가 식(expression)으로 이뤄진 경우 간단 표기법으로 정의가 가능하다.
Vector vec = new(3, 4);
vec.PrintIt();
var move = vec.Move(1, 2);
move.PrintIt();
class Vector
{
double x;
double y;
public Vector(double x, double y)
{
this.x = x;
this.y = y;
}
public Vector Move(double dx, double dy)
{
return new Vector(x + dx, y + dy);
}
public void PrintIt()
{
Console.WriteLine(this);
}
public override string ToString()
{
return string.Format($"x = {x}, y = {y}");
}
}
#출력
x = 3, y = 4
x = 4, y = 6
상기 Move, PrintIt, ToString 메서드 내의 코드는 모두 식으로 이루어졌기에 표현식을 이용해 아래 코드와 같이 간단히 바꿀 수 있다.
예제 11.1 람다식을 이용한 메서드 본문구현
Vector vec = new(3, 4);
vec.PrintIt();
var move = vec.Move(1, 2);
move.PrintIt();
class Vector
{
double x;
double y;
public Vector(double x, double y)
{
this.x = x;
this.y = y;
}
public Vector Move(double dx, double dy) => new Vector(x + dx, y + dy);
public void PrintIt() => Console.WriteLine(this);
public override string ToString() => string.Format($"x = {x}, y = {y}");
}
#출력
x = 3, y = 4
x = 4, y = 6
간편표기법 컴파일 시 기존 표기법으로 내부 변환되어 컴파일 된다.
속성 또한 내부적으로 메서드로 구현되기 때문에 표현식을 속성 정의에도 사용할 수 있다. (하기 예제 코드에선, Angle 속성)
Vector vec = new(3, 4);
vec.PrintIt();
var move = vec.Move(1, 2);
move.PrintIt();
class Vector
{
double x;
double y;
public double Angle => Math.Atan2(y, x); // get만 자동 정의되고 set 기능은 제공되지 않는다.
public Vector(double x, double y)
{
this.x = x;
this.y = y;
}
public Vector Move(double dx, double dy) => new Vector(x + dx, y + dy);
public void PrintIt() => Console.WriteLine(this);
public override string ToString() => string.Format($"x = {x}, y = {y}");
}
Angle 속성은 접근자(get) 메서드만 읽기 전용 속성으로 정의된다. 즉 속성 정의에서 설정자(set) 메서드에 대한 표현식 정의는 지원되지 않는다.
* C# 7.0에서는 속성과 인덱서의 set 접근자도 람다식을 이용해 구현할 수 있도록 바뀌었다. ['12.5절 람다식을 이용한 메서드 정의 확대' 부분 참고]
인덱서(indexer) 구문에서도 표현식 정의가 가능하다.
Vector vec = new(3, 5);
var result = vec["degree"];
Console.WriteLine(result);
class Vector
{
double x;
double y;
public double Angle => Math.Atan2(y, x);
// 인덱서의 get 접근자를 표현식으로 정의
// 복잡해도 결국 식이기만 하면 표현식 적용가능.
public double this[string angleType] =>
angleType == "radian" ? this.Angle :
angleType == "degree" ? RadianToDegree(this.Angle) : double.NaN;
private double RadianToDegree(double angle) => angle * (180.0 / Math.PI);
public Vector(double x, double y)
{
this.x = x;
this.y = y;
}
}
#출력
59.03624346792648
속성과 마찬가지로 읽기 전용 인덱서로만 동작한다.
C# 6.0까지는, 생성자/ 종료자, 이벤트의 add/remove 접근자의 경우 메서드이긴 하나 예외적으로 표현식을 이용한 구현을 할 수 없었다. 그러나 c# 7.0 에서 모두 람다식을 이용해 표현식을 구현할 수 있게 됐다.
식(expression)과 문(statement)의 개념
식: 0개 이상의 연산자(operator)에 의해 결합되어 단일 값으로 계산할 수 있는 경우로 여기에는 메서드 호출 및 리터럴 자체도 포함한다.
eg.
1. (x >= 100) || (x < 300)
2. System.Console.WriteLine("test")
3. Hello world"
위와 같은 식은 C# 문법 중 '표현식'을 요구하는 정의에 활용할 수 있다.
1. public bool _expression1 => ( x >= 100 ) || ( x < 300 );
2. public void WriteTest() => System.Console.WriteLine("test");
3. public string Text => "Hello World";
식 중에서도 컴파일 시점에 값을 정할 수 있는 경우의 식을 상수식(constant expression) 이라 한다. 대표적으로 switch의 case 조건이 바로 상수식을 요구한다.
문: 3.5절에서 배운 선택/반목/점프 및 변수의 선언문이 있으며, 이와 함께 식의 경우에도 대입, 호출, 증가/감소, await과 new에 한해 문으로 사용될 수 있다. 비어 있는 코드도 문이다.
eg.
1. int a = 100;
2. if ( x > 100 ) Console.WriteLine("test");
3. ; (세미콜론만 찍힌 비어있는 코드)
이러한 문을 표현식의 정의에 사용하면 문법 에러가 발생한다.
1. public void Statement1 => int a = 100; // 컴파일 에러
2. public void Statement2 => if ( x > 100) Console.WriteLine("test"); // 컴파일 에러
3. public void Statement3 => ; // 컴파일 에러
11.3 using static 구문을 이용한 타입명 생략
using static을 선언해 해당 소스코드 파일 범위 내에 그 타입의 정적 멤버를 타입명 없이 바로 호출할 수 있게 됐다.
using static System.Console;
using static MyDay;
using static BitMode;
MyDay day = Saturday;
int bit = ON;
WriteLine(day);
WriteLine(bit);
public enum MyDay
{
Saturday, Sunday, // enum 필드의 내부 구현은 static 속성을 가짐.
}
public class BitMode
{
// const 필드의 내부 구현은 static 속성을 가짐.
public const int ON = 1;
public const int OFF = 0;
}
#출력
Saturday
1
C# 3.0에 도입된 확장 메서드의 경우 내부적으로 static 메서드로 표현되지만 문법적인 모호성 문제로 using static 적용을 받지 않는다.
MS는 모호함을 피하고자 확장 메서드를 본연의 정적 메서드 처럼 호출하려 할 때는 타입명을 함께 사용하는 것으로 결정했다.
11.4 null 조건 연산자
'null' 조건 연산자' : 참조 변수 값이 null이면 그대로 null 반환, null 이 아니면 지정된 멤버를 호출한다.
이를 잘 사용 시 null 필터링 목적의 if문 사용을 대폭 줄일 수 있다.
List<int> list = GetList();
#if 문
if(list != null )
{
Console.WriteLine(list.Count);
}
#null 조건 연산자
Console.Write(list?.Count); // list == null 이면 null 반환, list!=null이면 list.Count 멤버 변수 값 반환
c# 6.0 컴파일러는 반환 값을 처리해야 하는 null 조건 연산자가 사용되면 내부적으로 다음과 같은 구문으로 자동 변경해서 컴파일 한다.
List<int> list = GetList();
Console.WriteLine(list != null ? new int?(list.Count) : null);
이전에는 예외발생을 막고자 별도처리를 해야했다.
//방법 1: 사용하기 전 null 확인
if (list != null)
{
for (int idx = 0; idx < list.Count; ++idx)
{
//목록 요소처리
}
}
//방법 2: for문의 탈출 조건절에 null 확인 추가
for (int idx = 0; list != null && idx < list.Count; ++idx)
{
//목록 요소 처리
}
그러나 null 조건 연산자를 사용 시 본래 목적에 충실한 코드를 작성할 수 있다.
for (int? idx = 0; idx < list?.Count; ++idx)
{
//목록 요소처리
}
null 조건 연산자는 단독 사용이 안되고, 해당 참조형 변수의 멤버 접근 또는 배열 인덱스와 같은 부가적인 접근을 필요로 한다.
배열이 null이 아니라면 첫 번째 항목의 문자열을 반환하는 사례
{
string[] lines = { "txt", "doc" };
string firstElement = lines?[0]; // lines != null이므로 lines[0] 요소 반환.
}
{
string[] lines = null;
string firstElement = lines?[0]; // lines == null이므로 null 반환.
}
주의 사항은 null 조건 연산자의 결괏값이 null이 포함될 수 있기에, 이를 저장하기 위한 null 값 처리가 가능한 타입을 사용해야한다.
int 타입의 경우 null을 대입할 수 없기에 다음과 같이 null 조건 연산자를 사용할 수 없다.
int cnt = list?.Count; // 컴파일 에러
// list == null 인 경우 null 반환해야하나 int 타입에 null 대입 불가.
대신 C# 2.0의 nullable 형식을 사용시 정상 컴파일이 된다.
int? cnt = list?.Count;
List<int> list = new();
list.Add(2);
int? cnt = list?.Count;
Console.WriteLine(cnt);
#출력 1
List<int> list = new();
list = null;
int? cnt = list?.Count;
Console.WriteLine(cnt);
#출력
??연산자와 함께 사용해 null 반환인 경우 값 형식으로 반환하는 방법이 있다.
List<int> list = new();
list = null;
int cnt = list?.Count ?? 0; // list? 가 null 반환하면 ?? 연산자로 인해 0을 반환
Console.WriteLine(cnt);
#출력
0
null 조건 연산자는 반환값이 없는 경우도 사용 가능하다. 반환값이 없어도 해당 멤버에 접근하는 것은 모두 허용된다.
List<int> list = null;
list?.Add(5);
list == null 이므로 Add 메서드를 호출하지 않는다. 실제로 Add(5)를 해도 list에 5 가 삽입되지 않음.
널 조건연산자를 하나의 함수에서 하나의 참조 변수에 대해 다중으로 사용시 효율적인 성능의 코드가 나오지 않는다.
다음과 같이
List<int> list = null;
if(list != null)
{
list?.Add(5);
list?.Add(6);
list?.Add(7);
}
null 조건 연산을 통해 Add메서드 호출은 효율적이지 못하다. 쓸데 없이 null 조건을 중복으로 확인하는 코드로 아래와 같이 변환되어 나오기 때문이다.
List<int> list = null;
if (list != null)
{
list.Add(5);
}
if (list != null)
{
list.Add(6);
}
if (list != null)
{
list.Add(7);
}
이떄는 그냥 list.Add(5) , ...로 작성하자.
List<int> list = null;
if (list != null)
{
list.Add(5);
list.Add(6);
list.Add(7);
}
11.5 문자열 내에 식(expression) 을 포함
1. System.String (문자열과 변수를 이어 붙인 식, eg: "이름: " + Name + "나이: " + Age;)
2. string.Format 메서드
3. $"{ 변수}" 표현식, C# 6.0부터 string.Format에 대한 약식표현으로 '문자열 보간(String interpolation)' 사용.
1->2->3 순으로 문자열 사용방식이 변화되었다.
3. 사용예제
$"이름: {Name.ToUpper()}, 나이 : {(Age > 19 ? "성년" : "미성년")}";
$"이름 : {Name,-10}, 나이: {Age,5:X}";
11.6 nameof 연산자
식별자 이름 그대로 출력하고 싶을 때 사용하는 nameof연산자.
예제 11.3 다양한 식별자에 대한 nameof 사용
Person person = new Person { Name = "Anderson", Age = 49 };
OutputPerson(person.Name, person.Age);
void OutputPerson(string name, int age)
{
//메서드 OutputPerson 식별자를 nameof에 전달.
string methodName = nameof(OutputPerson);
Console.WriteLine($"{methodName}.{nameof(name)} == {name}");
Console.WriteLine($"{methodName}.{nameof(age)} == {age}");
/* 출력
* OutputPerson.name == Anderson
OutputPerson.age == 49
*/
string localName = name;
// 지역 변수 localName 식별자를 nameof에 전달.
Console.WriteLine($"{methodName}.{nameof(localName)} == {localName}");
//출력: OutputPerson.localName == Anderson
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString() => $"이름: {Name}, 나이: {Age}";
}
nameof의 반환값은 식별자 이름의 마지막 이름만 반환된다.
string txt = nameof(System.Console);
# txt == "Console"
이로써 코드 내에서 식별자 이름을 하드 코딩하는 사례는 C# 6.0부터 볼수 없게 됐다.
OutputPerson의 첫번째 인자에 대한 이름을 리플렉션을 통해 구할 수 있는데 리플렉션과 nameof의 주요 차이점은 실행속도에 있다.
리플렉션은 코드가 실행돼야 이름이 구해진다.(런타임시) 반면, nameof는 C# 6.0 컴파일러가 컴파일 시점에 문자열로 치환해주기에 실행 시점에 부하가 전혀 없다.
리플렉션 예제.
using System.Diagnostics;
Person person = new Person { Name = "Anderson", Age = 49 };
OutputPerson(person.Name, person.Age);
void OutputPerson(string name, int age)
{
StackFrame sf = new();
System.Reflection.ParameterInfo[] parameters = sf.GetMethod().GetParameters();
string nameId = parameters[0].Name;
Console.WriteLine($"name Id :{nameId}");
#출력 name Id :name
}
class Person
{
public string Name { get; set; }
public int Age { get; set; }
public override string ToString() => $"이름: {Name}, 나이: {Age}";
}
11.7 Dictionary 타입의 인덱스 초기화
8.4 절 '컬렉션 초기화'에서 Dictionary 타입에 대한 초기화도 이미 다음과 같이 지원하고 있었다.
var weekends = new Dictionary<int, string>
{
{0, "Sunday" },
{6, "Saturday" },
};
상기 코드를 컴파일 하면 Add 메서드로 변경되어 다음과 같다.
var weekends = new Dictionary<int, string>();
weekends.Add(0, "Sunday");
weekends.Add(6, "Saturday");
C# 6.0버전에서는 추가로 키/값 개념에 좀 더 직관적인 초기화 구문을 지원한다.
var weekends = new Dictionary<int, string>
{
[0] = "Sunday",
[6] = "Saturday",
};
Console.WriteLine(weekends[6]);
#출력
Saturday
상기 코드를 컴파일 하면 인덱서 방식의 코드로 변경된다.
var weekends = new Dictionary<int, string>();
weekends[0] = "Sunday";
weekends[6] = "Saturday";
따라서 초기화 구문에 같은 키 값을 가진 경우 동작 방식이 달라진다. Dictionary.Add의 경우 같은 키 값의 요소를 추가하면 예외가 발생한다.
var weekends = new Dictionary<int, string>
{
{0, "Sunday" },
{6, "Saturday" },
{6, "Saturday2" },
};
Console.WriteLine(weekends[6]);
#예외 발생
인덱스 초기화 구문에서는 기존 키 값을 덮어 쓰는 방식으로 동작하기에 예외가 발생하지 않는다.
var weekends = new Dictionary<int, string>
{
[0] = "Sunday",
[6] = "Saturday",
[6] = "Saturday2", // 정상 실행
};
Console.WriteLine(weekends[6]);
#출력
Saturday2
초기화 구문은 배열의 인덱스가 아니다. Dictionary의 첫번째 형식 인자인 TKey 타입에 해당하는 인덱서 구문이기 때문에, 그에 맞는 값을 초기화 구문에 사용해야한다.
var weekends = new Dictionary<string, int>
{
["Anderson"] = 7, // TKey 타입이 string이므로 인덱서 초기화도 string 타입.
["Jason"] = 12,
};
11.8 예외 필터
try
{
// 코드
}
catch (예외타입 e) when (조건식)
{
// 코드
}
설명: catch에 지정된 예외 타입에 속하는 예외가 try 블록 내에서 발생한 경우
조건식이 true로 평가된 경우에만 해당 예외 처리기가 선택된다.
string filePath = @"c:\temp\test.txt";
try
{
string txt = File.ReadAllText(filePath);
}
catch(FileNotFoundException e)
{
Console.WriteLine(e.ToString());
}
상기 코드는 ReadAllText 메서드에 지정 파일 없는 예외 발생시무조건 FileNotFoundException 예외 핸들러가 선택되어 실행된다.
그런데 파일 경로가 'temp'를 포함하는 경우에만 예외처리를 하고자 하면 when 예약어와 함께 조건 명시가 가능하다.
string filePath = @"c:\temp\test.txt";
try
{
string txt = File.ReadAllText(filePath);
}
catch(FileNotFoundException e) when(filePath.IndexOf("temp") != -1)
{
Console.WriteLine("temp 포함");
Console.WriteLine(e.ToString());
}
//filePath.IndexOf("temp") != -1 은 메서드로 변환 가능.
예외처리 필터의 특이점: 해당 예외 필터의 조건식이 실행되는 시점은 아직 예외 처리 핸들러가 실행되는 시점이 아니기에, 예외 발생 시점의 호출 스택(Call stack)이 그대로 보존돼 있다. 그래서 기존 예외 처리구조에 영향을 주지않고도 부가 작업이 가능하다.
string filePath = @"c:\temp\test.txt";
static bool Log(Exception e)
{
Console.WriteLine(e.ToString());
return false;
}
try
{
string txt = File.ReadAllText(filePath);
}
catch (Exception e) when (Log(e))
{
Console.WriteLine("temp 포함");
Console.WriteLine(e.ToString());
}
상기 예외 필터 처리를 기존 C# 구문 catch문 내에서도 유사하게 흉내가 가능하나, 이 둘은 엄격히 다르다.
예외 필터는 닷넷의 IL(Intermediate Language) 수준에서 이미 지원하고 있었기에, 기존 C# 코드로 변경해서 처리하지 않고예외 필터의 IL 코드로 직접 변경된다. 결과적으로 보았을 때 기존 C# 구문으로는 복잡하게 처리 됐던 것을 예외 처리 필터로 간단히 구현 가능하다.
using System.Diagnostics;
using static System.Net.Mime.MediaTypeNames;
try
{
throw new FileNotFoundException("test.txt");
}
catch (FileNotFoundException e) when (Process(e)) { }
catch (Exception e) when (Process(e)) { }
bool Process(Exception e)
{
Console.WriteLine(e.ToString());
return false;
}
#출력
System.IO.FileNotFoundException: test.txt
at Program.<Main>$(String[] args) in C: \Users\asd57\source\repos\ConsoleApp6\ConsoleApp6\Program.cs:line 6
System.IO.FileNotFoundException: test.txt
at Program.<Main>$(String[] args) in C: \Users\asd57\source\repos\ConsoleApp6\ConsoleApp6\Program.cs:line 6
Unhandled exception. System.IO.FileNotFoundException: test.txt
at Program.<Main>$(String[] args) in C: \Users\asd57\source\repos\ConsoleApp6\ConsoleApp6\Program.cs:line 6
상기 코드는 Process 메서드가 예외 처리 흐름에 영향을 주지 않고 두번 불린다. 동일 코드를 기존 C# 구문으로 흉내 내려면 다음과 같이 복잡해진다.
using System.Diagnostics;
using static System.Net.Mime.MediaTypeNames;
try
{
try
{
throw new FileNotFoundException("test.txt");
}
catch (FileNotFoundException e)
{
Process(e);
throw;
}
}
catch (Exception e)
{
Process(e);
throw;
}
bool Process(Exception e)
{
Console.WriteLine(e.ToString());
return false;
}
#출력
System.IO.FileNotFoundException: test.txt
at Program.<Main>$(String[] args) in C: \Users\asd57\source\repos\ConsoleApp6\ConsoleApp6\Program.cs:line 9
System.IO.FileNotFoundException: test.txt
at Program.<Main>$(String[] args) in C: \Users\asd57\source\repos\ConsoleApp6\ConsoleApp6\Program.cs:line 9
Unhandled exception. System.IO.FileNotFoundException: test.txt
at Program.<Main>$(String[] args) in C: \Users\asd57\source\repos\ConsoleApp6\ConsoleApp6\Program.cs:line 9
기존 예외 처리 구문에서 동일 예외 타입의 catch 구문을 여러 개 두는 것이 불가능했다.
try
{
throw new FileNotFoundException("test.txt");
}
catch(FileNotFoundException e) { }
catch(FileNotFoundException e) { } // 컴파일 에러 발생.
그러나, 예외 필터 사용시 중복으로 예외 타입을 설정하는게 가능하다.
예제 11.4 예외 필터의 중복 사용
try
{
throw new FileNotFoundException("test.txt");
}
catch (FileNotFoundException e) when (Log(e)) { Console.WriteLine(1); }
catch (FileNotFoundException e) when (Log(e)) { Console.WriteLine(2); } // 컴파일 에러 발생.
catch (FileNotFoundException e) { Console.WriteLine(3); }
bool Log(Exception e)
{
return false;
}
#출력 3
# Log 메서드의 return을 true 할 시 '1' 만 출력되고 끝남
예외 필터의 when 조건문은 여러 번 실행이 가능하지만, 선택되는 catch 예외 핸들러는 오직 하나 뿐이다. 그래서 상기 코드도 출력이 하나만 나왔다.
11.9 컬렉션 초기화 구문에 확장 메서드로 정의한 Add 지원
8.4 절 '컬렉션 초기화'에서 설명한 구문이 컴파일되려면 반드시 해당 타입이 ICollection<T> 인터페이스를 구현하고 있어야 한다.
다음 NaturalNumber 타입은 ICollection<T> 인터페이스를 구현하지 않았으므로 컬렉션 초기화 구문을 사용할 수 없어 컴파일 시 에러가 발생한다.
using System.Collections;
NaturalNumber nums = new NaturalNumber() { 0, 1, 2, 3, 4 };
// c# 5.0 컴파일 에러 , CS1061 : NaturalNumber 에는 Add 에 대한 정의가 포함되어 있지 않다.
foreach(var item in nums)
{
Console.WriteLine(item);
}
public class NaturalNumber : IEnumerable
{
List<int> numbers = new();
public List<int> Numbers
{
get { return numbers; }
}
IEnumerator IEnumerable.GetEnumerator()
{
return numbers.GetEnumerator();
}
}
이 때 ICollection<T> 인터페이스를 추가함으로써 컬렉션 초기화 구문을 지원할 수 있게 만들 수 있다. 그렇지 않은 경우 상속 등의 우회적인 방법으로 해결해야한다.
c# 6.0에는 Add 메서드를 ICollection<T> 인터페이스가 없다면 확장 메서드로도 구현돼 있는지 다시 한번 더 찾는 기능을 추가했다. 따라서 다음과 같이 확장 메서드만 추가하면 위의 코드가 정상적으로 컴파일 된다.
using System.Collections;
NaturalNumber nums = new NaturalNumber() { 0, 1, 2, 3, 4 };
NaturalNumberExtension.Add(nums, 10);
NaturalNumberExtension.Add(nums, 11);
NaturalNumberExtension.Add(nums, 12);
NaturalNumberExtension.Add(nums, 13);
NaturalNumberExtension.Add(nums, 14);
NaturalNumberExtension.Add(nums, 15);
foreach (var item in nums)
{
Console.WriteLine(item);
}
public class NaturalNumber : IEnumerable
{
List<int> numbers = new();
public List<int> Numbers
{
get { return numbers; }
}
IEnumerator IEnumerable.GetEnumerator()
{
return numbers.GetEnumerator();
}
}
public static class NaturalNumberExtension
{
public static void Add(this NaturalNumber instance, int num)
{
instance.Numbers.Add(num);
}
}
#출력
0
1
2
3
4
10
11
12
13
14
15
NaturalNumber nums = new NaturalNumber() { 0, 1, 2, 3, 4 };
의 경우
NaturalNumber nums = new NaturalNumber();
NaturalNumberExtension.Add(0);
NaturalNumberExtension.Add(1);
NaturalNumberExtension.Add(2);
NaturalNumberExtension.Add(3);
NaturalNumberExtension.Add(4);
처럼 내부적으로 확장 메서드로 구현된 Add를 사용하는 것으로 변경해 컴파일을 성공시킨다.
11.10 기타 개선 사항
생략(중요x)
'C#(.Net)' 카테고리의 다른 글
[시작하세요 C# 12 프로그래밍 ] (#10) C# 5.0 (0) | 2025.05.02 |
---|---|
[시작하세요 C# 12 프로그래밍 ] (#9) C# 4.0 (0) | 2025.04.30 |
[시작하세요 C# 12 프로그래밍 ] (#7, #8) C# 2.0 ~ 3.0 (0) | 2025.04.24 |
[시작하세요 C# 12 프로그래밍 ] #6 BCL - 04 (6.9 ~ 6.10) (0) | 2025.04.20 |
[시작하세요 C# 12 프로그래밍 ] #6 BCL - 03 (6.7 ~ 6.8) (0) | 2025.04.12 |