क्या एक इटरेटर में एक गैर-विनाशकारी निहित अनुबंध होता है?


11

मान लें कि मैं एक कस्टम डेटा संरचना जैसे स्टैक या एक कतार (उदाहरण के लिए - कुछ अन्य मनमाने ढंग से ऑर्डर किए गए संग्रह हो सकता है, जिसमें तार्किक समतुल्य pushऔर popविधियाँ हो सकती हैं - जैसे विध्वंसक अभिगम विधि)।

यदि आप IEnumerable<T>इस संग्रह पर एक iterator (.NET में, विशेष रूप से ) लागू कर रहे हैं जो प्रत्येक पुनरावृत्ति पर पॉप होता है, तो क्या यह IEnumerable<T>अनुबंध को तोड़ रहा है ?

क्या IEnumerable<T>यह अनुबंधित है?

उदाहरण के लिए:

public IEnumerator<T> GetEnumerator()
{
    if (this.list.Count > 0)
        yield return this.Pop();
    else
        yield break;
}

जवाबों:


12

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

static void UpdateStatus<T>(IEnumerable<T> collection) where T : IBusinessObject
{
    foreach (var item in collection)
    {
        item.Status = BusinessObjectStatus.Foo;
    }
}

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

//developer uses your type without thinking about the details
var collection = GetYourEnumerableType<SomeBusinessObject>();

//developer uses my function without thinking about the details
UpdateStatus<SomeBusinessObject>(collection);

यहां तक ​​कि अगर डेवलपर विनाशकारी गणना के बारे में जानते हैं, तो वे संग्रह को एक ब्लैक-बॉक्स फ़ंक्शन को सौंपने के बारे में नहीं सोच सकते हैं। के लेखक के रूप में UpdateStatus, मैं शायद अपने डिजाइन में विनाशकारी गणना पर विचार नहीं करने जा रहा हूं।

हालाँकि, यह केवल एक निहित अनुबंध है। .NET संग्रह, सहित Stack<T>, उनके साथ एक स्पष्ट अनुबंध लागू करता है InvalidOperationException- "संग्रह के तुरंत बाद संशोधित किया गया था"। आप यह तर्क दे सकते हैं कि एक सच्चे पेशेवर के पास किसी भी कोड के प्रति एक चेतावनी भरा रवैया होता है जो उनका अपना नहीं है। एक विनाशकारी प्रगणक की खोज न्यूनतम परीक्षण के साथ की जाएगी।


1
+1 - on प्रिंसिपल ऑफ लिस्ट एस्टोनिशन ’के लिए मैं लोगों को उद्धृत करने जा रहा हूं।

+1 यह मूल रूप से मैं भी सोच रहा था, विनाशकारी होने के नाते LINQ IEnumerable एक्सटेंशन के अपेक्षित व्यवहार को तोड़ सकता है। यह सामान्य / विनाशकारी संचालन किए बिना इस प्रकार के आदेशित संग्रह पर पुनरावृति करना अजीब लगता है।
स्टीवन एवर्स

1

एक साक्षात्कार के लिए मुझे सबसे दिलचस्प कोडिंग चुनौतियों में से एक कार्यात्मक कतार बनाना था। आवश्यकता यह थी कि प्रत्येक कॉल करने के लिए एक नई कतार बनाई जाएगी जिसमें पुरानी कतार और पूंछ में नई वस्तु शामिल थी। Dequeue भी एक नई कतार और एक आउट परम के रूप में dequeued आइटम लौटाएगा।

इस कार्यान्वयन से एक IEnumerator बनाना nondestructive होगा। और मैं आपको एक कार्यात्मक कतार को लागू करने के बारे में बताता हूं जो एक अच्छा प्रदर्शन करता है, एक प्रदर्शनकारी कार्यात्मक स्टैक को लागू करने की तुलना में बहुत अधिक कठिन है (पूंछ पर स्टैक पुश / पॉप दोनों काम करते हैं, पूंछ पर एक क्यू एन्क्यू काम करता है, सिर पर काम करता है)।

मेरा कहना है ... यह अपने स्वयं के पॉइंटर तंत्र (स्टैकनोड <टी>) को लागू करके और एन्यूमरेटर में कार्यात्मक शब्दार्थ का उपयोग करके एक नॉनस्टेस्ट्रक्टिव स्टैक एन्यूमर बनाने के लिए तुच्छ है।

public class Stack<T> implements IEnumerator<T>
{
  private class StackNode<T>
  {
    private readonly T _data;
    private readonly StackNode<T> _next;
    public StackNode(T data, StackNode<T> next)
    {
       _data=data;
       _next=next;
    }
    public <T> Data{get {return _data;}}
    public StackNode<T> Next{get {return _Next;}}
  }

  private StackNode<T> _head;

  public void Push(T item)
  {
    _head =new StackNode<T>(item,_head);
  }

  public T Pop()
  {
    //Add in handling for a null head (i.e. fresh stack)
    var temp=_head.Data;
    _head=_head.Next;
    return temp;
  }

  ///Here's the fun part
  public IEnumerator<T> GetEnumerator()
  {
    //make a copy.
    var current=_head;
    while(current!=null)
    {
       yield return current.Data;
       current=_head.Next;
    }
  }    
}

ध्यान देने योग्य कुछ बातें। स्टेटमेंट = _हेड से पहले पुश या पॉप करने के लिए कॉल; पूर्णता आपको एन्यूमरेशन के लिए एक अलग स्टैक देगी यदि कोई मल्टीथ्रेडिंग नहीं था (आप इस से बचाव के लिए ReaderWriterLock का उपयोग करना चाह सकते हैं)। मैंने स्टैकनोड में फ़ील्ड्स को आसानी से बनाया लेकिन निश्चित रूप से अगर टी एक उत्परिवर्तनीय वस्तु है, तो आप इसके मान बदल सकते हैं। यदि आप एक Stack कंस्ट्रक्टर बनाने वाले थे जो StackNode को एक पैरामीटर के रूप में लेता था (और नोड में पारित किए गए सिर पर सेट करता है)। इस तरह से निर्मित दो ढेर एक-दूसरे को प्रभावित नहीं करेंगे (जैसा कि मैंने उल्लेख किया था कि एक उत्परिवर्ती टी के अपवाद के साथ)। आप एक स्टैक में सभी को पुश और पॉप कर सकते हैं, दूसरा नहीं बदलेगा।

और यह है कि मेरा दोस्त है कि आप एक स्टैक की गैर-विनाशकारी गणना कैसे करते हैं।


+1: मेरे गैर-विनाशकारी स्टैक और क्यू एन्युमेरेटर्स को समान रूप से लागू किया जाता है। मैं कस्टम तुलना के साथ PQs / हीप्स की तर्ज पर अधिक सोच रहा था।
स्टीवन एवर्स

1
मैं कहता हूं कि समान दृष्टिकोण का उपयोग कर सकते हैं ... मैं यह नहीं कह सकता कि मैंने पहले एक कार्यात्मक PQ लागू किया है ... ऐसा लगता है कि यह लेख गैर-रेखीय सन्निकटन के रूप में क्रमबद्ध अनुक्रमों का उपयोग करने का सुझाव देता है
माइकल ब्राउन

0

चीजों को "सरल" रखने के प्रयास में, .NET के पास केवल एक सामान्य / गैर-जेनेरिक जोड़ी इंटरफेस है जो उन चीजों के लिए है जिन्हें कॉल GetEnumerator()करके MoveNextऔर फिर उपयोग Currentकी गई वस्तु पर प्राप्त किया जाता है, भले ही कम से कम चार प्रकार की वस्तुएं हों जो केवल उन तरीकों का समर्थन करना चाहिए:

  1. चीजें जो कम से कम एक बार एनुमरेट की जा सकती हैं, लेकिन जरूरी नहीं कि इससे ज्यादा हो।
  2. जिन चीजों को मुक्त-थ्रेडेड संदर्भों में कई बार मनमाना माना जा सकता है, लेकिन हर बार मनमाने ढंग से सामग्री प्राप्त कर सकते हैं
  3. चीजें जो मुक्त-थ्रेडेड संदर्भों में कई बार मनमानी संख्या में गणना की जा सकती हैं, लेकिन गारंटी दे सकती हैं कि यदि कोड जो उन्हें बार-बार एन्यूमरेट करता है, तो किसी भी उत्परिवर्तन विधियों को कॉल नहीं करता है, सभी गणना समान सामग्री वापस कर देगी।
  4. जिन चीजों को बार-बार मनमानी किया जा सकता है, और जब तक वे मौजूद हैं हर बार उसी सामग्री को वापस करने की गारंटी दी जाती है।

कोई भी उदाहरण जो उच्चतर संख्या वाली परिभाषाओं में से एक को संतुष्ट करता है, वह सभी निचले लोगों को भी संतुष्ट करेगा, लेकिन कोड जिसे किसी उच्च परिभाषा को संतुष्ट करने वाली वस्तु की आवश्यकता होती है, यदि निम्न में से किसी एक को तोड़ दिया जाए।

ऐसा प्रतीत होता है कि Microsoft ने निर्णय लिया है कि जो कक्षाएं लागू होती हैं IEnumerable<T>उन्हें दूसरी परिभाषा को संतुष्ट करना चाहिए, लेकिन कुछ भी उच्चतर को संतुष्ट करने के लिए बाध्य नहीं हैं। यकीनन, इसका कोई कारण नहीं है कि कुछ ऐसी चीज़ जो केवल पहली परिभाषा को पूरा कर सकती है IEnumerable<T>बजाय इसके लागू होनी चाहिए IEnumerator<T>; यदि foreachलूप स्वीकार कर सकते हैं IEnumerator<T>, तो यह उन चीजों के लिए समझ में आता है जो केवल बाद के इंटरफ़ेस को लागू करने के लिए एक बार गणना की जा सकती हैं। दुर्भाग्य से, जो प्रकार केवल लागू IEnumerator<T>होते हैं वे C # और VB.NET में उपयोग करने के लिए कम सुविधाजनक होते हैं जो एक GetEnumeratorविधि है।

किसी भी मामले में, भले ही यह उपयोगी होगा अगर चीजों के लिए अलग-अलग गणना करने योग्य प्रकार थे जो अलग-अलग गारंटी दे सकते थे, और / या एक मानक तरीके से एक उदाहरण पूछने के लिए कि IEnumerable<T>यह क्या गारंटी देता है कि लागू हो सकता है, ऐसे कोई प्रकार अभी तक मौजूद नहीं हैं।


0

मुझे लगता है कि यह लिस्कोव प्रतिस्थापन सिद्धांत का उल्लंघन होगा जो बताता है,

एक कार्यक्रम में वस्तुओं को उस कार्यक्रम की शुद्धता में बदलाव किए बिना उनके उपप्रकारों के उदाहरणों के साथ बदली जानी चाहिए।

यदि मेरे पास एक तरीका है, जो मेरे कार्यक्रम में एक से अधिक बार कहा जाता है,

PrintCollection<T>(IEnumerable<T> collection)
{
    foreach (T item in collection)
    {
        Console.WriteLine(item);
    }
}

मुझे प्रोग्राम की शुद्धता में बदलाव किए बिना किसी भी IEnumerable को स्वैप करने में सक्षम होना चाहिए, लेकिन विनाशकारी कतार का उपयोग करने से प्रोग्राम के व्यवहार में बड़े पैमाने पर बदलाव होगा।

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