لماذا تنفيذ ienumerable (t) إذا كان بإمكاني تحديد getEnumerator واحد فقط؟

StackOverflow https://stackoverflow.com/questions/3737997

سؤال

تحديث: أنا أقدر كل التعليقات ، التي تضمنت بشكل أساسي معارضة بالإجماع. في حين أن كل اعتراض تم رفعه كان صالحًا ، أشعر أن الظفر النهائي في التابوت كان الملاحظة العذاء العذاء هذا ، في النهاية ، حتى واحد ضئيل الاستفادة من أن هذه الفكرة التي قدمتها ظاهريًا - القضاء على رمز البلوم - تم إلغاؤها من خلال حقيقة أن الفكرة نفسها تتطلبها ملك رمز الغلاية.

لذا نعم ، اعتبرني مقتنعًا: ستكون فكرة سيئة.

وفقط من نوع من إنقاذ كرامتي إلى حد ما: ربما كنت قد لعبت الأمر من أجل الحجة ، لكنني لم أبيع أبدًا هذه الفكرة لتبدأ - فضولي فقط لسماع ما قاله الآخرون حول هذا الموضوع. صادق.


قبل أن ترفض هذا السؤال باعتباره سخيفًا ، أطلب منك التفكير فيما يلي:

  1. IEnumerable<T> وراثة من* IEnumerable, مما يعني أن أي نوع ينفذ IEnumerable<T> عموما يجب أن تنفذ على حد سواء IEnumerable<T>.GetEnumerator و (صريح) IEnumerable.GetEnumerator. هذا يرقى أساسا إلى رمز الغلاية.
  2. تستطيع foreach على أي نوع لديه GetEnumerator الطريقة ، طالما أن هذه الطريقة تُرجع كائنًا من نوع ما باستخدام أ MoveNext الطريقة و Current منشأه. لذلك إذا كان نوعك يحدد واحد الطريقة مع التوقيع public IEnumerator<T> GetEnumerator(), ، من القانوني تعداده باستخدامه foreach.
  3. من الواضح أن هناك الكثير من التعليمات البرمجية التي تتطلب IEnumerable<T> واجهة - على سبيل المثال ، في الأساس جميع طرق تمديد LINQ. لحسن الحظ ، للذهاب من نوع يمكنك foreach إلى IEnumerable<T> هو تافهة باستخدام توليد التكرار التلقائي الذي يوفره C# عبر yield الكلمة الرئيسية.

لذا ، وضع هذا كله معًا ، كان لدي هذه الفكرة المجنونة: ماذا لو قمت فقط بتحديد واجهتي الخاصة التي تبدو هكذا:

public interface IForEachable<T>
{
    IEnumerator<T> GetEnumerator();
}

ثم كلما حددت نوعًا أريد أن أكون قابلاً للتعداد ، فإنني أقوم بتنفيذ هذه واجهة بدلا من IEnumerable<T>, والقضاء على الحاجة إلى تنفيذ اثنين GetEnumerator طرق (واحدة صريحة). فمثلا:

class NaturalNumbers : IForEachable<int>
{
   public IEnumerator<int> GetEnumerator()
   {
       int i = 1;
       while (i < int.MaxValue)
       {
           yield return (i++);
       }
   }

   // Notice how I don't have to define a method like
   // IEnumerator IEnumerable.GetEnumerator().
}

أخيراً, ، من أجل جعل هذا النوع متوافقًا مع الكود الذي يفعل توقع IEnumerable<T> الواجهة ، يمكنني فقط تحديد طريقة تمديد للذهاب من أي IForEachable<T> إلى IEnumerable<T> مثل ذلك:

public static class ForEachableExtensions
{
    public static IEnumerable<T> AsEnumerable<T>(this IForEachable<T> source)
    {
        foreach (T item in source)
        {
            yield return item;
        }
    }
}

يبدو لي أن القيام بذلك يمكّنني من تصميم الأنواع القابلة للاستخدام بكل طريقة لتطبيقات IEnumerable<T>, ، ولكن بدون هذا مزعج صريح IEnumerable.GetEnumerator التنفيذ في كل واحد.

فمثلا:

var numbers = new NaturalNumbers();

// I can foreach myself...
foreach (int x in numbers)
{
    if (x > 100)
        break;

    if (x % 2 != 0)
        continue;

    Console.WriteLine(x);
}

// Or I can treat this object as an IEnumerable<T> implementation
// if I want to...
var evenNumbers = from x in numbers.AsEnumerable()
                  where x % 2 == 0
                  select x;

foreach (int x in evenNumbers.TakeWhile(i => i <= 100))
{
    Console.WriteLine(x);
}

ماذا تعتقدون من هذه الفكرة يا رفاق؟ هل أفتقد بعض الأسباب لماذا سيكون هذا خطأ؟

أدركت أنه ربما يبدو وكأنه حل معقد للغاية لما لم يكن كبيرًا في الصفقة (أشك في أي شخص حقًا يهتم كثيرًا بالاضطرار إلى تحديد صريح IEnumerable واجهه المستخدم)؛ لكنها برزت في رأسي وأنا لا أرى أي مشاكل واضحة قد يطرحها هذا النهج.

بشكل عام ، إذا كان بإمكاني كتابة كمية معتدلة من التعليمات البرمجية ذات مرة لإنقاذ نفسي مشكلة في الاضطرار إلى كتابة كمية صغيرة من التعليمات البرمجية في كثير من الأحيان, بالنسبة لي ، الأمر يستحق ذلك.

*هل هذا هو المصطلحات الصحيحة للاستخدام؟ أنا دائمًا مترددة في قول واجهة واحدة "ترث من" آخر ، حيث لا يبدو أن هذا يلتقط العلاقة بينهما بشكل صحيح. ولكن ربما يكون صحيحا.

هل كانت مفيدة؟

المحلول

ألا تقم فقط بتحريك البلغم في مكان آخر - من كتابة IEnumerable.GetEnumeratorطريقة في كل فصل للاتصال بك AsEnumerable التمديد في كل مرة IEnumerable<T> متوقع؟ عادةً ما أتوقع استخدام نوع لا يطاق للاستعلام أكثر بكثير مما هو مكتوب (وهو مرة واحدة بالضبط). هذا يعني أن هذا النمط سيؤدي إلى أكثر غلاية ، في المتوسط.

نصائح أخرى

أنت تفتقد شيئًا ضخمًا -

إذا قمت بتنفيذ الواجهة الخاصة بك بدلاً من IEnumerable<T>, ، لن يعمل فصلك مع أساليب الإطار المتوقعة IEnumerable<T> - بشكل رئيسي ، لن تتمكن تمامًا من استخدام LINQ ، أو استخدام فصلك لإنشاء أ List<T>, ، أو العديد من التجريدات المفيدة الأخرى.

يمكنك تحقيق ذلك ، كما ذكرت ، من خلال طريقة تمديد منفصلة - ومع ذلك ، يأتي هذا بتكلفة. باستخدام طريقة تمديد للتحويل إلى IEnumerable<T>, ، أنت تضيف مستوى آخر من التجريد المطلوب من أجل استخدام فصلك (الذي ستقوم به في كثير من الأحيان أكثر من تأليف الفصل) ، وتقلل من الأداء (ستنشئ طريقة التمديد ، في الواقع ، تطبيق فئة جديد داخليًا داخليًا ، وهو أمر غير ضروري حقًا). الأهم من ذلك ، سيتعين على أي مستخدم آخر لفصلك (أو أنت لاحقًا) تعلم واجهة برمجة تطبيقات جديدة لا تحقق شيئًا - فأنت تجعل فصلك أكثر صعوبة في استخدام واجهات قياسية ، لأنه ينتهك توقعات المستخدم.

أنت على حق: إنه يفعل يبدو حلاً معقدًا للغاية لمشكلة سهلة للغاية.

كما أنه يقدم مستوى إضافيًا من عدم التوجيه لكل خطوة من التكرار. المحتمل ليست مشكلة في الأداء ، لكنها لا تزال غير ضرورية إلى حد ما عندما لا أعتقد أنك تكتسب حقًا أي شيء مهم للغاية.

أيضًا ، على الرغم من أن طريقة التمديد الخاصة بك تتيح لك تحويل أي IForEachable<T> إلى IEnumerable<T>, ، هذا يعني نوعك نفسه متعود تلبية قيد عام مثل هذا:

public void Foo<T>(T collection) where T : IEnumerable

أو ما شابه ذلك. في الأساس ، من خلال الاضطرار إلى إجراء التحويل الخاص بك ، فإنك تفقد القدرة على التعامل مع كائن واحد كما على حد سواء تنفيذ IEnumerable<T> و نوع الخرسانة الحقيقي.

أيضا ، من خلال عدم التنفيذ IEnumerable, ، أنت تحسب نفسك خارج نطاق التحصيل. (أنا في بعض الأحيان تنفذ IEnumerable صراحة فقط لاختيار تهيئة التجميع ، ورمي استثناء من GetEnumerator().)

أوه ، وقد قدمت أيضًا جزءًا إضافيًا من البنية التحتية غير المألو IEnumerable<T>.

في قاعدة بياناتنا ، ربما يكون هناك أكثر من 100 حالة من هذه المقتطف الدقيق:

IEnumerator IEnumerable.GetEnumerator()
{
    return this.GetEnumerator();
}

وأنا بخير حقا مع ذلك. إنه سعر صغير لدفع تكاليف التوافق الكامل مع كل قطعة أخرى من رمز .NET مكتوب على الإطلاق ؛)

يتطلب الحل المقترح بشكل أساسي كل مستهلك/متصل في الواجهة الجديدة لتذكر أيضًا استدعاء طريقة تمديد خاصة بها قبل ذلك مفيد.

إنه أسهل بكثير في الاستخدام IEnumerable للانعكاس. محاولة استدعاء واجهات عامة عن طريق الانعكاس هو هذا الألم. عقوبة الملاكمة عبر IEnumerable هل فقدت النفقات العامة للانعكاس نفسها فلماذا تهتم باستخدام واجهة عامة؟ بالنسبة للمثال ، يتبادر التسلسل إلى الذهن.

أعتقد أن الجميع قد ذكروا بالفعل الأسباب الفنية لعدم القيام بذلك ، لذلك سأضيف ذلك إلى المزيج: مطالبة المستخدم بالاتصال بـ ASENumerable () على مجموعتك لتتمكن من استخدام الملحقات التي لا يمكن تعدادها من شأنه أن ينتهك مبدأ المفاجأة الأقل.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top