.NET में एपीआई-ब्रेकिंग परिवर्तन के लिए एक निश्चित गाइड


227

मैं .NET .NET / CLR में API वर्जनिंग के बारे में अधिक से अधिक जानकारी एकत्र करना चाहूंगा, और विशेष रूप से कैसे एपीआई क्लाइंट एप्लिकेशन को तोड़ता है या नहीं तोड़ता है। सबसे पहले, कुछ शब्दों को परिभाषित करते हैं:

एपीआई परिवर्तन - किसी भी सार्वजनिक सदस्य सहित एक प्रकार की सार्वजनिक रूप से दिखाई देने वाली परिभाषा में परिवर्तन। इसमें बदलते प्रकार और सदस्य के नाम, प्रकार के आधार प्रकार को बदलना, एक प्रकार के कार्यान्वित इंटरफेस की सूची से इंटरफेस जोड़ना / हटाना, सदस्यों को जोड़ना / हटाना (ओवरलोड सहित), सदस्य दृश्यता बदलना, विधि और प्रकार के मापदंडों को बदलना, डिफ़ॉल्ट मान जोड़ना विधि मापदंडों के लिए, प्रकार और सदस्यों पर विशेषताओं को जोड़ना / हटाना, और प्रकारों और सदस्यों पर सामान्य प्रकार के मापदंडों को जोड़ना / हटाना (क्या मैं कुछ भी जीवित हूं?)। इसमें सदस्य निकायों में कोई भी बदलाव, या निजी सदस्यों में कोई बदलाव शामिल नहीं है (यानी हम इस पर ध्यान नहीं देते)।

बाइनरी-लेवल ब्रेक - एक एपीआई परिवर्तन जो क्लाइंट असेंबली में परिणाम करता है जो कि पुराने संस्करण के खिलाफ संकलित होता है, संभवतः नए संस्करण के साथ लोड नहीं हो रहा है। उदाहरण: बदलती विधि हस्ताक्षर, भले ही इसे पहले की तरह से ही बुलाया जाए (यानी: टाइप करने के लिए शून्य / पैरामीटर डिफ़ॉल्ट मान अधिभार)।

स्रोत-स्तरीय विराम - एक एपीआई परिवर्तन जिसके परिणामस्वरूप मौजूदा कोड को एपीआई के पुराने संस्करण के खिलाफ संकलन करने के लिए लिखा गया है, संभवतः नए संस्करण के साथ संकलन नहीं है। हालाँकि पहले से संकलित क्लाइंट असेंबलियाँ पहले की तरह काम करती हैं। उदाहरण: एक नया अधिभार जोड़ना जो विधि कॉल में अस्पष्टता के परिणामस्वरूप हो सकता है जो कि पिछले अस्पष्ट थे।

स्रोत-स्तरीय शांत शब्दार्थ परिवर्तन - एक एपीआई परिवर्तन जिसके परिणामस्वरूप मौजूदा कोड में एपीआई के पुराने संस्करण के खिलाफ संकलन करने के लिए चुपचाप अपने शब्दार्थ को बदलते हैं, जैसे एक अलग विधि को कॉल करके। हालांकि कोड को बिना किसी चेतावनी / त्रुटियों के संकलित करना जारी रखना चाहिए, और पहले संकलित असेंबलियों को पहले की तरह काम करना चाहिए। उदाहरण: किसी मौजूदा वर्ग पर एक नया इंटरफ़ेस लागू करना जिसके परिणामस्वरूप अधिभार संकल्प के दौरान एक अलग अधिभार चुना जाता है।

अंतिम लक्ष्य संभव के रूप में कई ब्रेकिंग और शांत शब्दार्थ एपीआई परिवर्तनों को सूचीबद्ध करना है, और ब्रेक्जिट के सटीक प्रभाव का वर्णन करना है, और कौन सी भाषाएं हैं और इससे प्रभावित नहीं हैं। उत्तरार्द्ध में विस्तार करने के लिए: जबकि कुछ परिवर्तन सभी भाषाओं को सार्वभौमिक रूप से प्रभावित करते हैं (जैसे कि एक इंटरफ़ेस में एक नया सदस्य जोड़ना किसी भी भाषा में उस इंटरफ़ेस के कार्यान्वयन को तोड़ देगा), कुछ को तोड़ने के लिए नाटक में प्रवेश करने के लिए बहुत विशिष्ट भाषा शब्दार्थ की आवश्यकता होती है। इस सबसे आम तौर पर विधि अधिभार शामिल है, और, सामान्य रूप से, अंतर्निहित प्रकार के रूपांतरणों के साथ कुछ भी करना है। सीएलएस-अनुरूप भाषाओं के लिए यहां तक ​​कि "कम से कम सामान्य भाजक" को परिभाषित करने का कोई भी तरीका प्रतीत नहीं होता है (यानी सीएलआई युक्ति में परिभाषित "सीएलएस उपभोक्ता" के नियमों के अनुसार कम से कम) - हालांकि मैं ' सराहना करेंगे अगर कोई मुझे गलत होने के रूप में यहाँ सही करता है - तो यह भाषा से भाषा में जाना होगा। सबसे अधिक रुचि स्वाभाविक रूप से वे हैं जो बॉक्स से .NET के साथ आते हैं: C #, VB और F #; लेकिन अन्य, जैसे आयरनपाइथन, आयरनरुबी, डेल्फी प्रिज्म आदि भी प्रासंगिक हैं। एक कोने के मामले में जितना अधिक होगा, उतना ही दिलचस्प होगा - सदस्यों को हटाने जैसी चीजें बहुत ही स्व-स्पष्ट हैं, लेकिन उदाहरण के लिए सूक्ष्म बातचीत जैसे कि विधि अधिभार, वैकल्पिक / डिफ़ॉल्ट पैरामीटर, लैम्ब्डा प्रकार का अनुमान, और रूपांतरण ऑपरेटर बहुत आश्चर्यचकित हो सकते हैं कभी कभी।

इसे किकस्टार्ट करने के लिए कुछ उदाहरण:

नई विधि अधिभार जोड़ना

प्रकार: स्रोत-स्तरीय विराम

प्रभावित भाषाएँ: C #, VB, F #

परिवर्तन से पहले एपीआई:

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

परिवर्तन के बाद एपीआई:

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

नमूना ग्राहक कोड परिवर्तन से पहले काम कर रहा है और उसके बाद टूट गया है:

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

नए निहित रूपांतरण ऑपरेटर को अधिभार में जोड़ना

प्रकार: स्रोत-स्तरीय विराम।

प्रभावित भाषाएँ: C #, VB

प्रभावित न होने वाली भाषाएँ: F #

परिवर्तन से पहले एपीआई:

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

परिवर्तन के बाद एपीआई:

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

नमूना ग्राहक कोड परिवर्तन से पहले काम कर रहा है और उसके बाद टूट गया है:

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

नोट: F # टूटा नहीं है, क्योंकि इसमें ओवरलोड ऑपरेटरों के लिए कोई भाषा स्तर का समर्थन नहीं है, न तो स्पष्ट और न ही अंतर्निहित - दोनों को सीधे op_Explicitऔर op_Implicitविधियों के रूप में बुलाया जाना है ।

नई आवृत्ति विधियों को जोड़ना

दयालु: स्रोत-स्तरीय शांत शब्दार्थ बदलते हैं।

प्रभावित भाषाएँ: C #, VB

प्रभावित न होने वाली भाषाएँ: F #

परिवर्तन से पहले एपीआई:

public class Foo
{
}

परिवर्तन के बाद एपीआई:

public class Foo
{
    public void Bar();
}

नमूना ग्राहक कोड जो एक शांत शब्दार्थ परिवर्तन से ग्रस्त है:

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

new Foo().Bar();

नोट: F # टूटा नहीं है, क्योंकि इसमें भाषा स्तर का समर्थन नहीं है ExtensionMethodAttributeऔर इसके लिए CLS एक्सटेंशन विधियों को स्टैटिक विधियों के रूप में बुलाया जाना आवश्यक है।


निश्चित रूप से Microsoft पहले से ही इसे कवर करता है ... msdn.microsoft.com/en-us/netframework/aa570326.aspx
रॉबर्ट हार्वे

1
@Robert: आपका लिंक कुछ बहुत अलग है - यह .NET फ्रेमवर्क में विशिष्ट ब्रेकिंग परिवर्तनों का वर्णन करता है। यह एक व्यापक प्रश्न है जो जेनेरिक पैटर्न का वर्णन करता है जो आपके स्वयं के एपीआई (पुस्तकालय / रूपरेखा लेखक के रूप में) में परिवर्तन को पेश कर सकता है । मैं एमएस से ऐसे किसी भी दस्तावेज के बारे में नहीं जानता, जो पूरा होगा, हालांकि ऐसे किसी भी लिंक, भले ही अधूरे वाले, निश्चित रूप से स्वागत योग्य हैं।
पावेल मिनाएव

इनमें से किसी भी "ब्रेक" श्रेणियों में, क्या कोई ऐसा है जिसमें समस्या केवल रनटाइम में स्पष्ट हो जाएगी?
रोहित

1
हां, "बाइनरी ब्रेक" श्रेणी। उस स्थिति में, आपके पास पहले से ही अपनी विधानसभा के सभी संस्करण के खिलाफ एक तीसरे पक्ष की विधानसभा है। यदि आप अपने विधानसभा के नए संस्करण को तीसरे स्थान पर छोड़ते हैं, तो थर्ड-पार्टी असेंबली काम करना बंद कर देती है - या तो यह रन-टाइम पर लोड नहीं करता है, या यह गलत तरीके से काम करता है।
पावेल मिनाएव

3
मैं उन्हें पोस्ट और टिप्पणियों में शामिल करूंगा blogs.msdn.com/b/ericlippert/archive/2012/01/09/…
Lukasz

जवाबों:


42

एक विधि हस्ताक्षर बदलना

किंड: बाइनरी-लेवल ब्रेक

प्रभावित भाषाएं: C # (VB और F # सबसे अधिक संभावना है, लेकिन अनछुए)

परिवर्तन से पहले एपीआई

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

परिवर्तन के बाद एपीआई

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

परिवर्तन से पहले काम करने वाला नमूना ग्राहक कोड

Foo.bar(13);

15
वास्तव में, यह एक स्रोत-स्तरीय ब्रेक भी हो सकता है, अगर कोई इसके लिए एक प्रतिनिधि बनाने की कोशिश करता है bar
पावेल मिनेव

यह सच भी है। मुझे यह विशेष समस्या तब लगी जब मैंने अपनी कंपनियों के आवेदन में मुद्रण उपयोगिताओं में कुछ बदलाव किए। जब अद्यतन जारी किया गया था, तो सभी डीएलएल जो संदर्भित नहीं थे उपयोगिताओं को पुन: उपयोग किया गया और जारी किया गया ताकि यह एक मेथोडफ़ाउंड अपवाद को फेंक दे।
जस्टिन ड्र्यू

1
यह इस तथ्य पर वापस जाता है कि वापसी के प्रकार विधि के हस्ताक्षर के लिए गणना नहीं करते हैं। आप केवल वापसी प्रकार के आधार पर दो कार्यों को अधिभार नहीं दे सकते। एक ही समस्या है।
जेसन शॉर्ट

1
इस उत्तर के प्रति विरोधाभास: क्या कोई जानता है कि एक डॉटनेट 4 डिफाल्व्यू को जोड़ने का निहितार्थ है 'सार्वजनिक स्थैतिक शून्य बार (int i = 0);' या उस मान को एक मान से दूसरे में बदल रहा है?
k3b

1
जो लोग इस पृष्ठ पर उतरने जा रहे हैं, उनके लिए मुझे लगता है कि C # (और "मुझे लगता है" अधिकांश अन्य ओओपी भाषाएँ), रिटर्न प्रकार विधि हस्ताक्षर में योगदान नहीं करते हैं। हाँ उत्तर सही है कि हस्ताक्षर परिवर्तन बाइनरी स्तर के परिवर्तन में योगदान करते हैं। लेकिन उदाहरण सही IMHO सही उदाहरण है कि मुझे लगता है कि कर सकते हैं की है लगता नहीं है इससे पहले कि सार्वजनिक दशमलव जोड़ (एक, int ख int) के बाद सार्वजनिक दशमलव जोड़ (दशमलव एक, दशमलव ख) कृपया इस MSDN लिंक का उल्लेख 3.6 हस्ताक्षर और अधिक भार
भानु छाबड़ा

40

डिफ़ॉल्ट मान के साथ एक पैरामीटर जोड़ना।

ब्रेक की तरह: बाइनरी-स्तरीय ब्रेक

यहां तक ​​कि अगर कॉलिंग सोर्स कोड को बदलने की जरूरत नहीं है, तब भी इसे फिर से शुरू करने की जरूरत है (ठीक उसी तरह जैसे कि एक नियमित पैरामीटर जोड़ते समय)।

ऐसा इसलिए है क्योंकि C # पैरामीटर के डिफ़ॉल्ट मानों को सीधे कॉलिंग असेंबली में संकलित करता है। इसका मतलब यह है कि यदि आप पुनर्मूल्यांकन नहीं करते हैं, तो आपको एक MissingMethodException मिलेगी क्योंकि पुरानी असेंबली कम तर्कों के साथ एक विधि को कॉल करने का प्रयास करती है।

एपीआई से पहले बदलें

public void Foo(int a) { }

एपीआई चेंज के बाद

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

नमूना क्लाइंट कोड जो बाद में टूट गया है

Foo(5);

क्लाइंट कोड Foo(5, null)को bytecode स्तर पर पुन: कनेक्ट करने की आवश्यकता है। कहा जाता विधानसभा केवल शामिल होंगे Foo(int, string), नहीं Foo(int)। ऐसा इसलिए है क्योंकि डिफ़ॉल्ट पैरामीटर मान विशुद्ध रूप से एक भाषा सुविधा है, .Net रनटाइम उनके बारे में कुछ भी नहीं जानता है। (यह भी समझाता है कि डिफ़ॉल्ट मानों को C # में संकलित-समय स्थिरांक क्यों होना चाहिए)।


2
स्रोत कोड स्तर के लिए भी यह एक ब्रेकिंग परिवर्तन है: Func<int> f = Foo;// यह परिवर्तित हस्ताक्षर के साथ विफल हो जाएगा
Vagaus

26

यह एक बहुत ही स्पष्ट नहीं था जब मैंने इसे खोजा, विशेष रूप से इंटरफेस के लिए समान स्थिति के साथ अंतर के प्रकाश में। यह बिल्कुल भी विराम नहीं है, लेकिन यह काफी आश्चर्यजनक है कि मैंने इसे शामिल करने का फैसला किया है:

कक्षा के सदस्यों को बेस क्लास में बदलना

तरह: नहीं एक ब्रेक!

प्रभावित भाषाएँ: कोई नहीं (अर्थात कोई भी टूटी हुई नहीं)

परिवर्तन से पहले एपीआई:

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

परिवर्तन के बाद एपीआई:

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 भाषा है जिसमें वर्चुअल बेस क्लास सदस्यों के लिए स्पष्ट इंटरफ़ेस कार्यान्वयन के अनुरूप निर्माण है - "स्पष्ट ओवरराइड"। मैं पूरी तरह से उम्मीद करता हूं कि इंटरफ़ेस के सदस्यों को आधार इंटरफ़ेस में ले जाने के दौरान उसी तरह की टूट-फूट हो सकती है (क्योंकि स्पष्ट ओवरराइड के लिए आईएल उत्पन्न स्पष्ट कार्यान्वयन के लिए समान है)। मेरे आश्चर्य के लिए, यह मामला नहीं है - भले ही उत्पन्न आईएल अभी भी निर्दिष्ट करता है कि इसके बजाय BarOverrideओवरराइड Foo::Barकरता है FooBase::Bar, असेंबली लोडर किसी भी शिकायत के बिना एक दूसरे के लिए सही ढंग से स्थानापन्न करने के लिए पर्याप्त स्मार्ट है - जाहिर है, Fooएक वर्ग जो है वह वास्तव में फर्क करता है। जाओ पता लगाओ...


3
जब तक आधार वर्ग एक ही विधानसभा में है। अन्यथा यह बाइनरी ब्रेकिंग परिवर्तन है।
जेरेमी

@ जेरेमी उस मामले में किस तरह का कोड टूटता है? क्या बाज के किसी भी बाहरी कॉलर का उपयोग () टूट जाएगा या यह केवल उन लोगों के साथ एक मुद्दा है जो फू को बढ़ाने और बाज को खत्म करने की कोशिश करते हैं?
चेसमेडलियन

यदि आप सेकंड हैंड यूजर हैं तो @CMMallallion तोड़ रहा है। उदाहरण के लिए, संकलित DLL फू के पुराने संस्करण का संदर्भ देता है और आप DLL को संकलित करते हैं, लेकिन फू DLL के नए संस्करण का भी उपयोग करते हैं। यह एक अजीब त्रुटि के साथ टूटता है, या कम से कम यह मेरे लिए पुस्तकालयों में मैंने पहले किया था।
जेरेमी

19

यह "इंटरफ़ेस सदस्यों को जोड़ने / हटाने" का एक शायद नहीं तो-स्पष्ट विशेष मामला है, और मुझे लगा कि यह एक और मामले के प्रकाश में अपनी प्रविष्टि के हकदार हैं जो मैं अगले पोस्ट करने जा रहा हूं। इसलिए:

बेस इंटरफ़ेस में इंटरफ़ेस सदस्यों को फिर से लाना

तरह: दोनों स्रोत और बाइनरी स्तर पर टूट जाता है

प्रभावित भाषाएँ: C #, VB, C ++ / CLI, F # (स्रोत विराम के लिए; द्विआधारी स्वाभाविक रूप से किसी भी भाषा को प्रभावित करती है)

परिवर्तन से पहले एपीआई:

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

परिवर्तन के बाद एपीआई:

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 सभी को इंटरफ़ेस इंटरफ़ेस कार्यान्वयन की घोषणा में सटीक इंटरफ़ेस नाम की आवश्यकता होती है; इस प्रकार, यदि सदस्य आधार इंटरफ़ेस में चला जाता है, तो कोड अब संकलित नहीं होगा।

बाइनरी ब्रेक इस तथ्य के कारण है कि स्पष्ट कार्यान्वयन के लिए इंटरफ़ेस विधि पूरी तरह से उत्पन्न आईएल में योग्य हैं, और इंटरफ़ेस नाम भी सटीक होना चाहिए।

लागू कार्यान्वयन जहां उपलब्ध हो (यानी C # और C ++ / CLI, लेकिन VB नहीं) स्रोत और बाइनरी दोनों स्तरों पर ठीक काम करेगा। मेथड कॉल या तो टूटता नहीं है।


यह सभी भाषाओं के लिए सही नहीं है। VB के लिए यह ब्रेकिंग सोर्स कोड परिवर्तन नहीं है। C # के लिए है।
जेरेमी

तो Implements IFoo.Barक्या पारदर्शी संदर्भ होगा IFooBase.Bar?
पावेल मिनाएव

हां, यह वास्तव में करता है, जब आप इसे लागू करते हैं, तो आप प्रत्यक्ष या अप्रत्यक्ष रूप से विरासत वाले इंटरफ़ेस के माध्यम से संदर्भ दे सकते हैं। हालाँकि, यह हमेशा एक टूटने वाला द्विआधारी परिवर्तन है।
जेरेमी

15

प्रगणित मानों को पुनः व्यवस्थित करना

विराम प्रकार: स्रोत-स्तर / बाइनरी-स्तर शांत शब्दार्थ परिवर्तन

भाषाएँ प्रभावित: सभी

प्रगणित मानों को पुन: व्यवस्थित करने से स्रोत-स्तर की संगतता बनी रहेगी क्योंकि शाब्दिक रूप में एक ही नाम है, लेकिन उनके क्रमबद्ध सूचकांकों को अपडेट किया जाएगा, जिससे कुछ प्रकार के मूक स्रोत-स्तर के विराम हो सकते हैं।

इससे भी बदतर मूक बाइनरी-स्तर के ब्रेक हैं जो पेश किए जा सकते हैं यदि क्लाइंट कोड नए एपीआई संस्करण के खिलाफ पुन: संकलित नहीं किया गया है। Enum मान संकलित समय स्थिरांक हैं और उनमें से किसी भी उपयोग को क्लाइंट असेंबली के IL में बेक किया जाता है। यह मामला विशेष रूप से कई बार हाजिर हो सकता है।

एपीआई से पहले बदलें

public enum Foo
{
   Bar,
   Baz
}

एपीआई चेंज के बाद

public enum Foo
{
   Baz,
   Bar
}

नमूना क्लाइंट कोड जो काम करता है लेकिन बाद में टूट जाता है:

Foo.Bar < Foo.Baz

12

यह वास्तव में व्यवहार में एक बहुत ही दुर्लभ चीज है, लेकिन फिर भी ऐसा होने पर आश्चर्य की बात है।

नए गैर-अतिभारित सदस्य जोड़ना

तरह: स्रोत के स्तर को तोड़ने या शांत शब्दार्थ परिवर्तन।

प्रभावित भाषाएँ: C #, VB

प्रभावित न होने वाली भाषाएँ: F #, C ++ / CLI

परिवर्तन से पहले एपीआई:

public class Foo
{
}

परिवर्तन के बाद एपीआई:

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

नमूना क्लाइंट कोड जो परिवर्तन से टूट गया है:

class Bar
{
    public void Frob() {}
}

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

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

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

टिप्पणियाँ:

ओवरलोड रेजोल्यूशन की उपस्थिति में यहाँ समस्या C # और VB में लैम्ब्डा टाइप इंट्रैक्शन के कारण होती है। बतख टाइपिंग का एक सीमित रूप यहां उन संबंधों को तोड़ने के लिए नियोजित किया जाता है जहां एक से अधिक प्रकार के मिलान होते हैं, यह जांच कर कि क्या लंबोदर का शरीर किसी दिए गए प्रकार के लिए समझ में आता है - यदि केवल एक प्रकार के परिणाम से शरीर का निर्माण होता है, तो वह चुना जाता है।

यहां खतरा यह है कि क्लाइंट कोड में एक अधिभार विधि समूह हो सकता है जहां कुछ विधियां अपने प्रकार के तर्क लेती हैं, और अन्य आपके पुस्तकालय द्वारा उजागर किए गए प्रकारों के तर्क लेते हैं। यदि उसका कोई भी कोड पूरी तरह से सदस्यों की उपस्थिति या अनुपस्थिति के आधार पर सही विधि का निर्धारण करने के लिए टाइप इनफॉरमेशन एल्गोरिथ्म पर निर्भर करता है, तो एक नए सदस्य को उसी प्रकार के नाम के साथ जोड़ रहा है जैसा कि क्लाइंट के किसी एक प्रकार में संभावित रूप से इनवेशन फेंक सकता है। बंद, अधिभार संकल्प के दौरान अस्पष्टता के परिणामस्वरूप।

ध्यान दें कि प्रकार Fooऔर Barइस उदाहरण में किसी भी तरह से संबंधित नहीं हैं, न कि विरासत से और न ही अन्यथा। एक एकल विधि समूह में उनका उपयोग केवल इसे ट्रिगर करने के लिए पर्याप्त है, और यदि यह क्लाइंट कोड में होता है, तो आपका इस पर कोई नियंत्रण नहीं है।

ऊपर का नमूना कोड एक सरल स्थिति प्रदर्शित करता है जहां यह एक स्रोत-स्तरीय विराम (यानी संकलक त्रुटि परिणाम) है। हालाँकि, यह एक मौन शब्दार्थ परिवर्तन भी हो सकता है, यदि अधिभार को जो अनुमान के माध्यम से चुना गया था, उसके पास अन्य तर्क थे जो अन्यथा इसे नीचे रैंक करने का कारण बनेंगे (जैसे कि डिफ़ॉल्ट मानों के साथ वैकल्पिक तर्क, या घोषित और वास्तविक तर्क के बीच बेमेल एक निहितार्थ की आवश्यकता होती है रूपांतरण)। ऐसे परिदृश्य में, अधिभार संकल्प अब विफल नहीं होगा, लेकिन एक अलग अधिभार को चुपचाप संकलक द्वारा चुना जाएगा। व्यवहार में, हालांकि, इस मामले में सावधानीपूर्वक निर्माण हस्ताक्षर के बिना सावधानीपूर्वक निर्माण करने के लिए इस मामले में इसे चलाने के लिए बहुत मुश्किल है।


9

एक स्पष्ट इंटरफ़ेस में एक अंतर्निहित इंटरफ़ेस कार्यान्वयन कन्वर्ट करें।

ब्रेक ऑफ काइंड: सोर्स एंड बाइनरी

भाषाएं प्रभावित: सभी

यह वास्तव में एक विधि की पहुंच को बदलने का केवल एक भिन्नता है - इसका सिर्फ थोड़ा अधिक सूक्ष्म है क्योंकि यह इस तथ्य की अनदेखी करना आसान है कि इंटरफ़ेस के तरीकों तक सभी पहुंच आवश्यक रूप से इंटरफ़ेस के प्रकार के संदर्भ में नहीं है।

परिवर्तन से पहले एपीआई:

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

एपीआई परिवर्तन के बाद:

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

नमूना क्लाइंट कोड जो परिवर्तन से पहले काम करता है और बाद में टूट जाता है:

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

7

एक स्पष्ट इंटरफ़ेस कार्यान्वयन को एक निहित में परिवर्तित करें।

ब्रेक ऑफ काइंड: सोर्स

भाषाएं प्रभावित: सभी

एक स्पष्ट एक में एक स्पष्ट इंटरफ़ेस कार्यान्वयन के refactoring कैसे यह एक एपीआई तोड़ सकता है में अधिक सूक्ष्म है। सतह पर, ऐसा लगता है कि यह अपेक्षाकृत सुरक्षित होना चाहिए, हालांकि, विरासत के साथ संयुक्त होने पर यह समस्या पैदा कर सकता है।

परिवर्तन से पहले एपीआई:

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

एपीआई परिवर्तन के बाद:

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 , और आप विधि को संदर्भ के माध्यम से बुला रहे हैं Foo। ।
पावेल मिनाएव

वास्तव में, मैंने स्मृति से एक उदाहरण को सरल बनाने का प्रयास किया और इसने 'फोब्बर' (दंड को क्षमा) किया। मैंने मामले को सही ढंग से प्रदर्शित करने के लिए (और संकलित होने के लिए) उदाहरण को अद्यतन किया।
LBushkin

मेरे उदाहरण में, समस्या केवल एक इंटरफ़ेस विधि के संक्रमण से अधिक होने के कारण होती है, जिसका तात्पर्य सार्वजनिक होने से है। यह इस बात पर निर्भर करता है कि सी # कंपाइलर किस विधि से फॉर्चर लूप में कॉल करता है। संकलक नियमों को देखते हुए संकलक के नियम, यह व्युत्पन्न वर्ग में संस्करण से आधार वर्ग में संस्करण में बदल जाता है।
LBushkin

आप भूल गए yield return "Bar":) लेकिन हाँ, मैं देख रहा हूँ कि यह अब कहाँ जा रहा है - foreachहमेशा सार्वजनिक पद्धति को नाम दिया जाता है GetEnumerator, भले ही यह वास्तविक कार्यान्वयन न हो IEnumerable.GetEnumerator। ऐसा लगता है कि आपके पास एक और कोण है: भले ही आपके पास सिर्फ एक वर्ग है, और यह लागू होता हैIEnumerable स्पष्ट रूप से , इसका मतलब है कि यह सार्वजनिक स्रोत को जोड़ने के लिए एक परिवर्तन तोड़ने वाला स्रोत हैGetEnumerator है, क्योंकि अब foreachइंटरफ़ेस कार्यान्वयन पर उस पद्धति का उपयोग करेगा। इसके अलावा, एक ही समस्या IEnumeratorकार्यान्वयन के लिए लागू होती है ...
पावेल मिनाएव

6

किसी प्रॉपर्टी में फ़ील्ड बदलना

ब्रेक की तरह: एपीआई

प्रभावित भाषाएँ: विजुअल बेसिक और C # *

जानकारी: जब आप किसी सामान्य फ़ील्ड या वैरिएबल को विज़ुअल बेसिक में किसी प्रॉपर्टी में बदलते हैं, तो किसी भी बाहरी कोड को उस सदस्य को किसी भी तरह से संदर्भित करना होगा।

परिवर्तन से पहले एपीआई:

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

एपीआई परिवर्तन के बाद:

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
यह वास्तव में सी # के रूप में अच्छी तरह से चीजों को तोड़ देगा, क्योंकि गुणों का उपयोग खेतों के विपरीत तरीकों के तर्क outऔर refतर्क के लिए नहीं किया जा सकता है , और एकात्मक &ऑपरेटर का लक्ष्य नहीं हो सकता है ।
पावेल मिनाएव

5

नाम स्थान का जोड़

स्रोत-स्तरीय विराम / Source-level शांत शब्दार्थ परिवर्तन

जिस तरह से नामस्थान रिज़ॉल्यूशन vb.Net में काम करता है, उसके कारण लाइब्रेरी में नेमस्पेस जोड़ने से विज़ुअल बेसिक कोड हो सकता है जो कि एपीआई के पिछले संस्करण के साथ संकलित किया गया है ताकि नए संस्करण का संकलन न किया जा सके।

नमूना ग्राहक कोड:

Imports System
Imports Api.SomeNamespace

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

अगर एपीआई का एक नया संस्करण नेमस्पेस जोड़ता है Api.SomeNamespace.Data , तो उपरोक्त कोड संकलित नहीं करेगा।

यह प्रोजेक्ट-स्तरीय नाम स्थान आयात के साथ अधिक जटिल हो जाता है। यदि Imports Systemउपरोक्त कोड से छोड़ा गया है, लेकिनSystem प्रोजेक्ट स्तर पर नाम स्थान आयात किया गया है, तो कोड में अभी भी त्रुटि हो सकती है।

हालाँकि, यदि Api DataRowमें अपने Api.SomeNamespace.Dataनामस्थान में एक वर्ग शामिल है , तो कोड संकलित drकरेगा लेकिन System.Data.DataRowजब एपीआई के पुराने संस्करण के साथ संकलित किया जाएगा,Api.SomeNamespace.Data.DataRow जब एपीआई के नए संस्करण के साथ संकलित।

तर्क नामकरण

स्रोत-स्तरीय विराम

तर्कों के नामों को बदलना vb.net में संस्करण 7 (?) (.Net संस्करण 1?) और c # .net से संस्करण 4 (.Net संस्करण 4) से एक परिवर्तन है।

परिवर्तन से पहले एपीआई:

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

परिवर्तन के बाद एपीआई:

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

रेफ पैरामीटर्स

स्रोत-स्तरीय विराम

एक ही हस्ताक्षर के साथ एक विधि को ओवरराइड जोड़ने के अलावा, एक पैरामीटर को मान के बजाय संदर्भ द्वारा पारित किया जाता है, vb स्रोत का कारण होगा जो फ़ंक्शन को हल करने में असमर्थ होने के लिए एपीआई का संदर्भ देता है। विजुअल बेसिक के पास इन तरीकों को कॉल बिंदु पर अलग करने का कोई तरीका (?) नहीं है जब तक कि उनके पास अलग-अलग तर्क नाम न हों, इसलिए इस तरह के बदलाव से दोनों सदस्य vb कोड से अनुपयोगी हो सकते हैं।

परिवर्तन से पहले एपीआई:

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

परिवर्तन के बाद एपीआई:

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

नमूना ग्राहक कोड:

Api.SomeNamespace.Foo.Bar(str)

संपत्ति में बदलाव का क्षेत्र

बाइनरी-लेवल ब्रेक / सोर्स-लेवल ब्रेक

स्पष्ट द्विआधारी-स्तरीय विराम के अलावा, यह स्रोत-स्तर के विराम का कारण बन सकता है यदि सदस्य को संदर्भ द्वारा विधि में पारित किया जाता है।

परिवर्तन से पहले एपीआई:

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

परिवर्तन के बाद एपीआई:

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

नमूना ग्राहक कोड:

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

4

एपीआई परिवर्तन:

  1. [अप्रचलित] विशेषता को जोड़ना (आप थोड़े इसे उल्लेख करने वाले गुणों के साथ कवर करते हैं; हालांकि, चेतावनी-ए-त्रुटि का उपयोग करते समय यह एक परिवर्तन हो सकता है।)

बाइनरी-स्तर विराम:

  1. एक प्रकार से एक विधानसभा से दूसरी में जाना
  2. एक प्रकार का नाम स्थान बदलना
  3. एक अन्य विधानसभा से एक आधार वर्ग प्रकार जोड़ना।
  4. एक नया सदस्य (संरक्षित घटना) जोड़ना जो एक अन्य विधानसभा (Class2) से टेम्पलेट तर्क बाधा के रूप में एक प्रकार का उपयोग करता है।

    protected void Something<T>() where T : Class2 { }
  5. जब इस वर्ग के लिए एक टेम्पलेट तर्क के रूप में कक्षा का उपयोग किया जाता है, तो किसी अन्य विधानसभा में एक प्रकार से प्राप्त करने के लिए एक बच्चा वर्ग (Class3) बदलना।

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

स्रोत-स्तरीय शांत शब्दार्थ परिवर्तन

  1. बराबरी (), GetHashCode (), या ToString () के ओवरराइड्स जोड़ना / हटाना / बदलना

(यकीन नहीं होता कि ये कहां फिट होते हैं)

परिनियोजन परिवर्तन:

  1. निर्भरता / संदर्भ जोड़ना / हटाना
  2. नए संस्करणों के लिए निर्भरता अद्यतन करना
  3. X86, Itanium, x64 या anycpu के बीच 'लक्ष्य प्लेटफ़ॉर्म' बदलना
  4. एक अलग फ्रेमवर्क इंस्टाल पर बिल्डिंग / टेस्टिंग (यानी .Net 2.0 बॉक्स पर 3.5 इंस्टाल करना एपीआई कॉल की अनुमति देता है जिसके लिए .Net 2.0 SP2 की आवश्यकता होती है)

बूटस्ट्रैप / कॉन्फ़िगरेशन परिवर्तन:

  1. कस्टम कॉन्फ़िगरेशन विकल्पों को जोड़ना / हटाना / बदलना (अर्थात App.config सेटिंग)
  2. आज के अनुप्रयोगों में IoC / DI के भारी उपयोग के साथ, डीआई पर निर्भर कोड के लिए बूटस्ट्रैपिंग कोड को फिर से कॉन्फ़िगर करना और बदलना आवश्यक है।

अपडेट करें:

क्षमा करें, मुझे महसूस नहीं हुआ कि मेरे लिए यह एकमात्र कारण था कि मैंने उन्हें टेम्पलेट बाधाओं में उपयोग किया।


"एक नया सदस्य जोड़ना (संरक्षित घटना) जो किसी अन्य विधानसभा से एक प्रकार का उपयोग करता है।" - IIRC, क्लाइंट को केवल निर्भर असेंबलियों को संदर्भित करने की आवश्यकता होती है जिसमें बेस प्रकार की असेंबली होती हैं जो पहले से ही संदर्भित होती हैं; यह उन विधानसभाओं को संदर्भित नहीं करता है जो केवल उपयोग की जाती हैं (भले ही प्रकार विधि हस्ताक्षर में हों); मैं इस बारे में 100% निश्चित नहीं हूं। क्या आपके पास इसके लिए सटीक नियमों का संदर्भ है? इसके अलावा, यदि TypeForwardedToAttributeउपयोग किया जाता है तो एक प्रकार का चलना गैर-ब्रेकिंग हो सकता है।
पावेल मिनाएव

वह "TypeForwardedTo" मेरे लिए समाचार है, मैं इसे देखूंगा। दूसरे के लिए, मैं भी उस पर 100% नहीं हूं ... मुझे देखने दें कि क्या मैं रिप्रोड कर सकता हूं और मैं पोस्ट अपडेट करूंगा।
csharptest.net

तो, -Werrorअपने बिल्ड-सिस्टम पर जारी टार्लेट्स के साथ आप को मजबूर न करें। यह ध्वज कोड के डेवलपर के लिए सबसे अधिक उपयोगी होता है और उपभोक्ता के लिए अक्सर अप्रभावी होता है।
दिनकर

@Binki उत्कृष्ट बिंदु, चेतावनियों को त्रुटियों के रूप में मानना ​​DEBUG में पर्याप्त होना चाहिए।
csharptest.net

3

डिफ़ॉल्ट मापदंडों के उपयोग को कम करने के लिए अधिभार विधियों को जोड़ना

ब्रेक की तरह: सोर्स-लेवल साइलेंट सिमेंटिक्स बदलते हैं

क्योंकि कंपाइलर मेथड डिफॉल्ट पैरामीटर वैल्यूज के साथ कॉलिंग को डिफॉल्ट वैल्यू के साथ कॉलिंग साइड पर डिफॉल्ट कॉल के साथ बदल देता है, मौजूदा संकलित कोड के लिए अनुकूलता दी जाती है; पहले से संकलित कोड के लिए सही हस्ताक्षर वाली एक विधि मिल जाएगी।

दूसरी ओर, वैकल्पिक मापदंडों के उपयोग के बिना कॉल को अब नए तरीके के लिए कॉल के रूप में संकलित किया जाता है जो वैकल्पिक पैरामीटर को याद कर रहा है। यह सब अभी भी ठीक काम कर रहा है, लेकिन अगर बुलाया कोड किसी अन्य विधानसभा में रहता है, तो नए संकलित कोड को कॉल करना अब इस विधानसभा के नए संस्करण पर निर्भर है। असेंबली को रिफ्लेक्ट किए गए कोड को कॉल किए बिना असेंबलिंग असेंबली को भी परावर्तित किए बिना असेंबली को रिफ्लेक्ट किए गए कोड में रहता है, जिसके परिणामस्वरूप "विधि नहीं मिली" अपवाद है।

परिवर्तन से पहले एपीआई

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

परिवर्तन के बाद एपीआई

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

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

नमूना कोड जो अभी भी काम कर रहा है

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

नमूना कोड जो अब संकलन करते समय नए संस्करण के लिए निर्भर है

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

1

एक इंटरफ़ेस का नाम बदलना

ब्रेक ऑफ किंडा: सोर्स एंड बाइनरी

प्रभावित भाषाएँ: सबसे अधिक संभावना है, C # में परीक्षण किया गया।

परिवर्तन से पहले एपीआई:

public interface IFoo
{
    void Test();
}

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

एपीआई परिवर्तन के बाद:

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

अशक्त प्रकार के पैरामीटर के साथ ओवरलोडिंग विधि

प्रकार: स्रोत-स्तरीय विराम

प्रभावित भाषाएँ: C #, VB

परिवर्तन से पहले एपीआई:

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

परिवर्तन के बाद एपीआई:

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

नमूना ग्राहक कोड परिवर्तन से पहले काम कर रहा है और उसके बाद टूट गया है:

new Foo().Bar(null);

अपवाद: कॉल निम्न विधियों या गुणों के बीच अस्पष्ट है।


0

एक्सटेंशन विधि का प्रचार

प्रकार: स्रोत-स्तरीय विराम

प्रभावित भाषाएँ: C # v6 और उच्चतर (शायद अन्य?)

परिवर्तन से पहले एपीआई:

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

परिवर्तन के बाद एपीआई:

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

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.