Panduan definitif untuk perubahan yang melanggar API di .NET

227

Saya ingin mengumpulkan informasi sebanyak mungkin mengenai versi API di .NET / CLR, dan secara khusus bagaimana perubahan API dilakukan atau tidak merusak aplikasi klien. Pertama, mari kita definisikan beberapa istilah:

Perubahan API - perubahan dalam definisi jenis yang terlihat secara publik, termasuk anggota publiknya. Ini termasuk mengubah tipe dan nama anggota, mengubah tipe dasar suatu tipe, menambah / menghapus antarmuka dari daftar antarmuka tipe yang diimplementasikan, menambah / menghapus anggota (termasuk kelebihan beban), mengubah visibilitas anggota, metode penamaan nama dan parameter tipe, menambahkan nilai default untuk parameter metode, menambah / menghapus atribut pada tipe dan anggota, dan menambahkan / menghapus parameter tipe umum pada tipe dan anggota (apakah saya melewatkan sesuatu?). Ini tidak termasuk perubahan apa pun dalam tubuh anggota, atau perubahan apa pun pada anggota pribadi (yaitu kami tidak mempertimbangkan Refleksi akun).

Istirahat tingkat biner - perubahan API yang menghasilkan kumpulan klien yang dikompilasi terhadap versi API yang lebih lama yang berpotensi tidak dimuat dengan versi baru. Contoh: mengubah metode tanda tangan, bahkan jika memungkinkan untuk dipanggil dengan cara yang sama seperti sebelumnya (yaitu: batal untuk mengembalikan tipe / parameter nilai default kelebihan beban).

Istirahat tingkat sumber - perubahan API yang menghasilkan kode yang ada ditulis untuk dikompilasi terhadap versi API yang lebih lama yang berpotensi tidak dikompilasi dengan versi yang baru. Namun, kumpulan klien yang telah dikompilasi berfungsi seperti sebelumnya. Contoh: menambahkan kelebihan baru yang dapat mengakibatkan ambiguitas dalam panggilan metode yang tidak ambigu sebelumnya.

Semantik diam tingkat sumber berubah - perubahan API yang menghasilkan kode yang ada ditulis untuk mengkompilasi terhadap versi lama dari API diam-diam mengubah semantiknya, misalnya dengan memanggil metode yang berbeda. Namun kode harus terus dikompilasi tanpa peringatan / kesalahan, dan majelis yang dikompilasi sebelumnya harus berfungsi seperti sebelumnya. Contoh: mengimplementasikan antarmuka baru pada kelas yang ada yang menghasilkan kelebihan beban yang berbeda yang dipilih selama resolusi kelebihan beban.

Tujuan utamanya adalah untuk mengkatalogkan sebanyak mungkin semantik dan semantik API yang berubah semaksimal mungkin, dan menjelaskan efek kerusakan yang tepat, dan bahasa mana yang dan tidak terpengaruh olehnya. Untuk memperluas yang terakhir: sementara beberapa perubahan memengaruhi semua bahasa secara universal (misalnya menambahkan anggota baru ke antarmuka akan memecah implementasi antarmuka itu dalam bahasa apa pun), beberapa memerlukan semantik bahasa yang sangat spesifik untuk ikut bermain untuk mendapatkan jeda. Ini biasanya melibatkan kelebihan metode, dan, secara umum, apa pun yang berkaitan dengan konversi tipe implisit. Tampaknya tidak ada cara untuk mendefinisikan "penyebut paling umum" di sini bahkan untuk bahasa yang sesuai CLS (yaitu yang paling tidak sesuai dengan aturan "konsumen CLS" sebagaimana didefinisikan dalam spesifikasi CLI) - meskipun saya ' Saya akan menghargai jika ada yang mengoreksi saya sebagai orang yang salah di sini - jadi ini harus dilakukan berdasarkan bahasa. Yang paling menarik tentu saja yang datang dengan .NET di luar kotak: C #, VB dan F #; tetapi yang lain, seperti IronPython, IronRuby, Delphi Prism dll juga relevan. Semakin banyak sudut kasus, semakin menarik - hal-hal seperti menghapus anggota cukup jelas, tetapi interaksi yang halus antara misalnya kelebihan metode, parameter opsional / default, inferensi tipe lambda, dan operator konversi bisa sangat mengejutkan kadang.

Beberapa contoh untuk memulai ini:

Menambahkan kelebihan metode baru

Jenis: istirahat level sumber

Bahasa yang terpengaruh: C #, VB, F #

API sebelum perubahan:

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

API setelah perubahan:

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

Contoh kode klien yang berfungsi sebelum perubahan dan rusak setelahnya:

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

Menambahkan kelebihan operator konversi implisit baru

Jenis: istirahat level sumber.

Bahasa yang terpengaruh: C #, VB

Bahasa tidak terpengaruh: F #

API sebelum perubahan:

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

API setelah perubahan:

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

Contoh kode klien yang berfungsi sebelum perubahan dan rusak setelahnya:

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

Catatan: F # tidak rusak, karena tidak memiliki dukungan tingkat bahasa untuk operator kelebihan beban, baik eksplisit maupun implisit - keduanya harus dipanggil langsung sebagai op_Explicitdan op_Implicitmetode.

Menambahkan metode instance baru

Jenis: perubahan semantik sunyi tingkat sumber.

Bahasa yang terpengaruh: C #, VB

Bahasa tidak terpengaruh: F #

API sebelum perubahan:

public class Foo
{
}

API setelah perubahan:

public class Foo
{
    public void Bar();
}

Contoh kode klien yang menderita perubahan semantik sunyi:

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

new Foo().Bar();

Catatan: F # tidak rusak, karena tidak memiliki dukungan tingkat bahasa untuk ExtensionMethodAttribute, dan memerlukan metode ekstensi CLS disebut sebagai metode statis.

Pavel Minaev
sumber
Tentunya Microsoft sudah membahas ini ... msdn.microsoft.com/en-us/netframework/aa570326.aspx
Robert Harvey
1
@Robert: tautan Anda adalah tentang sesuatu yang sangat berbeda - tautan ini menjelaskan beberapa perubahan spesifik pada .NET Framework . Ini adalah pertanyaan yang lebih luas yang menjelaskan pola umum yang dapat memperkenalkan perubahan pada API Anda sendiri (sebagai pustaka / framework framework). Saya tidak mengetahui adanya dokumen seperti itu dari MS yang akan lengkap, meskipun tautan ke sana, meskipun yang tidak lengkap, pasti diterima.
Pavel Minaev
Di salah satu kategori "break" ini, apakah ada masalah yang hanya akan terlihat saat runtime?
Rohit
1
Ya, kategori "binary break". Dalam hal ini, Anda sudah memiliki rakitan pihak ketiga yang dikompilasi dengan semua versi rakitan Anda. Jika Anda menjatuhkan versi baru rakitan Anda di tempat, rakitan pihak ketiga berhenti berfungsi - baik itu tidak memuat saat berjalan, atau itu bekerja secara tidak benar.
Pavel Minaev
3
Saya akan menambahkannya di pos dan komentar blogs.msdn.com/b/ericlippert/archive/2012/01/09/…
Lukasz Madon

Jawaban:

42

Mengubah tanda tangan metode

Jenis: Istirahat tingkat biner

Bahasa yang terpengaruh: C # (VB dan F # kemungkinan besar, tetapi tidak diuji)

API sebelum perubahan

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

API setelah perubahan

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

Contoh kode klien berfungsi sebelum perubahan

Foo.bar(13);
Justin Drury
sumber
15
Bahkan, ini bisa menjadi pemecah level sumber juga, jika seseorang mencoba membuat delegasi bar.
Pavel Minaev
Itu juga benar. Saya menemukan masalah khusus ini ketika saya membuat beberapa perubahan pada utilitas pencetakan dalam aplikasi perusahaan saya. Ketika pembaruan dirilis, tidak semua DLL yang mereferensikan utilitas ini dikompilasi dan dirilis sehingga membuang metode yang tidak ditemukan pengecualian.
Justin Drury
1
Ini kembali ke fakta bahwa jenis kembali tidak dihitung untuk tanda tangan metode. Anda tidak dapat membebani dua fungsi hanya berdasarkan pada tipe pengembalian. Permasalahan yang sama.
Jason Short
1
subqestion untuk jawaban ini: apakah ada yang tahu implikasi menambahkan dotnet4 defaultvalue 'public static void bar (int i = 0);' atau mengubah nilai default itu dari satu nilai ke yang lain?
k3b
1
Bagi mereka yang akan mendarat di halaman ini saya pikir untuk C # (dan "Saya pikir" sebagian besar bahasa OOP lainnya), Return Type tidak berkontribusi pada metode signature. Ya jawabannya benar bahwa perubahan Signature berkontribusi pada perubahan level Biner. TETAPI contohnya tampaknya tidak benar IMHO contoh yang benar yang dapat saya pikirkan adalah SEBELUM jumlah desimal publik (int a, int b) Setelah jumlah desimal publik (desimal a, desimal b) Mohon lihat tautan MSDN ini 3.6 Tanda tangan dan kelebihan beban
Bhanu Chhabra
40

Menambahkan parameter dengan nilai default.

Jenis Istirahat: Istirahat tingkat biner

Bahkan jika kode sumber panggilan tidak perlu diubah, masih perlu dikompilasi ulang (seperti saat menambahkan parameter biasa).

Itu karena C # mengkompilasi nilai default dari parameter secara langsung ke dalam unit panggilan. Ini berarti bahwa jika Anda tidak melakukan kompilasi ulang, Anda akan mendapatkan MissingMethodException karena majelis lama mencoba memanggil metode dengan argumen yang lebih sedikit.

API Sebelum Berubah

public void Foo(int a) { }

API Setelah Berubah

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

Contoh kode klien yang rusak setelahnya

Foo(5);

Kode klien perlu dikompilasi ulang Foo(5, null)pada level bytecode. Majelis yang dipanggil hanya akan berisi Foo(int, string), bukan Foo(int). Itu karena nilai parameter default adalah murni fitur bahasa, runtime .Net tidak tahu apa-apa tentang mereka. (Ini juga menjelaskan mengapa nilai default harus konstanta waktu kompilasi dalam C #).

Eldritch Conundrum
sumber
2
ini adalah perubahan besar bahkan untuk level kode sumber: Func<int> f = Foo;// ini akan gagal dengan tanda tangan yang diubah
Vagaus
26

Yang ini sangat tidak jelas ketika saya menemukannya, terutama mengingat perbedaannya dengan situasi yang sama untuk antarmuka. Ini sama sekali bukan istirahat, tapi cukup mengejutkan sehingga saya memutuskan untuk memasukkannya:

Refactoring anggota kelas menjadi kelas dasar

Jenis: bukan istirahat!

Bahasa yang terpengaruh: tidak ada (tidak ada yang rusak)

API sebelum perubahan:

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

API setelah perubahan:

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

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

Kode contoh yang terus berfungsi sepanjang perubahan (meskipun saya perkirakan akan rusak):

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

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

Catatan:

C ++ / CLI adalah satu-satunya bahasa .NET yang memiliki konstruksi implementasi analog dengan antarmuka eksplisit untuk anggota kelas dasar virtual - "timpa eksplisit". Saya sepenuhnya berharap untuk menghasilkan kerusakan yang sama seperti ketika memindahkan anggota antarmuka ke antarmuka dasar (karena IL yang dihasilkan untuk pengesampingan eksplisit sama dengan implementasi eksplisit). Yang mengejutkan saya, ini bukan masalahnya - meskipun IL yang dihasilkan masih menetapkan bahwa BarOverridemenimpa Foo::Bardaripada FooBase::Bar, perakitan loader cukup pintar untuk menggantikan satu dengan yang lain dengan benar tanpa keluhan - tampaknya, fakta bahwa Fookelas adalah yang membuat perbedaan. Go figure ...

Pavel Minaev
sumber
3
Selama kelas dasar berada di majelis yang sama. Kalau tidak, itu adalah perubahan melanggar biner.
Jeremy
@ Jeremy jenis kode apa yang rusak dalam kasus itu? Apakah penggunaan penelepon eksternal Baz () akan pecah atau hanya masalah dengan orang yang mencoba memperluas Foo dan menimpa Baz ()?
ChaseMedallion
@ChaseMedallion itu melanggar jika Anda adalah pengguna bekas. Misalnya, mengkompilasi DLL referensi Foo versi yang lebih lama dan Anda referensi yang mengkompilasi DLL, tetapi juga menggunakan versi yang lebih baru dari Foo DLL. Itu rusak dengan kesalahan aneh, atau setidaknya itu terjadi pada saya di perpustakaan yang telah saya kembangkan sebelumnya.
Jeremy
19

Yang ini mungkin merupakan kasus khusus yang tidak terlalu jelas tentang "menambah / menghapus anggota antarmuka", dan saya pikir itu layak masuk sendiri mengingat kasus lain yang akan saya posting selanjutnya. Begitu:

Anggota antarmuka Refactoring menjadi antarmuka dasar

Jenis: jeda di tingkat sumber dan biner

Bahasa yang terpengaruh: C #, VB, C ++ / CLI, F # (untuk source break; binary one secara alami memengaruhi bahasa apa pun)

API sebelum perubahan:

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

API setelah perubahan:

interface IFooBase 
{
    void Bar();
}

interface IFoo : IFooBase
{
    void Baz();
}

Contoh kode klien yang dipecah oleh perubahan di tingkat sumber:

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

Contoh kode klien yang dipecah oleh perubahan pada tingkat biner;

(new Foo()).Bar();

Catatan:

Untuk istirahat tingkat sumber, masalahnya adalah bahwa C #, VB dan C ++ / CLI semua membutuhkan nama antarmuka yang tepat dalam deklarasi implementasi anggota antarmuka; dengan demikian, jika anggota dipindahkan ke antarmuka basis, kode tidak akan lagi dikompilasi.

Biner istirahat karena fakta bahwa metode antarmuka sepenuhnya memenuhi syarat dalam IL yang dihasilkan untuk implementasi eksplisit, dan nama antarmuka juga harus tepat.

Implementasi implisit jika tersedia (yaitu C # dan C ++ / CLI, tetapi tidak VB) akan berfungsi dengan baik pada tingkat sumber dan biner. Metode panggilan juga tidak terputus.

Pavel Minaev
sumber
Itu tidak benar untuk semua bahasa. Untuk VB itu bukan perubahan kode sumber yang melanggar. Untuk C # itu.
Jeremy
Jadi Implements IFoo.Barakankah referensi transparan IFooBase.Bar?
Pavel Minaev
Ya, sebenarnya, Anda dapat mereferensikan anggota secara langsung atau tidak langsung melalui antarmuka yang diwariskan saat Anda menerapkannya. Namun, ini selalu merupakan perubahan biner yang melanggar.
Jeremy
15

Menyusun ulang nilai yang disebutkan

Jenis kerusakan: Semantik tingkat-sumber / Biner berubah

Bahasa yang terpengaruh: semua

Menyusun ulang nilai yang disebutkan akan menjaga kompatibilitas tingkat sumber karena literal memiliki nama yang sama, tetapi indeks ordinal mereka akan diperbarui, yang dapat menyebabkan beberapa jenis istirahat tingkat sumber diam.

Lebih buruk lagi adalah jeda tingkat biner diam yang dapat diperkenalkan jika kode klien tidak dikompilasi ulang terhadap versi API baru. Nilai-nilai Enum adalah konstanta waktu kompilasi dan karenanya setiap penggunaannya dimasukkan ke dalam IL perakitan klien. Kasing ini terkadang sangat sulit dikenali.

API Sebelum Berubah

public enum Foo
{
   Bar,
   Baz
}

API Setelah Berubah

public enum Foo
{
   Baz,
   Bar
}

Contoh kode klien yang berfungsi tetapi rusak setelahnya:

Foo.Bar < Foo.Baz
Lereng
sumber
12

Yang satu ini benar-benar hal yang sangat jarang dalam praktek, tetapi tetap mengejutkan ketika itu terjadi.

Menambahkan anggota baru yang tidak kelebihan muatan

Jenis: pemecah level sumber atau perubahan semantik sunyi.

Bahasa yang terpengaruh: C #, VB

Bahasa tidak terpengaruh: F #, C ++ / CLI

API sebelum perubahan:

public class Foo
{
}

API setelah perubahan:

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

Contoh kode klien yang rusak oleh perubahan:

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());        
    }
}

Catatan:

Masalahnya di sini disebabkan oleh inferensi tipe lambda di C # dan VB di hadapan resolusi kelebihan. Jenis terbatas dari pengetikan bebek digunakan di sini untuk memutuskan ikatan di mana lebih dari satu jenis cocok, dengan memeriksa apakah tubuh lambda masuk akal untuk jenis yang diberikan - jika hanya satu jenis menghasilkan tubuh yang dapat dikompilasi, yang dipilih.

Bahayanya di sini adalah bahwa kode klien mungkin memiliki grup metode kelebihan beban di mana beberapa metode mengambil argumen dari tipenya sendiri, dan yang lain mengambil argumen dari tipe yang diekspos oleh pustaka Anda. Jika salah satu kodenya bergantung pada jenis algoritma inferensi untuk menentukan metode yang benar hanya berdasarkan ada atau tidak adanya anggota, maka menambahkan anggota baru ke salah satu jenis Anda dengan nama yang sama seperti di salah satu jenis klien berpotensi dapat membuang inferensi off, menghasilkan ambiguitas selama resolusi kelebihan beban.

Perhatikan bahwa tipe Foodan Bardalam contoh ini tidak terkait dengan cara apa pun, bukan karena warisan atau sebaliknya. Hanya menggunakan mereka dalam grup metode tunggal sudah cukup untuk memicu ini, dan jika ini terjadi dalam kode klien, Anda tidak memiliki kontrol atasnya.

Kode contoh di atas menunjukkan situasi yang lebih sederhana di mana ini adalah istirahat tingkat sumber (yaitu hasil kesalahan kompiler). Namun, ini juga bisa menjadi perubahan semantik diam, jika kelebihan yang dipilih melalui inferensi memiliki argumen lain yang jika tidak akan menyebabkannya peringkat di bawah ini (misalnya argumen opsional dengan nilai default, atau ketik ketidakcocokan antara argumen yang dideklarasikan dan aktual yang membutuhkan implisit konversi). Dalam skenario seperti itu, resolusi kelebihan tidak akan lagi gagal, tetapi kelebihan beban yang berbeda akan dipilih dengan diam-diam oleh kompiler. Dalam praktiknya, bagaimanapun, sangat sulit untuk menemukan kasus ini tanpa secara hati-hati membuat tanda tangan metode untuk sengaja menyebabkannya.

Pavel Minaev
sumber
9

Ubah implementasi antarmuka implisit menjadi eksplisit.

Jenis Istirahat: Sumber dan Biner

Bahasa yang Terkena Dampak: Semua

Ini benar-benar hanya variasi dari mengubah aksesibilitas metode - ini hanya sedikit lebih halus karena mudah untuk mengabaikan fakta bahwa tidak semua akses ke metode antarmuka harus melalui referensi ke jenis antarmuka.

API Sebelum Perubahan:

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

API Setelah Perubahan:

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

Contoh kode Klien yang berfungsi sebelum perubahan dan rusak setelahnya:

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

Ubah implementasi antarmuka eksplisit menjadi implisit.

Jenis Istirahat: Sumber

Bahasa yang Terkena Dampak: Semua

Refactoring dari implementasi antarmuka eksplisit menjadi implisit lebih halus dalam bagaimana hal itu dapat merusak API. Di permukaan, tampaknya ini seharusnya relatif aman, namun ketika dikombinasikan dengan warisan dapat menyebabkan masalah.

API Sebelum Perubahan:

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

API Setelah Perubahan:

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

Contoh kode Klien yang berfungsi sebelum perubahan dan rusak setelahnya:

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"
LBushkin
sumber
Maaf, saya tidak cukup mengikuti - tentunya kode sampel sebelum perubahan API tidak dapat dikompilasi sama sekali, karena sebelum perubahan Footidak memiliki nama metode publik GetEnumerator, dan Anda memanggil metode tersebut melalui referensi tipe Foo.. .
Pavel Minaev
Memang, saya mencoba menyederhanakan contoh dari ingatan dan akhirnya menjadi 'foobar' (maaf the pun). Saya memperbarui contoh untuk menunjukkan kasus dengan benar (dan dapat dikompilasi).
LBushkin
Dalam contoh saya, masalahnya disebabkan oleh lebih dari sekedar transisi metode antarmuka dari yang implisit menjadi publik. Itu tergantung pada cara kompiler C # menentukan metode mana yang dipanggil dalam loop foreach. Mengingat aturan resolusi sebagai kompiler, ia beralih dari versi di kelas turunan ke versi di kelas dasar.
LBushkin
Anda lupa yield return "Bar":) tapi ya, saya melihat di mana ini sekarang - foreachselalu memanggil metode publik bernama GetEnumerator, bahkan jika itu bukan implementasi nyata untuk IEnumerable.GetEnumerator. Ini tampaknya memiliki satu sudut lagi: bahkan jika Anda hanya memiliki satu kelas, dan mengimplementasikannya IEnumerablesecara eksplisit, ini berarti bahwa itu adalah perubahan sumber untuk menambahkan metode publik yang dinamai GetEnumerator, karena sekarang foreachakan menggunakan metode itu di atas implementasi antarmuka. Juga, masalah yang sama berlaku untuk IEnumeratorimplementasi ...
Pavel Minaev
6

Mengubah bidang ke properti

Jenis Istirahat: API

Bahasa yang Terkena Dampak: Visual Basic dan C # *

Info: Ketika Anda mengubah bidang atau variabel normal menjadi properti di visual basic, kode luar yang mereferensikan anggota dengan cara apa pun perlu dikompilasi ulang.

API Sebelum Perubahan:

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

API Setelah Perubahan:

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    

Contoh kode klien yang berfungsi tetapi rusak setelahnya:

Foo.Bar = "foobar"
Hagelt18
sumber
2
Ini sebenarnya akan memecah hal-hal dalam C # juga, karena properti tidak dapat digunakan untuk outdan refargumen metode, tidak seperti bidang, dan tidak dapat menjadi target &operator unary .
Pavel Minaev
5

Penambahan Namespace

Istirahat level sumber / semantik tenang level sumber berubah

Karena cara resolusi namespace bekerja di vb.Net, menambahkan namespace ke perpustakaan dapat menyebabkan kode Visual Basic yang dikompilasi dengan versi API sebelumnya untuk tidak dikompilasi dengan versi baru.

Contoh kode klien:

Imports System
Imports Api.SomeNamespace

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

Jika versi baru API menambahkan namespace Api.SomeNamespace.Data, maka kode di atas tidak akan dikompilasi.

Ini menjadi lebih rumit dengan impor namespace tingkat proyek. Jika Imports Systemdihilangkan dari kode di atas, tetapi Systemnamespace diimpor pada tingkat proyek, maka kode tersebut mungkin masih menghasilkan kesalahan.

Namun, jika Api menyertakan kelas DataRowdalam Api.SomeNamespace.Datanamespace -nya , maka kode tersebut akan dikompilasi tetapi drakan menjadi instance System.Data.DataRowketika dikompilasi dengan versi lama API dan Api.SomeNamespace.Data.DataRowketika dikompilasi dengan versi baru API.

Pengubahan Nama Argumen

Istirahat tingkat sumber

Mengubah nama argumen adalah perubahan besar dalam vb.net dari versi 7 (?) (.Net versi 1?) Dan c # .net dari versi 4 (.Net versi 4).

API sebelum perubahan:

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

API setelah perubahan:

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

Contoh kode klien:

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

Parameter referensi

Istirahat tingkat sumber

Menambahkan metode override dengan tanda tangan yang sama kecuali bahwa satu parameter dilewatkan oleh referensi, bukan oleh nilai akan menyebabkan sumber vb yang referensi API tidak dapat menyelesaikan fungsi. Visual Basic tidak memiliki cara (?) Untuk membedakan metode ini pada titik panggilan kecuali mereka memiliki nama argumen yang berbeda, sehingga perubahan seperti itu dapat menyebabkan kedua anggota tidak dapat digunakan dari kode vb.

API sebelum perubahan:

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

API setelah perubahan:

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

Contoh kode klien:

Api.SomeNamespace.Foo.Bar(str)

Kolom ke Perubahan Properti

Istirahat tingkat biner / Istirahat tingkat sumber

Selain jeda level biner yang jelas, ini dapat menyebabkan jeda level sumber jika anggota dilewatkan ke metode dengan referensi.

API sebelum perubahan:

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

API setelah perubahan:

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

Contoh kode klien:

FooBar(ref Api.SomeNamespace.Foo.Bar);
jswolf19
sumber
4

Perubahan API:

  1. Menambahkan atribut [Usang] (Anda agak menutupi ini dengan menyebutkan atribut; namun, ini bisa menjadi perubahan besar saat menggunakan peringatan-sebagai-kesalahan.)

Istirahat tingkat biner:

  1. Memindahkan jenis dari satu unit ke unit lainnya
  2. Mengubah namespace dari suatu tipe
  3. Menambahkan jenis kelas dasar dari rakitan lain.
  4. Menambahkan anggota baru (acara dilindungi) yang menggunakan tipe dari majelis lain (Class2) sebagai batasan argumen templat.

    protected void Something<T>() where T : Class2 { }
  5. Mengubah kelas anak (Class3) untuk berasal dari tipe di majelis lain ketika kelas digunakan sebagai argumen templat untuk kelas ini.

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

Semantik hening tingkat sumber berubah:

  1. Menambahkan / menghapus / mengubah override Equals (), GetHashCode (), atau ToString ()

(tidak yakin di mana ini cocok)

Perubahan penerapan:

  1. Menambah / menghapus dependensi / referensi
  2. Memperbarui dependensi ke versi yang lebih baru
  3. Mengubah 'platform target' antara x86, Itanium, x64, atau anycpu
  4. Bangunan / pengujian pada instalasi kerangka kerja yang berbeda (yaitu menginstal 3.5 pada kotak .Net 2.0 memungkinkan panggilan API yang kemudian membutuhkan .Net 2.0 SP2)

Perubahan Bootstrap / Konfigurasi:

  1. Menambah / Menghapus / Mengubah opsi konfigurasi khusus (mis. Pengaturan App.config)
  2. Dengan penggunaan IoC / DI dalam aplikasi todays yang berat, kadang diperlukan untuk mengkonfigurasi ulang dan / atau mengubah kode bootstrap untuk kode dependen DI.

Memperbarui:

Maaf, saya tidak menyadari bahwa satu-satunya alasan ini melanggar bagi saya adalah karena saya menggunakannya dalam kendala template.

csharptest.net
sumber
"Menambahkan anggota baru (dilindungi acara) yang menggunakan tipe dari majelis lain." - IIRC, klien hanya perlu mereferensikan majelis tergantung yang berisi tipe dasar majelis yang sudah dirujuk; itu tidak harus merujuk rakitan yang hanya digunakan (bahkan jika jenis dalam tanda tangan metode); Saya tidak 100% yakin tentang ini. Apakah Anda memiliki referensi untuk aturan yang tepat untuk ini? Juga, memindahkan suatu tipe bisa tidak melanggar jika TypeForwardedToAttributedigunakan.
Pavel Minaev
"TypeForwardedTo" itu adalah berita bagiku, aku akan memeriksanya. Adapun yang lain, saya juga tidak 100% di atasnya ... biarkan saya melihat apakah dapat repro dan saya akan memperbarui posting.
csharptest.net
Jadi, jangan memaksakan -Werrorsistem builds yang Anda kirimkan dengan tarbal rilis. Bendera ini paling membantu pengembang kode dan paling sering tidak membantu konsumen.
binki
@binki titik yang sangat baik, memperlakukan peringatan sebagai kesalahan harus cukup dalam membangun DEBUG saja.
csharptest.net
3

Menambahkan metode overload untuk mematikan penggunaan parameter default

Jenis istirahat: Semantik sunyi tingkat sumber berubah

Karena kompiler mengubah panggilan metode dengan nilai parameter default yang hilang ke panggilan eksplisit dengan nilai default di sisi panggilan, kompatibilitas untuk kode kompilasi yang ada diberikan; metode dengan tanda tangan yang benar akan ditemukan untuk semua kode yang dikompilasi sebelumnya.

Di sisi lain, panggilan tanpa penggunaan parameter opsional sekarang dikompilasi sebagai panggilan ke metode baru yang tidak memiliki parameter opsional. Semuanya masih berfungsi dengan baik, tetapi jika kode yang dipanggil berada di rakitan lain, pemanggilan kode yang baru dikompilasi sekarang tergantung pada versi baru rakitan ini. Menyebarkan majelis yang memanggil kode refactored tanpa juga menggunakan majelis tempat kode refactored berada menghasilkan "metode tidak ditemukan" pengecualian.

API sebelum perubahan

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

API setelah perubahan

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

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

Kode contoh yang masih berfungsi

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

Kode sampel yang sekarang tergantung pada versi baru saat dikompilasi

  public int CodeDependentToNewVersion()
  {
     return MyMethod(5); 
  }
Udontknow
sumber
1

Mengganti nama antarmuka

Agak Istirahat: Sumber dan Biner

Bahasa yang Terkena Dampak: Kemungkinan besar semua, diuji dalam C #.

API Sebelum Perubahan:

public interface IFoo
{
    void Test();
}

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

API Setelah Perubahan:

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

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

Contoh kode klien yang berfungsi tetapi rusak setelahnya:

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

Metode overloading dengan parameter tipe nullable

Jenis: Istirahat level sumber

Bahasa yang terpengaruh: C #, VB

API sebelum perubahan:

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

API setelah perubahan:

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

Contoh kode klien yang berfungsi sebelum perubahan dan rusak setelahnya:

new Foo().Bar(null);

Pengecualian: Panggilan tidak jelas antara metode atau properti berikut.

Bohdan Spilnyi
sumber
0

Promosi ke Metode Ekstensi

Jenis: istirahat level sumber

Bahasa yang terpengaruh: C # v6 dan lebih tinggi (mungkin yang lain?)

API sebelum perubahan:

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

API setelah perubahan:

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

Contoh kode klien yang berfungsi sebelum perubahan dan rusak setelahnya:

using static Foo;

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

Info Lebih Lanjut: https://github.com/dotnet/csharplang/issues/665

rory.ap
sumber