لماذا تستخدم الكلمة الرئيسية "المرجع" عند تمرير كائن؟

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

سؤال

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

فمثلا:

class Program
{
    static void Main(string[] args)
    {
        TestRef t = new TestRef();
        t.Something = "Foo";

        DoSomething(t);
        Console.WriteLine(t.Something);
    }

    static public void DoSomething(TestRef t)
    {
        t.Something = "Bar";
    }
}


public class TestRef
{
    public string Something { get; set; }
}

الإخراج هو "شريط" مما يعني أنه تم تمرير الكائن كمرجع.

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

المحلول

تمرير أ ref إذا كنت تريد تغيير ما هو الكائن:

TestRef t = new TestRef();
t.Something = "Foo";
DoSomething(ref t);

void DoSomething(ref TestRef t)
{
  t = new TestRef();
  t.Something = "Not just a changed t, but a completely different TestRef object";
}

بعد استدعاء شيء ، t لا يشير إلى الأصل new TestRef, ، ولكن يشير إلى كائن مختلف تماما.

قد يكون هذا مفيدًا أيضًا إذا كنت تريد تغيير قيمة كائن ثابت ، على سبيل المثال string. لا يمكنك تغيير قيمة أ string بمجرد إنشاءه. ولكن باستخدام أ ref, ، يمكنك إنشاء وظيفة تغير السلسلة لآخر لها قيمة مختلفة.

تحرير: كما ذكر الآخرين. إنها ليست فكرة جيدة للاستخدام ref ما لم يكن هناك حاجة. استخدام ref يعطي طريقة حرية تغيير الحجة لشيء آخر ، وسيحتاج المتصلين من هذه الطريقة إلى ترميز لضمان معالجة هذا الاحتمال.

أيضًا ، عندما يكون نوع المعلمة كائنًا ، فإن متغيرات الكائن تعمل دائمًا كمراجع إلى الكائن. هذا يعني أنه عندما ref يتم استخدام الكلمة الرئيسية لديك لديك إشارة إلى مرجع. يتيح لك ذلك القيام بالأشياء كما هو موضح في المثال الوارد أعلاه. ولكن عندما يكون نوع المعلمة قيمة بدائية (على سبيل المثال int) ، إذا تم تعيين هذه المعلمة داخل الطريقة ، فسيتم تغيير قيمة الوسيطة التي تم تمريرها بعد إرجاع الطريقة:

int x = 1;
Change(ref x);
Debug.Assert(x == 5);
WillNotChange(x);
Debug.Assert(x == 5); // Note: x doesn't become 10

void Change(ref int x)
{
  x = 5;
}

void WillNotChange(int x)
{
  x = 10;
}

نصائح أخرى

تحتاج إلى التمييز بين "تمرير مرجع بالقيمة" ، و "تمرير معلمة/وسيطة بالرجوع".

لقد كتبت أ مقالة طويلة بشكل معقول حول هذا الموضوع لتجنب الاضطرار إلى الكتابة بعناية في كل مرة يظهر فيها هذا على مجموعات الأخبار :)

في .NET عند تمرير أي معلمة إلى طريقة ، يتم إنشاء نسخة. في أنواع القيمة ، تعني أن أي تعديل تقوم بالقيمة في نطاق الطريقة ، ويضيع عند الخروج من الطريقة.

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

كما قال الناس من قبل ، فإن المهمة هي تعديل للإشارة ، وبالتالي تضيع:

public void Method1(object obj) {   
 obj = new Object(); 
}

public void Method2(object obj) {  
 obj = _privateObject; 
}

الأساليب أعلاه لا تعدل الكائن الأصلي.

القليل من التعديل لمثالك

 using System;

    class Program
        {
            static void Main(string[] args)
            {
                TestRef t = new TestRef();
                t.Something = "Foo";

                DoSomething(t);
                Console.WriteLine(t.Something);

            }

            static public void DoSomething(TestRef t)
            {
                t = new TestRef();
                t.Something = "Bar";
            }
        }



    public class TestRef
    {
    private string s;
        public string Something 
        { 
            get {return s;} 
            set { s = value; }
        }
    }

نظرًا لأن TestRef عبارة عن فئة (وهي كائنات مرجعية) ، يمكنك تغيير المحتويات داخل T دون تمريرها كمرجع. ومع ذلك ، إذا قمت بتمرير T كمرجع ، يمكن لـ TestRef تغيير ما يشير إليه T الأصلي. أي جعلها تشير إلى كائن مختلف.

مع ref يمكنك كتابة:

static public void DoSomething(ref TestRef t)
{
    t = new TestRef();
}

وسيتم تغيير T بعد الانتهاء من الطريقة.

فكر في المتغيرات (على سبيل المثال foo) من الأنواع المرجعية (على سبيل المثال List<T>) كما عقد معرفات الكائن من النموذج "الكائن #24601". لنفترض البيان foo = new List<int> {1,5,7,9}; أسباب foo لعقد "كائن #24601" (قائمة بأربعة عناصر). ثم الاتصال foo.Length سوف يسأل الكائن #24601 عن طوله ، وسوف يستجيب 4 ، لذلك foo.Length سوف تساوي 4.

إذا foo يتم تمريره إلى طريقة دون استخدام ref, ، قد تقوم هذه الطريقة بإجراء تغييرات على الكائن #24601. نتيجة لمثل هذه التغييرات ، foo.Length ربما لم تعد متساوية 4. الطريقة نفسها ، ومع ذلك ، لن تكون قادرة على التغيير foo, ، والتي ستستمر في عقد "كائن #24601".

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

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

هذا يشبه تمرير مؤشر إلى مؤشر في C. في .NET ، سيسمح لك هذا بتغيير ما يشير إليه T الأصلي ، شخصيا على الرغم من أنني أعتقد أنه إذا كنت تفعل ذلك في .NET ، فمن المحتمل أن تكون لديك مشكلة في التصميم!

باستخدام ref الكلمة الرئيسية مع أنواع المراجع ، أنت تمرير مرجعًا فعليًا إلى المرجع. من نواح كثيرة ، يكون ذلك هو استخدام out الكلمة الرئيسية ولكن مع الاختلاف البسيط بأنه لا يوجد ضمان بأن الطريقة ستعين في الواقع أي شيء إلى refالمعلمة ed.

ref يقلد (أو يتصرف) كمنطقة عالمية فقط لشخصين:

  • المتصل
  • كالي.

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

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

التمرير بالرجوع لا يرتبط بلغة ؛ إنها استراتيجية ربط معلمة بجوار قيمة التمرير ، تمر بالاسم ، تمرير حسب الحاجة وما إلى ذلك ...

A Sidenote: اسم الفصل TestRef هو اختيار سيء بشكل مخيف في هذا السياق ؛).

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