What is Covariance, Contravariance & Invariance: इस पूरे Chapter के विभिन्न Programs में हमने देखा है कि जब हम Generic Type का Instance Create करते हैं, तो Compiler Compile Time में Specified Parameter Type के आधार पर Generics को Modify करके Constructed Type Create करता है, लेकिन सामान्यत: Generics Based Program Create करते समय ये समझने में Mistake हो जाती है कि हम किसी Derived Type के Delegate का Reference किसी Base Type के Delegate Variable को Assign कर सकते हैं। जबकि ऐसा नहीं होता और अब हम इसी Topic पर चर्चा करेंगे, जिसे C# में Variance नाम से जाना जाता है। C# में मूल रूप से तीन तरह के Variance होते हैं, जिन्हें Covariance, Contravariance व Invariance नाम से जाना जाता है।
C# के Latest Version में Variance Support Define किया गया है। जिसके अन्तर्गत C# में सभी Delegates Types द्वारा Matching Method Signatures वाले Methods तो Support होते ही हैं, साथ ही उसके Variance भी Support होते हैं। यानी हम किसी Delegate Type को न केवल Matching Signature वाले Callback Methods Assign कर सकते हैं, बल्कि हम वे Methods भी Assign कर सकते हैं, जिन्हें ज्यादा Derived Type में Implement किया गया है। इस तरह के Callback Methods को .NET Framework में Covariance नाम से जाना जाता है।
इसी तरह से हम Delegate Type द्वारा Accept किए जाने वाले Parameter के रूप में ऐसे Derived Types को भी Pass कर सकते हैं, जिन्हें कम Derived Type में Define किया गया है और इस तरह के Arguments को .NET Framework में Contravariance के नाम से जाना जाता है। जबकि Delegate Type के साथ Use किए जा सकने वाले इन Type Variations को Variance के नाम से जाना जाता है।
जब हम कोई Base Class Create करते हैं और उस Base Class को Inherit करके कोई Derived Class Create करते हैं, तो हमारा Base Class कम Derived Type होता है जबकि Derived Class अधिक Derived Type Class को Represent करता है।
जैसाकि हम जानते हैं कि जब हम किसी Base Type का Reference Variable Create करते हैं, तो उसकी किसी भी Derived Type के Object का Reference हम इस Base Type के Reference Variable में Store करके उस Derived Type के Members को Base Type के Reference के माध्यम से Access कर सकते हैं। इस प्रक्रिया को Assignment Compatibility के नाम से जाना जाता है।
यानी Assignment Compatibility प्रक्रिया का प्रयोग करके हम ज्यादा Derived Type के Object का Reference किसी कम Derived Type के Reference Variable में Assign कर सकते हैं। लेकिन Delegate Types के साथ इस तरीके को सामान्य तरीके से Use नहीं किया जा सकता। यानी किसी ज्यादा Derived Type के Delegate को किसी कम Derived Type के Delegate में Directly Assign नहीं किया जा सकता। इस Concept को समझने के लिए हम निम्नानुसार एक Program Create कर सकते हैं:
File Name: AssignmentCompatibilityWithDelegates.cs using System; namespace CSharpGenerics { delegate T MyDelegate<T>(); //Generic Delegate class BaseClass { public void Display() { Console.WriteLine("BaseClass Display Method."); } } class DerivedClass : BaseClass { } class Program { static DerivedClass CreateDerivedObject() //Delegate Matching Method { return new DerivedClass(); } static void Main(string[] args) { //Create Derived Object via Delegate MyDelegate<DerivedClass> derivedObjectCreator = CreateDerivedObject; //Assign Derived Object’s reference to Base Reference via Delegate MyDelegate<BaseClass> baseObjectCreator = derivedObjectCreator; baseObjectCreator().Display(); } } } Output: Error
जब ये Program Run होता है, तो सबसे पहले निम्न Statement Execute होता है:
MyDelegate<DerivedClass> derivedObjectCreator = CreateDerivedObject;
ये Statement DerivedClass Type का Delegate Object Create करता है और CreateDerivedObject नाम के Method का Reference, derivedObjectCreator नाम के Delegate Object की Invocation List में Store कर देता है। यानी derivedObjectCreator Delegate में Currently DerivedClass के Method का Reference Stored है। फिर निम्नानुसार अगला Code Execute होता है:
MyDelegate<BaseClass> baseObjectCreator = derivedObjectCreator;
इस बार ये Statement फिर से एक Delegate Object Create करता है जिसका नाम baseObjectCreator है और इस Delegate Object की Invocation List में derivedObjectCreator Delegate का Reference Store कर देता है, जिसमें पिछले Statement के माध्यम से DerivedClass Type के Method का Reference Stored है।
Theoretically हम ऐसा समझ सकते हैं कि जिस Delegate का Parameter Type BaseClass Specified है, उसमें उस Delegate के Object का Reference Store किया जा सकता है, जिसे DerivedClass Parameter Type के साथ Declare किया गया है। लेकिन Practically ऐसा नहीं होता। क्योंकि यदि ऐसा होताए तो जब हमारे Program का निम्न Statement Execute होता है:
baseObjectCreator().Display();
तब Error नहीं बल्कि Appropriate Output आता। यानी baseObjectCreator के साथ Display Method को Call करने पर Derived Class के Object के लिए Display() Method Invoke होता।
उपरोक्त Program में BaseClass के लिए Created Delegate Type Reference में DerivedClass के लिए Created Delegate Type के Object का Reference इसलिए Assign नहीं किया जा सकताए क्योंकि हालांकि DerivedClass, BaseClass से ही Inherited है, लेकिन MyDelegate<DerivedClass>, MyDelegate<BaseClass> से Inherited नहीं है।
बल्कि दोनों ही Constructed Delegate Types यानी MyDelegate<DerivedClass>, MyDelegate<BaseClass> वास्तव में delegate Type से Derive हो रहे हैं और delegate Type स्वयं .NET Framework के Top Level Class object Type से Derive हो रहा है।
इसलिए इस Program के अनुसार Situation होने पर हम किसी Base Class Delegate Type के Reference Variable में किसी Derived Class Delegate Type के Object का Reference Store नहीं कर सकते। क्योंकि दोनों ही MyDelegate Types के बीच आपस में Parent-Child Relationship नहीं है।
लेकिन Professional Development के दौरान इस तरह की Situation होने पर हम सामान्यत: यही चाहते हैं कि Assignment Compatibility Operation की मदद से Base Type का Delegate Reference Derived Type के Delegate Object को Refer करे, ताकि Base Type के Delegate Reference के माध्यम से Polymorphism की सुविधा प्राप्त करते हुए Derived Type के Delegate Type के लिए Derived Type Members को Access किया जा सके।
C# 3.5 तक हम Base Class Type के Delegate में Derived Class Type के Object का Reference Store नहीं कर सकते थे, लेकिन C# 4.0 में हम इस प्रकार का Delegate Create कर सकते हैं, जो Derived Class के Objects के Reference को Base Class के Reference Variable में Store कर सके और Base Class के Delegate Type Reference Variable के माध्यम से Derived Class के Members को Access किया जा सके। C# हमें ये सुविधा out Modifier के माध्यम से Provide करता है, जिसे हमें निम्नानुसार Use करना होता है:
delegate T MyDelegate<out T>(); //Generic Delegate
जब हम Generic Type को out Parameter के साथ Specify करते हैं, तो Main() Method का निम्न Statement Execute होने पर:
MyDelegate<BaseClass> baseObjectCreator = derivedObjectCreator;
तो baseObjectCreator Object में BaseClass का Reference Pass होता है। क्योंकि इस Statement के कारण Create होने वाला Constructed Delegate Type निम्नानुसार बनता है:
delegate BaseClass MyDelegate<out BaseClass>(); //Generic Delegate
परिणामस्वरूप अब जब baseObjectCreator में derivedObjectCreator Method को Assign किया जाता है, तो Create होने वाले baseObjectCreator Delegate की Invocation List में derivedObjectCreator() Method का Reference Store हो जाता है। इसलिए जब निम्न Statement Execute होता है:
baseObjectCreator().Display();
तो baseObjectCreator के माध्यम से तो derivedObjectCreator Object का Reference Return होता है, जिसके लिए Display() Method को Invoke किया जाता है और हमें निम्नानुसार Output प्राप्त होता है:
File Name: AssignmentCompatibilityCovarianceWithDelegates.cs using System; namespace CSharpGenerics { delegate T MyDelegate<out T>(); //Generic Delegate class BaseClass { public void Display() { Console.WriteLine("BaseClass Display Method."); } } class DerivedClass : BaseClass { } class Program { static DerivedClass CreateDerivedObject() //Delegate Matching Method { return new DerivedClass(); } static void Main(string[] args) { //Create Derived Object via Delegate MyDelegate<DerivedClass> derivedObjectCreator = CreateDerivedObject; //Assign Derived Object’s reference to Base Reference via Delegate MyDelegate<BaseClass> baseObjectCreator = derivedObjectCreator; baseObjectCreator().Display(); } } } Output: BaseClass Display Method.
Derived Type को केवल Output Value के रूप में Use करने के लिए जो Delegate Constructed Type Create होता है, उसे ही Covariance के नाम से जाना जाता है। यानी ऐसा Delegate Constructed Type, जो कि Derived Type को केवल Output Value के रूप में ही Use करे, Covariance कहलाता है। अत: हमारे इस Program में निम्नानुसार Defined Delegate Type एक Covariance का उदाहरण है:
delegate T MyDelegate<out T>(); //Generic Delegate – Covariance
जिस तरह से हम किसी Delegated Type को Base Class के Reference के माध्यम से Derived Class के Object का Reference Hold करने के लिए उपरोक्तानुसार Covariance Create कर सकते हैं। उसी तरह से हम निम्नानुसार तरीके से एक और Delegate Type Create कर सकते हैं, जो कि Parameter Type के रूप में किसी Base Class Type के लिए Derived Class के Object के Reference को Hold कर सकता है:
delegate T MyDelegate<in T>(); //Generic Delegate – Contravariance
इस प्रकार के Delegate Type को Contravariance नाम से जाना जाता है और Contravariance Create करते समय हमें Generic Type के साथ in Keyword को Use करना होता है। जैसे:
File Name: ContravarianceWithDelegates.cs using System; namespace CSharpGenerics { delegate void MyDelegate<in T>(T x); //Generic Delegate – Contravariance class BaseClass { public void Display() { Console.WriteLine("BaseClass Display Method."); } } class DerivedClass : BaseClass { } class Program { static void BaseClassAction(BaseClass baseReference) //Delegate Matching Method { Console.WriteLine("Main Program Method."); } static void Main(string[] args) { MyDelegate<BaseClass> baseRefDel = BaseClassAction; MyDelegate<DerivedClass> derivedRefDel = baseRefDel; derivedRefDel(new DerivedClass()); } } } Output: Main Program Method.
इस Program में हमने हमारे Delegate को निम्नानुसार तरीके से Define किया है, जो कि ऐसे Callback Methods को Invoke कर सकता है, जो कोई Value Return नहीं करताए लेकिन Parameter के रूप में Base Class का एक Object Accept करता है:
delegate void MyDelegate<in T>(T x); //Generic Delegate – Contravariance
जब ये Program Run होता है, तो सबसे पहले निम्न Statement द्वारा MyDelegate<BaseClass> Type का एक Reference Variable baseRefDel Create करता है और उस Variable में BaseClassAction() नाम के Static Method को Callback Method की तरह अपनी Invocation List में Add करता है:
MyDelegate<BaseClass> baseRefDel = BaseClassAction;
फिर निम्नानुसार अगला Statement Execute होता है, जो कि MyDelegate<DerivedClass> Type का derivedRefDel नाम का एक Delegate Create करता है और इस Derived Class के Delegate Object में MyDelegate<BaseClass> Type के Delegate Object baseRefDel का Reference Store करता है:
MyDelegate<DerivedClass> derivedRefDel = baseRefDel;
चूंकि baseRefDel BaseClass Type के Methods के लिए Define किया गया है और इस Statement द्वारा हम एक DerivedClass Delegate Object में BaseClass Delegate Object को Initialize कर रहे हैं, इसलिए C# Compiler सामान्य रूप से इस Conversion को Implicitly Perform नहीं करता।
लेकिन हमने हमारे इस Program में MyDelegate को निम्नानुसार तरीके से in Modifier के साथ Define किया है:
delegate void MyDelegate<in T>(T x); //Generic Delegate – Contravariance
इसलिए ये Delegate वास्तव में एक Contravariance है, क्योंकि in Modifier का प्रयोग करने के कारण C# Compiler समझ जाता है कि हम किसी ज्यादा Derived Delegate (DerivedClass) Object में किसी कम Derived Delegate <BaseClass> Object का Reference Store करना चाहते हैं। परिणामस्वरूप ये Program Normal तरीके से Run होता है।
लेकिन यदि हम इस Delegate Declaration से in Modifier को Remove कर दें, तो फिर इस Program को Run करने पर C# Compiler Error Generate करता है। क्योंकि अब ये Delegate एक Normal Delegate बन जाता है न कि Contravariance Delegate और Normal Delegate Object किसी Derived Class Reference Variable में Base Class Object का Reference Store नहीं कर सकताए जब तक कि हम Manually Type Casting न करें। यानी जब हम BaseClass Delegate Reference में Derived Class Delegate Object का Reference Hold करना चाहते हैं, तो Normal Delegate Type द्वारा हम ऐसा नहीं कर सकते।
इसलिए हम Delegate Declaration में out Parameter Type Specify करके C# Compiler को इस बात की Instruction देते है, कि कोई BaseClass Delegate Reference Variable किसी Derived Class के Object का Reference, बिना Explicit Type Casting किए हुए Hold कर सकता है।
इस प्रकार से ज्यादा Derived Type (DerivedClass) के Object का Reference कम Derived Type (BaseClass) के Reference Variable में Hold करने के लिए out Modifier के साथ Declare किए गए Delegate को Covariance कहते हैं। यानी जहां कम Derived Type के Object की जरूरत हो, वहां अधिक Derived Type के Object को Assign करने के लिए Define किया गया Delegate, Covariance कहलाता है।
इसी तरह से जब हम Delegate द्वारा Invoke किए जा सकने वाले Methods में Parameter के रूप में किसी DerivedClass Reference Variable में BaseClass Object के Reference को Hold करना चाहते हैं, तो Normal Delegate Type द्वारा हम ऐसा नहीं कर सकते।
इसलिए हम Delegate Declaration के Method Signature में Arguments के साथ in Parameter Type Specify करके C# Compiler को इस बात का Instruction देते है, कि वह किसी DerivedClass Delegate Reference Variable में किसी BaseClass Delegate Object का Reference बिना Explicit Type Casting किए हुए Hold कर सकता है।
इस प्रकार से ज्यादा Derived Type (DerivedClass) के Object का Reference कम Derived Type (BaseClass) के Reference Variable में Hold करने के लिए in Modifier के साथ Declare किए गए Delegate को Contravariance कहते हैं। यानी जहां अधिक Derived Type के Object की जरूरत हो, वहां कम Derived Type के Object को Assign करने के लिए Define किया गया Delegate, Contravariance कहलाता है।
पिछले उदाहरणों में हमने देखा कि BaseClass व DerivedClass Type के Delegates के लिए हमें Manually Covariance या Contravariance Crete करना पडता है। लेकिन कई बार ऐसी स्थिति भी होती है, जब C# Compiler स्वयं ही किसी Specific Constructed Delegate Type के लिए Implicitly इस बात का पता लगा लेता है कि Delegate Type, Covariance है या Contravariance है और Constructed Delegate Type के आधार पर Automatically Type Casting कर लेता है।
हालांकि Compiler Delegate Constructed Type के लिए कुछ परिस्थितियों में Implicit Casting कर लेता है, लेकिन फिर भी किसी भी तरह की परेशानी से बचने के लिए हमें हमेंशा out व in Modifiers को उपयोग में ले लेना चाहिए।
Covariance and Contravariance with Interface
जिस तरह से हम Delegates को Covariance व Contravariance की तरह Define करते हैं, ठीक उसी तरह से हम Interfaces Declaration के साथ out व in Keywords Specify करके Interface को भी Covariance व Contravariance के लिए Define कर सकते हैं। क्योंकि हम Interface Type के Reference में BaseClass व DerivedClass दोनों ही तरह के References को Hold कर सकते हैं।
जब हम कोई Parameter Type T के लिए कोई Generic Interface Define करते हैं, तो out Keyword को Specify करके हम C# Compiler को इसी बात का Instruction देते हैं कि ये Interface एक Covariant है।
जबकि Interface में Declared Methods के Argument List में जब हम इस तरह के Generic Interface को Parameter की तरह Specify करते हैं, तो वह Method हमारे Generic Interface Constructed Type को Parameter के रूप में Use कर सकता है। इस तरह से Argument के रूप में Specified Interface Type को जब in Parameter Type के साथ Define करते हैं, तो ऐसा Interface Contravariance कहलाता है।
यदि हम हमारे पिछले Demo Program को ही Covariance व Contravariance के लिए Modify करें, तो हमारा Program कुछ निम्नानुसार बनेगा:
File Name: CovarianceWithInterface.cs using System; namespace CSharpGenerics { public class BaseClass { public void Display() { Console.WriteLine("Base Class Method"); } } public class DerivedClass : BaseClass { } public interface MyInterface<out T> //Covariance { void Display(); } class GenClass<T> : MyInterface<T> { public void Display() { Console.WriteLine("BaseClass Display Method."); } } class Program { static void Action(MyInterface<BaseClass> interfaceRef) //Contravariance { interfaceRef.Display(); } static void Main(string[] args) { GenClass<DerivedClass> derivedObject = new GenClass<DerivedClass>(); MyInterface<BaseClass> baseClassInterface = derivedObject; Action(baseClassInterface); Action(derivedObject); } } } // Output: BaseClass Display Method. BaseClass Display Method.
इस Program में हमने BaseClass व DerivedClass नाम की दो Normal Classes Define की हैं और GenClass नाम की एक Generic Class Define की है, जिसमें MyInterface नाम के Interface को Implement किया है।
जब Main() Method Execute होता है, तो निम्नानुसार Statement द्वारा सबसे पहले DerivedClass Parameter Type के आधार पर GenClass का Constructed Type Create होता है, जिसके आधार पर derivedObject नाम का DerivedClass Type का एक Object Create होता है:
GenClass<DerivedClass> derivedObject = new GenClass<DerivedClass>();
फिर निम्नानुसार BaseClass Parameter Type का baseClassInterface नाम का एक Interface Create होता है और इस Interface में DerivedObject का Reference Store कर दिया जाता है:
MyInterface<BaseClass> baseClassInterface = derivedObject;
हम BaseClass Type के Constructed Interface Reference Variable में DerivedClass Type के Constructed Class Type के Object का Reference इसलिए Store कर सकते हैं, क्योंकि हमने MyInterface को निम्नानुसार out Parameter के साथ Specify करके इसे Covariance बनाया है:
public interface MyInterface<out T> //Covariance
यदि हम इस Interface Declaration से out Keyword को Remove कर दें, तो उस स्थिति में हमें C# Compiler द्वारा Compile Time Error Return होगा, क्योंकि उस स्थिति में जब हम MyInterface<BaseClass> Type के Reference में GenClass<DerivedClass> Type के Object का Reference Store करेंगे, तो C# Compiler Implicit Type Conversion नहीं करेगा और Interface तथा Class दोनों के अलग-अलग Type का होने के कारण Compile Time Error Return होगा। इसी तरह से जब Program का निम्नानुसार अगला Statement Execute होगा:
Action(baseClassInterface);
तो ये Statement Action नाम के Static Method को Invoke करेगा, जो कि Parameter के रूप में एक MyInterface<BaseClass> Type का Interface Reference Accept करता है। चूंकि baseClassInterface Reference Variable, BaseClass Type का ही है, इसलिए बिना किसी परेशानी के baseClassInterface का Reference Action नाम के Method के interfaceRef नाम के BaseClass Interface Constructed Type में Store हो जाता है। लेकिन जब निम्नानुसार अगला Statement Execute होता है:
Action(derivedObject);
तब भी Action Static Method बिना किसी तरह का Error Return किए हुए Normal तरीके से इसलिए Run होता है, क्योंकि derivedObject, GenClass<BaseClass> Type का Interface है और BaseClass Type के Constructed Interface Reference में हम DerivedClass Type के Constructed Class Object का Reference Store कर सकते हैं। क्योंकि BaseClass का Interface Reference अपनी किसी भी Derived Class के Object के Reference को बिना किसी तरह का Type Cast किए हुए Store कर सकता है।
ये Article इस वेबसाईट पर Selling हेतु उपलब्ध EBook C#.NET in Hindi से लिया गया है। इसलिए यदि ये Article आपके लिए उपयोगी रहा, तो निश्चित रूप से ये पुस्तक भी आपके लिए काफी उपयोगी साबित होगी।
C#.NET in Hindi | Page:908 | Format: PDF