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_Explicit
dan op_Implicit
metode.
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.
sumber
Jawaban:
Mengubah tanda tangan metode
Jenis: Istirahat tingkat biner
Bahasa yang terpengaruh: C # (VB dan F # kemungkinan besar, tetapi tidak diuji)
API sebelum perubahan
API setelah perubahan
Contoh kode klien berfungsi sebelum perubahan
sumber
bar
.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
API Setelah Berubah
Contoh kode klien yang rusak setelahnya
Kode klien perlu dikompilasi ulang
Foo(5, null)
pada level bytecode. Majelis yang dipanggil hanya akan berisiFoo(int, string)
, bukanFoo(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 #).sumber
Func<int> f = Foo;
// ini akan gagal dengan tanda tangan yang diubahYang 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:
API setelah perubahan:
Kode contoh yang terus berfungsi sepanjang perubahan (meskipun saya perkirakan akan rusak):
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
BarOverride
menimpaFoo::Bar
daripadaFooBase::Bar
, perakitan loader cukup pintar untuk menggantikan satu dengan yang lain dengan benar tanpa keluhan - tampaknya, fakta bahwaFoo
kelas adalah yang membuat perbedaan. Go figure ...sumber
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:
API setelah perubahan:
Contoh kode klien yang dipecah oleh perubahan di tingkat sumber:
Contoh kode klien yang dipecah oleh perubahan pada tingkat biner;
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.
sumber
Implements IFoo.Bar
akankah referensi transparanIFooBase.Bar
?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
API Setelah Berubah
Contoh kode klien yang berfungsi tetapi rusak setelahnya:
sumber
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:
API setelah perubahan:
Contoh kode klien yang rusak oleh perubahan:
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
Foo
danBar
dalam 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.
sumber
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:
API Setelah Perubahan:
Contoh kode Klien yang berfungsi sebelum perubahan dan rusak setelahnya:
sumber
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:
API Setelah Perubahan:
Contoh kode Klien yang berfungsi sebelum perubahan dan rusak setelahnya:
sumber
Foo
tidak memiliki nama metode publikGetEnumerator
, dan Anda memanggil metode tersebut melalui referensi tipeFoo
.. .yield return "Bar"
:) tapi ya, saya melihat di mana ini sekarang -foreach
selalu memanggil metode publik bernamaGetEnumerator
, bahkan jika itu bukan implementasi nyata untukIEnumerable.GetEnumerator
. Ini tampaknya memiliki satu sudut lagi: bahkan jika Anda hanya memiliki satu kelas, dan mengimplementasikannyaIEnumerable
secara eksplisit, ini berarti bahwa itu adalah perubahan sumber untuk menambahkan metode publik yang dinamaiGetEnumerator
, karena sekarangforeach
akan menggunakan metode itu di atas implementasi antarmuka. Juga, masalah yang sama berlaku untukIEnumerator
implementasi ...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:
API Setelah Perubahan:
Contoh kode klien yang berfungsi tetapi rusak setelahnya:
sumber
out
danref
argumen metode, tidak seperti bidang, dan tidak dapat menjadi target&
operator unary .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:
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 System
dihilangkan dari kode di atas, tetapiSystem
namespace diimpor pada tingkat proyek, maka kode tersebut mungkin masih menghasilkan kesalahan.Namun, jika Api menyertakan kelas
DataRow
dalamApi.SomeNamespace.Data
namespace -nya , maka kode tersebut akan dikompilasi tetapidr
akan menjadi instanceSystem.Data.DataRow
ketika dikompilasi dengan versi lama API danApi.SomeNamespace.Data.DataRow
ketika 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:
API setelah perubahan:
Contoh kode klien:
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:
API setelah perubahan:
Contoh kode klien:
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:
API setelah perubahan:
Contoh kode klien:
sumber
Perubahan API:
Istirahat tingkat biner:
Menambahkan anggota baru (acara dilindungi) yang menggunakan tipe dari majelis lain (Class2) sebagai batasan argumen templat.
Mengubah kelas anak (Class3) untuk berasal dari tipe di majelis lain ketika kelas digunakan sebagai argumen templat untuk kelas ini.
Semantik hening tingkat sumber berubah:
(tidak yakin di mana ini cocok)
Perubahan penerapan:
Perubahan Bootstrap / Konfigurasi:
Memperbarui:
Maaf, saya tidak menyadari bahwa satu-satunya alasan ini melanggar bagi saya adalah karena saya menggunakannya dalam kendala template.
sumber
TypeForwardedToAttribute
digunakan.-Werror
sistem builds yang Anda kirimkan dengan tarbal rilis. Bendera ini paling membantu pengembang kode dan paling sering tidak membantu konsumen.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
API setelah perubahan
Kode contoh yang masih berfungsi
Kode sampel yang sekarang tergantung pada versi baru saat dikompilasi
sumber
Mengganti nama antarmuka
Agak Istirahat: Sumber dan Biner
Bahasa yang Terkena Dampak: Kemungkinan besar semua, diuji dalam C #.
API Sebelum Perubahan:
API Setelah Perubahan:
Contoh kode klien yang berfungsi tetapi rusak setelahnya:
sumber
Metode overloading dengan parameter tipe nullable
Jenis: Istirahat level sumber
Bahasa yang terpengaruh: C #, VB
API sebelum perubahan:
API setelah perubahan:
Contoh kode klien yang berfungsi sebelum perubahan dan rusak setelahnya:
Pengecualian: Panggilan tidak jelas antara metode atau properti berikut.
sumber
Promosi ke Metode Ekstensi
Jenis: istirahat level sumber
Bahasa yang terpengaruh: C # v6 dan lebih tinggi (mungkin yang lain?)
API sebelum perubahan:
API setelah perubahan:
Contoh kode klien yang berfungsi sebelum perubahan dan rusak setelahnya:
Info Lebih Lanjut: https://github.com/dotnet/csharplang/issues/665
sumber