कन्वर्ट सूची <DerivedClass> को सूची <BaseClass>


179

जब हम आधार वर्ग / इंटरफ़ेस से विरासत में प्राप्त कर सकते हैं, तो हम एक List<> ही वर्ग / इंटरफ़ेस का उपयोग करने की घोषणा क्यों नहीं कर सकते हैं ?

interface A
{ }

class B : A
{ }

class C : B
{ }

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        List<A> listOfA = new List<C>(); // compiler Error
    }
}

क्या कोई रास्ता है?


जवाबों:


230

इस काम को करने का तरीका सूची पर पुनरावृति करना और तत्वों को डालना है। यह ConvertAll का उपयोग करके किया जा सकता है:

List<A> listOfA = new List<C>().ConvertAll(x => (A)x);

आप Linq का उपयोग भी कर सकते हैं:

List<A> listOfA = new List<C>().Cast<A>().ToList();

2
एक अन्य विकल्प: सूची <A> listOfA = listOfC.ConvertAll (x => (A) x);
अहलियाव लोमड़ी

6
कौन सा तेज है? ConvertAllया Cast?
मार्टिन ब्रौन

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

ModiX का उत्तर: ConvertAll सूची <T> पर एक विधि है, इसलिए यह केवल एक सामान्य सूची पर काम करेगा; यह IEnumerable या IEnumerable <T> पर काम नहीं करेगा; ConvertAll सभी कस्टम रूपांतरण भी कर सकता है, न कि केवल कास्टिंग, जैसे ConvertAll (इंच => इंच * 25.4)। Cast <A> एक LINQ एक्सटेंशन विधि है, इसलिए किसी भी IEnumerable <T> पर काम करता है (और गैर-सामान्य IEnumerable के लिए भी काम करता है), और LINQ के अधिकांश की तरह यह आस्थगित निष्पादन का उपयोग करता है, अर्थात केवल उतने ही आइटमों को रूपांतरित करता है जितने कि पुनः प्राप्त किया। इसके बारे में और अधिक पढ़ें: codeblog.jonskeet.uk/2011/01/13/…
एडवर्ड

172

सबसे पहले, ए, बी, सी जैसे असंभव-से-समझने वाले वर्ग नामों का उपयोग करना बंद करें, पशु, स्तनपायी, जिराफ़, या खाद्य, फल, नारंगी या कुछ और जहां रिश्ते स्पष्ट हैं, का उपयोग करें।

आपका प्रश्न यह है कि "मैं जिराफ़ की सूची को पशु की प्रकार की सूची के एक चर को क्यों नहीं सौंप सकता, क्योंकि मैं जिराफ़ को एक प्रकार के जानवर के चर के लिए आवंटित कर सकता हूं?"

जवाब है: मान लीजिए कि आप कर सकते थे। फिर क्या गलत हो सकता है?

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

हम बाद का चयन करते हैं।

इस तरह के रूपांतरण को "सहसंयोजक" रूपांतरण कहा जाता है। C # 4 में हम आपको रूपांतरणों और प्रतिनिधियों को सहसंयोजक रूपांतरण बनाने की अनुमति देंगे जब रूपांतरण हमेशा सुरक्षित होने के लिए जाना जाता है । मेरे ब्लॉग के लेखों को विवरण के लिए सहसंयोजक और विरोधाभासी देखें। (इस सप्ताह के दोनों सोमवार और गुरुवार को इस विषय पर नए सिरे से चर्चा होगी।)


3
क्या कोई आईलिस्ट <T> या ICollection <T> नॉन-जेनेरिक IList को लागू करने के बारे में कुछ भी असुरक्षित होगा, लेकिन गैर-जेनेरिक Ilist / ICollection को लागू करना IsReadOnly के लिए सही है, और इसे संशोधित करने वाले किसी भी तरीके या गुणों के लिए NotupupportedException को फेंक दें?
सुपरकैट

33
जबकि इस जवाब में काफी स्वीकार्य तर्क है कि यह वास्तव में "सच" नहीं है। इसका सरल उत्तर यह है कि C # इसका समर्थन नहीं करता है। जानवरों की एक सूची होने का विचार जिसमें जिराफ और बाघ शामिल हैं, पूरी तरह से मान्य है। एकमात्र मुद्दा तब आता है जब आप उच्च स्तर की कक्षाओं का उपयोग करना चाहते हैं। वास्तव में यह एक पैरेंट क्लास को एक फंक्शन के पैरामीटर के रूप में पारित करने के लिए अलग नहीं है और फिर इसे एक अलग सिबलिंग क्लास में डालने की कोशिश की जाती है। कलाकारों को लागू करने के साथ तकनीकी मुद्दे हो सकते हैं लेकिन ऊपर दिए गए स्पष्टीकरण से कोई कारण नहीं मिलता है कि यह एक बुरा विचार क्यों होगा।
पॉल कोल्ड्रे

2
@EricLippert IEnumerableइसके बजाय हम यह रूपांतरण क्यों कर सकते हैं List? यानी: List<Animal> listAnimals = listGiraffes as List<Animal>;संभव नहीं है, लेकिन IEnumerable<Animal> eAnimals = listGiraffes as IEnumerable<Animal>काम करता है।
LINQ

3
@jbueno: मेरे उत्तर के अंतिम पैराग्राफ को पढ़ें। वहाँ रूपांतरण सुरक्षित होने के लिए जाना जाता है । क्यों? क्योंकि जिराफ के अनुक्रम को जानवरों के अनुक्रम में बदलना असंभव है और फिर जानवरों के अनुक्रम में एक बाघ डाल दियाIEnumerable<T>और IEnumerator<T>दोनों कोविरेंस के लिए सुरक्षित के रूप में चिह्नित हैं, और संकलक ने सत्यापित किया है कि।
एरिक लिपर्ट

60

एरिक के महान विवरण को उद्धृत करने के लिए

क्या होता है? क्या आप चाहते हैं कि जिराफ की सूची में बाघ हो? क्या आप एक दुर्घटना चाहते हैं? या क्या आप चाहते हैं कि संकलक आपको पहले स्थान पर असाइनमेंट को अवैध बनाकर दुर्घटना से बचाए? हम बाद का चयन करते हैं।

लेकिन क्या होगा यदि आप एक संकलन त्रुटि के बजाय एक रनटाइम क्रैश के लिए चुनना चाहते हैं? आप आम तौर पर कास्ट <या ConvertAll <> का उपयोग करेंगे, लेकिन फिर आपको 2 समस्याएं होंगी: यह सूची की एक प्रति तैयार करेगा। यदि आप नई सूची में कुछ जोड़ते या हटाते हैं, तो यह मूल सूची में परिलक्षित नहीं होगा। और दूसरी बात, एक बड़ा प्रदर्शन और मेमोरी पेनल्टी है क्योंकि यह मौजूदा वस्तुओं के साथ एक नई सूची बनाता है।

मुझे भी यही समस्या थी और इसलिए मैंने एक रैपर क्लास बनाई, जो पूरी तरह से नई सूची बनाए बिना एक जेनेरिक सूची तैयार कर सकती है।

मूल प्रश्न में आप तब उपयोग कर सकते हैं:

class Test
{
    static void Main(string[] args)
    {
        A a = new C(); // OK
        IList<A> listOfA = new List<C>().CastList<C,A>(); // now ok!
    }
}

और यहाँ रैपर क्लास (+ आसान उपयोग के लिए एक एक्सट्रैक्शन विधि कास्टलिस्ट)

public class CastedList<TTo, TFrom> : IList<TTo>
{
    public IList<TFrom> BaseList;

    public CastedList(IList<TFrom> baseList)
    {
        BaseList = baseList;
    }

    // IEnumerable
    IEnumerator IEnumerable.GetEnumerator() { return BaseList.GetEnumerator(); }

    // IEnumerable<>
    public IEnumerator<TTo> GetEnumerator() { return new CastedEnumerator<TTo, TFrom>(BaseList.GetEnumerator()); }

    // ICollection
    public int Count { get { return BaseList.Count; } }
    public bool IsReadOnly { get { return BaseList.IsReadOnly; } }
    public void Add(TTo item) { BaseList.Add((TFrom)(object)item); }
    public void Clear() { BaseList.Clear(); }
    public bool Contains(TTo item) { return BaseList.Contains((TFrom)(object)item); }
    public void CopyTo(TTo[] array, int arrayIndex) { BaseList.CopyTo((TFrom[])(object)array, arrayIndex); }
    public bool Remove(TTo item) { return BaseList.Remove((TFrom)(object)item); }

    // IList
    public TTo this[int index]
    {
        get { return (TTo)(object)BaseList[index]; }
        set { BaseList[index] = (TFrom)(object)value; }
    }

    public int IndexOf(TTo item) { return BaseList.IndexOf((TFrom)(object)item); }
    public void Insert(int index, TTo item) { BaseList.Insert(index, (TFrom)(object)item); }
    public void RemoveAt(int index) { BaseList.RemoveAt(index); }
}

public class CastedEnumerator<TTo, TFrom> : IEnumerator<TTo>
{
    public IEnumerator<TFrom> BaseEnumerator;

    public CastedEnumerator(IEnumerator<TFrom> baseEnumerator)
    {
        BaseEnumerator = baseEnumerator;
    }

    // IDisposable
    public void Dispose() { BaseEnumerator.Dispose(); }

    // IEnumerator
    object IEnumerator.Current { get { return BaseEnumerator.Current; } }
    public bool MoveNext() { return BaseEnumerator.MoveNext(); }
    public void Reset() { BaseEnumerator.Reset(); }

    // IEnumerator<>
    public TTo Current { get { return (TTo)(object)BaseEnumerator.Current; } }
}

public static class ListExtensions
{
    public static IList<TTo> CastList<TFrom, TTo>(this IList<TFrom> list)
    {
        return new CastedList<TTo, TFrom>(list);
    }
}

1
मैंने अभी इसे MVC व्यू मॉडल के रूप में उपयोग किया है और इसे अच्छा सार्वभौमिक रेजर आंशिक रूप से देखने को मिला है। अद्भुत विचार! मैं बहुत खुश हूं कि मैंने इसे पढ़ा।
स्टीफन केबुलक

5
मैं केवल एक "जहां TTo: TFrom" को वर्ग घोषणा में जोड़ूंगा, इसलिए संकलक गलत उपयोग के खिलाफ चेतावनी दे सकता है। असंबंधित प्रकारों का कास्टेडलिस्ट बनाना वैसे भी निरर्थक है, और "कैस्टेडलिस्ट <TBase, TDerived>" बनाना व्यर्थ होगा: आप इसमें नियमित रूप से TBase ऑब्जेक्ट नहीं जोड़ सकते हैं, और मूल सूची से आपको हटाए गए किसी भी TDERived का पहले से ही उपयोग किया जा सकता है। एक टीबी
वोल्फज़ून

2
@PaCColdrey Alas, एक छह साल की सिर शुरुआत दोष है।
वोल्फज़ून

@Wolfzoon: मानक .Cast <T> () या तो यह प्रतिबंध नहीं है। मैं व्यवहार को वैसा ही बनाना चाहता था। इसलिए जानवरों की सूची को टाइगर्स की सूची में डालना संभव है, और इसका अपवाद तब होगा जब इसमें जिराफ उदाहरण के लिए होगा। जैसा कि यह कास्ट के साथ होगा ...
बिगजिम

हाय @Bigjim, मुझे यह समझने में समस्या हो रही है कि जिराफ के मौजूदा सरणी को एनिमल्स (बेस क्लास) की एक सरणी में डालने के लिए, अपने रैपर क्लास का उपयोग कैसे करें। किसी भी सुझाव का अनुमोदन किया जाएगा :)
डेनिस विटेज़

28

यदि तुम प्रयोग करते हो IEnumerable इसके बजाय करते हैं, तो यह काम करेगा (कम से कम C # 4.0 में, मैंने पिछले संस्करणों की कोशिश नहीं की है)। यह सिर्फ एक कास्ट है, निश्चित रूप से, यह अभी भी एक सूची होगी।

के बजाय -

List<A> listOfA = new List<C>(); // compiler Error

प्रश्न के मूल कोड में, का उपयोग करें -

IEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)


वास्तव में उस मामले में IEnumerable का उपयोग कैसे करें?
व्लाडियस डिस

1
List<A> listOfA = new List<C>(); // compiler Errorप्रश्न के मूल कोड के बजाय , दर्ज करेंIEnumerable<A> listOfA = new List<C>(); // compiler error - no more! :)
PhistucK

एक पैरामीटर के रूप में IEnumerable <BaseClass> लेने की विधि विरासत में मिली श्रेणी के लिए एक सूची के रूप में पारित करने की अनुमति देगा। इसलिए ऐसा करने के लिए बहुत कम है लेकिन पैरामीटर प्रकार बदलें।
beauXjames 17

27

जहां तक ​​यह काम क्यों नहीं करता है, यह सहसंयोजक और विरोधाभासी समझने में मददगार हो सकता है ।

बस यह दिखाने के लिए कि यह काम क्यों नहीं करना चाहिए , यहां आपके द्वारा प्रदान किए गए कोड में बदलाव है:

void DoesThisWork()
{
     List<C> DerivedList = new List<C>();
     List<A> BaseList = DerivedList;
     BaseList.Add(new B());

     C FirstItem = DerivedList.First();
}

क्या यह काम करना चाहिए? सूची में पहला आइटम टाइप "B" का है, लेकिन DerivedList आइटम का प्रकार C है।

अब, मान लें कि हम वास्तव में एक सामान्य कार्य करना चाहते हैं जो किसी प्रकार की सूची पर कार्य करता है जो कि ए को लागू करता है, लेकिन हमें परवाह नहीं है कि यह किस प्रकार है:

void ThisWorks<T>(List<T> GenericList) where T:A
{

}

void Test()
{
     ThisWorks(new List<B>());
     ThisWorks(new List<C>());
}

"क्या यह काम करना चाहिए?" - मैं हाँ और नहीं करूँगा। मुझे कोई कारण नहीं दिखाई देता है कि आप कोड क्यों नहीं लिख पा रहे हैं और यह संकलन के समय में विफल हो गया है क्योंकि यह वास्तव में उस समय एक अमान्य रूपांतरण कर रहा है जब आप FirstItem को 'C' के रूप में एक्सेस करते हैं। सी # को उड़ाने के कई अनुरूप तरीके हैं जो समर्थित हैं। यदि आप वास्तव में एक अच्छे कारण के लिए इस कार्यशीलता को प्राप्त करना चाहते हैं (और कई हैं) तो नीचे दिए गए बड़जिम का जवाब बहुत बढ़िया है।
पॉल कोल्ड्रे

16

आप केवल आसानी से सूची में डाल सकते हैं। उदाहरण के लिए:

IEnumerable<A> enumOfA = new List<C>();//This works
IReadOnlyCollection<A> ro_colOfA = new List<C>();//This works
IReadOnlyList<A> ro_listOfA = new List<C>();//This works

और आप इसे सूची के लिए नहीं कर सकते हैं जो बचत तत्वों का समर्थन करते हैं। कारण क्यों है:

List<string> listString=new List<string>();
List<object> listObject=(List<object>)listString;//Assume that this is possible
listObject.Add(new object());

अब क्या? याद रखें कि listObject और listString वास्तव में एक ही सूची है, इसलिए listString में अब ऑब्जेक्ट एलिमेंट है - यह संभव नहीं होना चाहिए और यह नहीं है।


1
यह मेरे लिए सबसे समझ में आने वाला जवाब था। विशेष रूप से क्योंकि यह उल्लेख करता है कि IReadOnlyList नामक एक चीज है, जो काम करेगी क्योंकि यह गारंटी देता है कि कोई और तत्व नहीं जोड़ा जाएगा।
7

यह शर्म की बात है कि आप IReadOnlyDictionary <TKey, TValue> के लिए ऐसा नहीं कर सकते। ऐसा क्यों है?
एलिस्टेयर माव

बहुत अच्छा सुझाव है। मैं सुविधा के लिए हर जगह सूची <> का उपयोग करता हूं, लेकिन अधिकांश समय मैं चाहता हूं कि इसे आसानी से पढ़ा जाए। अच्छा सुझाव है क्योंकि यह इस कास्ट की अनुमति देकर मेरे कोड में 2 तरह से सुधार करेगा और जहां मैं आसानी से निर्दिष्ट करता हूं, वहां सुधार करूंगा।
रिक लव

1

मैं व्यक्तिगत रूप से कक्षाओं के एक्सटेंशन के साथ लिबास बनाना पसंद करता हूं

public static List<TTo> Cast<TFrom, TTo>(List<TFrom> fromlist)
  where TFrom : class 
  where TTo : class
{
  return fromlist.ConvertAll(x => x as TTo);
}

0

क्योंकि C # उस प्रकार की अनुमति नहीं देता है विरासतइस समय रूपांतरण ।


6
पहला, यह परिवर्तनीयता का प्रश्न है, वंशानुक्रम का नहीं। दूसरा, सामान्य प्रकार के सहसंयोजक केवल इंटरफ़ेस और प्रतिनिधि प्रकारों पर, वर्ग प्रकारों पर काम नहीं करेंगे।
एरिक लिपपार्ट

5
खैर, मैं शायद ही आपसे बहस कर सकूं।
दोपहर सिल्क रेशम

0

यह बिगजिम के शानदार जवाब का विस्तार है ।

मेरे मामले में मेरे पास एक शब्दकोश के NodeBaseसाथ एक वर्ग था Children, और मुझे बच्चों से ओ (1) रूप-रंग देखने के लिए एक तरीके की आवश्यकता थी। मैं एक निजी डिक्शनरी के क्षेत्र में वापसी की कोशिश कर रहा था Children, इसलिए जाहिर है कि मैं महंगी कॉपी / पुनरावृति से बचना चाहता था। इसलिए मैंने Dictionary<whatever specific type>जेनेरिक कास्ट करने के लिए बिगजिम के कोड का उपयोग किया Dictionary<NodeBase>:

// Abstract parent class
public abstract class NodeBase
{
    public abstract IDictionary<string, NodeBase> Children { get; }
    ...
}

// Implementing child class
public class RealNode : NodeBase
{
    private Dictionary<string, RealNode> containedNodes;

    public override IDictionary<string, NodeBase> Children
    {
        // Using a modification of Bigjim's code to cast the Dictionary:
        return new IDictionary<string, NodeBase>().CastDictionary<string, RealNode, NodeBase>();
    }
    ...
}

इसने अच्छा काम किया। हालाँकि, मैं अंततः असंबंधित सीमाओं में चला गया और FindChild()बेस क्लास में एक अमूर्त पद्धति का निर्माण किया, जो इसके बजाय लुकअप करते थे। जैसा कि यह पता चला है कि यह पहली बार में डाले गए शब्दकोश की आवश्यकता को समाप्त कर देता है। (मैं IEnumerableअपने उद्देश्यों के लिए इसे एक साधारण से बदलने में सक्षम था ।)

तो सवाल जो आप पूछ सकते हैं (विशेषकर यदि प्रदर्शन एक मुद्दा है जो आपको उपयोग करने से रोक रहा है ) .Cast<>या .ConvertAll<>है:

"क्या मुझे वास्तव में पूरे संग्रह को बनाने की आवश्यकता है, या क्या मैं कार्य करने के लिए आवश्यक विशेष ज्ञान रखने के लिए एक सार पद्धति का उपयोग कर सकता हूं और इस तरह सीधे संग्रह तक पहुंचने से बच सकता हूं?"

कभी-कभी सबसे सरल समाधान सबसे अच्छा होता है।


0

आप System.Runtime.CompilerServices.Unsafeउसी का संदर्भ बनाने के लिए NuGet पैकेज का भी उपयोग कर सकते हैं List:

using System.Runtime.CompilerServices;
...
class Tool { }
class Hammer : Tool { }
...
var hammers = new List<Hammer>();
...
var tools = Unsafe.As<List<Tool>>(hammers);

ऊपर के नमूने को देखते हुए, आप चर Hammerका उपयोग करके सूची में मौजूदा उदाहरणों तक पहुंच सकते हैं tools। सूची में Toolउदाहरण जोड़ने से एक फेंकता हैArrayTypeMismatchException अपवाद को है क्योंकि toolsउसी चर को संदर्भित करता है hammers


0

मैंने इस पूरे सूत्र को पढ़ा है, और मैं केवल यह बताना चाहता हूं कि मेरे लिए एक असंगतता क्या है।

संकलक आपको सूचियों के साथ असाइनमेंट करने से रोकता है:

List<Tiger> myTigersList = new List<Tiger>() { new Tiger(), new Tiger(), new Tiger() };
List<Animal> myAnimalsList = myTigersList;    // Compiler error

लेकिन संकलक सरणियों के साथ पूरी तरह से ठीक है:

Tiger[] myTigersArray = new Tiger[3] { new Tiger(), new Tiger(), new Tiger() };
Animal[] myAnimalsArray = myTigersArray;    // No problem

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

myAnimalsArray[1] = new Giraffe();

मुझे एक रनटाइम अपवाद "ArrayTypeMismatchException" मिलता है। कोई इसे कैसे समझाता है? यदि कंपाइलर वास्तव में मुझे कुछ बेवकूफी करने से रोकना चाहता है, तो मुझे एरे असाइनमेंट करने से रोकना चाहिए था।

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