.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#は壊れていません。F#はオーバーロードされた演算子に対する言語レベルのサポートがなく、明示的でも暗黙的でもないためです。どちらもメソッドop_Explicitop_Implicitメソッドとして直接呼び出す必要があります。

新しいインスタンスメソッドの追加

種類:ソースレベルの静かなセマンティクスが変更されました。

影響を受ける言語: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#ExtensionMethodAttributeはの言語レベルのサポートがないため、壊れていません。また、CLS拡張メソッドを静的メソッドとして呼び出す必要があります。


確かにマイクロソフトはすでにこれをカバーしています... msdn.microsoft.com/en-us/netframework/aa570326.aspx
Robert Harvey

1
@Robert:あなたのリンクは非常に異なる何かについてです-それは.NET Framework自体の特定の重大な変更を説明しています。これは、(ライブラリ/フレームワーク作成者としての)独自の APIに重大な変更もたらす可能性がある一般的なパターンを説明する幅広い質問です。私は、MSからのそのような完全な文書を知りませんが、不完全なものであっても、そのような文書へのリンクは大歓迎です。
Pavel Minaev 2009

これらの「中断」カテゴリのいずれかで、実行時にのみ問題が明らかになるものはありますか?
Rohit

1
はい、「バイナリブレーク」カテゴリ。その場合、アセンブリのすべてのバージョンに対してコンパイルされたサードパーティアセンブリがすでにあります。アセンブリの新しいバージョンをインプレースでドロップすると、サードパーティのアセンブリが機能しなくなります。実行時にロードされないか、正しく動作しません。
Pavel Minaev

3
私はそれらを投稿とコメントに追加しますblogs.msdn.com/b/ericlippert/archive/2012/01/09/…–
Lukasz

回答:


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

それも本当です。会社のアプリケーションの印刷ユーティリティにいくつかの変更を加えたときに、この特定の問題が見つかりました。更新がリリースされたときに、ユーティリティを参照するすべてのDLLが再コンパイルおよびリリースされたわけではないため、methodnotfound例外がスローされました。
ジャスティンドゥルーリー

1
これは、戻り値の型がメソッドのシグネチャにカウントされないという事実に戻ります。戻り値の型のみに基づいて2つの関数をオーバーロードすることもできません。同じ問題。
Jason Short

1
この答えのサブ質問:dotnet4 defaultvalue 'public static void bar(int i = 0);'を追加することの影響を誰かが知っていますか?またはそのデフォルト値をある値から別の値に変更しますか?
k3b 2011年

1
私がC#(および他のほとんどのOOP言語を「思う」)でこのページにアクセスしようとしている人にとって、戻り値の型はメソッドの署名に寄与しません。 はい、署名の変更がバイナリレベルの変更に寄与するという答えは正しいです。 しかし例が正しく私見思われません、私は考えることができることを正しい例は、である 、BEFORE公共進和(int型A、int型b)の 、公開進和(小数、小数b)は親切にこのMSDNのリンクを参照してください3.6署名をし、過負荷します
バヌチャブラ2017

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;
重大な

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
基本クラスが同じアセンブリ内にある限り。それ以外の場合は、バイナリの重大な変更です。
ジェレミー

@ジェレミーその場合、どのようなコードが壊れますか?外部の呼び出し元によるBaz()の使用は中断されますか、それともFooを拡張してBaz()をオーバーライドしようとする人々の問題だけですか?
ChaseMedallion 2016

@ChaseMedallionは、中古のユーザーである場合、壊れています。たとえば、コンパイル済みDLLは古いバージョンのFooを参照し、そのコンパイル済みDLLを参照しますが、新しいバージョンのFoo DLLも使用します。それは奇妙なエラーで壊れるか、少なくとも私が以前に開発したライブラリでは私にはうまくいきました。
ジェレミー

19

これはおそらく「インターフェイスメンバーの追加/削除」のそれほど明白ではない特別なケースであり、次に投稿する別のケースに照らして、独自のエントリに値すると考えました。そう:

インターフェイスメンバーをベースインターフェイスにリファクタリングする

種類:ソースレベルとバイナリレベルの両方で中断

影響を受ける言語:C#、VB、C ++ / CLI、F#(ソースブレーク用、バイナリ1は自然にすべての言語に影響します)

変更前の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#の場合です。
ジェレミー

だから、Implements IFoo.Bar透過的に参照しますかIFooBase.Bar
Pavel Minaev 2016年

はい、実際にはそうです。実装するときに、継承するインターフェイスを介して直接または間接的にメンバーを参照できます。ただし、これは常に重大なバイナリ変更です。
ジェレミー

15

列挙値の並べ替え

ブレークの種類:ソースレベル/バイナリレベルの静かなセマンティクスの変更

影響を受ける言語:すべて

列挙された値を並べ替えると、リテラルの名前が同じであるためソースレベルの互換性が維持されますが、それらの序数インデックスが更新され、ある種のサイレントソースレベルのブレークが発生する可能性があります。

さらに悪いのは、クライアントコードが新しいAPIバージョンに対して再コンパイルされない場合に発生する可能性がある、サイレントバイナリレベルのブレークです。列挙値はコンパイル時の定数であり、そのため、それらの使用はすべてクライアントアセンブリの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でのラムダ型の推論が原因です。ここでは、制限された形式のダックタイピングを使用して、ラムダのボディが特定のタイプに対して意味があるかどうかをチェックすることにより、複数のタイプが一致する場合の関係を解消しています。

ここでの危険は、クライアントコードがオーバーロードされたメソッドグループを持ち、一部のメソッドが独自の型の引数を取り、他のメソッドがライブラリによって公開された型の引数を受け取ることです。彼のコードのいずれかが型推論アルゴリズムに依存して、メンバーの存在または不在のみに基づいて正しいメソッドを決定する場合、クライアントのタイプの1つと同じ名前のタイプの1つに新しいメンバーを追加すると、推論がスローされる可能性がありますオフ、オーバーロードの解決時にあいまいになります。

FooBarこの例では、継承やその他の方法では関係ありません。これをトリガーするには、1つのメソッドグループでそれらを使用するだけで十分です。これがクライアントコードで発生した場合、それを制御することはできません。

上記のサンプルコードは、これがソースレベルのブレークであるより単純な状況を示しています(つまり、コンパイラエラーの結果)。ただし、推論を介して選択されたオーバーロードに他の方法でランク付けされる他の引数があった場合、これはサイレントセマンティクスの変更になる場合もあります(たとえば、デフォルト値を持つオプションの引数、または暗黙的を必要とする宣言された引数と実際の引数の間の型の不一致変換)。このようなシナリオでは、オーバーロードの解決は失敗しなくなりますが、別のオーバーロードがコンパイラによって静かに選択されます。ただし、実際には、メソッドシグネチャを慎重に作成して意図的に引き起こさない限り、このケースに遭遇することは非常に困難です。


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"

申し訳ありませんが、完全には従いません。変更前にFooという名前のパブリックメソッドがなくGetEnumerator、型の参照を介してメソッドを呼び出しているため、API変更前のサンプルコードはコンパイルできませんFoo。 。
パベルMinaev

確かに、私はメモリから例を単純化しようとしました、そしてそれは「愚か者」(駄洒落を許して)になりました。例を更新して、ケースを正しく説明しました(そしてコンパイル可能にしました)。
LBushkin

私の例では、問題は、インターフェイスメソッドの暗黙的なものからパブリックなものへの移行だけではありません。これは、C#コンパイラがforeachループで呼び出すメソッドを決定する方法によって異なります。コンパイラーが解決する解決ルールを指定すると、派生クラスのバージョンから基本クラスのバージョンに切り替わります。
LBushkin 2009年

あなたは忘れましたyield return "Bar":)しかし、そうです、これが今どこにあるのかわかります- の実際の実装ではない場合でもforeach、常にという名前のパブリックメソッドを呼び出します。あなただけの1つのクラスを持っている場合でも、それは実装しています。これは、1つ以上の角度を持っているように見えることが名前のパブリックメソッドを追加するソース・破壊の変更だと、この手段、明示的に今ので、それには、インタフェース実装の上にそのメソッドを使用します。また、同じ問題が実装にも当てはまります...GetEnumeratorIEnumerable.GetEnumeratorIEnumerableGetEnumeratorforeachIEnumerator
Pavel Minaev 09/10/30

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 2013年

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の名前空間にクラスDataRowが含まれている場合、Api.SomeNamespace.Dataコードはコンパイルされますが、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

参照パラメーター

ソースレベルの区切り

1つのパラメーターが値ではなく参照によって渡されることを除いて、同じシグニチャーでメソッドのオーバーライドを追加すると、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. [Obsolete]属性を追加します(これについては属性について言及しましたが、warning-as-errorを使用すると、重大な変更になる可能性があります)

バイナリレベルの区切り:

  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、2009年

「TypeForwardedTo」は私にとってはニュースです。チェックします。もう1つについては、私も100%ではありません...再現できるかどうかを確認して、投稿を更新します。
csharptest.net 2009年

したがって、-Werrorリリースtarballとともに出荷するビルドシステムを無理に実行しないでください。このフラグは、コードの開発者にとって最も役立ち、消費者にとってはほとんどの場合役に立ちません。
binki 2014年

@binkiの優れた点は、警告をエラーとして扱うことはDEBUGビルドでのみ十分であることです。
csharptest.net 2014年

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

インターフェースの名前を変更する

Break of Kinda:Source and バイナリ

影響を受ける言語:ほとんどの場合、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

null許容型のパラメーターを持つオーバーロードメソッド

種類:ソースレベルの区切り

影響を受ける言語: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.