.net - لماذا يسمح المحول البرمجي C#صريحة صريحة بين IEnumerable<T>و TAlmostAnything؟




generics compiler-errors (5)

أفترض أنه بسبب IEnumerable<T> هو واجهة حيث يمكن أن يكون بعض التنفيذ صريحا إلى Banana - بغض النظر عن مدى سخيفة من شأنه أن يكون.

من ناحية أخرى ، يعرف المحول البرمجي أنه لا يمكن إرسال List<T> بشكل صريح إلى Banana .

اختيار جيد من الأمثلة ، بالمناسبة!

إضافة مثال للتوضيح. ربما سيكون لدينا بعض "المعدود" الذي يجب أن يحتوي دائمًا على كمية واحدة من Banana :

public class SingleItemList<T>:Banana, IEnumerable<T> where T:Banana {
    public static explicit operator T(SingleItemList<T> enumerable) {
        return enumerable.SingleOrDefault();
    }

    // Others omitted...
}

ثم يمكنك فعل هذا بالفعل:

IEnumerable<Banana> aBunchOfBananas = new SingleItemList<Banana>();
Banana justOneBanana = (Banana)aBunchOfBananas;

كما هو الحال في كتابة ما يلي ، والذي يكون المترجم سعيدًا تمامًا بما يلي:

Banana justOneBanana = aBunchOfBananas.SingleOrDefault();

تمنحك التعليمة البرمجية التالية خطأ في المحول البرمجي ، كما تتوقع:

List<Banana> aBunchOfBananas = new List<Banana>();

Banana justOneBanana = (Banana)aBunchOfBananas;

ومع ذلك ، عند استخدام IEnumerable<Banana> ، يمكنك فقط الحصول على خطأ وقت التشغيل.

IEnumerable<Banana> aBunchOfBananas = new List<Banana>();

Banana justOneBanana = (Banana)aBunchOfBananas;

لماذا يسمح مترجم C # بهذا؟


وفقًا لمواصفات اللغة (6.2.4) "التحويلات المرجعية الصريحة هي: من أي فئة من نوع S إلى أي نوع T من الواجهة ، بشرط ألا تكون S مختومة ، بشرط عدم تطبيق S على T ... التحويلات المرجعية الصريحة هي تلك التحويلات بين أنواع المرجعية التي تتطلب عمليات تحقق من وقت التشغيل للتأكد من صحتها ... "

لذلك المترجم لا يتحقق من تحقيق واجهة أثناء التجميع. يفعل CLR في وقت التشغيل. وهي تتحقق من البيانات الوصفية التي تحاول أن تحقق إدراكًا في الفصل أو بين والديها. أنا لا أعرف لماذا يتصرف مثل هذا. ربما يستغرق الكثير من الوقت. لذلك هذا الرمز يجمع بشكل صحيح:

public interface IInterface
{}

public class Banana
{
}

class Program
{
    static void Main( string[] args )
    {
        Banana banana = new Banana();

        IInterface b = (IInterface)banana;
    }
}

من ناحية أخرى ، إذا حاولنا إلقاء الموز على الطبقة ، يقوم المجمع البرمجي بفحص البيانات الوصفية الخاصة به وإلقاء خطأ:

 FileStream fs = (FileStream)banana;

عندما تقول Y y = (Y)x; يقول هذا المصبوب للمترجم "ثق بي ، أيا كان x ، في وقت التشغيل يمكن أن يلقي إلى Y ، لذلك ، فقط تفعل ذلك ، حسنا؟"

لكن عندما تقول

List<Banana> aBunchOfBananas = new List<Banana>();
Banana justOneBanana = (Banana)aBunchOfBananas;

يمكن أن ينظر المترجم إلى التعريفات لكل من هذه الطبقات الملموسة ( Banana List<Banana> ) ، ويرى أنه لا يوجد static explicit operator Banana(List<Banana> bananas) محدد (تذكر ، يجب تحديد قالب صريح إما النوع الذي يتم إرساله أو النوع الذي يتم إرساله إليه ، وهذا من المواصفات ، القسم 17.9.4). إنه يعلم في وقت التجميع أن ما تقوله لا يمكن أن يكون حقيقة. لذلك يصرخ عليك أن تتوقف عن الكذب.

لكن عندما تقول

IEnumerable<Banana> aBunchOfBananas = new List<Banana>();
Banana justOneBanana = (Banana)aBunchOfBananas;

حسنا ، الآن لا يعرف المجمع. قد يكون من الجيد جدا أن يكون أي نوع من aBunchOfBananas في وقت التشغيل ، ويمكن أن يكون نوعه الخرساني " X قد حدد تعريف static explicit operator Banana(X bananas) . لذا يثق بك المجمع ، كما طلبت منه.


قد يرجع السبب في ذلك إلى أن المحول البرمجي يعرف أن Banana لا يمدّد List<T> ، ولكن هناك احتمال بأن بعض الكائنات التي تنفذ IEnumerable<T> قد تمد أيضًا Banana جعله صالحًا.


يمكن أن يحدث هذا الخطأ أيضًا بإضافة نشاط إلى مساحة اسم مختلفة لمساحة الاسم الجذر لحزمك.

على سبيل المثال ، إذا كان com.example.myapp هو مساحة اسم الجذر com.example.myapp ، يمكنك عندئذٍ إضافة نشاط إلى مساحة الاسم com.example.myapp.activities .

هذا سينتج الخطأ "لا يمكن حل R".

لإصلاح الاستيراد ، يجب أن يكون R في مساحة الاسم الافتراضية في نشاطك:

import com.example.myapp.R;




c# .net generics compiler-errors