2013-07-02 9 views
24

मैं एजेंडे नियुक्तियों के साथ काम करने के लिए इंटरफेस और सामान्य वर्गों के एक जोड़े को बनाया है:एक सामान्य प्रकार की बाधा का परिणाम कोई अंतर्निहित संदर्भ रूपांतरण त्रुटि क्यों नहीं होता है?

interface IAppointment<T> where T : IAppointmentProperties 
{ 
    T Properties { get; set; } 
} 

interface IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties> 
{ 
    DateTime Date { get; set; } 
    T Appointment { get; set; } 
} 

interface IAppointmentProperties 
{ 
    string Description { get; set; } 
} 

class Appointment<T> : IAppointment<T> where T : IAppointmentProperties 
{ 
    public T Properties { get; set; } 
} 

class AppointmentEntry<T> : IAppointmentEntry<T> where T : IAppointment<IAppointmentProperties> 
{ 
    public DateTime Date { get; set; } 
    public T Appointment { get; set; } 
} 

class AppointmentProperties : IAppointmentProperties 
{ 
    public string Description { get; set; } 
} 

मैं प्रकार पैरामीटर पर कुछ बाधाओं का उपयोग करने के लिए सुनिश्चित करें कि वैध प्रकार निर्दिष्ट किया जा सकता कोशिश कर रहा हूँ।

class MyAppointment : Appointment<MyAppointmentProperties> 
{ 
} 

// This goes wrong: 
class MyAppointmentEntry : AppointmentEntry<MyAppointment> 
{ 
} 

class MyAppointmentProperties : AppointmentProperties 
{ 
    public string ExtraInformation { get; set; } 
} 

त्रुटि है:

The type 'Example.MyAppointment' cannot be used as type parameter 'T' in the generic type or method 'Example.AppointmentEntry<T>'. There is no implicit reference conversion from 'Example.MyAppointment' to 'Example.IAppointment<Example.IAppointmentProperties>'.

किसी को क्यों समझा सकते हैं हालांकि, जब एक बाधा को परिभाषित है कि TIAppointment<IAppointmentProperties> को लागू करना चाहिए निर्दिष्ट करते समय, संकलक एक त्रुटि जब एक वर्ग है कि है Appointment<AppointmentProperties> का उपयोग कर देता है यह काम नहीं करता?

+5

यह विषम है। ** लेकिन **: यह जेनेरिकों का एक उग्र अति प्रयोग है। मैं मुश्किल से पढ़ सकता हूं (मुझे लगता है) बहुत, बहुत सरल कोड। –

उत्तर

91

के आसान बनाने में करते हैं:

interface IAnimal { ... } 
interface ICage<T> where T : IAnimal { void Enclose(T animal); } 
class Tiger : IAnimal { ... } 
class Fish : IAnimal { ... } 
class Cage<T> : ICage<T> where T : IAnimal { ... } 
ICage<IAnimal> cage = new Cage<Tiger>(); 

आपका सवाल यह है: क्यों अंतिम पंक्ति गैर कानूनी है?

अब जब मैंने इसे सरल बनाने के लिए कोड को फिर से लिखा है, तो यह स्पष्ट होना चाहिए। एक ICage<IAnimal> एक पिंजरा है जिसमें आप किसी भी जानवर रख सकते हैं, लेकिन Cage<Tiger>केवल बाघ रख सकता है, इसलिए यह अवैध होना चाहिए।

तो यह गैरकानूनी नहीं होता तो आपको ऐसा कर सकता है:

cage.Enclose(new Fish()); 

और हे, तो आप सिर्फ एक शेर के पिंजरे में एक मछली डाल दिया।

टाइप सिस्टम उस रूपांतरण की अनुमति नहीं देता है क्योंकि ऐसा करने से नियम का उल्लंघन होगा कि स्रोत प्रकार की क्षमताओं को लक्ष्य प्रकार की क्षमताओं की तुलना में कम नहीं होना चाहिए। (यह प्रसिद्ध "लिस्कोव प्रतिस्थापन सिद्धांत" का एक रूप है।)

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

+0

बनाने के लिए देखूंगा आपका सरलीकरण इसे बहुत स्पष्ट बनाता है! मैं जेनरिक्स के क्लीनर तरीके से उपयोग करने के लिए कक्षाओं को फिर से डिजाइन करने की कोशिश करूंगा। उत्तर के रूप में चिह्नित। – Rens

+1

यह एक बहुत अच्छा/सरल स्पष्टीकरण है। मुझे जॉन स्कीट की पुस्तक सी # गहराई में पढ़ना याद है, यह सी # जेनेरिक प्रकार कोविरिएन्स contravariance बहुत अच्छी तरह से बताता है। बहुत अधिक सिफारिश की जाती है। –

+0

"आप बस एक बाघ पिंजरे में एक मछली डाल दिया।" क्या यह बाघ मछली थी? :-) – twelveFunKeys

6

क्योंकि आपने इंटरफेस के बजाय ठोस प्रकार का उपयोग करके MyAppointment कक्षा घोषित की है। आपको निम्नानुसार घोषित करना चाहिए:

class MyAppointment : Appointment<IAppointmentProperties> { 
} 

अब रूपांतरण पूरी तरह से हो सकता है।

बाधा where T: IAppointment<IAppointmentProperties> आप एक अनुबंध जिससे AppointmentEntry<T>के लिए अनिर्दिष्ट प्रकार किसी भी प्रकार कि IAppointmentProperties साथ घोषित किया जाता है में समा जाने चाहिए पैदा कर रहे साथ AppointmentEntry<T> घोषित करने से। कंक्रीट क्लास के साथ इस प्रकार की घोषणा करके आपने उस अनुबंध का उल्लंघन किया है (यह IAppointmentProperties का प्रकार लागू करता है लेकिन प्रकार नहीं)।

+0

हाँ, आप सही हैं। लेकिन मैं 'IAppointmentProperties' के गुणों का विस्तार करने के लिए कंक्रीट प्रकार 'MyAppointmentProperties' (यह अब तक उदाहरण में नहीं था) का उपयोग करके' MyAppointment' क्लास घोषित करना चाहता हूं। क्या एक अनुबंध निर्दिष्ट करना संभव है जो इसके लिए अनुमति देता है? – Rens

+0

आप उन्हें दोहराने के बजाए दो स्पष्ट जेनेरिक प्रकार पैरामीटर के साथ घोषित कर सकते हैं: 'क्लास अपॉइंटमेंट एंटर्री <टीएपॉइंटमेंट, टीप्रोपर्टीज>: IAppointmentEntry जहां TAppointment: IAppointment 'हालांकि, मैं आपको सावधानी बरतता हूं (दूसरों के रूप में) आपके प्रकार पदानुक्रम को ओवरकस्ट्रेन नहीं करना जब तक ऐसा करने के लिए एक बहुत ही अनिवार्य कारण नहीं है। –

+0

स्पष्टीकरण के लिए धन्यवाद। मैं प्रकार पदानुक्रम कम जटिल के साथ-साथ – Rens

0

अगर आप से नमूना इंटरफ़ेस फिर से परिभाषित यह काम करेगा:

interface ICage<T> 

interface ICage<out T> 

को

(out कीवर्ड नोटिस कृपया) उसके बाद निम्न कथन सही है:

ICage<IAnimal> cage = new Cage<Tiger>(); 
6

एरिक द्वारा पहले से ही एक बहुत अच्छा जवाब है। बस Invariance, कोविरिएन्स और Contravariance के बारे में बात करने का यह मौका लेना चाहता था।

परिभाषा देखने के लिए कृपया https://msdn.microsoft.com/en-us/library/dd799517(v=vs.110).aspx


मान लीजिए कि एक चिड़ियाघर है चलो देखते हैं।

abstract class Animal{} 
abstract class Bird : Animal{} 
abstract class Fish : Animal{} 
class Dove : Bird{} 
class Shark : Fish{} 

चिड़ियाघर स्थानांतरित हो रहा है, इसलिए इसके जानवरों को पुराने चिड़ियाघर से नए में स्थानांतरित करने की आवश्यकता है।

निश्चरता

इससे पहले कि हम उन्हें ले जाते हैं, हम अलग अलग कंटेनर में पशुओं डाल करने के लिए की जरूरत है। कंटेनर सभी एक ही ऑपरेशन करते हैं: इसमें एक जानवर डाल दें या इससे बाहर निकलें।

class FishTank<T> : IContainer<T> where T : Fish 
{ 
    public void Put(T t){} 
    public T Get(int id){return default(T);} 
} 

तो मछली में रखा जा सकता और (उम्मीद अब भी जिंदा) टैंक से बाहर निकलना:

IContainer<Fish> fishTank = new FishTank<Fish>(); //Invariance, the two types have to be the same 
fishTank.Put(new Shark());   
var fish = fishTank.Get(8); 

मान लीजिए हम

interface IContainer<T> where T : Animal 
{ 
    void Put(T t); 
    T Get(int id); 
} 

मछली के लिए स्पष्ट रूप से हम एक टैंक की जरूरत है को IContainer<Animal> पर बदलने के लिए अनुमति दी गई है, तो आप गलती से टैंक में एक कबूतर डाल सकते हैं, जो अनजाने में त्रासदी होगी।

IContainer<Animal> fishTank = new FishTank<Fish>(); //Wrong, some animal can be killed 
fishTank.Put(new Shark()); 
fishTank.Put(new Dove()); //Dove will be killed 

contravariance

दक्षता में सुधार करने के लिए, चिड़ियाघर प्रबंधन टीम dicides लोड को अलग करने और प्रक्रिया (प्रबंधन हमेशा ऐसा करते हैं) अनलोड करने के लिए। तो हमारे पास दो अलग-अलग ऑपरेशन हैं, केवल लोड के लिए, दूसरा अनलोड।

class BirdCage<T> : ILoad<T> where T : Bird 
{ 
    public void Put(T t) 
    { 
    } 
} 

ILoad<Bird> normalCage = new BirdCage<Bird>(); 
normalCage.Put(new Dove()); //accepts any type of birds 

ILoad<Dove> doveCage = new BirdCage<Bird>();//Contravariance, Bird is less specific then Dove 
doveCage.Put(new Dove()); //only accepts doves 

सहप्रसरण नए चिड़ियाघर हम जानवरों उतारने के लिए एक टीम है में

:

interface ILoad<in T> where T : Animal 
{ 
    void Put(T t); 
} 

फिर हम एक पक्षी पिंजरे की है।

interface IUnload<out T> where T : Animal 
{ 
    IEnumerable<T> GetAll(); 
} 

class UnloadTeam<T> : IUnload<T> where T : Animal 
{ 
    public IEnumerable<T> GetAll() 
    { 
     return Enumerable.Empty<T>(); 
    } 
} 

IUnload<Animal> unloadTeam = new UnloadTeam<Bird>();//Covariance, since Bird is more specific then Animal 
var animals = unloadTeam.GetAll(); 

टीम के दृष्टिकोण से, इससे कोई फर्क नहीं पड़ता कि यह अंदर क्या है, वे सिर्फ कंटेनरों से जानवरों को उतार देते हैं।

संबंधित मुद्दे