이 문서에서는 .NET 10 일반 릴리스(.NET SDK 버전 10.0.100)에서 .NET 11 일반 릴리스(.NET SDK 버전 11.0.100) 이후 Roslyn의 알려진 주요 변경 내용을 나열합니다.
Span/ReadOnlySpan 형식의 컬렉션 식의 safe-context는 이제 선언 블록입니다.
C# 컴파일러는 컬렉션 식 기능 사양의 ref 안전 규칙을 제대로 준수하기 위해 호환성이 손상되는 변경을 했습니다. 특히 다음 절은 다음과 같습니다.
- 대상 형식이 범위 형식
System.Span<T>이거나System.ReadOnlySpan<T>컬렉션 식의 안전 컨텍스트가 선언 블록인 경우
이전에는 컴파일러가 이 상황에서 safe-context 함수 멤버 를 사용했습니다. 이제 사양에 따라 선언 블록을 사용하도록 변경했습니다. 이로 인해 아래 시나리오와 같이 기존 코드에 새 오류가 표시될 수 있습니다.
scoped Span<int> items1 = default;
scoped Span<int> items2 = default;
foreach (var x in new[] { 1, 2 })
{
Span<int> items = [x];
if (x == 1)
items1 = items; // previously allowed, now an error
if (x == 2)
items2 = items; // previously allowed, now an error
}
코드가 이 호환성이 손상되는 변경의 영향을 받는 경우 관련 컬렉션 식에 배열 형식을 대신 사용하는 것이 좋습니다.
scoped Span<int> items1 = default;
scoped Span<int> items2 = default;
foreach (var x in new[] { 1, 2 })
{
int[] items = [x];
if (x == 1)
items1 = items; // ok, using 'int[]' conversion to 'Span<int>'
if (x == 2)
items2 = items; // ok
}
또는 컬렉션 식을 할당이 허용되는 범위로 이동합니다.
scoped Span<int> items1 = default;
scoped Span<int> items2 = default;
Span<int> items = [0];
foreach (var x in new[] { 1, 2 })
{
items[0] = x;
if (x == 1)
items1 = items; // ok
if (x == 2)
items2 = items; // ok
}
https://github.com/dotnet/csharplang/issues/9750을 참조하세요.
컴파일러가 ref readonly 반환 대리자를 합성해야 하는 시나리오에서는 이제 System.Runtime.InteropServices.InAttribute 형식의 가용성이 필요합니다.
C# 컴파일러는 반환하는 ref readonly의 메타데이터를 제대로 내보내기 위해 호환성을 깨는 변경을 했습니다.
이렇게 하면 아래 시나리오와 같이 "오류 CS0518: 미리 정의된 형식 'System.Runtime.InteropServices.InAttribute'가 정의되거나 가져오지 않았습니다."가 기존 코드에 표시될 수 있습니다.
var d = this.MethodWithRefReadonlyReturn;
var d = ref readonly int () => ref x;
코드가 이 호환성이 깨진 변경의 영향을 받는 경우, System.Runtime.InteropServices.InAttribute를 정의된 어셈블리에 대한 참조를 프로젝트에 추가하는 것이 좋습니다.
로컬 함수를 ref readonly 활용하는 시나리오에서는 이제 형식의 System.Runtime.InteropServices.InAttribute 가용성이 필요합니다.
C# 컴파일러는 로컬 함수를 반환하는 ref readonly 메타데이터를 제대로 내보내기 위해 호환성을 깨는 변경을 했습니다.
이렇게 하면 아래 시나리오와 같이 "오류 CS0518: 미리 정의된 형식 'System.Runtime.InteropServices.InAttribute'가 정의되거나 가져오지 않았습니다."가 기존 코드에 표시될 수 있습니다.
void Method()
{
...
ref readonly int local() => ref x;
...
}
코드가 이 호환성이 깨진 변경의 영향을 받는 경우, System.Runtime.InteropServices.InAttribute를 정의된 어셈블리에 대한 참조를 프로젝트에 추가하는 것이 좋습니다.
왼쪽 피연산자가 인터페이스로 정적으로 형식화된 경우 연산자의 동적 평가 &&/|| 가 허용되지 않습니다.
이제 인터페이스 형식이 논리 && 또는 || 연산자의 왼쪽 피연산자로 사용되고, dynamic를 오른쪽 피연산자로 사용할 경우 C# 컴파일러가 오류를 발생시킵니다.
이전에는 코드가 연산자를 사용하는 인터페이스 형식 true/false 에 대해 컴파일되었지만 런타임 바인더가 인터페이스에 정의된 연산자를 호출할 수 없기 때문에 런타임 RuntimeBinderException 시 실패합니다.
이 변경은 대신 컴파일 시간에 보고하여 런타임 오류를 방지합니다. 오류 메시지는 다음과 같습니다.
오류 CS7083: 식은 암시적으로 부울로 변환할 수 있어야 합니다. 또는 'I1' 형식은 인터페이스가 아니어야 하며 연산자 'false'를 정의해야 합니다.
interface I1
{
static bool operator true(I1 x) => false;
static bool operator false(I1 x) => false;
}
class C1 : I1
{
public static C1 operator &(C1 x, C1 y) => x;
public static bool operator true(C1 x) => false;
public static bool operator false(C1 x) => false;
}
void M()
{
I1 x = new C1();
dynamic y = new C1();
_ = x && y; // error CS7083: Expression must be implicitly convertible to Boolean or its type 'I1' must not be an interface and must define operator 'false'.
}
코드가 이 호환성의 파괴적인 변경에 영향을 받는 경우, 왼쪽 피연산자의 정적 형식을 인터페이스 타입에서 구체적인 클래스 타입으로 또는 dynamic 타입으로 변경하는 것을 고려하십시오.
void M()
{
I1 x = new C1();
dynamic y = new C1();
_ = (C1)x && y; // Valid - uses operators defined on C1
_ = (dynamic)x && y; // Valid - uses operators defined on C1
}
https://github.com/dotnet/roslyn/issues/80954을 참조하세요.
nameof(this.) in 속성은 허용되지 않습니다.
Visual Studio 2026 버전 18.3 및 .NET 10.0.200에서 도입됨
이전에는 C# 12 이후 Roslyn에서 this 또는 base 키워드를 nameof 내에 사용하는 것이 의도치 않게 허용되었으나, 이제 언어 사양에 맞게 제대로 금지되었습니다.
이 호환성을 손상시키는 변경 사항은 this. 한정자를 제거하고 멤버에 접근함으로써 완화할 수 있습니다.
https://github.com/dotnet/roslyn/issues/82251을 참조하세요.
class C
{
string P;
[System.Obsolete(nameof(this.P))] // now disallowed
[System.Obsolete(nameof(P))] // workaround
void M() { }
}
switch-expression-arm 내에서 'with'의 구문 분석
참조 https://github.com/dotnet/roslyn/issues/81837 및 https://github.com/dotnet/roslyn/pull/81863
이전에는 다음이 표시될 때 (X.Y)when를 컴파일러가 캐스트 표현식으로 처리했습니다. 컨텍스트 식별자 when를 (X.Y)로 변환합니다.
x switch
{
(X.Y) when
}
이것은 바람직하지 않았으며, 패턴(when)의 간단한 (X.Y) when a > b => 검사가 올바르게 처리되지 않는다는 것을 의미했습니다. 이제 이 패턴은 상수 패턴 (X.Y) 과 다음 when clause으로 처리됩니다.
with()컬렉션 식 요소로 컬렉션 생성 인수로 처리 됩니다.
with(...) 컬렉션 식에서 요소로 사용되고 LangVersion이 15 이상으로 설정된 경우 명명된 메서드 with의 호출 식이 아니라 컬렉션을 만드는 데 사용되는 생성자 또는 팩터리 메서드에 전달되는 인수로 바인딩됩니다.
명명 with된 메서드에 바인딩하려면 대신 사용합니다 @with .
object x, y, z = ...;
object[] items;
items = [with(x, y), z]; // C# 14: call to with() method; C# 15: error args not supported for object[]
items = [@with(x, y), z]; // call to with() method
object with(object a, object b) { ... }
포인터 형식에는 더 이상 안전하지 않은 컨텍스트가 필요하지 않습니다.
Visual Studio 2026 버전 18.7에 도입되었습니다
향후 C# 버전(langversion:preview에서 현재 사용 중)에서는 포인터 형식(예: int*, delegate*<void>)이 더 이상 'unsafe' 컨텍스트가 필요하지 않습니다.
포인터 간접 작업(역참조, 멤버 액세스 ->, 요소 액세스 등)만 안전하지 않은 것이 필요합니다.
이는 안전하지 않은 진화 기능의 일부입니다.
이제 포인터 형식이 안전한 컨텍스트에서 적합하므로 오버로드 확인은 이전에 제외된 후보를 고려할 수 있습니다. 이로 인해 다음과 같은 새로운 모호성 오류가 발생할 수 있습니다.
using System;
class Program
{
static void Main()
{
M(x => { }); // C# 14: prints "2"; C# preview: error CS0121 (ambiguous)
}
static void M(F1 f) { Console.WriteLine(1); }
static void M(F2 f) { Console.WriteLine(2); }
}
unsafe delegate void F1(int* x);
delegate void F2(int x);
이전에 람다 x => { }를 F1로 안전한 컨텍스트에서 변환할 수 없었던 이유는 int*가 안전하지 않은 컨텍스트를 요구했기 때문이며, 따라서 M(F2)만 적용할 수 있었습니다.
int* 이제 안전한 컨텍스트에서 유효하므로 람다는 두 대리자 모두로 변환할 수 있으므로 모호성 오류가 발생합니다.
코드가 모호성 변경의 영향을 받는 경우 람다에 명시적 매개 변수 형식을 추가하여 명확하게 합니다.
M((int x) => { }); // Resolves to M(F2)
safe 는 상황에 맞는 키워드입니다.
Visual Studio 2026 버전 18.9에 도입됨
향후 C# 버전에서(현재는 langversion:preview) safe은 멤버 선언에서 한정자로 사용될 때 키워드입니다.
이로 인해 이전에는 타입을 참조하던 경우에 문제가 발생할 수 있습니다.
중단을 완화하기 위해서는 @를 사용할 수 있습니다.
class safe { }
class C
{
safe M1() => new safe(); // previously `safe` refers to a type, now it is a keyword
@safe M2() => new safe(); // workaround
}
Roslyn breaking changes