.NET'teki API kırıcı değişikliklere ilişkin kesin bir kılavuz


227

.NET / CLR'de API sürümlendirmesi ve özellikle API değişikliklerinin istemci uygulamalarını nasıl kırdığı veya bozmadığı hakkında mümkün olduğunca fazla bilgi toplamak istiyorum. İlk olarak, bazı terimleri tanımlayalım:

API değişikliği - herkese açık üyeleri de dahil olmak üzere bir türün herkes tarafından görülebilir tanımında yapılan bir değişiklik. Bu, tür ve üye adlarını değiştirme, bir türün temel türünü değiştirme, bir türün uygulanmış arabirimler listesine arabirim ekleme / kaldırma, üye ekleme / kaldırma (aşırı yükler dahil), üye görünürlüğünü değiştirme, yeniden adlandırma yöntemi ve tür parametreleri, varsayılan değerleri ekleme içerir yöntem parametreleri, türler ve üyeler üzerinde öznitelik ekleme / kaldırma ve türler ve üyeler üzerinde genel tür parametreleri ekleme / kaldırma (bir şey kaçırdım mı?). Bu, üye organlarındaki herhangi bir değişikliği veya özel üyelerde yapılan değişiklikleri içermez (yani, Yansımayı dikkate almayız).

İkili düzey sonu - potansiyel olarak yeni sürümle yüklenmeyen API'nin eski sürümüne karşı derlenen istemci derlemelerine neden olan bir API değişikliği. Örnek: önceki gibi aynı şekilde çağrılmasına izin verse bile yöntem imzasını değiştirme (yani: tür / parametre varsayılan değerlerinin aşırı yüklenmesi için geçersiz).

Kaynak düzeyinde kesme - potansiyel olarak yeni sürümle derlenmeyen API'nin eski sürümüne karşı derlenmek üzere yazılmış kodla sonuçlanan bir API değişikliği. Zaten derlenmiş istemci derlemeleri daha önce olduğu gibi çalışır. Örnek: daha önce kesin olmayan yöntem çağrılarında belirsizliğe yol açabilecek yeni bir aşırı yük eklemek.

Kaynak düzeyinde sessiz semantik değişikliği - mevcut kodun API'nin eski sürümüne göre derlenmesi için yazılan bir API değişikliği, örneğin farklı bir yöntem çağırarak anlambilimini sessizce değiştirir. Ancak kod hiçbir uyarı / hata olmadan derlemeye devam etmeli ve daha önce derlenmiş derlemeler önceki gibi çalışmalıdır. Örnek: Aşırı yük çözünürlüğü sırasında farklı bir aşırı yükün seçilmesine neden olan mevcut bir sınıfa yeni bir arayüz uygulamak.

Nihai amaç, mümkün olduğunca çok sayıda kırılma ve sessiz anlambilim API değişikliklerini kataloglamak ve kırmanın kesin etkisini ve hangi dillerin bundan etkilendiğini ve etkilenmediğini tanımlamaktır. İkincisini genişletmek için: bazı değişiklikler evrensel olarak tüm dilleri etkilerken (örn. Bir arayüze yeni bir üye eklemek, herhangi bir dilde bu arayüzün uygulamalarını kıracaktır), bazıları bir mola için oyuna girmek için çok özel bir dil semantiği gerektirir. Bu tipik olarak yöntem aşırı yüklemesini ve genel olarak örtük tip dönüşümlerle ilgili olan her şeyi içerir. Burada "en az ortak payda" yı CLS uyumlu diller için bile tanımlamanın bir yolu yok gibi görünüyor (yani en azından CLI spesifikasyonunda tanımlandığı gibi "CLS tüketici" kurallarına uyanlar) - Birisi beni burada yanlış olarak düzeltirse sevinirim - bu yüzden dile göre dil gitmek zorunda kalacak. En çok ilgilenenler doğal olarak .NET ile birlikte gelenlerdir: C #, VB ve F #; ancak IronPython, IronRuby, Delphi Prism vb. diğerleri de geçerlidir. Bir köşe vakası ne kadar fazlaysa, o kadar ilginç olur - üyeleri kaldırmak gibi şeyler oldukça belirgindir, ancak yöntem aşırı yüklenmesi, isteğe bağlı / varsayılan parametreler, lambda tipi çıkarım ve dönüşüm operatörleri arasındaki ince etkileşimler çok şaşırtıcı olabilir zaman zaman.

Bunu başlatmak için birkaç örnek:

Yeni yöntem aşırı yüklemeleri ekleme

Tür: kaynak düzeyinde mola

Etkilenen diller: C #, VB, F #

Değişiklikten önce API:

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

Değişiklikten sonra API:

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

Değişiklikten önce çalışan ve bundan sonra kırılan örnek istemci kodu:

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

Yeni örtülü dönüşüm operatörü aşırı yüklemeleri ekleme

Tür: kaynak düzeyinde mola.

Etkilenen diller: C #, VB

Etkilenmeyen diller: F #

Değişiklikten önce API:

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

Değişiklikten sonra API:

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

Değişiklikten önce çalışan ve bundan sonra kırılan örnek istemci kodu:

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

Notlar: F # bozuk değildir, çünkü aşırı yüklenmiş işleçler için herhangi bir dil düzeyi desteği yoktur, ne açık ne de örtük - her ikisi de doğrudan olarak op_Explicitve op_Implicityöntemler olarak adlandırılmalıdır .

Yeni örnek yöntemleri ekleme

Tür: kaynak düzeyinde sessiz semantik değişim.

Etkilenen diller: C #, VB

Etkilenmeyen diller: F #

Değişiklikten önce API:

public class Foo
{
}

Değişiklikten sonra API:

public class Foo
{
    public void Bar();
}

Sessiz bir anlam değişikliği içeren örnek istemci kodu:

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

new Foo().Bar();

Notlar: F # bozuk değildir, çünkü dil seviyesi desteği yoktur ExtensionMethodAttributeve CLS genişletme yöntemlerinin statik yöntem olarak adlandırılmasını gerektirir.


Şüphesiz Microsoft bunu zaten kapsamaktadır ... msdn.microsoft.com/en-us/netframework/aa570326.aspx
Robert Harvey

1
@Robert: bağlantınız çok farklı bir şeyle ilgilidir - .NET Framework'ün kendisinde belirli değişiklikler olduğunu açıklar . Bu açıklar daha geniş bir sorudur jenerik tanıtabilirsiniz kalıplarını kırma değişiklikleri de kendi (kütüphane / çerçeve yazar olarak) API'leri. MS'den tamamlanacak böyle bir belgenin farkında değilim, ancak eksik olanlar olsa bile, bu tür bağlantılar kesinlikle kabul edilebilir.
Pavel Minaev

Bu "mola" kategorilerinden herhangi birinde, sorunun sadece çalışma zamanında belirginleşeceği herhangi bir şey var mı?
Rohit

1
Evet, "ikili ara" kategorisi. Bu durumda, derlemenizin tüm sürümlerine göre derlenmiş bir üçüncü taraf derlemeniz zaten var. Derlemenizin yeni bir sürümünü yerinde bırakırsanız, üçüncü taraf derleme çalışmayı durdurur - ya çalışma zamanında yüklenmez ya da yanlış çalışır.
Pavel Minaev

Yanıtlar:


42

Yöntem imzasını değiştirme

Tür: İkili Seviye Mola

Etkilenen diller: C # (VB ve F # büyük olasılıkla, ancak test edilmemiş)

Değişiklikten önce API

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

Değişiklikten sonra API

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

Değişiklikten önce çalışan örnek müşteri kodu

Foo.bar(13);

15
Aslında, birisi için temsilci oluşturmaya çalışırsa, kaynak düzeyinde bir mola da olabilir bar.
Pavel Minaev

Bu da doğru. Şirketler uygulamamdaki yazdırma yardımcı programlarında bazı değişiklikler yaptığımda bu sorunu buldum. Güncelleştirme yayımlandığında, bu yardımcı programlara başvuran tüm DLL'ler yeniden derlenmemiş ve serbest bırakılmamıştır, böylece bir yöntem için bir istisna atmaz.
Justin Drury

1
Bu, dönüş türlerinin yöntemin imzası için sayılmadığı gerçeğine dayanır. Yalnızca dönüş türüne bağlı olarak iki işlevi aşırı yükleyemezsiniz. Aynı sorun.
Jason Short

1
Bu yanıta bir alt soru: Herkes bir dotnet4 varsayılan değeri 'genel statik boşluk çubuğu (int i = 0);' veya bu varsayılan değeri bir değerden diğerine değiştirmek mi?
k3b

1
Bu sayfaya inecek olanlar için C # (ve "sanırım" çoğu diğer OOP dili) için, Dönüş Türleri yöntem imzasına katkıda bulunmaz. Evet yanıtı, İmza değişikliklerinin İkili düzey değişikliğine katkıda bulunduğu doğrudur. ANCAK örnek doğru görünmüyor IMHO genel ondalık toplam ÖNCE olduğunu düşünebildiğim doğru örnek Sum (int a, int b) Genel ondalık toplamdan sonra Sum (ondalık a, ondalık b) Lütfen bu MSDN bağlantısını 3.6 İmzalar ve aşırı yükleme
Bhanu Chhabra

40

Varsayılan değere sahip bir parametre ekleme.

Tür Arası: İkili seviye sonu

Çağıran kaynak kodunun değiştirilmesi gerekmese bile, yine de derlenmesi gerekir (normal bir parametre eklerken olduğu gibi).

Çünkü C #, parametrelerin varsayılan değerlerini doğrudan çağıran derlemeye derler. Yeniden derlemezseniz, eski derleme daha az bağımsız değişkeni olan bir yöntemi çağırmaya çalıştığı için bir MissingMethodException elde edersiniz.

Değişiklikten Önce API

public void Foo(int a) { }

Değişiklikten Sonra API

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

Daha sonra kırılan örnek istemci kodu

Foo(5);

İstemci kodunun Foo(5, null)bayt kodu düzeyinde yeniden derlenmesi gerekir . Çağrılan derleme yalnızca içerecektir Foo(int, string), içermeyecektir Foo(int). Varsayılan parametre değerlerinin yalnızca bir dil özelliği olması nedeniyle, .Net çalışma zamanı bunlar hakkında hiçbir şey bilmez. (Bu ayrıca varsayılan değerlerin neden C # 'da derleme zamanı sabitleri olması gerektiğini açıklar).


2
bu, kaynak kodu düzeyi için bile bir kırılma değişikliğidir: Func<int> f = Foo;// bu, değiştirilen imza ile başarısız olur
Vagaus

26

Bu, özellikle arabirimler için aynı durumdaki farkın ışığında, keşfettiğimde çok açık değildi. Hiç bir ara değil, ama dahil etmeye karar vereceğim kadar şaşırtıcı:

Sınıf üyelerini temel sınıfa yeniden düzenleme

Tür: ara değil!

Etkilenen diller: yok (yani hiçbiri kırık değil)

Değişiklikten önce API:

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

Değişiklikten sonra API:

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

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

(Kırılması bekleniyor olsa bile) değişiklik boyunca çalışmaya devam eden örnek kod:

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

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

Notlar:

C ++ / CLI, sanal temel sınıf üyeleri için "açık geçersiz kılma" için açık arabirim uygulamasına benzer bir yapıya sahip tek .NET dilidir. Arayüz üyelerini bir temel arayüze taşırken aynı kırılma ile sonuçlanmasını bekledim (çünkü açık geçersiz kılma için oluşturulan IL, açık uygulama ile aynıdır). Şaşırtıcı bir şekilde, durum böyle değil - üretilen IL hala BarOverridegeçersiz kılmaları Foo::Baryerine geçersiz kıldığını belirtmesine rağmen FooBase::Bar, montaj yükleyicisi herhangi bir şikayeti olmadan diğerini doğru bir şekilde ikame edecek kadar akıllıdır - görünüşe göre, Foobir sınıf olması, farkı yaratan şeydir. Şekle git ...


3
Temel sınıf aynı montajda olduğu sürece. Aksi takdirde, ikili bir kırılma değişikliğidir.
Jeremy

@ Jeremy bu durumda ne tür bir kod kırıyor? Herhangi bir harici arayanın Baz () kullanımı kırılacak mı yoksa sadece Foo'yu genişletmeye ve Baz () 'ı geçersiz kılmaya çalışan kişilerle ilgili bir sorun mu?
ChaseMedallion

@ İkinci Aşama Eğer ikinci el bir kullanıcı iseniz kırılıyor. Örneğin, derlenmiş DLL, Foo'nun eski bir sürümüne başvuruyor ve bu derlenmiş DLL'ye başvuruyor, ancak aynı zamanda Foo DLL'in daha yeni bir sürümünü kullanıyorsunuz. Garip bir hatayla kırılıyor ya da en azından benim için daha önce geliştirdiğim kütüphanelerde yaptı.
Jeremy

19

Bu "arayüz üyeleri ekleme / çıkarma" belki de o kadar da belirgin olmayan özel bir durum, ve ben bir sonraki göndereceğim başka bir dava ışığında kendi girişini hak ettiğini düşündüm. Yani:

Arabirim üyelerini temel bir arabirime yeniden düzenleme

Tür: hem kaynak hem de ikili seviyelerde kesmeler

Etkilenen diller: C #, VB, C ++ / CLI, F # (kaynak molası için; ikili dil doğal olarak herhangi bir dili etkiler)

Değişiklikten önce API:

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

Değişiklikten sonra API:

interface IFooBase 
{
    void Bar();
}

interface IFoo : IFooBase
{
    void Baz();
}

Kaynak düzeyinde değişiklik nedeniyle kesilen örnek istemci kodu:

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

İkili düzeydeki değişiklik nedeniyle kesilen örnek istemci kodu;

(new Foo()).Bar();

Notlar:

Kaynak düzeyinde kopma için sorun, C #, VB ve C ++ / CLI'nin tümü , arabirim üyesi uygulama bildiriminde tam arabirim adı gerektirmesidir ; bu nedenle, üye bir temel arayüze taşınırsa, kod artık derlenmeyecektir.

İkili kırılma, arabirim yöntemlerinin, açık uygulamalar için oluşturulan IL'de tam olarak kalifiye olmasından kaynaklanır ve arabirim adı da kesin olmalıdır.

Mümkün olduğunda örtük uygulama (yani C # ve C ++ / CLI, ancak VB değil) hem kaynak hem de ikili düzeyde iyi çalışır. Yöntem çağrıları da kesilmez.


Bu, tüm diller için geçerli değildir. VB için bu bir kaynak kodu değişikliği değildir. C # için öyle.
Jeremy

Yani Implements IFoo.Barşeffaf referans alır IFooBase.Bar?
Pavel Minaev

Evet, aslında, bir üyeyi uyguladığınızda devralma arabirimi aracılığıyla doğrudan veya dolaylı olarak başvurabilirsiniz. Ancak, bu her zaman kırıcı bir ikili değişikliktir.
Jeremy

15

Numaralandırılmış değerleri yeniden sıralama

Mola türü: Kaynak seviyesi / İkili seviye sessiz semantik değişikliği

Etkilenen diller: hepsi

Numaralandırılmış değerleri yeniden sıralamak, değişmez değerler aynı ada sahip olduğu için kaynak düzeyinde uyumluluğu korur, ancak sıralı dizinleri güncellenir, bu da bazı sessiz kaynak düzeyinde kesintilere neden olabilir.

Daha da kötüsü, istemci kodu yeni API sürümüne göre yeniden derlenmezse uygulanabilecek sessiz ikili düzey sonlarıdır. Enum değerleri derleme zamanı sabitleridir ve bu nedenle bunların herhangi bir kullanımı istemci derlemesinin IL'sine dönüştürülür. Bu vakanın zaman zaman tespit edilmesi özellikle zor olabilir.

Değişiklikten Önce API

public enum Foo
{
   Bar,
   Baz
}

Değişiklikten Sonra API

public enum Foo
{
   Baz,
   Bar
}

Çalışan ancak daha sonra bozulan örnek istemci kodu:

Foo.Bar < Foo.Baz

12

Bu gerçekten pratikte çok nadir bir şeydir, ancak yine de olduğunda şaşırtıcı bir şeydir.

Aşırı yüklenmemiş yeni üyeler ekleme

Tür: kaynak düzeyinde kopma veya sessiz anlambilim değişikliği.

Etkilenen diller: C #, VB

Etkilenmeyen diller: F #, C ++ / CLI

Değişiklikten önce API:

public class Foo
{
}

Değişiklikten sonra API:

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

Değişiklik nedeniyle bozulan örnek istemci kodu:

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

Notlar:

Buradaki sorun, aşırı yük çözünürlüğü varlığında C # ve VB'deki lambda tipi çıkarımdan kaynaklanmaktadır. Burada, lambda gövdesinin belirli bir tip için mantıklı olup olmadığını kontrol ederek, birden fazla tipin eşleştiği bağları koparmak için sınırlı bir ördek tiplemesi kullanılır - eğer sadece bir tip derlenebilir gövdeyle sonuçlanırsa, bu seçilir.

Buradaki tehlike, istemci kodunun, bazı yöntemlerin kendi türlerinde bağımsız değişkenler aldığı ve diğerlerinin kütüphaneniz tarafından ortaya çıkarılan tür argümanları aldığı aşırı yüklenmiş bir yöntem grubuna sahip olabilmesidir. Kodlarından herhangi biri yalnızca üyelerin varlığına veya yokluğuna bağlı olarak doğru yöntemi belirlemek için tür çıkarım algoritmasına dayanıyorsa, istemcinin türlerinden biriyle aynı ada sahip türlerinize yeni bir üye eklemek potansiyel olarak çıkarım yapabilir aşırı yük çözünürlüğü sırasında belirsizliğe neden olur.

Türlerin Foove Barbu örnekte hiçbir şekilde kalıtımla veya başka bir şekilde ilişkili olmadığını unutmayın. Bunları tek bir yöntem grubunda kullanmak, bunu tetiklemek için yeterlidir ve bu istemci kodunda gerçekleşirse, üzerinde herhangi bir kontrolünüz yoktur.

Yukarıdaki örnek kod, bunun kaynak düzeyinde bir kopma olduğu daha basit bir durumu gösterir (yani derleyici hatası sonuçları). Bununla birlikte, çıkarım yoluyla seçilen aşırı yükün aşağıda sıralanmasına neden olacak başka argümanlar varsa (örn. Varsayılan değerlere sahip isteğe bağlı argümanlar veya örtük gerektiren beyan edilen ve gerçek argüman arasındaki tür uyuşmazlığı) sessiz semantik bir değişiklik de olabilir. dönüştürmek). Böyle bir senaryoda, aşırı yük çözünürlüğü artık başarısız olmaz, ancak derleyici tarafından farklı bir aşırı yüklenme sessizce seçilir. Bununla birlikte, pratikte, kasıtlı olarak buna neden olmak için yöntem imzalarını dikkatlice oluşturmadan bu davaya girmek çok zordur.


9

Örtük bir arayüz uygulamasını açık bir uygulamaya dönüştürün.

Tür Bir Ara: Kaynak ve İkili

Etkilenen Diller: Tümü

Bu gerçekten sadece bir yöntemin erişilebilirliğini değiştirmenin bir çeşididir - bu sadece biraz daha incedir çünkü bir arayüzün yöntemlerine tüm erişimin mutlaka arayüzün türüne atıfta bulunmadığı gerçeğini göz ardı etmek kolaydır.

Değişiklikten Önce API:

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

Değişiklikten Sonra API:

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

Değişiklikten önce çalışan ve daha sonra bozulan örnek İstemci kodu:

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

7

Açık bir arabirim uygulamasını örtük bir uygulamaya dönüştürün.

Tür Bir Ara: Kaynak

Etkilenen Diller: Tümü

Açık bir arayüz uygulamasının örtük bir uygulamaya yeniden uyarlanması, bir API'yi nasıl parçalayabileceğinden daha incedir. Yüzeyde, bunun nispeten güvenli olması gerektiği görülüyor, ancak kalıtımla birleştirildiğinde sorunlara neden olabilir.

Değişiklikten Önce API:

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

Değişiklikten Sonra API:

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

Değişiklikten önce çalışan ve daha sonra bozulan örnek İstemci kodu:

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"

Maalesef, tam olarak takip etmiyorum - değişiklikten önce Fooherkese açık bir yöntem bulunmadığı için API değişikliğinden önce örnek kod hiç derlenmeyecekti GetEnumeratorve yöntemi tür referansı ile çağırıyorsunuz Foo. .
Pavel Minaev

Gerçekten de, hafızadan bir örnek sadeleştirmeye çalıştım ve sonuç 'foobar' (pardon pun). Örneği doğru bir şekilde göstermek için güncelledim (ve derlenebilir).
LBushkin

Örneğimde, sorun sadece bir arayüz yönteminin örtük olmaktan kamusal olmaya geçişinden daha fazlasıdır. C # derleyicisinin bir foreach döngüsünde hangi yöntemi çağıracağını belirleme şekline bağlıdır. Derleyici ses çözünürlük kuralları göz önüne alındığında, türetilmiş sınıf sürümü temel sınıf sürümüne geçer.
LBushkin

Unuttun yield return "Bar":) ama evet, bunun nereye gittiğini görüyorum - gerçek uygulama olmasa bile, foreachher zaman adlandırılan genel yöntemi çağırır . Bunun bir açısı daha var: sadece bir sınıfınız olsa bile ve açıkça uygularsa , bunun adı verilen bir genel yöntem eklemek için kaynak kırma değişikliği olduğu anlamına gelir , çünkü şimdi bu yöntemi arayüz uygulaması üzerinde kullanacaktır. Ayrıca, aynı sorun uygulama için de geçerlidir ...GetEnumeratorIEnumerable.GetEnumeratorIEnumerableGetEnumeratorforeachIEnumerator
Pavel Minaev

6

Bir alanı bir mülke değiştirme

Mola Türü: API

Etkilenen Diller: Visual Basic ve C # *

Bilgi: Normal bir alanı veya değişkeni visual basic içinde bir özelliğe değiştirdiğinizde, o üyeye herhangi bir şekilde başvuran dış kodların yeniden derlenmesi gerekir.

Değişiklikten Önce API:

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

Değişiklikten Sonra 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    

Çalışan ancak daha sonra bozulan örnek istemci kodu:

Foo.Bar = "foobar"

2
C # 'daki şeyleri de kıracaktır, çünkü özellikler alanlardan farklı olarak ve yöntemlerin argümanları için kullanılamaz outve reftekli &operatörün hedefi olamaz .
Pavel Minaev

5

Ad Alanı Ekleme

Kaynak düzeyinde kesme / Kaynak düzeyinde sessiz anlambilim değişikliği

Ad alanı çözümlemesinin vb.Net'te çalışma şekli nedeniyle, kitaplığa ad alanı eklemek, API'nin önceki bir sürümü ile derlenen Visual Basic kodunun yeni bir sürümle derlenmemesine neden olabilir.

Örnek müşteri kodu:

Imports System
Imports Api.SomeNamespace

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

API'nın yeni bir sürümü ad alanını eklerse Api.SomeNamespace.Data, yukarıdaki kod derlenmez.

Proje düzeyinde ad alanı ithalatı ile daha karmaşık hale gelir. Eğer Imports Systemyukarıdaki koddan atlanırsa, ancak Systemad proje düzeyinde içe, sonra kod hala bir hataya yol açabilir.

Ancak, Api DataRowkendi Api.SomeNamespace.Dataad alanında bir sınıf içeriyorsa , kod derlenecek, ancak API'nin eski sürümü ile derlendiğinde ve API'nin yeni sürümü ile derlendiğinde drbunun bir örneği olacaktır .System.Data.DataRowApi.SomeNamespace.Data.DataRow

Bağımsız Değişken Yeniden Adlandırma

Kaynak düzeyinde kopma

Bağımsız değişkenlerin adlarını değiştirmek vb.net'te sürüm 7'den (?) (.Net sürüm 1?) Ve c # .net'ten sürüm 4'ten (.Net sürüm 4) önemli bir değişikliktir.

Değişiklikten önce API:

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

Değişiklikten sonra API:

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

Örnek müşteri kodu:

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

Ref Parametreleri

Kaynak düzeyinde kopma

Tek bir parametrenin değere göre başvuru tarafından geçirilmesi dışında aynı imzayla bir yöntem geçersiz kılma eklenmesi, API'ye başvuran vb kaynağının işlevi çözümleyememesine neden olur. Visual Basic, farklı bağımsız değişken adları olmadığı sürece bu yöntemleri çağrı noktasında ayırt etmek için hiçbir yolu (?), Bu tür bir değişiklik her iki üyenin vb kod kullanılamaz neden olabilir.

Değişiklikten önce API:

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

Değişiklikten sonra API:

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

Örnek müşteri kodu:

Api.SomeNamespace.Foo.Bar(str)

Sahadan Mülke Değişiklik

İkili düzey sonu / Kaynak düzeyinde kesinti

Belirgin ikili düzeydeki kesintinin yanı sıra, üye başvuru ile bir yönteme geçirilirse kaynak düzeyinde bir kesintiye neden olabilir.

Değişiklikten önce API:

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

Değişiklikten sonra API:

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

Örnek müşteri kodu:

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

4

API değişikliği:

  1. [Eskimiş] özniteliğini eklemek (bunu, söz konusu özniteliklerden bahsetmişsinizdir; ancak, hata olarak uyarı kullanırken bu bir kırılma değişikliği olabilir.)

İkili seviye sonu:

  1. Bir türü bir montajdan diğerine taşıma
  2. Bir türün ad alanını değiştirme
  3. Başka bir derlemeden temel sınıf türü ekleme.
  4. Şablon bağımsız değişkeni kısıtlaması olarak başka bir montajdan (Class2) bir tür kullanan yeni bir üye (olay korumalı) ekleme.

    protected void Something<T>() where T : Class2 { }
  5. Sınıf bu sınıf için şablon bağımsız değişkeni olarak kullanıldığında, başka bir derlemedeki türden türetilecek bir alt sınıfın (Sınıf3) değiştirilmesi.

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

Kaynak düzeyinde sessiz semantik değişikliği:

  1. Eşittir (), GetHashCode () veya ToString () geçersiz kılmalarını ekleme / kaldırma / değiştirme

(bunların nereye uyduğundan emin değilim)

Dağıtım değişiklikleri:

  1. Bağımlılık / referans ekleme / kaldırma
  2. Bağımlılıkları daha yeni sürümlere güncelleme
  3. 'Hedef platformu' x86, Itanium, x64 veya anycpu arasında değiştirme
  4. Farklı bir çerçeve yüklemesi oluşturma / test etme (ör. Bir .Net 2.0 kutusuna 3.5 yükleme, .Net 2.0 SP2 gerektiren API çağrılarına izin verir)

Önyükleme / Yapılandırma değişiklikleri:

  1. Özel yapılandırma seçenekleri Ekleme / Kaldırma / Değiştirme (örn. App.config ayarları)
  2. Günümüz uygulamalarında IoC / DI'nın yoğun kullanımı nedeniyle, DI bağımlı kod için önyükleme kodunu yeniden yapılandırmak ve / veya değiştirmek gerekir.

Güncelleme:

Üzgünüm, bunun benim için kırılmasının tek sebebinin onları şablon kısıtlamalarında kullanmam olduğunun farkında değildim.


"Başka bir derleme türünü kullanan yeni bir üye (olay korumalı) ekleme." - IIRC, istemcinin yalnızca zaten başvurduğu derlemelerin temel türlerini içeren bağımlı derlemelere başvurması gerekir; yalnızca kullanılan montajlara başvurmak zorunda değildir (türler yöntem imzalarında olsa bile); Bundan% 100 emin değilim. Bunun için kesin kurallar için bir referansınız var mı? Ayrıca, bir türün taşınması TypeForwardedToAttributekullanılıyorsa kırılmaz olabilir .
Pavel Minaev

Bu "TypeForwardedTo" benim için bir haber, kontrol edeceğim. Diğerine gelince, ben de% 100 değilim ... üreme yapabilir miyim bakalım ve yazı güncelleyeceğim.
csharptest.net

Öyleyse, -Werrorserbest bırakma tarball'larıyla birlikte gönderdiğiniz yapı sisteminizde zorlanmayın. Bu bayrak, kodun geliştiricisi için en yararlıdır ve çoğu zaman tüketici için yararsızdır.
binki

@binki mükemmel nokta, uyarıların hata olarak ele alınması sadece DEBUG yapılarında yeterli olmalıdır.
csharptest.net

3

Varsayılan parametre kullanımını azaltmak için aşırı yük yöntemleri ekleme

Bir tür mola: Kaynak düzeyinde sessiz anlambilim değişikliği

Derleyici, varsayılan parametre değerleri olmayan yöntem çağrılarını, çağıran tarafta varsayılan değeri olan açık bir çağrıya dönüştürdüğü için, mevcut derlenmiş kod için uyumluluk verilir; önceden derlenmiş tüm kodlar için doğru imzalı bir yöntem bulunacaktır.

Diğer taraftan, isteğe bağlı parametreler kullanılmadan yapılan çağrılar artık isteğe bağlı parametreyi içermeyen yeni yönteme çağrı olarak derlenmektedir. Her şey hala iyi çalışıyor, ancak çağrılan kod başka bir derlemede bulunuyorsa, derlenen yeni derlenmiş kod şimdi bu derlemenin yeni sürümüne bağımlıdır. Ayrıca, yeniden düzenlenmiş kodun bulunduğu derleme de dağıtılmadan yeniden düzenlenmiş kodu çağıran derlemeleri dağıtmak, "yöntem bulunamadı" özel durumlarına neden olur.

Değişiklikten önce API

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

Değişiklikten sonra API

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

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

Hala çalışacak örnek kod

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

Derleme sırasında şimdi yeni sürüme bağımlı olan örnek kod

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

1

Bir arayüzü yeniden adlandırma

Kinda of Break: Kaynak ve İkili

Etkilenen Diller: Büyük olasılıkla C # 'da test edilmiştir.

Değişiklikten Önce API:

public interface IFoo
{
    void Test();
}

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

Değişiklikten Sonra API:

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

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

Çalışan ancak daha sonra bozulan örnek istemci kodu:

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

1

Sıfırlanabilir tipte bir parametre ile aşırı yükleme yöntemi

Tür: Kaynak düzeyinde mola

Etkilenen diller: C #, VB

Değişiklikten önce API:

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

Değişiklikten sonra API:

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

Değişiklikten önce çalışan ve bundan sonra kırılan örnek istemci kodu:

new Foo().Bar(null);

İstisna: Aşağıdaki yöntemler veya özellikler arasında çağrı belirsizdir.


0

Bir Genişletme Yöntemine Yükseltme

Tür: kaynak düzeyinde mola

Etkilenen diller: C # v6 ve üstü (belki diğerleri?)

Değişiklikten önce API:

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

Değişiklikten sonra API:

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

Değişiklikten önce çalışan ve bundan sonra kırılan örnek istemci kodu:

using static Foo;

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

Daha fazla bilgi: https://github.com/dotnet/csharplang/issues/665

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.