.NET 10 미리보기 3이 릴리스 되었습니다.
릴리스 노트를 통해 미리보기 3에 적용된 내용을 살펴보실 수 있습니다.
.NET 10에 대한 누적된 내용은 릴리스 노트의 Stay up-to-date를 통해 살펴보실 수 있습니다.
.NET 10 미리보기 3이 릴리스 되었습니다.
릴리스 노트를 통해 미리보기 3에 적용된 내용을 살펴보실 수 있습니다.
.NET 10에 대한 누적된 내용은 릴리스 노트의 Stay up-to-date를 통해 살펴보실 수 있습니다.
AOT-안전한 ValidationContext 생성자 도입: ValidationContext 클래스에 AOT 컴파일 시 안전한 새로운 생성자가 추가되어 네이티브 빌드에서 경고 없이 사용
ActivitySource 및 Meter의 Telemetry Schema URL 지원: ActivitySource와 Meter 클래스가 Telemetry Schema URL을 지원하여 데이터의 일관성과 호환성을 보장
BPE 토크나이저의 바이트 수준 지원: BpeTokenizer에 바이트 수준 인코딩이 추가되어 UTF-8 바이트로 어휘를 처리할 수 있음
ML.NET의 LightGBM 트레이너에 결정론적 옵션 추가: LightGBM 트레이너에 결정론적 훈련을 위한 옵션이 추가되어 일관된 결과를 보장
Tensor 개선: Tensor 클래스에 비제네릭 인터페이스가 추가되어 성능을 희생하지 않고 데이터 접근이 용이해졌으며, 슬라이스 작업 시 데이터 복사가 제거
.NET 10 미리보기 3의 .NET Runtime에서는 두 가지 주요 개선 사항이 도입.
이러한 개선 사항은 코드 크기를 줄이고 캐시 효율성을 높여 전반적인 성능을 향상시킴
CLI: --interactive
옵션이 기본적으로 활성화되어 사용자 친화적인 상호작용을 지원함. 또한, dotnet completions generate [SHELL]
명령어로 다양한 셸에 대한 네이티브 탭 완성 스크립트를 생성할 수 있음. global.json
파일을 통해 로컬 SDK 설치를 지원.
컨테이너: 콘솔 앱이 네이티브로 컨테이너 이미지를 생성할 수 있으며, <ContainerImageFormat>
속성을 통해 이미지 형식을 Docker 또는 OCI로 명시적으로 설정할 수 있음.
테스트: dotnet test
가 Microsoft Testing Platform을 지원하며, dotnet.config
파일을 통해 활성화할 수 있음.
extension
블록을 사용하여 WhereGreaterThan
과 IsEmpty
속성을 정의할 수 있음.public static class Extensions
{
extension(IEnumerable<int> source)
{
public IEnumerable<int> WhereGreaterThan(int threshold)
=> source.Where(x => x > threshold);
public bool IsEmpty
=> !source.Any();
}
}
customer?.Age = newAge;
와 같이 사용할 수 있음.사용 중단된 API 및, 전체 화면 비디오 재생 기능, 그리고 성능 개선에 대한 내용을 소개함
컴포넌트와 서비스의 상태를 선언적으로 유지할 수 있는 모델이 도입되었고, Blazor WebAssembly 앱에서 지문화된 정적 웹 자산을 참조할 수 있게 되었음. HttpClient의 응답 스트리밍이 기본 활성화되었으며, 환경 설정을 빌드 시점에 지정할 수 있게 되었음. 최소 API에서의 유효성 검사 지원과 OpenAPI 지원이 기본 활성화되었고, 서버 전송 이벤트(SSE) 지원이 추가되었음. 이러한 업데이트는 개발자 경험을 개선하고 성능을 향상시킴.
Preview 3에는 4개의 개선 사항, 2개의 회귀 버그 수정, 2개의 비회귀 버그 수정이 포함됨
Windows Forms .NET 10 Preview 3의 새로운 기능 요약:
버그 수정: DrawListViewColumnEditorEventArgs 색상 관련 회귀 문제, PropertyGrid 컨트롤의 NVDA 화면 읽기 문제, 여러 ToolStrip 컨트롤의 키보드 탐색 문제 해결하였음.
엔지니어링 개선: WPF와의 클립보드 코드 통합 작업 진행 중, DemoConsole 테스트 애플리케이션 업데이트, 불필요한 패키지 참조 제거 및 코드 스타일 정리하였음.
성능 개선
Fluent 스타일 변경
버그 수정
기타 변경 사항
10이 LTS라서 잘 나오길
field
키워드 하나 추가하는것만 가지고도 정말 이렇게 결정나도 되는걸까 싶어지는데
extension
키워드는 정말 작정하고 급진적이네요 아예 다른언어 보는거같았어요
불편한지도 모르고 십수년간 정착된 익숙한 코드패턴들을 모조리 뒤바꿀수있는 엄청난 파괴력을 가졌다는 생각이 듭니다.
string
, enum
, delegate
등 객체도 아닐 아무 값에다 대고 온갖 임의의 패턴매칭을 효율적으로 구사하게끔 될거라 생각하니 미쳤네요
헌데 null-conditional-assignment
는 사소한 편의성 증가 대비 부정적인 파급효과만 큰거같아서 별로입니다.
기존에 이미 Nullable
값 과의 중위연산부터 충분히 혼란스럽기 때문에 별차이없이 일관된 발전형으로써 불필요할수 있는 연산을 최대한 생략하고 null을 전염시키는것이 논리적으로 올바른 개연성이라곤 하지만
var a = b?.c = d;
같은 코드를 예시로 들었을때, b
가 null인지? 아닌지에 따라 d
라는 연산을 아예 하지않음으로써 반환될 null일수도 있는 값을 a
에도 그대로 할당해버린다는 코드맥락을 표현만 보고서 직관적으로 읽어내는건 쉽지 않아보입니다.
??=
같은 다른 조건부할당과 어떤 시너지를 발휘할수 있는것도 아니고, 그저 각자의 논리에다 곱해진 경우의 수만큼 절차가 런타임에서 뒤엉키게 만들 뿐입니다. 후위!
연산자 처럼 신중히 작성해야할 코드표현을 오히려 쉽사리 오용할수있도록 방조하는 문법에 가까운거같습니다.
반드시 선택적으로 비활성화 할 방법이 있으면 좋겠네요.
확장 맴버 기능은 매우 흥미롭군요.
스위치 식으로 코드를 작성할 때 아쉬운 순간은 when
을 사용할 때인데 확장 맴버로 해결되는 경우가 많을 거 같군요.
bool IsOverThree (IEnumerable<string> x) => x switch
{
{} v when v.Count() > 3 => true,
_ => false,
};
bool IsOverThree (IEnumerable<string> x) => x switch
{
{ Length: > 3 } => true,
_ => false,
};
public static class EnumerableExtension
{
extension<T>(IEnumerable<T> enumerable)
{
public int Length => enumerable.Count();
}
}
저는 이건 너무 까지는 아니지만 극단적인 것 같습니다. 개인적으로 가독성이 좋아 보이지 않네요. 그리고 너무 꼬여진 코드 같네요.
맞습니다. golang같은 다른언어처럼 객체의 맴버선언블럭과 함수선언블럭을 아예 분리하는 코드스타일로 수렴하게 만들거 같습니다.
public static class EnumerableExtension
{
extension<T>(IEnumerable<T> enumerable)
{
int Length => enumerable.Count();
public bool IsOverThree() => enumerable switch
{
{ Length: > 3 } => true,
_ => false,
};
}
}
작은 참조 타입이 Length로 결정되는 걸까요? 어느정도 크기의 배열이 stack에 할당되는지 모르겠네요.
뭔가 글을 보다보니
static void Print()
{
string[] words = {"Hello", "World!"};
foreach (var str in words)
{
Console.WriteLine(str);
}
}
The lifetime of
words
is scoped to the"Hello"
and"world!"
. However, the fact thatwords
is an array ofstrings
, a reference type, would previously stop the JIT from stack-allocating it. Now, the JIT can eliminate every heap allocation in the above example. At the assembly level, the code for
이 말이 리터럴 문자열에 대해서 words 라는 배열의 사용처가 Print라는 메서드로 한정된 스코프내의 수명주기를 갖고 있다보니 이런데에서 스택에 할당할 수 있다는 느낌이군요. 이전이라면 Intern Pool에 할당했겠지만요.
그런거라면… 어차피 Intern Pool도 GC의 대상이 아닌데 수명주기가 정해진 것들에 대해서만 최적화를 적용해주는거라면 드라마틱한 성능 변화는 아닐 수도 있겠군요.
확장 맴버를 static class 안에 작성하는게 꽤 이상하다고 생각했는데
어쩌면 나중에 static class가 제거 될 수도 있군요.
visual studio 17.14.0 preview 3.0 이 나왔군요.
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<LangVersion>preview</LangVersion>
</PropertyGroup>
새로운 문법도 Visual Studio에서 사용할 수 있습니다.
.net 9에서 WPF Fluent 스타일 적용하면 Listview에 header가 사라지는 버그가 있던데 그것은 여러 버그 해결에 들어가는거겠죠? 들어갔으면 좋겠네요
var a = b?.c = d; // a 는 D? 타입
위 코드가 직관적이지 않은 이유는 문법 자체 보다는, a
변수의 의도가 명확하지 않은 데서 기인한 것 같습니다.
a
변수가 존재해야 할 이유가 딱히 떠 오르지 않는데, 굳이 하나 들자면, 할당 여부 파악?
만약 그 의도라면, 그 의도를 명확하게 해야 직관적으로 보일 것입니다.
if ((b?.c = d) is { })
{
// codes when assigned
}
그러나, 이 경우, null-conditional-assignment
의 효용이 없으므로, 아래와 같이 표현하는 것이 더 직관적이라 할 수 있습니다.
if (b is { })
{
b.c = d;
// codes when assigned
}
위와 같은 케이스가 아니라면, 보일러 코드를 줄이기 위한 본래의 의도는 훌륭하다고 생각합니다.
if (a is null || a.b is null) return;
a.b.c = d;
a?.b?.c = d;
var a = b.c ??= d();
Console.WriteLine($"result of pure method : {a}");
이런 형태라면 어떤 의도에 대한 예시였는지 보다 선명할거같습니다.
d()
가 무의미하게 여러번 호출되지 않고, b.c
경로에 연산결과를 저장해야하지만, 이후절차에서 여러번 b.c
경로로 다시접근하는 과정없이 a
라는 캐시된 지역변수를 계속 사용한다고 가정해보면,
기존에는 b
가 null
일경우 어차피 예외가 발생할것이고, 대입연산 이전부터 암시적캐스팅이 이뤄질경우를 제외하고 대입연산처리 자체의 입력과 출력값이 상이할 가능성은 이제껏 없었으므로
var a = d();
b.c = a;
var a = b.c = d();
위 두 코드스타일은 지역변수의 할당시점에만 차이를 보일뿐 런타임에서는 사실상 동일한 동작을 할것으로 기대할수가 있습니다.
하지만 물음표만 붙이면 위 두 코드는 완전히 다른 동작을 가지게됩니다.조건부 할당이 허용됨으로써, 전통적인 =
가 ??=
처럼 아예 처리를 생략하는 제어문의 역할도 겸하는것 같아집니다, 대입연산의 처리결과가 우측연산의 결과와 동일한것이 아닐수 있다는, 사실상 완전히 새로운 role이 추가된것마냥 보입니다.
아무래도 내부적으로는 없던 규칙이 생겼다기보단 null
인 데이터에 ?.
을 사용한 순간부터는 이후엔 대입연산자가 나오건 뭘 적어놨던 오히려 기존보다 더 예외없이 처리를 생략해버리고 null
이나 반환해 주도록 규칙을 간소화 시킨것이 아닐까 싶습니다 만,
?.
로 저지를수있는 파급효과가 너무나도 광범위해진탓에 사용하는입장에서는 맴버 접근과정에서 b.c
로할지 b?.c
로 할지, 무심코 ?
한글자 넣고뺏다는 차이 하나때문에 같은 입력에 대한 위 두 코드스타일의 처리와 상태를 완전히 달라지게할수가 있게되는데도, 그 차이를 단번에 간파하는게 가능할지 모르겠습니다.
예를들어 기존에 분명 NullReferenceException
이 적절히 나야하는게 맞았던 코드사이에 아무것도 모르는 후임이 ?
하나 집어넣은덕분에 반드시 유의미한 값이 할당되야 했던 변수가 간혈적으로 null이 할당되고있고, 처리과정에서 반드시 호출될것이 자명했던 함수가 아예 호출되지 않는 경우가 생겨버리게 됬는데, 연쇄작용을 맞은 한참뒤의 코드블럭에가고나서 알고보니 기존코드에 이제껏 몰랏던 문제가 있었다며 추가적인 방어코드를 덕지덕지 붙이는 미래를 상상해볼수가 있습니다.
해괴한 나비효과의 원인이 정녕 물음표 한글자 차이 때문이었다는 은밀한 진실을, 디버깅 한참 해본다고 도출해내는것이 가능할지, 코드리뷰 열심히 해본다고 걸러낼수 있을지. 표적조사가 아닌이상 가망이 전혀 안보입니다.
원숭이마냥 아무튼 절대 건드리지 말라고 교육하던가, 조건부 할당의 영향을 받을 경우가 아예 없도록 코드스타일을 전부 뒤바꾸지 않는 이상 조건부 할당 피쳐로 인해 장기적으로 기존 코드의 의도가 쉽사리 훼손될 가능성으로부터 적절히 보호할 방법이 있을지 모르겠습니다.
다 망할것같이 호들갑떨었지만 뭐 프로젝트의 모든 코드가 nullable
을 철저히 준수하고있다면 어느정도 억제할수 있을 사례일것입니다. 허나 그것만으로는 온전히 통제하기에 충분하지 않고, 그런 천국은 겪어본적이 없어서 모르겠습니다. 게다가 상정한 최악의 상황은 아무래도 nullable
을 어설프게라도 활용해보다가 겪게될 배신이기때문에 더욱 해롭습니다.
단순히 컴파일에러나던 코드스타일 하나가 가능해지도록 하기위해서 지불하기엔, 내가 호출하고자 한 함수나 연산전체가 어떤 직접적인 조건문이나 조건부연산자나 조건부엑세스도 모자라 어느 대입연산자 건너편에 있을 조건부엑세스로 인해서 생략될 가능성마저 감안해야 한다는게, 편의성 대비 무리한 인지부하를 요구하는 트레이드오프가 아닌가 생각됩니다.
누군가는 반드시 이 새로운 피쳐로 인해 자기 이해바깥의 뒤통수를 맞게될것이고, 그것이 C#을 더 사랑하는 계기가 되지는 않을것 같네요.
아래 코드에서 nullable 한 변수는 c
이고,
var a = b.c ??= d();
아래에서는 b
라서, 상황 자체가 다릅니다.
var a = b?.c = d();
위 부분은 null-conditional-assignment
에 관한 것이라기 보다는, 그것이 기반하고 있는 nullable-reference-type(nullable 기능)
에 관한 것 같습니다.
저는 사실, NullReferenceException
이 적절하게 나야 하는 상황이 뭔지는 모르겠습니다. 예방이 가능한 상황이면, 최대한 예방하는 게 맞지 않나요?
nullable 기능은, 내가 콘트롤 가능한 컨텍스트에서는 null 예외를 다 처리할 수 있도록 도움을 줍니다. 물론, 그럴 의지가 없다면, 쓸 모 없기는 합니다.
전에 .net 8.0 을 쓰면서도 nullable 기능을 끄지 않고, null 경고를 단순히 그냥 놔두는 초보 개발자와 협업을 한 적이 있는데, 그 사람의 프로젝트를 참조 하는 순간 수 백개의 nullable 경고가 즉시 나타나서 아연 실색했던 순간이 떠 오르네요.
그 사람이 null 처리도 하지 않은 채, nullable 기능을 껐거나, 프로젝트가 아니라 assembly 참조를 한 경우의 참사가 그려졌기 때문입니다. (그 경고 때문에, 내 빌드가 클린해 보이지 않는 불편함은 덤 이었고요)
저는 이 기능이 도입된 이후로, 항상 enable 해왔고, 그에 맞게 코드를 작성하고 있는데, 말씀하신 미래는 오히려 반대입니다.
제 코드와 연관된 null 예외는, nullable 기능을 쓰지 않는 클라이언트 코드나, dependency가 원인이라고 100% 확신합니다.
왜냐하면, 제 코드는 null context 를 전부 전파시켰으니까요.
내 매개 변수에
?
가 안 붙으면, null 확인한 값만 던져.
만약 붙어 있다면, 그냥 되는 대로 던져.
내 반환값에
?
가 안 붙으면, 그냥 쓰면 돼.
만약 붙어 있다면, 꼭 null 확인하고 써야 돼.
그런 입장에서, 위 두 코드의 의미도 쉽게 간파가 가능했지만, 사실 컴파일러가 어떤 놈이 위험한 지 알려 주기 때문에, 그런 간파력(?)을 갖출 이유도 없습니다.
??=
은 예시를 좀더 구체적으로 설정하기위한 사족에 가깝습니다.
당연히 숙련된 개발자를 대상으로 하는 우려가 아닙니다. 언제나 최신문법과 본질을 완벽히 이해하고있는 노련한 개발자가 잘 알고서 프로젝트가 이상적으로 구성되어진채 운영하고있다면 뭔들 문제될일이 생기겠습니까. 제가 우려하는 요점은 자기코드를 책임질 능력이 없는 초보개발자가 아예 모르거나 놓쳐서 이미 부적절하게 사용해버린 경우에, 이를 직관적으로 진단하거나 원인임을 알아내 고치기까지 어렵게만들수 있는 문법같다는겁니다.
b.c = d();
b?.c = d();
아시겠지만 전자에서 후자로 물음표만 붙인다는게 그저 예외만 안생기는게 막아주는게 아닙니다. 조건부로 함수를 호출할때와 같이 기존에는 일단 처리되던 대입연산자 우측의 d()
라는 연산자체를 아예 생략하고 null
을 반환하는것입니다.
B? b = null;
b.SetC(d()); // d()의 return을 파라미터로 호출하려다 b가 null이라 에러.
b?.SetC(d()); // b가 null이라는걸 우선 평가 후 d()자체가 실행되지않음.
매서드와 대입연산에 동일한 규칙을 적용하는것이 논리적인 하자는 없습니다. 다만 그것이 매서드체인 혹은 연속적인 대입연산이 되었을떄 제가 그것들을 눈으로 읽어내던 방향이 서로 다르고, 연관성을 분리해 짚어냈었기때문에 당혹스러운것입니다. 아마 대다수가 그러하다 느낄것이기에 C#14 이전까지는 Illegal 이었던것일겁니다.
그리고 여담이지만 nullable
을 활용해 런타임에서 유창하게 null context
를 핸들링하고 전파하는것도 좋지만, 저는 반대로 nullable
을 활용해 물음표가 안붙은것은 절대 null context
가 아닐것으로 쓸수 있다는점에 주목하는편입니다.
Exception
발생은 회피해야하는것이 아니라 경우에따라서는 일부러라도 더 빨리 throw
하여 상정외의 상태를 감지하고 진압할수 있도록 해야한다고 생각합니다. 의도한적없는 상황을 그때그때 묵인하기위한 동작들로 프로그램을 아무리 감싸려해도 어차피 프로그램 자신에게 벌어질 모든 경우의 수를 감당할수는 없을것이기 떄문입니다. 차라리 올바른동작을 보장할 아주 협소한 경우를상정하고 한치라도 벗어나는 모든 예외(例外)와 격리함으로써 시스템이 절대적인 신뢰와 효율하에 동작할수 있도록 만들고자 합니다.
원래 그런 접근법의 코드들은 사소한변화에도 격렬한 파장을 띄는편이라, null-conditional-assignment
에도 민감한 반응을 보이는것입니다.
찾아보니 ts나 js는 기존C#처럼 불가능하지만 kotlin은 이미 가능한것같네요.
혹시 nullable-reference-type
을 사용하고 계신지 여쭤봐도 될까요?
행간으로는 사용하고 있지 않은 것처럼 느꺼집니다.
사실 말씀하신 숙련도에 따른 우려 상황은 사용하고 있지 않은 사람들의 기우에 불과하다고 할 수 있습니다.
이 기능은 컴파일러 기능이라, 개발자 숙련도와 상관이 없이 동작합니다.
즉, 보여 주신 코드를 컴파일러에게 드리 밀면, 문제의 지점을 바로 지적해주기 때문에 해석이나 오해의 여지가 없습니다.
저는 하도 지적질을 당해, 뇌가 순응한 상태인데, 이를 숙련됐다고 표현하기 보다는, null 예외를 일으키지 않겠다는 의지의 닷넷 히어로 정도가 맞는 표현인 것 같습니다.
숙련도를 떠나, 그 기능을 꺼서, 컴파일러 입에 재갈을 물리거나, 켜서 의지의 실천에 도움을 받느냐 선택의 문제입니다.
? , . , =
몇 글자 되지도 않는 문자들의 조합에 많은 철학과 응원이 담겨 있는 것 같아 아름답기까지 합니다.