سؤال

تأخذ التالية:

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= compile-time error: 
                     // "The 'ref' argument doesn't match the parameter type"
    }

    void Foo(A a) {}

    void Foo2(ref A a) {}  
}

لماذا أعلاه ترجمة في الوقت الخطأ ؟ وهذا يحدث مع كل من ref و out الحجج.

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

المحلول

=============

تحديث:أنا استخدم هذا الجواب كأساس هذا بلوق الدخول:

لماذا المرجع والخروج المعلمات لا تسمح نوع الاختلاف ؟

انظر صفحة بلوق لمزيد من التعليق على هذه المسألة.شكرا على السؤال الكبير.

=============

دعونا نفترض أن لديك دروس Animal, Mammal, Reptile, Giraffe, Turtle و Tiger, مع واضحة subclassing العلاقات.

الآن افترض أن لديك طريقة void M(ref Mammal m). M على حد سواء يمكن قراءة وكتابة m.


يمكنك تمرير متغير من نوع Animal إلى M?

لا.هذا المتغير يمكن أن تحتوي على Turtle, ولكن M سوف نفترض أنه يحتوي فقط على الثدييات.A Turtle ليس Mammal.

الاستنتاج 1: ref المعلمات لا يمكن أن يكون "أكبر".(هناك أكثر من الحيوانات من الثدييات ، لذلك المتغير هو الحصول على "أكبر" لأنها يمكن أن تحتوي على المزيد من الأشياء.)


يمكنك تمرير متغير من نوع Giraffe إلى M?

لا. M يمكن الكتابة إلى m, ، M قد ترغب في كتابة Tiger في m.الآن كنت قد وضعت Tiger إلى متغير الذي هو في الواقع من نوع Giraffe.

الاستنتاج 2: ref المعلمات لا يمكن أن يتم "أصغر".


تنظر الآن N(out Mammal n).

يمكنك تمرير متغير من نوع Giraffe إلى N?

لا. N يمكن الكتابة إلى n, ، N قد ترغب في كتابة Tiger.

الاستنتاج 3: out المعلمات لا يمكن أن يتم "أصغر".


يمكنك تمرير متغير من نوع Animal إلى N?

همم.

حسنا, لماذا لا ؟ N لا يمكن قراءة من n, ، فإنه يمكن فقط كتابة على ذلك ، أليس كذلك ؟ تكتب Tiger إلى متغير من نوع Animal و كنت كل مجموعة ، أليس كذلك ؟

من الخطأ.القاعدة ليست "N يمكن فقط كتابة n".

القواعد هي بإيجاز:

1) N عليه أن يكتب إلى n قبل N يعود بشكل طبيعي.(إذا N يلقي كل الرهانات.)

2) N أن أكتب شيئا n قبل أن يقرأ شيئا من n.

يسمح هذا التسلسل من الأحداث:

  • تعلن حقل x من نوع Animal.
  • تمر x كما out المعلمة N.
  • N يكتب Tiger في n, الذي هو اسم مستعار x.
  • في موضوع آخر, شخص ما يكتب Turtle في x.
  • N محاولات لقراءة محتويات n, و يكتشف Turtle في ما يعتقد هو متغير من نوع Mammal.

من الواضح أننا نريد أن نجعل هذا غير قانوني.

الاستنتاج 4: out المعلمات لا يمكن أن يكون "أكبر".


الاستنتاج النهائي: لا ref ولا out قد تختلف المعلمات أنواعها.أن تفعل خلاف ذلك هو كسر يمكن التحقق منها نوع الأمان.

إذا هذه المسائل الأساسية نوع نظرية الفائدة لك النظر في القراءة سلسلة بلدي على مدى التغاير و contravariance العمل في C# 4.0.

نصائح أخرى

ولأنه في كلتا الحالتين يجب أن تكون قادرا على تعيين القيمة إلى المرجع / من المعلمة.

إذا كنت في محاولة لتمرير ب في طريقة Foo2 كمرجع، وفي Foo2 محاولة assing و= A جديد ()، وهذا من شأنه أن يكون غير صالح.
ولنفس السبب لا يمكن أن يكتب:

B b = new A();

أنت تكافح مع الكلاسيكية OOP مشكلة التغاير (و contravariance) ، ويكيبيديا:بقدر هذا الواقع قد تتحدى بديهية التوقعات حسابيا للسماح استبدال الفئات المشتقة بدلا من قاعدة منها قابلة للتغيير (احالة) حجج (و أيضا حاويات البنود التي يتم احالة فقط نفس السبب) في حين لا يزال احترام Liskov مبدأ.السبب في ذلك هو رسمت في القائمة إجابات ، استكشافها بعمق أكثر في هذه مقالات و روابط منها.

OOP اللغات التي تظهر على القيام بذلك في حين تبقى تقليديا بشكل ثابت typesafe هي "الغش" (إدراج خفية نوع الديناميكي الشيكات ، أو التي تتطلب تجميع الوقت دراسة جميع مصادر التحقق);الاختيار الأساسي هو:إما التخلي عن هذا التغاير و تقبل الممارسين الحيرة (مثل C# لا هنا), أو الانتقال إلى دينامية الكتابة النهج (أول OOP لغة Smalltalk ، هل) ، أو الانتقال إلى ثابتة (واحد-انتداب) البيانات ، مثل وظيفية لغة (في ظل ثبات ، يمكنك دعم التغاير ، وأيضا تجنب الأخرى ذات الصلة الألغاز مثل حقيقة أن كنت لا يمكن أن يكون ساحة فرعية المستطيل في ي البيانات العالمية).

والنظر فيما يلي:

class C : A {}
class B : A {}

void Foo2(ref A a) { a = new C(); } 

B b = null;
Foo2(ref b);

وسيكون تنتهك نوع السلامة

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

class A {}

class B : A {}

class C
{
    C()
    {
        var b = new B();
        Foo(b);
        Foo2(ref b); // <= no compile error!
    }

    void Foo(A a) {}

    void Foo2<AType> (ref AType a) where AType: A {}  
}

ولأن إعطاء Foo2 على ref B شأنه أن يؤدي إلى كائن مشوه بسبب Foo2 يعرف فقط كيفية ملء A جزء من B.

أليس هذا المترجم أقول لك ذلك نود منك أن يلقي صراحة الكائن بحيث أنه يمكن أن يكون متأكد من أنك تعرف ما هي نواياكم؟

Foo2(ref (A)b)

والمنطقي من وجهة نظر السلامة، ولكن كنا نفضل لو أعطى المترجم تحذيرا بدلا من خطأ، لأن هناك استخدامات مشروعة للكائنات polymoprhic تمريرها حسب المرجع. منها مثلا.

class Derp : interfaceX
{
   int somevalue=0; //specified that this class contains somevalue by interfaceX
   public Derp(int val)
    {
    somevalue = val;
    }

}


void Foo(ref object obj){
    int result = (interfaceX)obj.somevalue;
    //do stuff to result variable... in my case data access
    obj = Activator.CreateInstance(obj.GetType(), result);
}

main()
{
   Derp x = new Derp();
   Foo(ref Derp);
}

وهذا لن تجميع، ولكنه يعمل؟

إذا كنت تستخدم أمثلة عملية لأنواع بك، سترى ما يلي:

SqlConnection connection = new SqlConnection();
Foo(ref connection);

والآن لديك وظيفة الخاص أن يأخذ <م> سلف (<م> أي بمعنى Object):

void Foo2(ref Object connection) { }

وماذا يمكن أن ربما يكون الخطأ في ذلك؟

void Foo2(ref Object connection)
{
   connection = new Bitmap();
}

وأنت مجرد تمكن من تعيين Bitmap إلى SqlConnection الخاص بك.

وهذا ليس جيدا.


وحاول مرة أخرى مع الآخرين:

SqlConnection conn = new SqlConnection();
Foo2(ref conn);

void Foo2(ref DbConnection connection)
{
    conn = new OracleConnection();
}

وأنت محشوة على <لأ href = "https://msdn.microsoft.com/en-us/library/system.data.oracleclient.oracleconnection(v=vs.110).aspx" يختلط = "نوفولو noreferrer" > OracleConnection الإفراط في أعلى SqlConnection الخاص بك.

في حالتي قبلت لي وظيفة كائن وأنا لا يمكن أن ترسل في أي شيء حتى أنني ببساطة لم

object bla = myVar;
Foo(ref bla);

والذي يعمل

وبلدي فو في VB.NET وبالتحقق من نوع داخل ويفعل الكثير من المنطق

وأعتذر إذا جوابي هو مكررة ولكن البعض الآخر وقتا طويلا

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