Page cover image

Ref keyword

ขวัญปีใหม่ พึ่งไชย 650710531

ในที่นี้เราจะมาพูดถึง ref keyword ในภาษา C# เราจะใช้ตัวแปร ref เพื่อส่งค่าไปยังเมธอดหรือรับค่าจากเมธอด ซึ่งหมายความว่า เมธอดสามารถเปลี่ยนแปลงค่าของพารามิเตอร์ได้โดยตรงเมื่อแก้ไขค่าที่มีการจองพื้นที่อยู่ก่อนแล้ว เมื่อใช้ ref การแก้ไขค่าภายในเมธอดจะส่งผลต่อค่าจริงในหน่วยความจำทันที ทำให้ไม่ต้องคัดลอกข้อมูล ช่วยให้โค้ดทำงานเร็วขึ้น โดยเฉพาะกับข้อมูลขนาดใหญ่

📄 การใช้งาน ref

  1. การประกาศ

    • ต้องประกาศตัวแปร ref ไว้ด้านหน้าพารามิเตอร์ของเมธอด

    • ต้องประกาศตัวแปร refไว้ด้านหน้าตัวแปรที่ประกาศไว้ตอนการเรียกใช้เมธอด

  2. การเปลี่ยนแปลงค่า

    • ถ้าส่งพารามิเตอร์เข้าไปในเมธอดด้วย ref เมธอดนั้นจะสามารถเปลี่ยนแปลงค่าของตัวแปรต้นฉบับได้โดยตรง

💻 การใช้ ref

สามารถใช้ ref ได้ในกรณีต่อไปนี้:

1. ส่งอาร์กิวเมนต์ไปยังเมธอดโดยการอ้างอิงถึงตัวแปรนั้น

(To pass an argument to a method by its reference) ซึ่งการใช้ ref จะอนุญาตให้เมธอดนั้นแก้ไขค่าของตัวแปรต้นฉบับได้

using System;

namespace TomAndJerryGame
{
    class Program
    {
        static void Main(string[] args)
        {
            //กำหนดคะแนนเริ่มต้น
            int tomScore = 50;
            int jerryScore = 45;

            Console.WriteLine($"Initial scores: Tom = {tomScore}, Jerry = {jerryScore}");

            ModifyScores(ref tomScore, ref jerryScore);

            Console.WriteLine($"Modified scores: Tom = {tomScore}, Jerry = {jerryScore}");
        }
        
   // ฟังก์ชัน ModifyScores สำหรับอัปเดตคะแนน โดยรับออบเจ็กต์ Character แบบอ้างอิง (ref)
        public static void ModifyScores(ref int tomScore, ref int jerryScore)
        {
            tomScore += 15;
            jerryScore += 10;
        }
    }
}

Output

Initial scores: Tom = 50, Jerry = 45
Modified scores: Tom = 65, Jerry = 55

การส่งค่าหลายค่าด้วย ref

แม้ว่าโดยปกติแล้วเมธอดจะสามารถส่งคืนค่าได้เพียงค่าเดียว แต่ถ้าเราใช้ ref เราสามารถส่งกลับค่าหลายค่าได้ โดยการส่งตัวแปรหลายตัวเข้าไปในเมธอดเป็นพารามิเตอร์แบบ ref


2. กำหนดลายเซ็นของเมธอดให้ส่งคืนอ้างอิงของตัวแปร

(To define a method signature to return a reference of the variable) การทำให้เมธอดสามารถส่งคืนตำแหน่งในหน่วยความจำของตัวแปรได้แทนที่จะส่งคืนแค่ค่าของตัวแปรนั้น

using System;
namespace TomAndJerryGame
{
    class Program
    {
        static void Main(string[] args)
        {
            int[] scores = { 20, 50, 80, 100 }; 
            
            // ใช้ ref เพื่ออ้างอิงไปยังคะแนนที่ต้องการ (50) และรับการอ้างอิงกลับมา
            ref int tomScore = ref FindScore(ref scores, 50);  
            Console.WriteLine("Tom's current score found: " + tomScore);

            tomScore += 20;
            Console.WriteLine("Tom's updated score: " + tomScore);

            Console.WriteLine("\nUpdated Scores:");
            foreach (var score in scores)
            {
                Console.WriteLine(score);
            }
        }

   // ฟังก์ชัน FindScore สำหรับหาคะแนน โดยรับออบเจ็กต์ Character แบบอ้างอิง (ref) 1 ตัว
        public static ref int FindScore(ref int[] scores, int target)
        {
            for (int i = 0; i < scores.Length; i++)
            {
                if (scores[i] == target)
                {
                    Console.WriteLine("Score found! Updating Tom's score...");
                    return ref scores[i]; 
                }
            }
            throw new InvalidOperationException("Score not found.");
        }
    }
}

Output

Score found! Updating Tom's score...
Tom's current score found: 50
Tom's updated score: 70

Updated Scores:
20
70
80
100

3. ประกาศโครงสร้างเป็น ref struct

(To declare a struct as a ref struct) การประกาศ ref struct จะช่วยให้มั่นใจว่าโครงสร้างนี้จะถูกเก็บใน stack เท่านั้น การที่โครงสร้างอยู่ใน stack จะช่วยลดภาระในการจัดการหน่วยความจำและทำให้การเข้าถึงข้อมูลรวดเร็วขึ้น

using System;
namespace TomAndJerryGame
{
    // กำหนด struct แบบ ref struct ที่จะจำกัดให้ถูกใช้งานเฉพาะกับ stack memory เท่านั้น
    public ref struct TomJerryPosition
    {
        public int TomX, TomY;
        public int JerryX, JerryY;
        public TomJerryPosition(int tomX, int tomY, int jerryX, int jerryY)
        {
            TomX = tomX;
            TomY = tomY;
            JerryX = jerryX;
            JerryY = jerryY;
        }
        public void DisplayPositions(string message)
        {            
            Console.WriteLine(message);
            Console.WriteLine($"Tom is at ({TomX}, {TomY})");
            Console.WriteLine($"Jerry is at ({JerryX}, {JerryY})");
        }
    }
    class Program
    {
        static void Main()
        {
            TomJerryPosition positions = new TomJerryPosition(10, 20, 30, 40);
            positions.DisplayPositions("Initial Positions:");
            positions.TomX += 5;
            positions.JerryY -= 10;
            positions.DisplayPositions("Updated Positions:");
        }
    }
}

Output

Initial Positions:
Tom is at (10, 20)
Jerry is at (30, 40)

Updated Positions:
Tom is at (15, 20)
Jerry is at (30, 30)


4. การอ้างอิงในท้องถิ่น

(As local reference) การสร้างตัวแปรที่เชื่อมโยงกับตัวแปรอื่นภายในพื้นที่เดียวกัน ถ้าเราเปลี่ยนแปลงค่าของตัวแปรที่เชื่อมโยง ตัวแปรเดิมก็จะเปลี่ยนไปด้วย

using System;

namespace TomAndJerryGame
{
    class Program
    {
        static void Main(string[] args)
        {
            // สร้างออบเจ็กต์ของตัวละคร Tom และ Jerry พร้อมคะแนนเริ่มต้น
            Character tom = new Character("Tom", 30);
            Character jerry = new Character("Jerry", 40);

            Console.WriteLine("Before change:");
            tom.Display();
            jerry.Display();

            // ใช้ ref เพื่ออ้างอิงถึงออบเจ็กต์ของ Tom โดยตรง
            ref Character activeCharacter = ref tom;

            activeCharacter.Score += 20;

            Console.WriteLine("After changing Tom's score through reference:");
            tom.Display();
            jerry.Display();
        }
    }

    class Character
    {
 
        public string Name { get; set; }
        public int Score { get; set; }

        public Character(string name, int score)
        {
            Name = name;
            Score = score;
        }

        public void Display()
        {
            Console.WriteLine($"{Name} has a score of {Score}");
        }
    }
}

Output

Before change:
Tom has a score of 30
Jerry has a score of 40

After changing Tom's score through reference:
Tom has a score of 50
Jerry has a score of 40

⚠️ ข้อควรระวัง

  • ต้องกำหนดค่าเริ่มต้น: ตัวแปรที่ส่งเข้าไปโดยใช้ ref ต้องมีค่าเริ่มต้นก่อน ไม่งั้นจะเกิดปัญหา

  • ต้องใช้คำสำคัญในทั้งสองที่: เมื่อเราประกาศพารามิเตอร์ในเมธอดก็ต้องใช้ ref และเมื่อเราจะเรียกใช้เมธอดก็ต้องใส่ ref ด้วยเช่นกัน

การใช้ ref ใน C# ช่วยให้เราสามารถส่งพารามิเตอร์โดยอ้างอิง พร้อมเปลี่ยนแปลงค่าตัวแปรต้นฉบับได้ เพิ่มประสิทธิภาพในการจัดการข้อมูลขนาดใหญ่ได้เร็วขึ้น


🆚 เปรียบเทียบกับภาษา Java/C/Python

การใช้คีย์เวิร์ด ref ใน C# เป็นการส่งผ่านค่าผ่านการอ้างอิง (pass by reference) ซึ่งมีความแตกต่างจากการส่งผ่านค่าปกติ (pass by value) ที่เป็นการส่งค่าจากตัวแปรไปยังฟังก์ชันหรือเมธอดอื่น ๆ ในรูปแบบที่ค่าตัวแปรนั้นถูกส่งตรงตามที่อยู่ในหน่วยความจำ ไม่ใช่ค่าสำเนาของตัวแปรนั้น ๆ

การทำงานของ ref ใน C#

  1. ใน C# การใช้ ref จำเป็นต้องใช้ทั้งในตอนที่เรียกใช้และตอนที่ประกาศฟังก์ชัน เพื่อบอกว่าอาร์กิวเมนต์ถูกส่งผ่านโดยการอ้างอิง

  2. เมื่อส่งตัวแปรไปยังฟังก์ชันด้วย ref การเปลี่ยนแปลงในตัวแปรภายในฟังก์ชันจะสะท้อนผลกลับมายังตัวแปรที่ส่งไป

using System;

class Program
{
    static void UpdateValue(ref int number)
    {
        number = 20;
    }

    static void Main()
    {
        int value = 10;
        UpdateValue(ref value);
        Console.WriteLine(value);
    }
}

Output

20

การเปรียบเทียบกับภาษาอื่น ๆ

1. Java

เมื่อเราส่งผ่านการอ้างอิง (pass by reference) หมายความว่า เราส่ง "ตัวจริง" ของออบเจ็กต์เข้าไปในฟังก์ชัน ไม่ได้ส่งสำเนา เมื่อฟังก์ชันทำอะไรกับออบเจ็กต์นั้น เช่น เปลี่ยนค่าในออบเจ็กต์ ค่าที่ถูกเปลี่ยนจะมีผลกับตัวจริงเลย เพราะทั้งตัวที่ส่งเข้าไปและตัวที่ใช้ในฟังก์ชันต่างก็ชี้ไปที่ออบเจ็กต์เดียวกัน ไม่ได้แยกกันทำงาน

ดังนั้น ถ้าเราส่งออบเจ็กต์ไปฟังก์ชันแล้วไปเปลี่ยนค่าอะไรในนั้น ค่าในตัวจริงก็จะเปลี่ยนตามไปด้วย

เพราะฉะนั้น ใน Java ไม่มีคีย์เวิร์ดเฉพาะแบบ ref เพราะ Java ใช้การส่งค่าตัวแปรแบบ pass-by-value ในทุกกรณี แต่หากส่งออบเจ็กต์จะเป็นการส่งที่อยู่ของออบเจ็กต์นั้น (pass-by-reference) คล้ายกับการใช้งาน ref ใน C#

import java.io.*;

class updateValue {
	int Number;
	
	void updateValue() { 
		Number = 0; 
	}

	static void update(updateValue ob) { 
		ob.Number++; 
	}

	public static void main(String[] args)
	{
		updateValue ob = new updateValue();

		System.out.println("Number value " + (ob.Number));

		update(ob);

		System.out.println("Updated Number value " + (ob.Number));
	}
}

Output

Number value 0
Updated Number value 1

2. C

ในภาษา C สามารถทำสิ่งที่คล้ายกับ ref ได้ด้วยการใช้ pointer (ตัวชี้) ซึ่งเป็นการส่งตัวแปรผ่านการอ้างอิงที่ตรงไปตรงมา

pointer (ตัวชี้)

ตัวชี้ถูกกำหนดให้เป็นชนิดข้อมูลที่ได้รับมาซึ่งสามารถจัดเก็บที่อยู่ของตัวแปร C อื่นๆ หรือตำแหน่งหน่วยความจำ เราสามารถเข้าถึงและจัดการข้อมูลที่จัดเก็บในตำแหน่งหน่วยความจำนั้นได้โดยใช้ตัวชี้

#include <stdio.h>

void updateValue(int* number) {
    *number = 20;
}

int main() {
    int value = 10;
    updateValue(&value);
    printf("%d\n", value);
    return 0;
}

Output

20

3. Python

Python ส่งผ่านค่าตัวแปรแบบ pass-by-object-reference ซึ่งมีลักษณะคล้ายกับการ pass-by-reference ใน C# เมื่อทำการส่งออบเจ็กต์ (เช่น list หรือ dictionary) แต่หากส่งค่าที่เป็น immutable (เช่น integer, string) จะคล้ายกับ pass-by-value

รูปแบบการส่งอาร์กิวเมนต์ของ Python ไม่ใช่ “Pass by Value” หรือ “Pass by Reference” แต่เป็น “Pass by Object Reference”

def update_value(my_list):
    my_list[0] = 50  

numbers = [10, 20, 30]
print("before update : ", numbers)
update_value(numbers)
print("after update : ", numbers)

Output

before update : [10, 20, 30]
after update :  [50, 20, 30]

สรุป

  • C#: ใช้ ref สำหรับ pass-by-reference ทั้งการประกาศและการเรียกฟังก์ชัน

  • Java: ไม่มี ref โดยตรง แต่การส่งออบเจ็กต์มีลักษณะคล้ายการ pass-by-reference

  • C: ใช้ pointer เพื่อส่งผ่านค่าผ่านการอ้างอิง

  • Python: ใช้การส่งผ่านออบเจ็กต์ (pass-by-object-reference) ขึ้นอยู่กับชนิดข้อมูล

การใช้ ref ทำให้ควบคุมการส่งผ่านค่าผ่านการอ้างอิงได้ชัดเจนขึ้นใน C#


🎞️Presentation

presentation ref keyword

📚 แหล่งที่มา

1. GeeksforGeeks. (July 16, 2021). Ref in C#.

Retrieved from https://www.geeksforgeeks.org/ref-in-c-sharp/

2. Microsoft. (January 8, 2024). Ref keyword.

Retrieved from https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/ref

3. TutorialsTeacher. (January 7, 2020). Ref keyword.

Retrieved from https://www.tutorialsteacher.com/articles/ref-keyword

4. Microsoft. (July 30, 2024). Ref struct.

Retrieved fromhttps://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/ref-struct

  1. GeeksforGeeks. (October 17, 2022). Different Ways to Achieve Pass By Reference in Java.

Retrieved from https://www.geeksforgeeks.org/different-ways-to-achieve-pass-by-reference-in-java/

  1. GeeksforGeeks. (July 24, 2024). C Pointers.

Retrieved from https://www.geeksforgeeks.org/c-pointers/

  1. GeeksforGeeks. (August 9, 2024). Pass by reference vs value in Python.

Retrieved from https://www.geeksforgeeks.org/pass-by-reference-vs-value-in-python/

Last updated