जवाबों:
सवाल यह है कि "कोविरियन और कंट्रोवर्सी में क्या अंतर है?"
कोवरियनस और कंट्रोवर्सी एक मैपिंग फ़ंक्शन के गुण हैं जो एक सेट के एक सदस्य को दूसरे के साथ जोड़ते हैं । अधिक विशेष रूप से, एक मैपिंग उस सेट पर एक संबंध के संबंध में सहसंयोजक या कंट्रावेरिएंट हो सकती है ।
सभी सी # प्रकार के सेट के निम्नलिखित दो सबसेट पर विचार करें। प्रथम:
{ Animal,
Tiger,
Fruit,
Banana }.
और दूसरा, यह स्पष्ट रूप से संबंधित सेट:
{ IEnumerable<Animal>,
IEnumerable<Tiger>,
IEnumerable<Fruit>,
IEnumerable<Banana> }
पहले सेट से दूसरे सेट तक एक मैपिंग ऑपरेशन होता है। अर्थात्, पहले सेट में प्रत्येक T के लिए, दूसरे सेट में संबंधित प्रकार है IEnumerable<T>
। या, संक्षिप्त रूप में, मानचित्रण है T → IE<T>
। ध्यान दें कि यह एक "पतला तीर" है।
मेरे साथ इतनी दूर?
अब एक संबंध पर विचार करते हैं । पहले सेट में प्रकारों के जोड़े के बीच एक असाइनमेंट संगतता संबंध है । प्रकार का एक मान प्रकार के Tiger
एक चर को सौंपा जा सकता है Animal
, इसलिए इन प्रकारों को "असाइनमेंट संगत" कहा जाता है। आइए लिखते हैं "एक प्रकार के एक मूल्य X
को एक प्रकार के एक चर को सौंपा जा सकता है Y
" X ⇒ Y
। ध्यान दें कि यह एक "वसा तीर" है।
तो हमारे पहले सबसेट में, यहाँ सभी असाइनमेंट संगतता रिश्ते हैं:
Tiger ⇒ Tiger
Tiger ⇒ Animal
Animal ⇒ Animal
Banana ⇒ Banana
Banana ⇒ Fruit
Fruit ⇒ Fruit
C # 4 में, जो कुछ इंटरफेस के सहवर्ती कार्य संगतता का समर्थन करता है, दूसरे सेट में प्रकारों के जोड़े के बीच असाइनमेंट संगतता संबंध है:
IE<Tiger> ⇒ IE<Tiger>
IE<Tiger> ⇒ IE<Animal>
IE<Animal> ⇒ IE<Animal>
IE<Banana> ⇒ IE<Banana>
IE<Banana> ⇒ IE<Fruit>
IE<Fruit> ⇒ IE<Fruit>
ध्यान दें कि मानचित्रण T → IE<T>
असाइनमेंट संगतता के अस्तित्व और दिशा को संरक्षित करता है । यही है, अगर X ⇒ Y
, तो यह भी सच है IE<X> ⇒ IE<Y>
।
यदि हमारे पास वसा तीर के दोनों ओर दो चीजें हैं, तो हम दोनों पक्षों को एक समान पतले तीर के दाहिने हाथ की ओर से कुछ के साथ बदल सकते हैं।
एक मैपिंग जिसमें एक विशेष संबंध के संबंध में यह संपत्ति होती है, उसे "सहसंयोजक मानचित्रण" कहा जाता है। यह समझ में आना चाहिए: टाइगर्स के एक अनुक्रम का उपयोग किया जा सकता है जहां जानवरों के अनुक्रम की आवश्यकता होती है, लेकिन विपरीत सच नहीं है। जानवरों के एक अनुक्रम का उपयोग नहीं किया जा सकता है जहां बाघों के अनुक्रम की आवश्यकता होती है।
वह कोविरेंस है। अब सभी प्रकार के सेट के इस सबसेट पर विचार करें:
{ IComparable<Tiger>,
IComparable<Animal>,
IComparable<Fruit>,
IComparable<Banana> }
अब हमारे पास पहले सेट से तीसरे सेट तक मैपिंग है T → IC<T>
।
C # 4 में:
IC<Tiger> ⇒ IC<Tiger>
IC<Animal> ⇒ IC<Tiger> Backwards!
IC<Animal> ⇒ IC<Animal>
IC<Banana> ⇒ IC<Banana>
IC<Fruit> ⇒ IC<Banana> Backwards!
IC<Fruit> ⇒ IC<Fruit>
यही है, मानचित्रण T → IC<T>
ने अस्तित्व को संरक्षित किया है लेकिन असाइनमेंट संगतता की दिशा को उलट दिया है । वह है, अगर X ⇒ Y
, तो IC<X> ⇐ IC<Y>
।
एक मानचित्रण जो संरक्षित करता है, लेकिन एक संबंध को उलट देता है, एक contravariant मानचित्रण कहलाता है ।
फिर से, यह स्पष्ट रूप से सही होना चाहिए। एक उपकरण जो दो जानवरों की तुलना कर सकता है, वह दो बाघों की तुलना कर सकता है, लेकिन एक उपकरण जो दो बाघों की तुलना कर सकता है, वह आवश्यक रूप से किसी भी दो जानवरों की तुलना नहीं कर सकता है।
तो यह है कि C # 4 में कोविरियन और कंट्रोवर्सी के बीच का अंतर है। कोवरियनस एसेसेबिलिटी की दिशा को बनाए रखता है। Contravariance पराजयों यह।
IEnumerable<Tiger>
करने के लिए IEnumerable<Animal>
सुरक्षित रूप से? क्योंकि जिराफ को इनपुट करने का कोई तरीका नहीं है IEnumerable<Animal>
। क्यों हम एक परिवर्तित कर सकते हैं IComparable<Animal>
करने के लिए IComparable<Tiger>
? क्योंकि जिराफ को बाहर निकालने का कोई तरीका नहीं है IComparable<Animal>
। सही बात?
उदाहरण देना शायद सबसे आसान है - यह निश्चित रूप से है कि मैं उन्हें कैसे याद करता हूं।
सहप्रसरण
विहित उदाहरण: IEnumerable<out T>
,Func<out T>
आप से परिवर्तित कर सकते हैं IEnumerable<string>
करने के लिए IEnumerable<object>
, या Func<string>
करने के लिए Func<object>
। इन वस्तुओं से ही मूल्य निकलते हैं।
यह काम करता है क्योंकि यदि आप केवल API से मान निकाल रहे हैं, और यह कुछ विशिष्ट (जैसे string
) वापस करने जा रहा है , तो आप उस लौटे मान को अधिक सामान्य प्रकार (जैसे object
) मान सकते हैं ।
contravariance
विहित उदाहरण: IComparer<in T>
,Action<in T>
आप से परिवर्तित कर सकते हैं IComparer<object>
करने के लिए IComparer<string>
, या Action<object>
करने के लिए Action<string>
; मूल्यों केवल जाना में इन वस्तुओं।
इस बार यह काम करता है क्योंकि अगर एपीआई कुछ सामान्य (जैसे object
) की उम्मीद कर रहा है, तो आप इसे कुछ और अधिक विशिष्ट (जैसे string
) दे सकते हैं ।
आम तौर पर
आप एक इंटरफेस है, तो IFoo<T>
उस में covariant हो सकता है T
(यानी यह घोषणा के रूप में IFoo<out T>
करता है, तो T
केवल (जैसे एक वापसी प्रकार) इंटरफ़ेस के भीतर एक निर्गम स्थिति में प्रयोग किया जाता है। यह में contravariant हो सकता है T
(यानी IFoo<in T>
) यदि T
केवल एक इनपुट स्थिति में प्रयोग किया जाता है ( जैसे एक पैरामीटर प्रकार)।
यह संभावित रूप से भ्रामक हो जाता है क्योंकि "आउटपुट पोजिशन" बहुत सरल नहीं है क्योंकि यह लगता है - प्रकार Action<T>
का एक पैरामीटर अभी भी केवल T
एक आउटपुट पोजीशन में उपयोग कर रहा है - Action<T>
यह दौर के विपरीत , यदि आप देखते हैं कि मेरा क्या मतलब है। यह एक "आउटपुट" है जिसमें मान कॉलर के कोड की ओर पद्धति के कार्यान्वयन से गुजर सकते हैं , ठीक उसी तरह जैसे रिटर्न वैल्यू कर सकते हैं। आमतौर पर इस तरह की बात नहीं आती है, सौभाग्य से :)
Action<T>
का पैरामीटर अभी भी केवल T
एक आउटपुट स्थिति में उपयोग कर रहा है" । Action<T>
वापसी प्रकार शून्य है, यह T
आउटपुट के रूप में कैसे उपयोग कर सकता है ? या यह है कि इसका क्या मतलब है, क्योंकि यह कुछ भी नहीं देता है जिसे आप देख सकते हैं कि यह कभी भी नियम का उल्लंघन नहीं कर सकता है?
मुझे उम्मीद है कि मेरी पोस्ट विषय की भाषा-अज्ञेयवादी दृष्टिकोण प्राप्त करने में मदद करती है।
हमारे आंतरिक प्रशिक्षण के लिए मैंने अद्भुत पुस्तक "स्मालटाक, ऑब्जेक्ट्स एंड डिज़ाइन (चामोंड लिउ)" के साथ काम किया है और मैंने निम्नलिखित उदाहरणों को पुनःप्रकाशित किया है।
"संगति" का क्या अर्थ है? विचार उच्च-प्रकार के प्रकार के साथ सुरक्षित प्रकार की पदानुक्रमों को डिजाइन करने के लिए है। यदि आप एक वैधानिक रूप से टाइप की गई भाषा में काम करते हैं, तो इस स्थिरता को प्राप्त करने की कुंजी उप-प्रकार आधारित अनुरूपता है। (हम यहां उच्च स्तर पर लिसकोव प्रतिस्थापन सिद्धांत (एलएसपी) पर चर्चा करेंगे।)
व्यावहारिक उदाहरण (C # में छद्म कोड / अमान्य):
सहसंयोजक: मान लें कि पक्षी जो "टाइपिंग" लगातार स्थैतिक रूप से अंडे देते हैं: यदि पक्षी बर्ड एग देता है, तो क्या बर्ड का उपप्रकार एग का उपप्रकार नहीं रखेगा? उदा। प्रकार डक एक डक लेग देता है, फिर संगति दी जाती है। यह सुसंगत क्यों है? क्योंकि इस तरह की अभिव्यक्ति में: Egg anEgg = aBird.Lay();
संदर्भ बर्ड को कानूनी रूप से एक बर्ड या डक उदाहरण द्वारा प्रतिस्थापित किया जा सकता है। हम कहते हैं कि वापसी प्रकार, प्रकार में सहसंयोजक है, जिसमें ले () को परिभाषित किया गया है। एक उप-प्रकार का ओवरराइड एक अधिक विशिष्ट प्रकार लौटा सकता है। => "वे अधिक वितरित करते हैं।"
कंट्रोवर्सी: मान लीजिए कि पियानोस स्थिर टाइपिंग के साथ पियानोवादक "लगातार" खेल सकते हैं: यदि एक पियानोवादक पियानो बजाता है, तो क्या वह ग्रैंडपियानो खेलने में सक्षम होगा? बल्कि एक कलाप्रवीण व्यक्ति एक GrandPiano नहीं खेलेंगे? (चेतावनी दी है, एक मोड़ है!) यह असंगत है! क्योंकि ऐसी अभिव्यक्ति में: aPiano.Play(aPianist);
पियानो को कानूनी रूप से पियानो या ग्रैंडपियानो उदाहरण द्वारा प्रतिस्थापित नहीं किया जा सकता है! एक GrandPiano केवल एक Virtuoso द्वारा खेला जा सकता है, पियानोवादक बहुत सामान्य हैं! GrandPianos को अधिक सामान्य प्रकारों द्वारा बजाने योग्य होना चाहिए, फिर नाटक सुसंगत है। हम कहते हैं कि पैरामीटर प्रकार प्रकार के विपरीत है, जिसमें प्ले () परिभाषित है। एक उपप्रकार के ओवरराइड एक अधिक सामान्यीकृत प्रकार को स्वीकार कर सकते हैं। => "उन्हें कम आवश्यकता होती है।"
C # पर वापस:
क्योंकि C # मूल रूप से एक टाइप की गई भाषा है, एक प्रकार के इंटरफ़ेस का "स्थान" जो कि सह-या विपरीतार्थी होना चाहिए (जैसे पैरामीटर और रिटर्न प्रकार), उस प्रकार के निरंतर उपयोग / विकास की गारंटी देने के लिए स्पष्ट रूप से चिह्नित होना चाहिए। , एलएसपी काम ठीक करने के लिए। गतिशील रूप से टाइप की जाने वाली भाषाओं में एलएसपी स्थिरता आमतौर पर एक समस्या नहीं है, दूसरे शब्दों में आप पूरी तरह से .Net इंटरफेस और डेलिगेट्स पर "और" कॉन्ट्रोवर्शियल "मार्कअप" से छुटकारा पा सकते हैं, यदि आपने केवल अपने प्रकारों में टाइप डायनेमिक का उपयोग किया है। - लेकिन यह C # में सबसे अच्छा समाधान नहीं है (आपको सार्वजनिक इंटरफेस में गतिशील का उपयोग नहीं करना चाहिए)।
सिद्धांत पर वापस:
वर्णित अनुरूपता (सहसंयोजक वापसी प्रकार / कंट्राविरेंट पैरामीटर प्रकार) सैद्धांतिक आदर्श है (भाषाओं पन्ना और पीओएल -1 द्वारा समर्थित)। कुछ ऊप भाषाओं (जैसे एफिल) ने एक और प्रकार की संगति, एस्प लागू करने का निर्णय लिया। सहसंयोजक पैरामीटर प्रकार भी, क्योंकि यह सैद्धांतिक आदर्श की तुलना में वास्तविकता का बेहतर वर्णन करता है। सांख्यिकीय रूप से टाइप की गई भाषाओं में "डबल डिस्पैचिंग" और "विजिटर" जैसे डिज़ाइन पैटर्न के अनुप्रयोग द्वारा वांछित स्थिरता अक्सर प्राप्त की जानी चाहिए। अन्य भाषाएँ तथाकथित "कई प्रेषण" या बहु विधियाँ प्रदान करती हैं (यह मूल रूप से रन समय में फ़ंक्शन अधिभार का चयन करती है , जैसे CLOS के साथ) या डायनामिक टाइपिंग का उपयोग करके वांछित प्रभाव प्राप्त करें।
Bird
परिभाषित करता है public abstract BirdEgg Lay();
, तो लागू होना Duck : Bird
चाहिएpublic override BirdEgg Lay(){}
ताकि आपके दावे BirdEgg anEgg = aBird.Lay();
में किसी भी प्रकार का विचरण हो, बस असत्य है। स्पष्टीकरण के बिंदु का आधार होने के नाते, पूरा बिंदु अब चला गया है। आप चाहेंगे के बजाय का कहना है कि सहप्रसरण कार्यान्वयन जहां एक DuckEgg परोक्ष बाहर / वापसी प्रकार BirdEgg को डाला जाता है के भीतर मौजूद है? किसी भी तरह, कृपया मेरा भ्रम दूर करें।
DuckEgg Lay()
लिए एक वैध ओवरराइड नहीं है , और यह क्रूक्स है। C # covariant रिटर्न प्रकारों का समर्थन नहीं करता है, लेकिन Java के साथ-साथ C ++ भी करता है। मैंने C # के समान सिंटैक्स का उपयोग करके सैद्धांतिक आदर्श का वर्णन किया। C # में आपको बर्ड और डक को एक सामान्य इंटरफ़ेस लागू करने की आवश्यकता होती है, जिसमें ले को परिभाषित किया जाता है कि एक सहसंयोजक वापसी (यानी आउट-स्पेसिफिकेशन) प्रकार है, फिर मामले एक साथ फिट होते हैं! Egg Lay()
extends
, उपभोक्ता super
" के लिए है।
कनवर्टर प्रतिनिधि मुझे अंतर समझने में मदद करता है।
delegate TOutput Converter<in TInput, out TOutput>(TInput input);
TOutput
सहसंयोजक का प्रतिनिधित्व करता है जहां एक विधि अधिक विशिष्ट प्रकार देता है ।
TInput
विरोधाभासी का प्रतिनिधित्व करता है जहां एक विधि एक कम विशिष्ट प्रकार से पारित की जाती है ।
public class Dog { public string Name { get; set; } }
public class Poodle : Dog { public void DoBackflip(){ System.Console.WriteLine("2nd smartest breed - woof!"); } }
public static Poodle ConvertDogToPoodle(Dog dog)
{
return new Poodle() { Name = dog.Name };
}
List<Dog> dogs = new List<Dog>() { new Dog { Name = "Truffles" }, new Dog { Name = "Fuzzball" } };
List<Poodle> poodles = dogs.ConvertAll(new Converter<Dog, Poodle>(ConvertDogToPoodle));
poodles[0].DoBackflip();
सह और कॉन्ट्रा विचरण काफी तार्किक चीजें हैं। भाषा प्रकार प्रणाली हमें वास्तविक जीवन तर्क का समर्थन करने के लिए मजबूर करती है। उदाहरण द्वारा समझना आसान है।
उदाहरण के लिए आप एक फूल खरीदना चाहते हैं और आपके शहर में दो फूलों की दुकान है: गुलाब की दुकान और डेज़ी की दुकान।
यदि आप किसी से पूछते हैं "फूलों की दुकान कहाँ है?" और कोई आपको बताता है कि गुलाब की दुकान कहां है, क्या यह ठीक होगा? हां, क्योंकि गुलाब एक फूल है, अगर आप एक फूल खरीदना चाहते हैं तो आप गुलाब खरीद सकते हैं। वही लागू होता है यदि किसी ने आपको डेज़ी शॉप के पते के साथ उत्तर दिया।
यह सहसंयोजकता का उदाहरण है : यदि आप जेनेरिक मान (फ़ंक्शन से परिणाम के रूप में रिटर्न) पैदा करते हैं , तो आपको कास्ट A<C>
करने की अनुमति है A<B>
, जहां C
उप-वर्ग है । Covariance उत्पादकों के बारे में है, इसीलिए C # covariance के लिए कीवर्ड का उपयोग करते हैं ।B
A
out
प्रकार:
class Flower { }
class Rose: Flower { }
class Daisy: Flower { }
interface FlowerShop<out T> where T: Flower {
T getFlower();
}
class RoseShop: FlowerShop<Rose> {
public Rose getFlower() {
return new Rose();
}
}
class DaisyShop: FlowerShop<Daisy> {
public Daisy getFlower() {
return new Daisy();
}
}
प्रश्न "फूलों की दुकान कहाँ है?", उत्तर "गुलाब की दुकान है":
static FlowerShop<Flower> tellMeShopAddress() {
return new RoseShop();
}
उदाहरण के लिए आप अपनी प्रेमिका को एक फूल भेंट करना चाहते हैं और आपकी गर्लफ्रेंड किसी भी फूल को पसंद करती है। क्या आप उसे एक ऐसे व्यक्ति के रूप में मान सकते हैं जो गुलाब से प्यार करता है, या एक ऐसे व्यक्ति के रूप में जो प्यार करता है? हां, क्योंकि अगर वह किसी भी फूल से प्यार करती है तो वह गुलाब और डेज़ी दोनों से प्यार करती है।
यह विरोधाभासी का एक उदाहरण है : यदि आपको जेनेरिक मूल्य का उपभोग करने की अनुमति A<B>
है A<C>
, जहां आपको C
उप-वर्ग में प्रवेश करने की अनुमति है । उपभोक्ताओं के बारे में विरोधाभासी है, यही कारण है कि सी # कंट्रोवर्सी के लिए कीवर्ड का उपयोग करते हैं ।B
A
in
प्रकार:
interface PrettyGirl<in TFavoriteFlower> where TFavoriteFlower: Flower {
void takeGift(TFavoriteFlower flower);
}
class AnyFlowerLover: PrettyGirl<Flower> {
public void takeGift(Flower flower) {
Console.WriteLine("I like all flowers!");
}
}
आप अपनी प्रेमिका पर विचार कर रहे हैं, जो किसी भी फूल को प्यार करती है, जो गुलाब से प्यार करता है, और उसे गुलाब दे रहा है:
PrettyGirl<Rose> girlfriend = new AnyFlowerLover();
girlfriend.takeGift(new Rose());