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




api clr (9)

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

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

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

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

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

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

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

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

तरह: स्रोत-स्तर ब्रेक

प्रभावित भाषाएं: सी #, वीबी, एफ #

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

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]);

नए निहित रूपांतरण ऑपरेटर ओवरलोड जोड़ना

तरह: स्रोत-स्तर ब्रेक।

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

भाषा प्रभावित नहीं: एफ #

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

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

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

नए उदाहरण विधियों को जोड़ना

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

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

भाषा प्रभावित नहीं: एफ #

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

public class Foo
{
}

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

public class Foo
{
    public void Bar();
}

नमूना क्लाइंट कोड जो एक शांत अर्थशास्त्र परिवर्तन को पीड़ित करता है:

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

new Foo().Bar();

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


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

ब्रेक की किंडा: स्रोत और बाइनरी

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

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

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

एक संपत्ति में एक क्षेत्र बदल रहा है

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

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

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

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

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"

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

ब्रेक की तरह: स्रोत-स्तर शांत अर्थशास्त्र बदलते हैं

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

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

बदलाव से पहले एपीआई

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

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

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

भले ही कॉलिंग स्रोत कोड को बदलने की आवश्यकता नहीं है, फिर भी इसे फिर से सम्मिलित करने की आवश्यकता है (जैसे नियमित पैरामीटर जोड़ना)।

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

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

public void Foo(int a) { }

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

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

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

Foo(5);

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


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

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

प्रभावित भाषाएं: सी # (वीबी और एफ # सबसे अधिक संभावना है, लेकिन untested)

बदलाव से पहले एपीआई

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

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

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

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

Foo.bar(13);

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

ब्रेक की तरह: स्रोत

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

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

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

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"

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

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

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

प्रभावित भाषाएं: सी #, वीबी, सी ++ / सीएलआई, एफ # (स्रोत ब्रेक के लिए; बाइनरी एक स्वाभाविक रूप से किसी भी भाषा को प्रभावित करता है)

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

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

टिप्पणियाँ:

स्रोत स्तर के ब्रेक के लिए, समस्या यह है कि सी #, वीबी और सी ++ / सीएलआई को इंटरफ़ेस सदस्य कार्यान्वयन की घोषणा में सटीक इंटरफ़ेस नाम की आवश्यकता होती है; इस प्रकार, यदि सदस्य बेस इंटरफ़ेस में स्थानांतरित हो जाता है, तो कोड अब संकलित नहीं होगा।

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

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


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

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

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

प्रभावित भाषाएं: कोई नहीं (यानी कोई भी टूटा नहीं जाता है)

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

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 {}
};

टिप्पणियाँ:

सी ++ / सीएलआई एकमात्र .NET भाषा है जिसमें वर्चुअल बेस क्लास सदस्यों - "स्पष्ट ओवरराइड" के लिए स्पष्ट इंटरफ़ेस कार्यान्वयन के समान निर्माण होता है। मुझे पूरी तरह से उम्मीद थी कि उसी तरह के ब्रेकेज के परिणामस्वरूप इंटरफ़ेस सदस्यों को मूल इंटरफ़ेस में ले जाया जा रहा है (चूंकि स्पष्ट ओवरराइड के लिए उत्पन्न आईएल स्पष्ट कार्यान्वयन के समान है)। मेरे आश्चर्य के लिए, यह मामला नहीं है - भले ही जेनरेटेड आईएल अभी भी निर्दिष्ट करता है कि BarOverride Foo::Bar बजाय Foo::Bar ओवरराइड करता है, असेंबली लोडर किसी भी शिकायत के बिना किसी अन्य के लिए सही जगह पर विकल्प बदलने के लिए पर्याप्त स्मार्ट है - जाहिर है, तथ्य यह है कि Foo एक वर्ग है जो अंतर बनाता है। जाओ पता लगाओ...


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

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

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

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

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

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

स्रोत-स्तर शांत अर्थशास्त्र बदलते हैं:

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

(सुनिश्चित नहीं है कि ये कहाँ फिट हैं)

तैनाती में परिवर्तन:

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

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

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

अद्यतन करें:

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







cls-compliant