다음을 통해 공유


호환성 규칙 변경

기록 전체에서 .NET은 버전 간 및 .NET 구현 간에 높은 수준의 호환성을 유지하려고 했습니다. .NET 5(및 .NET Core) 이상 버전은 .NET Framework에 비해 새로운 기술로 간주될 수 있지만 두 가지 주요 요인으로 .NET 구현의 기능이 .NET Framework와 다른 것으로 제한됩니다.

  • 많은 개발자가 원래 .NET Framework 애플리케이션을 개발했거나 계속 개발하고 있습니다. .NET 구현에서 일관된 동작이 예상됩니다.
  • .NET Standard 라이브러리 프로젝트를 통해 개발자는 .NET Framework 및 .NET 5(및 .NET Core) 이상 버전에서 공유하는 공통 API를 대상으로 하는 라이브러리를 만들 수 있습니다. 개발자는 .NET 애플리케이션에서 사용되는 라이브러리가 .NET Framework 애플리케이션에서 사용되는 동일한 라이브러리와 동일하게 작동해야 한다고 예상합니다.

개발자는 .NET 구현 간 호환성과 함께 지정된 .NET 구현 버전 간에 높은 수준의 호환성을 기대합니다. 특히 이전 버전의 .NET Core용으로 작성된 코드는 .NET 5 이상 버전에서 원활하게 실행되어야 합니다. 실제로 많은 개발자는 새로 릴리스된 .NET 버전에서 찾은 새 API가 해당 API가 도입된 시험판 버전과도 호환되어야 한다고 예상합니다.

이 문서에서는 호환성에 영향을 주는 변경 내용과 .NET 팀이 각 변경 유형을 평가하는 방식을 간략하게 설명합니다. .NET 팀이 가능한 호환성이 손상되는 변경에 접근하는 방식을 이해하는 것은 기존 .NET API의 동작을 수정하는 끌어오기 요청을 여는 개발자에게 특히 유용합니다.

다음 섹션에서는 .NET API에 대한 변경 내용의 범주와 애플리케이션 호환성에 미치는 영향에 대해 설명합니다. 변경은 허용(✔️), 허용되지 않음(❌) 또는 이전 동작이 얼마나 예측 가능하고 명백하며 일관된지에 대한 판단 및 평가가 ❓ 필요합니다.

비고

  • 라이브러리 개발자는 .NET 라이브러리의 변경 내용을 평가하는 방법에 대한 가이드 역할을 할 뿐만 아니라 이러한 조건을 사용하여 여러 .NET 구현 및 버전을 대상으로 하는 라이브러리의 변경 내용을 평가할 수도 있습니다.
  • 호환성 범주(예: 정방향 및 역방향 호환성)에 대한 자세한 내용은 코드 변경이 호환성에 영향을 줄 수 있는 방법을 참조하세요.

공개 계약에 대한 수정

이 범주의 변경 내용은 형식의 공용 노출 영역을 수정합니다. 이 범주의 변경 내용은 대부분 이전 버전과의 호환성을 위반하기 때문에 허용되지 않습니다(이전 버전의 API로 개발된 애플리케이션이 이후 버전에서 다시 컴파일하지 않고 실행할 수 있음).

유형

  • ✔️ 허용: 인터페이스가 이미 기본 형식에 의해 구현된 경우 형식에서 인터페이스 구현 제거

  • 판단 필요: 형식에 새 인터페이스 구현 추가

    이는 기존 클라이언트에 부정적인 영향을 주지 않으므로 허용 가능한 변경입니다. 형식에 대한 모든 변경 내용은 새 구현이 허용 가능한 상태로 유지되도록 여기에 정의된 허용 가능한 변경 내용의 경계 내에서 작동해야 합니다. 디자이너 또는 직렬 변환기의 기능에 직접적인 영향을 주는 인터페이스를 추가하여 하위 수준에서 사용할 수 없는 코드 또는 데이터를 생성하는 경우 주의해야 합니다. 인터페이스를 예로 들면 다음과 같습니다 ISerializable .

  • 판단 필요: 새 기본 클래스 도입

    추상 멤버를 도입하거나 기존 형식의 의미 체계 또는 동작을 변경하지 않는 경우 두 기존 형식 간의 계층 구조에 형식을 도입할 수 있습니다. 예를 들어, .NET Framework 2.0에서는 DbConnection 클래스가 기존에 SqlConnection에서 직접 파생되었지만 Component를 새로운 기본 클래스로 사용하게 되었습니다.

  • ✔️ 허용됨: 한 어셈블리에서 다른 어셈블리로 형식 이동

    이전 어셈블리는 새 어셈블리를 TypeForwardedToAttribute 가리키는 것으로 표시되어야 합니다.

  • ✔️ 허용됨: 구조체 형식을 readonly struct 형식으로 변경

    형식을 readonly struct 형식으로 struct 변경할 수 없습니다.

  • ✔️ 허용됨: sealed 또는 추상 키워드를 액세스 가능한 (공개 또는 보호된) 생성자가 없는 형식에 추가

  • ✔️ 허용됨: 형식의 가시성 확장

  • 허용되지 않음: 형식의 네임스페이스 또는 이름 변경

  • 허용되지 않음: 공용 형식 이름 바꾸기 또는 제거

    이렇게 하면 이름이 변경되거나 제거된 형식을 사용하는 모든 코드가 중단됩니다.

    비고

    드문 경우에서 .NET은 공용 API를 제거할 수 있습니다. 자세한 내용은 .NET에서 API 제거를 참조하세요. 에 대한 자세한 내용은 NET의 지원 정책은 .NET 지원 정책을 참조하세요.

  • 허용 안 함: 열거형의 기본 형식 변경

    이는 컴파일 시간 및 동작 호환성이 손상되는 변경뿐만 아니라 특성 인수를 분리할 수 없는 이진 호환성이 손상되는 변경입니다.

  • 허용 안 함: 이전에 봉인되지 않았던 형식 봉인

  • 허용 안 함: 인터페이스의 기본 형식 집합에 인터페이스 추가

    인터페이스가 이전에 구현하지 않은 인터페이스를 구현하는 경우 원래 버전의 인터페이스를 구현한 모든 형식이 손상됩니다.

  • 판단 필요: 기본 클래스의 집합에서 클래스 제거 또는 구현된 인터페이스의 집합에서 인터페이스 제거

    인터페이스 제거 규칙에는 한 가지 예외가 있습니다. 제거된 인터페이스에서 파생되는 인터페이스의 구현을 추가할 수 있습니다. 예를 들어, 형식 또는 인터페이스가 이제 IDisposable을 구현하고 있는 경우 IComponent를 구현하는 IDisposable를 제거할 수 있습니다.

  • 허용 안 함: 형식을 readonly struct구조체 형식으로 변경

    그러나 형식을 struct 형식으로 readonly struct 변경할 수 있습니다.

  • 허용되지 않음: 구조체 형식을 ref struct 형식으로 변경하거나 그 반대로 변경하는 것

  • 허용되지 않음: 형식의 가시성 감소

    그러나 유형의 가시성을 높일 수 있습니다.

구성원

  • ✔️ 허용됨: 가상이 아닌 멤버의 가시성 확대

  • ✔️ 허용됨: 액세스 가능(public 또는 protected) 생성자가 없거나 형식이 봉인된 공용 형식에 추상 멤버 추가

    그러나 액세스 가능(public 또는 protected) 생성자가 있는 형식이 sealed가 아닌 경우, 해당 형식에 추상 멤버를 추가할 수 없습니다.

  • ✔️ 허용됨: 형식에 액세스할 수 있는(공용 또는 보호) 생성자가 없거나 형식이 봉인된 경우 보호된 멤버의 표시 유형 제한

  • ✔️ 허용됨: 멤버를 제거된 형식보다 계층 구조에서 더 높은 클래스로 이동

  • ✔️ 허용됨: 오버라이드 추가 또는 제거

    재정의를 도입하면 이전 사용자가 기본을 호출할 때 재정의를 건너뛸 수 있습니다.

  • ✔️ 허용됨: 클래스에 이전에 생성자가 없는 경우 매개 변수가 없는 생성자와 함께 클래스에 생성자 추가

    그러나 매개 변수가 없는 생성자를 추가 하지 않고 이전에 생성자가 없었던 클래스에 생성자를 추가하는 것은 허용되지 않습니다.

  • ✔️ 허용됨: 멤버를 추상에서 가상으로 변경

  • ✔️ 허용: 반환 값을 ref readonly에서 ref로 변경(가상 메서드 또는 인터페이스 제외)

  • ✔️ 허용됨: 필드의 정적 형식이 변경 가능한 값 형식이 아닌 경우, 필드에서 읽기 전용 속성을 제거함

  • ✔️ 허용됨: 이전에 정의되지 않은 새 이벤트 호출

  • 판단 필요: 형식에 새 인스턴스 필드 추가

    이 변경 사항은 직렬화에 영향을 줍니다.

  • 허용 안 함: 공용 멤버 또는 매개 변수 이름 바꾸기 또는 제거

    이렇게 하면 이름이 변경되거나 제거된 멤버 또는 매개 변수를 사용하는 모든 코드가 중단됩니다.

    여기에는 속성에서 getter 또는 setter를 제거하거나 이름을 바꾸는 것뿐만 아니라 열거형 멤버의 이름을 바꾸거나 제거하는 것이 포함됩니다.

  • 판단 필요: 인터페이스에 멤버 추가

    최소 .NET 버전이 .NET Core 3.0(C# 8.0)으로 상승한다는 점에서 호환성이 손상되는 변경이지만, 기본 인터페이스 멤버(DIMs)가 도입된 이후 인터페이스에는 정적, 비추상, 비가상 멤버를 추가할 수 있습니다.

    구현을 제공하는 경우 기존 인터페이스에 새 멤버를 추가해도 반드시 다운스트림 어셈블리에서 컴파일 오류가 발생하는 것은 아닙니다. 그러나 모든 언어가 DIM을 지원하는 것은 아닙니다. 또한 일부 시나리오에서는 런타임에서 호출할 기본 인터페이스 멤버를 결정할 수 없습니다. 일부 시나리오에서는 인터페이스가 형식별로 ref struct 구현됩니다. c0 형태를 박싱할 수 없기 때문에 인터페이스 형식으로 변환할 수 없습니다. 따라서 형식은 ref struct 모든 인터페이스 멤버에 대한 암시적 구현을 제공해야 합니다. 인터페이스에서 제공하는 기본 구현을 사용할 수 없습니다. 이러한 이유로 기존 인터페이스에 멤버를 추가할 때 판단을 사용합니다.

  • 허용 안 함: 공용 상수 또는 열거형 멤버의 값 변경

  • 허용되지 않음: 속성, 필드, 매개 변수 또는 반환 값의 형식 변경

  • 허용되지 않음: 매개 변수의 순서 추가, 제거 또는 변경

  • 허용 안 함: 매개 변수 에서 in, out 또는 ref 키워드 추가 또는 제거

  • 허용 안 함: 매개 변수 이름 바꾸기(대/소문자 변경 포함)

    이것은 두 가지 이유로 중단된 것으로 간주됩니다.

    • Visual Basic의 레이트 바운딩 기능 및 C#의 동적과 같은 레이트 바운드 시나리오를 중단합니다.

    • 개발자가 명명된 인수를 사용하는 경우 원본 호환성이 중단됩니다.

  • 허용되지 않음: ref 반환 값을 ref readonly 반환 값으로 변경하는 것

  • ❌️ 허용되지 않음: 가상 메서드 또는 인터페이스에서 ref readonlyref 반환 값으로 변경

  • 허용 안 함: 멤버에서 추상 추가 또는 제거

  • 허용 안 함: 멤버에서 가상 키워드 제거

  • 허용 안 함: 멤버에 가상 키워드 추가

    C# 컴파일러가 가상이 아닌 메서드를 호출하는 IL(callvirt Intermediate Language) 명령을 내보내는 경향이 있기 때문에 이러한 변경은 종종 중대한 변화가 아닙니다 (callvirt는 null 검사를 수행하나 일반 호출은 하지 않습니다.) 하지만 이러한 동작이 여러 이유로 인해 항상 동일한 것은 아닙니다.

    • C#은 .NET이 대상으로 하는 유일한 언어가 아닙니다.
    • C# 컴파일러는 대상 메서드가 가상이 아니고 null이 아닐 때마다(예: callvirt자를 통해 액세스하는 메서드) 일반 호출에 최적화 하려고 점점 더 많이 시도합니다.

    메서드를 가상으로 만들면 소비자 코드가 비가상으로 호출하는 경우가 많습니다.

  • 허용 안 함: 가상 멤버 추상화

    가상 멤버는 파생 클래스에서 재정의할 수 있는 메서드 구현을 제공합니다. 추상 멤버는 구현을 제공하지 않으며 재정의되어야 합니다.

  • 허용 안 함: 인터페이스 멤버에 봉인된 키워드 추가

    기본 인터페이스 멤버에 추가 sealed 하면 가상이 아니어 해당 멤버의 파생 형식 구현이 호출되지 않습니다.

  • 허용 안 함: 액세스 가능(public 또는 protected) 생성자가 있고 봉인되지 않은 공용 형식에 추상 멤버 추가

  • 허용 안 함: 멤버에서 정적 키워드 추가 또는 제거

  • 허용 안 함: 기존 오버로드를 배제하고 다른 동작을 정의하는 오버로드 추가

    이렇게 하면 이전 오버로드에 바인딩된 기존 클라이언트가 중단됩니다. 예를 들어, 클래스에 UInt32를 허용하는 단일 버전의 메서드가 있는 경우, 기존 사용자는 Int32 값을 전달할 때 해당 오버로드에 성공적으로 바인딩할 수 있습니다. 그러나 Int32를 매개변수로 받는 오버로드를 추가하면, 다시 컴파일하거나 런타임에 바인딩을 사용할 때 컴파일러는 이제 새 오버로드에 바인딩됩니다. 다른 동작이 발생하는 경우 이는 호환성이 손상되는 변경입니다.

  • 허용 안 함: 매개 변수가 없는 생성자를 추가하지 않고 이전에 생성자가 없었던 클래스에 생성자 추가

  • ❌️ 허용 안 함: 필드에 읽기 전용 으로 추가

  • 허용 안 함: 멤버의 가시성을 줄이는 것

    여기에는 액세스 가능(또는public) 생성자가 있고 형식이 봉인protected 경우 보호된 멤버의 표시 여부를 줄이는 것이 포함됩니다. 그렇지 않은 경우 보호된 멤버의 가시성을 낮추는 것이 허용됩니다.

    멤버의 가시성을 높일 수 있습니다.

  • 허용되지 않음: 멤버의 형식 변경

    메서드의 반환 값 또는 속성 또는 필드의 형식을 수정할 수 없습니다. 예를 들어 반환 Object 하는 메서드의 시그니처는 반환 String하도록 변경할 수 없으며 그 반대의 경우도 마찬가지입니다.

  • 허용 안 함: 비공유 필드가 없는 구조체에 인스턴스 필드 추가

    구조체에 공용 필드만 있거나 필드가 전혀 없는 경우, 호출자는 모든 공용 필드를 처음 사용하기 전에 구조체에 설정하기만 하면, 구조체의 생성자를 호출하거나 default(T)로 로컬 변수를 초기화하지 않고 해당 구조체 형식의 로컬 변수를 선언할 수 있습니다. 이제 컴파일러에서 추가 필드를 초기화해야 하므로 이러한 구조체에 공용 또는 비퍼블릭이라는 새 필드를 추가하는 것은 이러한 호출자에 대한 소스 호환성이 손상되는 변경입니다.

    또한 필드가 없거나 공용 필드만 있는 구조체에 공개 또는 비공개 새 필드를 추가하는 것은 [SkipLocalsInit]를 코드에 적용한 호출자에 대해 이진 호환성을 깨는 변경 사항입니다. 컴파일러는 컴파일 시간에 이러한 필드를 인식하지 못했기 때문에 구조체를 완전히 초기화하지 않는 IL을 내보내 초기화되지 않은 스택 데이터에서 구조체를 만들 수 있습니다.

    구조체에 비공개 필드가 있는 경우, 컴파일러는 이미 생성자를 통해 초기화를 강제하며, default(T) 또는 새 인스턴스 필드를 추가하는 것은 호환성을 깨뜨리는 변경 사항이 아닙니다.

  • 허용되지 않음: 이전에 실행되지 않았을 때 기존 이벤트 발생

동작 변경

어셈블리

  • ✔️ 허용됨: 동일한 플랫폼이 계속 지원되는 경우 어셈블리 이식 가능

  • 허용되지 않음: 어셈블리 이름 변경

  • 허용되지 않음: 어셈블리의 공개 키 변경

속성, 필드, 매개 변수 및 반환 값

  • ✔️ 허용됨: 속성, 필드, 반환 값 또는 out 매개 변수의 값을 더 파생된 형식으로 변경

    예를 들어, Object 형식을 반환하는 메서드는 String 인스턴스를 반환할 수 있습니다. 그러나 메서드 서명은 변경할 수 없습니다.

  • ✔️ 허용: 멤버가 가상이 아닌 경우 속성 또는 매개 변수에 허용되는 값 범위 늘리기

    메서드에 전달되거나 멤버가 반환하는 값의 범위는 확장할 수 있지만 매개 변수 또는 멤버 형식은 확장할 수 없습니다. 예를 들어 메서드에 전달된 값이 0-124에서 0-255로 확장될 수 있지만 매개 변수 형식은 변경할 ByteInt32수 없습니다.

  • 허용 안 함: 멤버가 가상인 경우 속성 또는 매개 변수에 허용되는 값의 범위 늘리기

    이 변경은 기존에 재정의된 멤버의 기능을 손상시켜서, 확장된 값 범위에 대해 정상적으로 작동하지 않게 합니다.

  • 허용 안 함: 속성 또는 매개 변수에 허용되는 값 범위 감소

  • 허용 안 함: 속성, 필드, 반환 값 또는 out 매개 변수에 대해 반환된 값의 범위 늘리

  • 허용 안 함: 속성, 필드, 메서드 반환 값 또는 out 매개 변수에 대해 반환된 값 변경

  • 허용 안 함: 속성, 필드 또는 매개 변수의 기본값 변경

    매개 변수 기본값을 변경하거나 제거하는 것은 이진 나누기가 아닙니다. 매개 변수 기본값을 제거하는 것은 원본 중단이며, 매개 변수 기본값을 변경하면 다시 컴파일한 후 동작이 중단될 수 있습니다.

    이러한 이유로 매개 변수 기본값을 제거하는 것은 모호성을 제거하기 위해 해당 기본값을 새 메서드 오버로드로 "이동"하는 특정 경우에 허용됩니다. 예를 들어 기존 메서드를 고려합니다 MyMethod(int a = 1). 두 개의 선택적 매개 변수 MyMethoda가 포함된 오버로드 b를 도입하는 경우, a의 기본값을 새 오버로드로 이동하여 호환성을 유지할 수 있습니다. 이제 두 오버로드는 다음과 같습니다 MyMethod(int a)MyMethod(int a = 1, int b = 2). 이 패턴을 사용하면 MyMethod()을(를) 컴파일할 수 있습니다.

  • 허용되지 않음: 숫자 반환 값의 정밀도 변경

  • 판단 필요: 입력 구문 분석을 변경하고 새로운 예외를 발생시키는 경우 (설명서에 구문 분석 동작이 지정되지 않은 경우에도)

예외

  • ✔️ 허용됨: 기존 예외보다 더 파생된 예외를 던지기

    새 예외는 기존 예외의 하위 클래스이므로 이전 예외 처리 코드는 예외를 계속 처리합니다. 예를 들어, .NET Framework 4에서는, 문화권을 찾을 수 없는 경우, 문화권 생성 및 검색 메서드가 CultureNotFoundException 대신 ArgumentException을(를) 던지기 시작했습니다. CultureNotFoundException이(가) ArgumentException에서 파생되었기 때문에, 이는 허용 가능한 변경입니다.

  • ✔️ 허용됨: NotSupportedException, NotImplementedException, NullReferenceException보다 더 구체적인 예외를 throw합니다.

  • ✔️ 허용됨: 복구할 수 없는 것으로 간주되는 예외를 던지는 것

    복구 불가능한 예외는 catch하지 말고 포괄적인 상위 처리기에서 처리해야 합니다. 따라서 사용자에게는 이러한 명시적 예외를 catch하는 코드가 없을 것으로 예상됩니다. 복구할 수 없는 예외는 다음과 같습니다.

  • ✔️ 허용됨: 새로운 코드 경로에서 새 예외를 던집니다

    예외는 새 매개 변수 값 또는 상태로 실행되고 이전 버전을 대상으로 하는 기존 코드에서 실행할 수 없는 새 코드 경로에만 적용되어야 합니다.

  • ✔️ 허용됨: 더 강력한 동작 또는 새 시나리오를 사용하도록 예외 제거

    예를 들어, 이전에 양수 값만 처리하고, 그렇지 않은 경우에는 예외를 발생시키던 Divide 메서드를 음수 값과 양수 값을 모두 지원하도록 변경할 수 있습니다. 이 경우 예외는 발생하지 않습니다.

  • ✔️ 허용: 오류 메시지의 텍스트 변경

    개발자는 사용자의 문화권에 따라 변경되는 오류 메시지의 텍스트에 의존해서는 안 됩니다.

  • 허용 안 함: 위에 나열되지 않은 다른 경우에 예외를 던지기

  • 허용 안 함: 위에 나열되지 않은 다른 경우에 예외 제거

특성

  • ✔️ 허용: 관찰할 수 없는 특성 값 변경

  • 허용 안 함 : 관찰 가능한 특성 값 변경

  • 판단 필요: 특성 제거

    대부분의 경우 특성(예: NonSerializedAttribute)을 제거하는 것은 호환성이 손상되는 변경입니다.

플랫폼 지원

  • ✔️ 허용됨: 이전에 지원되지 않았던 플랫폼에서 작업 지원

  • 허용되지 않음: 플랫폼에서 이전에 지원되었던 작업에 대해 특정 서비스 팩을 더 이상 지원하지 않거나 지금은 요구함

내부 구현 변경 내용

  • 판단 필요: 내부 유형의 표면적 변경

    이러한 변경 내용은 일반적으로 허용되지만 개인 리플렉션을 중단합니다. 인기 있는 타사 라이브러리 또는 많은 수의 개발자가 내부 API에 의존하는 경우 이러한 변경이 허용되지 않을 수 있습니다.

  • 판단 필요: 멤버의 내부 구현 변경

    일반적으로 이러한 변경 내용은 개인 리플렉션을 중단하지만 허용됩니다. 고객 코드가 개인 리플렉션에 자주 의존하거나 변경으로 의도하지 않은 부작용이 발생하는 경우 이러한 변경이 허용되지 않을 수 있습니다.

  • ✔️ 허용됨: 작업의 성능 향상

    작업의 성능을 수정하는 기능은 필수이지만 이러한 변경으로 작업의 현재 속도에 의존하는 코드가 손상될 수 있습니다. 비동기 작업의 타이밍에 따라 달라지는 코드의 경우 특히 그렇습니다. 성능 변경은 문제의 API의 다른 동작에 영향을 주지 않아야 합니다. 그렇지 않으면 변경 내용이 중단됩니다.

  • ✔️ 허용됨: 간접적으로(그리고 종종 부정적인) 작업 성능 변경

    문제의 변경 내용이 다른 이유로 인해 호환성이 손상되는 것으로 분류되지 않는 경우 이는 허용됩니다. 추가 작업을 포함하거나 새 기능을 추가하는 작업을 수행해야 하는 경우가 많습니다. 이는 거의 항상 성능에 영향을 주지만 문제의 API가 예상대로 작동하도록 하는 데 필수적일 수 있습니다.

  • 허용되지 않음: 동기 API를 비동기로 변경(또는 그 반대)

코드 변경

  • ✔️ 허용됨: 매개 변수 설정에 params 추가

  • 허용 안 함: 구조체클래스 로 변경하고 그 반대의 경우도 마찬가지입니다.

  • 허용 안 함: 코드 블록에 확인된 문 추가

    이 변경으로 인해 이전에 실행한 코드가 throw OverflowException 되고 허용되지 않을 수 있습니다.

  • 허용 안 함: 매개 변수에서 매개 변수 제거

  • 허용되지 않음: 이벤트가 발생하는 순서 변경

    개발자는 이벤트가 동일한 순서로 실행되기를 합리적으로 예상할 수 있으며 개발자 코드는 이벤트가 발생하는 순서에 따라 자주 달라집니다.

  • 허용되지 않음: 지정된 작업에서 이벤트 발생 제거

  • 허용 안 함: 지정된 이벤트가 호출되는 횟수 변경

  • 허용 안 함: FlagsAttribute을(를) 열거형에 추가

참고하십시오