c# - لماذا لا يدعم "ref" و "out" تعدد الأشكال؟



polymorphism out-parameters (6)

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

UPDATE: لقد استخدمت هذه الإجابة كأساس لإدخال المدونة:

لماذا لا تسمح معلمات المرجع والخروج بتنوع الكتابة؟

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

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

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

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

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

لا ، هذا المتغير قد يحتوي على Turtle ، لكن M تفترض أنه يحتوي فقط على الثدييات. 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 : لا يمكن جعل المعلمات "أصغر".

يمكنك تمرير متغير من نوع 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 ولا المعلمات تغيير أنواعها. للقيام خلاف ذلك هو كسر سلامة نوع يمكن التحقق منها.

إذا كانت هذه القضايا في نظرية النوع الأساسي تهمك ، فكر في قراءة سلسلتي حول كيفية عمل التباين والتباين في C # 4.0 .

https://ffff65535.com

خذ ما يلي:

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) {}  
}

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


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

Foo2(ref (A)b)

إذا كنت تستخدم أمثلة عملية لأنواعك ، فستشاهدها:

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();
}

لقد قمت بحشو جهاز OracleConnection في SqlConnection الخاص بك.


في حين أن الاستجابات الأخرى قد أوضحت بشكل منطقي الأسباب الكامنة وراء هذا السلوك ، أعتقد أنه من الجدير بالذكر أنه إذا كنت حقا بحاجة إلى القيام بشيء من هذا النوع ، يمكنك تحقيق وظائف مماثلة من خلال جعل 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 {}  
}

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

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

B b = new A();

من المنطقي من منظور السلامة ، ولكن كنت أفضل لو كان المحلل أعطى تحذيرا بدلا من خطأ ، لأن هناك استخدامات مشروعة للكائنات 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);
}

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





ref-parameters