.NET의 API 주요 변경 사항에 대한 결정적인 가이드


227

.NET / CLR의 API 버전 관리, 특히 API 변경으로 인해 클라이언트 응용 프로그램이 중단되는 방법에 관한 정보를 최대한 많이 수집하고 싶습니다. 먼저 몇 가지 용어를 정의하겠습니다.

API 변경 -공개 멤버를 포함한 유형의 공개적으로 정의 된 정의의 변경. 여기에는 유형 및 멤버 이름 변경, 유형의 기본 유형 변경, 구현 된 유형의 인터페이스 목록에서 인터페이스 추가 / 제거, 멤버 추가 / 제거 (오버로드 포함), 멤버 가시성 변경, 메소드 이름 변경 및 유형 매개 변수, 기본값 추가가 포함됩니다. 메소드 매개 변수, 유형 및 멤버에 속성 추가 / 제거 및 유형 및 멤버에 일반 유형 매개 변수 추가 / 제거 (아무것도 놓쳤습니까?) 여기에는 회원 기관의 변경 사항이나 개인 회원의 변경 사항이 포함되지 않습니다 (즉, 우리는 반영을 고려하지 않습니다).

이진 수준 중단 -이전 버전의 API에 대해 클라이언트 어셈블리가 컴파일되어 잠재적으로 새 버전으로로드되지 않는 API 변경입니다. 예 : 이전과 같은 방법으로 호출 할 수있는 경우에도 메소드 서명 변경 (예 : 유형 / 매개 변수 기본값 오버로드를 리턴하는 void)

소스 레벨 중단 -기존 버전의 API를 컴파일하기 위해 작성된 기존 코드가 새 버전으로 컴파일되지 않을 수있는 API 변경입니다. 그러나 이미 컴파일 된 클라이언트 어셈블리는 이전과 같이 작동합니다. 예 : 새로운 오버로드를 추가하여 이전에 모호하지 않은 메소드 호출이 모호해질 수 있습니다.

소스 레벨 자동 의미 변경 -이전 버전의 API에 대해 컴파일하기 위해 작성된 기존 코드로 인해 의미가 자동으로 변경 되는 API 변경 (예 : 다른 메소드 호출). 그러나 코드는 경고 / 오류없이 계속 컴파일되어야하며 이전에 컴파일 된 어셈블리는 이전과 같이 작동해야합니다. 예 : 기존 클래스에서 새 인터페이스를 구현하여 과부하 해결 중에 다른 과부하가 선택되었습니다.

궁극적 인 목표는 가능한 많은 중단 및 조용한 의미 론적 API 변경 사항을 카탈로그 화하고, 중단의 정확한 영향과 그 영향을받는 언어 및 언어를 설명하는 것입니다. 후자를 확장하기 위해 : 일부 변경 사항은 모든 언어에 보편적으로 영향을 미치지 만 (예 : 인터페이스에 새 멤버를 추가하면 모든 언어에서 해당 인터페이스의 구현이 중단 될 수 있음), 일부는 휴식을 취하기 위해 매우 구체적인 언어 의미론이 필요합니다. 여기에는 일반적으로 메서드 오버로드가 포함되며 일반적으로 암시 적 형식 변환과 관련된 모든 작업이 필요합니다. CLS 호환 언어 (예 : CLI 사양에 정의 된대로 "CLS 소비자"의 규칙을 준수하는 언어)에 대해서도 "최소 공통 분모"를 정의 할 수있는 방법이 없습니다. 누군가가 나를 잘못 여기로 고치면 감사하겠습니다. 그래서 언어별로 언어를 이동해야합니다. 가장 관심있는 것은 당연히 .NET과 함께 제공되는 것들입니다. C #, VB 및 F #; IronPython, IronRuby, Delphi Prism 등과 같은 다른 것들도 관련이 있습니다. 코너 케이스가 많을수록 멤버를 제거하는 것과 같은 일이 자명하지만 방법 오버로드, 선택적 / 기본 매개 변수, 람다 형식 유추 및 변환 연산자 간의 미묘한 상호 작용은 매우 놀랍습니다. 때때로.

이것을 시작하는 몇 가지 예 :

새로운 메소드 오버로드 추가

종류 : 소스 수준의 휴식

영향을받는 언어 : C #, VB, F #

변경 전 API :

public class Foo
{
    public void Bar(IEnumerable x);
}

변경 후 API :

public class Foo
{
    public void Bar(IEnumerable x);
    public void Bar(ICloneable x);
}

변경 전에 작동하고 그 후에 깨진 샘플 클라이언트 코드 :

new Foo().Bar(new int[0]);

새로운 암시 적 변환 연산자 오버로드 추가

종류 : 소스 수준의 휴식.

영향을받는 언어 : C #, VB

영향을받지 않는 언어 : F #

변경 전 API :

public class Foo
{
    public static implicit operator int ();
}

변경 후 API :

public class Foo
{
    public static implicit operator int ();
    public static implicit operator float ();
}

변경 전에 작동하고 그 후에 깨진 샘플 클라이언트 코드 :

void Bar(int x);
void Bar(float x);
Bar(new Foo());

참고 : F #은 오버로드 된 연산자에 대한 언어 수준의 지원이 없으므로 명시 적이든 암시 적이든 아니므로 직접적으로 op_Explicitop_Implicit메서드를 호출해야하기 때문에 F #은 손상되지 않습니다 .

새로운 인스턴스 메소드 추가

종류 : 소스 수준의 조용한 의미 체계 변경.

영향을받는 언어 : C #, VB

영향을받지 않는 언어 : F #

변경 전 API :

public class Foo
{
}

변경 후 API :

public class Foo
{
    public void Bar();
}

조용한 의미 체계 변경이 발생하는 샘플 클라이언트 코드 :

public static class FooExtensions
{
    public void Bar(this Foo foo);
}

new Foo().Bar();

참고 : F #은 언어 수준을 지원하지 않으므로 ExtensionMethodAttributeCLS 확장 메소드를 정적 메소드로 호출해야 하므로 깨지지 않습니다 .


분명히 Microsoft는 이미 이것을 다루고 있습니다 ... msdn.microsoft.com/en-us/netframework/aa570326.aspx
Robert Harvey

1
@Robert : 당신의 연결은 매우 다른 무언가에 대해 - 그것은 설명 특정 의 파괴 변경 .NET 프레임 워크 자체를. 이것은 라이브러리 / 프레임 워크 작성자로서 자신의 API 에 중대한 변화 를 일으킬 수있는 일반적인 패턴 을 설명하는 더 넓은 질문입니다 . 불완전한 문서라도 링크는 환영하지만 MS가 작성한 문서는 아직 잘 모릅니다.
Pavel Minaev

이러한 "중단"범주 중 하나에서 런타임시에만 문제가 명백 해지는 문제가 있습니까?
Rohit

1
예, "이진 구분"범주입니다. 이 경우 이미 모든 버전의 어셈블리에 대해 타사 어셈블리가 컴파일되어 있습니다. 새 버전의 어셈블리를 제자리에 놓으면 타사 어셈블리가 작동을 멈 춥니 다. 런타임시로드되지 않거나 잘못 작동합니다.
Pavel Minaev

3
나는 게시물에 그것들을 추가하고 blogs.msdn.com/b/ericlippert/archive/2012/01/09/…를
Lukasz Madon

답변:


42

메소드 서명 변경

종류 : 이진 수준의 휴식

영향을받는 언어 : C # (VB 및 F #이 가장 가능성이 높지만 테스트되지 않음)

변경 전 API

public static class Foo
{
    public static void bar(int i);
}

변경 후 API

public static class Foo
{
    public static bool bar(int i);
}

변경 전에 작동하는 샘플 클라이언트 코드

Foo.bar(13);

15
실제로 누군가가의 대리자를 만들려고하는 경우에도 소스 수준의 휴식이 될 수 있습니다 bar.
Pavel Minaev

그것도 마찬가지입니다. 회사 응용 프로그램에서 인쇄 유틸리티를 약간 변경했을 때이 특정 문제를 발견했습니다. 업데이트가 릴리스 될 때 thi 유틸리티를 참조하는 모든 DLL이 재 컴파일 및 릴리스되지 않아 메소드를 찾을 수없는 예외가 발생합니다.
Justin Drury

1
이것은 반환 유형이 메소드의 서명으로 계산되지 않는다는 사실로 되돌아갑니다. 리턴 유형만을 기반으로 두 함수를 오버로드 할 수 없습니다. 같은 문제입니다.
Jason Short

1
이 답변에 대한 인용 : 누구든지 dotnet4 기본값 'public static void bar (int i = 0);'을 추가하는 의미를 알고 있습니까? 또는 그 기본값을 한 값에서 다른 값으로 변경 하시겠습니까?
k3b

1
이 페이지에 방문하려는 사람들에게는 C # (및 대부분의 다른 OOP 언어를 "생각합니다"), 리턴 유형은 메소드 서명에 기여하지 않는다고 생각합니다. , 서명 변경이 이진 수준 변경에 기여한다는 것이 정답입니다. 하지만 예 내가 생각할 수있는 올바른 예입니다 올바른 이럴 것 같습니다하지 않습니다 하기 전에 공공 진수 합 (A, INT B INT) 친절하게 공공 진수 합 (소수점 A, 소수 B)를이 MSDN 링크를 참조 3.6 서명을하고 과부하를
Bhanu Chhabra

40

기본값으로 매개 변수 추가

휴식의 종류 : 이진 수준 휴식

호출 소스 코드를 변경할 필요가 없더라도 일반 매개 변수를 추가 할 때와 마찬가지로 다시 컴파일해야합니다.

C #이 매개 변수의 기본값을 호출 어셈블리로 직접 컴파일하기 때문입니다. 다시 컴파일하지 않으면 이전 어셈블리가 더 적은 인수로 메서드를 호출하기 때문에 MissingMethodException이 발생합니다.

변경 전 API

public void Foo(int a) { }

변경 후 API

public void Foo(int a, string b = null) { }

나중에 고장난 샘플 클라이언트 코드

Foo(5);

클라이언트 코드 Foo(5, null)는 바이트 코드 수준에서 다시 컴파일해야 합니다. 호출 된 어셈블리에는을 포함 Foo(int, string)하지 않습니다 Foo(int). 기본 매개 변수 값은 순수한 언어 기능이므로 .Net 런타임은 그에 대해 아무것도 알지 못합니다. 또한 C #에서 기본값이 컴파일 타임 상수 여야하는 이유도 설명합니다.


2
이것은 소스 코드 레벨에서도 획기적인 변화입니다. Func<int> f = Foo;// 변경된 서명으로 실패합니다
Vagaus

26

이것은 인터페이스를 위해 동일한 상황과의 차이점에 비추어 내가 그것을 발견했을 때 매우 분명하지 않았습니다. 전혀 휴식이 아니지만 그것을 포함하기로 결정한 것은 놀랍습니다.

클래스 멤버를 기본 클래스로 리팩토링

종류 : 휴식이 아님!

영향을받는 언어 : 없음

변경 전 API :

class Foo
{
    public virtual void Bar() {}
    public virtual void Baz() {}
}

변경 후 API :

class FooBase
{
    public virtual void Bar() {}
}

class Foo : FooBase
{
    public virtual void Baz() {}
}

변경을 통해 계속 작동하는 샘플 코드 (단, 깨질 것으로 예상 되더라도) :

// C++/CLI
ref class Derived : Foo
{
   public virtual void Baz() {{

   // Explicit override    
   public virtual void BarOverride() = Foo::Bar {}
};

노트:

C ++ / CLI는 가상 기본 클래스 멤버에 대한 명시 적 인터페이스 구현과 유사한 구조를 가진 유일한 .NET 언어입니다 ( "명시 적 재정의"). 명시 적 재정의를 위해 생성 된 IL이 명시 적 구현과 동일하기 때문에 인터페이스 멤버를 기본 인터페이스로 이동할 때와 동일한 종류의 손상이 발생할 것으로 예상했습니다. 놀랍게도, 이것은 생성 된 IL이 여전히 BarOverride오버라이드 Foo::Bar대신 오버라이드를 지정한다고 명시하더라도 FooBase::Bar어셈블리 로더는 불만없이 올바르게 다른 것으로 대체 할 수있을만큼 똑똑합니다-분명히 Foo클래스 라는 사실이 차이를 만드는 것입니다. 그림을 이동...


3
기본 클래스가 동일한 어셈블리에있는 한. 그렇지 않으면 바이너리 이진 변경입니다.
제레미

@Jeremy이 경우 어떤 종류의 코드가 중단됩니까? 외부 발신자가 Baz ()를 사용하면 Foo를 확장하고 Baz ()를 재정의하려는 사람들에게만 문제가됩니까?
ChaseMedallion 2016

@ChaseMedallion 당신이 중고 사용자라면 그것은 깨지고 있습니다. 예를 들어 컴파일 된 DLL은 이전 버전의 Foo를 참조하고 컴파일 된 DLL을 참조하지만 최신 버전의 Foo DLL을 사용합니다. 이상한 오류가 발생하거나 적어도 내가 전에 개발 한 라이브러리에서 나에게 적합합니다.
Jeremy

19

이것은 아마도 "인터페이스 멤버 추가 / 제거"와 같은 분명하지 않은 특별한 경우이며, 다음에 게시 할 또 다른 경우에 비추어 자체적으로 입장 할 가치가 있다고 생각했습니다. 그래서:

인터페이스 멤버를 기본 인터페이스로 리팩토링

종류 : 소스 및 이진 수준 모두에서 중단

영향을받는 언어 : C #, VB, C ++ / CLI, F # (소스 나누기의 경우, 바이너리 언어는 자연스럽게 모든 언어에 영향을 미침)

변경 전 API :

interface IFoo
{
    void Bar();
    void Baz();
}

변경 후 API :

interface IFooBase 
{
    void Bar();
}

interface IFoo : IFooBase
{
    void Baz();
}

소스 레벨에서 변경하여 깨진 샘플 클라이언트 코드 :

class Foo : IFoo
{
   void IFoo.Bar() { ... }
   void IFoo.Baz() { ... }
}

이진 수준의 변경으로 인해 끊어진 샘플 클라이언트 코드.

(new Foo()).Bar();

노트:

소스 레벨 중단의 경우 문제점은 C #, VB 및 C ++ / CLI 모두 인터페이스 멤버 구현 선언에서 정확한 인터페이스 이름이 필요하다는 것입니다. 따라서 멤버가 기본 인터페이스로 이동하면 코드가 더 이상 컴파일되지 않습니다.

이진 중단은 명시 적 구현을 ​​위해 생성 된 IL에서 인터페이스 메소드가 정규화되어 있고 인터페이스 이름도 정확해야하기 때문입니다.

사용 가능한 경우 암시 적 구현 (예 : C # 및 C ++ / CLI, VB는 아님)은 소스 및 이진 수준 모두에서 잘 작동합니다. 메소드 호출도 중단되지 않습니다.


모든 언어에 해당되는 것은 아닙니다. VB의 경우 주요 소스 코드 변경이 아닙니다. C #의 경우입니다.
Jeremy

그래서 Implements IFoo.Bar투명하게 참조 할 것 IFooBase.Bar?
Pavel Minaev 2016 년

그렇습니다. 실제로 구현할 때 상속 인터페이스를 통해 직접 또는 간접적으로 멤버를 참조 할 수 있습니다. 그러나 이것은 항상 주요 바이너리 변경입니다.
제레미

15

열거 된 값의 순서 변경

휴식의 종류 : 소스 레벨 / 바이너리 레벨 조용한 의미 론적 변화

영향을받는 언어 : 모두

열거 된 값의 순서를 바꾸면 리터럴의 이름이 동일하기 때문에 소스 수준의 호환성이 유지되지만 서수 인덱스가 업데이트되어 일부 종류의 자동 소스 수준이 중단 될 수 있습니다.

클라이언트 코드가 새 API 버전에 대해 다시 컴파일되지 않으면 도입 될 수있는 자동 2 진 레벨 중단이 더 나쁩니다. 열거 형 값은 컴파일 타임 상수이며 이러한 사용은 클라이언트 어셈블리의 IL에 구워집니다. 이 경우는 때때로 발견하기 어려울 수 있습니다.

변경 전 API

public enum Foo
{
   Bar,
   Baz
}

변경 후 API

public enum Foo
{
   Baz,
   Bar
}

작동하지만 나중에 고장난 샘플 클라이언트 코드 :

Foo.Bar < Foo.Baz

12

이것은 실제로는 매우 드문 일이지만 그럼에도 불구하고 놀라운 일입니다.

오버로드되지 않은 새 멤버 추가

종류 : 소스 레벨 중단 또는 조용한 의미 체계 변경.

영향을받는 언어 : C #, VB

영향을받지 않는 언어 : F #, C ++ / CLI

변경 전 API :

public class Foo
{
}

변경 후 API :

public class Foo
{
    public void Frob() {}
}

변경으로 인해 깨지는 샘플 클라이언트 코드 :

class Bar
{
    public void Frob() {}
}

class Program
{
    static void Qux(Action<Foo> a)
    {
    }

    static void Qux(Action<Bar> a)
    {
    }

    static void Main()
    {
        Qux(x => x.Frob());        
    }
}

노트:

여기서 문제는 과부하 해결이있을 때 C # 및 VB의 람다 형식 유추로 인해 발생합니다. 람다의 본문이 주어진 유형에 적합한 지 여부를 확인하여 하나 이상의 유형이 일치하는 관계를 끊기 위해 제한된 형식의 오리 타이핑이 사용됩니다.

여기서 위험은 클라이언트 코드에 오버로드 된 메소드 그룹이있을 수 있으며 일부 메소드는 자체 유형의 인수를 사용하고 다른 메소드는 라이브러리에 의해 노출 된 유형의 인수를 사용합니다. 그의 코드 중 하나가 유형 유추 알고리즘에 의존하여 구성원의 유무에 따라 올바른 방법을 결정하는 경우 클라이언트 유형 중 하나와 동일한 이름을 가진 유형 중 하나에 새 구성원을 추가하면 잠재적으로 유추를 던질 수 있습니다 과부하 해결시 모호함이 발생합니다.

그 유형을 참고 Foo하고 Bar이 예에없는 상속에 의해도, 그렇지 않으면, 어떤 식 으로든 관련이 없습니다. 단일 메소드 그룹에서 이들을 사용하는 것만으로도이를 트리거 할 수 있으며, 이것이 클라이언트 코드에서 발생하면 제어 할 수 없습니다.

위의 샘플 코드는 이것이 소스 레벨 중단 (예 : 컴파일러 오류 결과) 인 더 간단한 상황을 보여줍니다. 그러나 추론을 통해 선택된 과부하에 다른 인수가있을 경우 (예 : 기본값이있는 선택적 인수 또는 암시 적을 요구하는 선언 된 인수와 실제 인수간에 유형이 일치하지 않는 경우) 이는 자동 의미 론적 변경 일 수도 있습니다. 변환). 이러한 시나리오에서는 과부하 해결이 더 이상 실패하지 않지만 컴파일러가 다른 과부하를 자동으로 선택합니다. 그러나 실제로는 의도적으로 메소드 시그니처를 신중하게 구성하지 않고이 경우를 실행하기가 매우 어렵습니다.


9

암시 적 인터페이스 구현을 명시 적 인터페이스 구현으로 변환하십시오.

휴식의 종류 : 출처와 이진

영향을받는 언어 : 모두

이것은 실제로 메소드의 접근성을 변경 한 변형입니다. 인터페이스의 메소드에 대한 모든 액세스가 반드시 인터페이스 유형에 대한 참조를 통한 것은 아니라는 사실을 간과하기 쉽기 때문에 조금 더 미묘합니다.

변경 전 API :

public class Foo : IEnumerable
{
    public IEnumerator GetEnumerator();
}

변경 후 API :

public class Foo : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator();
}

변경 전에 작동하고 나중에 고장난 샘플 클라이언트 코드 :

new Foo().GetEnumerator(); // fails because GetEnumerator() is no longer public

7

명시 적 인터페이스 구현을 암시 적 인터페이스 구현으로 변환하십시오.

휴식의 종류 : 출처

영향을받는 언어 : 모두

명시 적 인터페이스 구현을 암시 적 인터페이스 구현으로 리팩토링하는 것은 API를 중단 할 수있는 방법에서 더 미묘합니다. 표면적으로는 비교적 안전해야하지만 상속과 결합하면 문제가 발생할 수 있습니다.

변경 전 API :

public class Foo : IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator() { yield return "Foo"; }
}

변경 후 API :

public class Foo : IEnumerable
{
    public IEnumerator GetEnumerator() { yield return "Foo"; }
}

변경 전에 작동하고 나중에 고장난 샘플 클라이언트 코드 :

class Bar : Foo, IEnumerable
{
    IEnumerator IEnumerable.GetEnumerator() // silently hides base instance
    { yield return "Bar"; }
}

foreach( var x in new Bar() )
    Console.WriteLine(x);    // originally output "Bar", now outputs "Foo"

죄송합니다, 확실히 따르지 않습니다-API 변경 전에 샘플 코드가 전혀 컴파일되지 않을 것입니다. 변경 사항에 Foo라는 공개 메소드가 없기 때문에 GetEnumerator유형의 참조를 통해 메소드를 호출하고 있기 때문입니다 Foo. .
파벨 Minaev

실제로, 나는 메모리에서 예제를 단순화하려고 시도했고 결국 'foobar'(말장난을 용서)했다. 사례를 올바르게 설명하고 컴파일 할 수 있도록 예제를 업데이트했습니다.
LBushkin

내 예제에서 문제는 인터페이스 메서드가 암시 적에서 공개로 전환되는 것 이상의 문제로 인해 발생합니다. C # 컴파일러가 foreach 루프에서 호출 할 메소드를 결정하는 방법에 따라 다릅니다. 컴파일러가 결정하는 해결 규칙이 주어지면 파생 클래스의 버전에서 기본 클래스의 버전으로 전환됩니다.
LBushkin

당신은 잊어 버렸습니다 yield return "Bar":) 그렇습니다. 지금 어디로 가고 있는지 알 수 있습니다- 실제 구현이 아니더라도 foreach항상라는 공개 메소드를 호출합니다 . 이것은 하나의 각도를 가지고있는 것처럼 보입니다. 클래스가 하나 뿐이고 명시 적으로 구현하더라도 이것은 인터페이스 구현보다 해당 메소드를 사용 하기 때문에 이름이 지정된 공용 메소드를 추가하는 것이 소스 변경이라는 것을 의미합니다 . 또한 동일한 문제가 구현 에도 적용됩니다 .GetEnumeratorIEnumerable.GetEnumeratorIEnumerableGetEnumeratorforeachIEnumerator
Pavel Minaev

6

필드를 속성으로 변경

휴식의 종류 : API

영향을받는 언어 : Visual Basic 및 C # *

정보 : Visual Basic에서 일반 필드 또는 변수를 속성으로 변경하는 경우 어떤 방식 으로든 해당 멤버를 참조하는 외부 코드를 다시 컴파일해야합니다.

변경 전 API :

Public Class Foo    
    Public Shared Bar As String = ""    
End Class

변경 후 API :

Public Class Foo
    Private Shared _Bar As String = ""
    Public Shared Property Bar As String
        Get
            Return _Bar
        End Get
        Set(value As String)
            _Bar = value
        End Set
    End Property
End Class    

작동하지만 나중에 고장난 샘플 클라이언트 코드 :

Foo.Bar = "foobar"

2
이 속성을 사용할 수 없기 때문에 실제로뿐만 아니라 C #에서 문제를 일으킬 것입니다 outref필드와는 달리, 메소드의 인수 및 단항의 대상이 될 수 없다 &연산자.
Pavel Minaev

5

네임 스페이스 추가

소스 레벨 휴식 / 소스 레벨 조용한 의미 변경

네임 스페이스 확인이 vb.Net에서 작동하는 방식으로 인해 네임 스페이스를 라이브러리에 추가하면 이전 버전의 API로 컴파일 된 Visual Basic 코드가 새 버전으로 컴파일되지 않을 수 있습니다.

샘플 클라이언트 코드 :

Imports System
Imports Api.SomeNamespace

Public Class Foo
    Public Sub Bar()
        Dim dr As Data.DataRow
    End Sub
End Class

새 버전의 API가 네임 스페이스를 추가 Api.SomeNamespace.Data하면 위의 코드는 컴파일되지 않습니다.

프로젝트 수준의 네임 스페이스 가져 오기로 인해 더 복잡해집니다. 경우 Imports System위의 코드에서 생략되어 있지만 System네임 스페이스는 프로젝트 수준에서 가져온 다음 코드는 여전히 오류가 발생할 수 있습니다.

그러나 Api DataRowApi.SomeNamespace.Data네임 스페이스에 클래스 를 포함하는 경우 코드는 컴파일되지만 이전 버전의 API로 컴파일 될 때와 새 버전의 API로 컴파일 될 때 dr의 인스턴스가 System.Data.DataRow됩니다 Api.SomeNamespace.Data.DataRow.

인수 이름 바꾸기

소스 레벨 휴식

인수 이름을 변경하면 버전 7 (?) (. Net 버전 1?)에서 vb.net과 버전 4 (.Net 버전 4)에서 c # .net이 크게 변경됩니다.

변경 전 API :

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string x) {
           ...
        }
    }
}

변경 후 API :

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string y) {
           ...
        }
    }
}

샘플 클라이언트 코드 :

Api.SomeNamespace.Foo.Bar(x:"hi"); //C#
Api.SomeNamespace.Foo.Bar(x:="hi") 'VB

참조 매개 변수

소스 레벨 휴식

하나의 매개 변수가 값 대신 참조로 전달된다는 점을 제외하고 동일한 서명으로 메서드 재정의를 추가하면 API를 참조하는 vb 소스가 함수를 확인할 수 없습니다. Visual Basic에는 인수 이름이 다른 경우가 아니면 호출 지점에서 이러한 메서드를 구별 할 수있는 방법 (?)이 없으므로 이러한 변경으로 인해 두 멤버를 모두 vb 코드에서 사용할 수 없게 될 수 있습니다.

변경 전 API :

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string x) {
           ...
        }
    }
}

변경 후 API :

namespace SomeNamespace {
    public class Foo {
        public static void Bar(string x) {
           ...
        }
        public static void Bar(ref string x) {
           ...
        }
    }
}

샘플 클라이언트 코드 :

Api.SomeNamespace.Foo.Bar(str)

필드에서 속성으로 변경

이진 수준 나누기 / 소스 수준 나누기

명백한 이진 수준 나누기 외에도 멤버가 참조로 메서드에 전달되면 소스 수준 나누기가 발생할 수 있습니다.

변경 전 API :

namespace SomeNamespace {
    public class Foo {
        public int Bar;
    }
}

변경 후 API :

namespace SomeNamespace {
    public class Foo {
        public int Bar { get; set; }
    }
}

샘플 클라이언트 코드 :

FooBar(ref Api.SomeNamespace.Foo.Bar);

4

API 변경 :

  1. [단종] 속성 추가 (이 속성은 언급 된 속성으로 덮여 있지만 오류로 경고를 사용할 때 주요 변경 사항이 될 수 있습니다.)

이진 수준의 휴식 :

  1. 한 어셈블리에서 다른 어셈블리로 유형 이동
  2. 유형의 네임 스페이스 변경
  3. 다른 어셈블리에서 기본 클래스 유형 추가
  4. 템플릿 인수 제약 조건으로 다른 어셈블리 (Class2)의 형식을 사용하는 새 멤버 (이벤트 보호) 추가

    protected void Something<T>() where T : Class2 { }
  5. 클래스가이 클래스의 템플릿 인수로 사용될 때 다른 어셈블리의 유형에서 파생되도록 자식 클래스 (Class3)를 변경합니다.

    protected class Class3 : Class2 { }
    protected void Something<T>() where T : Class3 { }

소스 수준의 조용한 의미 체계 변경 :

  1. Equals (), GetHashCode () 또는 ToString ()의 재정의 추가 / 제거 / 변경

(어디에 맞는지 확실하지 않음)

배포 변경 사항 :

  1. 종속성 / 참조 추가 / 제거
  2. 최신 버전으로 종속성 업데이트
  3. x86, Itanium, x64 또는 anycpu간에 '대상 플랫폼'변경
  4. 다른 프레임 워크 설치에서 빌드 / 테스트 (예 : .Net 2.0 상자에 3.5를 설치하면 .Net 2.0 SP2가 필요한 API 호출 허용)

부트 스트랩 / 구성 변경 :

  1. 사용자 정의 구성 옵션 추가 / 제거 / 변경 (예 : App.config 설정)
  2. 오늘날의 응용 프로그램에서 IoC / DI를 많이 사용하기 때문에 DI 종속 코드에 대한 부트 스트랩 코드를 재구성 및 / 또는 변경해야합니다.

최신 정보:

죄송합니다.이 문제가 유일한 이유는 템플릿 제약 조건에서 사용했기 때문입니다.


"다른 어셈블리의 유형을 사용하는 새 멤버 추가 (이벤트 보호)." -IIRC, 클라이언트는 이미 참조하는 어셈블리의 기본 유형을 포함하는 종속 어셈블리 만 참조하면됩니다. 형식이 메서드 서명에 있더라도 단순히 사용되는 어셈블리를 참조 할 필요는 없습니다. 나는 이것에 대해 100 % 확신하지 못합니다. 이에 대한 정확한 규칙에 대한 참조가 있습니까? 또한 사용하는 경우 유형 이동이 중단되지 않을 수 있습니다 TypeForwardedToAttribute.
Pavel Minaev

"TypeForwardedTo"는 저에게 뉴스입니다. 확인해 보겠습니다. 다른 사람은 100 %가 아닙니다 ... 재현 할 수 있는지 확인하고 게시물을 업데이트하겠습니다.
csharptest.net

따라서 -Werror릴리스 타르볼과 함께 제공되는 빌드 시스템을 강요하지 마십시오 . 이 플래그는 코드 개발자에게 가장 도움이되고 소비자에게는 도움이되지 않는 경우가 많습니다.
binki

@binki의 우수한 점은 오류로 경고를 처리하면 DEBUG 빌드에서만 충분해야합니다.
csharptest.net

3

기본 매개 변수 사용을 소멸시키기 위해 과부하 방법 추가

휴식의 종류 : 소스 수준의 조용한 의미 체계 변경

컴파일러는 기본 매개 변수 값이 누락 된 메소드 호출을 호출 측의 기본값을 사용하는 명시 적 호출로 변환하므로 기존의 컴파일 된 코드에 대한 호환성이 제공됩니다. 이전에 컴파일 된 모든 코드에 대해 올바른 서명이있는 메소드를 찾을 수 있습니다.

한편, 선택적 매개 변수를 사용하지 않는 호출은 이제 선택적 매개 변수가없는 새 메소드에 대한 호출로 컴파일됩니다. 모든 것이 여전히 잘 작동하지만 호출 된 코드가 다른 어셈블리에 있으면 새로 컴파일 된 코드 호출은 이제이 어셈블리의 새 버전에 의존합니다. 리팩토링 된 코드가있는 어셈블리를 배치하지 않고 리팩토링 된 코드를 호출하는 어셈블리를 배치하면 "메소드를 찾을 수 없음"예외가 발생합니다.

변경 전 API

  public int MyMethod(int mandatoryParameter, int optionalParameter = 0)
  {
     return mandatoryParameter + optionalParameter;
  }    

변경 후 API

  public int MyMethod(int mandatoryParameter, int optionalParameter)
  {
     return mandatoryParameter + optionalParameter;
  }

  public int MyMethod(int mandatoryParameter)
  {
     return MyMethod(mandatoryParameter, 0);
  }

여전히 작동하는 샘플 코드

  public int CodeNotDependentToNewVersion()
  {
     return MyMethod(5, 6); 
  }

컴파일 할 때 새 버전에 종속되는 샘플 코드

  public int CodeDependentToNewVersion()
  {
     return MyMethod(5); 
  }

1

인터페이스 이름 바꾸기

휴식의 킨다 : 근원과 이진

영향을받는 언어 : 대부분 C #에서 테스트되었습니다.

변경 전 API :

public interface IFoo
{
    void Test();
}

public class Bar
{
    IFoo GetFoo() { return new Foo(); }
}

변경 후 API :

public interface IFooNew // Of the exact same definition as the (old) IFoo
{
    void Test();
}

public class Bar
{
    IFooNew GetFoo() { return new Foo(); }
}

작동하지만 나중에 고장난 샘플 클라이언트 코드 :

new Bar().GetFoo().Test(); // Binary only break
IFoo foo = new Bar().GetFoo(); // Source and binary break

1

nullable 형식의 매개 변수를 사용하는 오버로드 방법

종류 : 소스 레벨 휴식

영향을받는 언어 : C #, VB

변경 전 API :

public class Foo
{
    public void Bar(string param);
}

변경 후 API :

public class Foo
{
    public void Bar(string param);
    public void Bar(int? param);
}

변경 전에 작동하고 변경 후 샘플 클라이언트 코드 :

new Foo().Bar(null);

예외 : 다음 방법 또는 속성간에 호출이 모호합니다.


0

확장 방법으로 승격

종류 : 소스 수준의 휴식

영향을받는 언어 : C # v6 이상 (다른 언어도 가능)

변경 전 API :

public static class Foo
{
    public static void Bar(string x);
}

변경 후 API :

public static class Foo
{
    public void Bar(this string x);
}

변경 전에 작동하고 그 후에 깨진 샘플 클라이언트 코드 :

using static Foo;

class Program
{
    static void Main() => Bar("hello");
}

추가 정보 : https://github.com/dotnet/csharplang/issues/665

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.