C#. Параметри методів. Модифікатори ref і out. Приклади. Відмінності між модифікаторами ref і out




Параметри методів. Модифікатори ref і out. Приклади. Відмінності між модифікаторами ref і out


Зміст


Пошук на інших ресурсах:

1. Застосування модифікатора параметру ref. Яке призначення модифікатора параметру ref?

Модифікатор ref призначений для вказання того, що параметр методу повинен передаватись за посиланням а не за значенням. Іншими словами, якщо в метод потрібно примусово передати аргумент за посиланням, то при оголошенні методу перед відповідним формальним параметром потрібно вказати модифікатор ref.

Модифікатор параметру ref використовується в описі формального параметру методу. Модифікатор параметру ref вказується перед типом формального параметру, як показано нижче:

ref type param

Загальна форма методу в класі, що отримує формальний параметр ref наступна:

access return_type MethodName(ref type param)
{
    // ...
}

де

  • MethodName – ім’я методу;
  • access – тип доступу до методу (private, protected, public, internal);
  • return_type – тип, що повертає метод;
  • type – тип параметру з іменем param, який отримує метод;
  • param – ім’я формального параметру.

При виклику такого методу з іншого коду, перед параметром також ставиться модифікатор ref:

MethodName(ref argument);

де argument – аргумент, що передається в метод. Цього вимагає синтаксис

 

2. Приклади методів, що використовують модифікатор параметру ref

Приклад 1. Реалізація методу, що множить число на 5. Число є вхідним параметром методу. Метод отримує значення числа, множить його на 5 і повертає завдяки модифікатору ref. Тобто метод збільшує самий аргумент у 5 разів.

// метод, що збільшує параметр x у 5 разів
public void Mult5(ref int x)
{
    x = x * 5;
}

Виклик методу Mult5() з іншого програмного коду

int d = 10;
qe.Mult5(ref d); // d = 10*5 = 50

Після виклику, значення змінної d стає рівне 50. Згідно синтаксису C#, перед викликом змінної d потрібно ставити модифікатор параметру ref.



Приклад 2. Розв’язок квадратного рівняння. Задано клас QuadraticEquation, що містить дані та метод розв’язку квадратного рівняння.
У класі реалізовано метод Calc(), який отримує два параметри x1, x2 з модифікатором ref. Ці параметри змінюють свої значення у методі, у випадку, якщо рівняння має розв’язок.

// клас, що реалізує дані та метод розв'язку квадратного рівняння
class QuadraticEquation
{
    public double a, b, c;

    // конструктор класу, отримує параметрами коефіцієнти a, b, c рівняння
    public QuadraticEquation(double _a, double _b, double _c)
    {
        if (IsSolution(a, b, c))
        {
            a = _a;
            b = _b;
            c = _c;
        }
        else
            a = b = c = 0;
    }

    // внутрішній метод, що визначає, чи рівняння має розв'язок
    bool IsSolution(double a, double b, double c)
    {
        double d = b * b - 4 * a * c;
        if (d < 0) return false;
        return true;
    }

    // Метод, що розв'язує квадратне рівняння
    // Метод повертає true та результат в x1, x2, якщо рівняння має розв'язок,
    // в іншому випадку метод повертає false
    public bool Calc(ref double x1, ref double x2)
    {
        // перевірка, чи рівняння має розв'язок
        if (!IsSolution(a, b, c))
        return false;

        // якщо розв'язок є, то обчислення x1, x2
        double d = b * b - 4 * a * c;
        x1 = (-b - Math.Sqrt(d)) / (2 * a);
        x2 = (-b + Math.Sqrt(d)) / (2 * a);

        return true;
    }
}

Виклик методу з іншого класу може бути таким як описано нижче

class Program
{
    static void Main(string[] args)
    {
        // створення об'єкту класу QuadraticEquation
        QuadraticEquation qe = new QuadraticEquation(2, -8, 5);

        double x1 = 0, x2 = 0;

        if (qe.Calc(ref x1, ref x2)) // виклик методу Calc(), перед x1, x2 задається модифікатор ref
        {
            // якщо є розв'язок, то вивести корені рівняння x1, x2
            Console.WriteLine("x1 = {0}", x1);
            Console.WriteLine("x2 = {0}", x2);
        }
        else
            Console.WriteLine("Рівняння не має коренів.");
        return;
    }
}

Варто зауважити, що при виклику методу Calc() обов’язково вказуються модифікатори ref:

Calc(ref x1, ref x2)

Приклад 3. Дано рядок s. Розробити метод, що видаляє з рядка s заданий символ c. Символ c є вхідним параметром-значенням. Рядок s повинен бути параметром-посиланням та результатом.
Нижче наведено клас Str з реалізацією методу DeleteSymbol(). Також продемонстровано виклик методу DeleteSymbol() з функції Main().

class Str
{
    // метод видаляє з рядка s символ c
    public void DeleteSymbol(ref string s, char c)
    {
        string s2="";
        for (int i=0; i<s.Length; i++)
            if (s[i] != c)
                s2 = s2 + s[i];
        s = s2;
    }
}

class Program
{
    static void Main(string[] args)
    {
        Str S = new Str();
        string str = "This is a textbook.";

        // виклик методу DeleteSymbol
        S.DeleteSymbol(ref str, 'i'); // str = "Ths s a textbook."
        Console.WriteLine(str);
        return;
    }
}

 

3. Застосування модифікатора параметру out. Яке призначення модифікатора параметру out?

Модифікатор out використовується, якщо потрібно виконання двох умов:

  • методу не потрібно передавати значення;
  • метод обов’язково повинен повертати значення з допомогою параметру.

Модифікатор out для параметру з іменем param типу type вказується на початку його оголошення

out type param

Загальна форма методу, що отримує один параметр з модифікатором out має вигляд

access return_type MethodName(out type param)
{
    // ...
}

де

  • MethodName – ім’я методу;
  • access – тип доступу до методу (private, protected, public, internal);
  • return_type – тип, що повертає метод;
  • type – тип параметру з іменем param, який отримує метод;
  • param – ім’я формального параметру.

 

4. Приклади методів, що використовують модифікатор параметру out

Приклад 1. Розробити метод, що повертає число Авогадро. Число Авогадро задається параметром методу.
Текст методу:

// число Авогадро
void GetAvogadro(out double Avg)
{
    Avg = 6.022140857e23; 
}

Виклик методу з іншого програмного коду

// виклик методу GetAvogadro() з іншого методу
double Avg;
GetAvogadro(out Avg); // вказання модифікатора out - обов'язкове
// Avg = 6.022140857E+23

Як видно з вищенаведеного коду, при виклику методу, що містить out-параметр, обов’язково потрібно вказувати модифікатор out

GetAvogadro(out Avg);

Приклад 2. Розробити метод CalcEquation(), що розв’язує квадратне рівняння. Метод отримує вхідними параметрами коефіцієнти рівняння a, b, c. Ці параметри передаються в рівняння за значеннями.
Метод повертає розв’язок рівняння з допомогою параметрів x1, x2, які оголошені з модифікатором out.
Метод повертає true з допомогою оператора return, якщо рівняння має розв’язок. В іншому випадку, метод повертає false.

Нижче наведено клас Program, в якому реалізовано метод CalcEquation().

class Program
{
    static bool CalcEquation(double a, double b,  double c, out double x1, out double x2)
    {
        double d;
        d = b * b - 4 * a * c;
        if (d>=0)
        {
            x1 = (-b - Math.Sqrt(d)) / (2 * a);
            x2 = (-b + Math.Sqrt(d)) / (2 * a);
            return true;
        }
        else
        {
            x1 = x2 = 0; // обов'язково, інакше помилка
            return false;
        }
    }

    static void Main(string[] args)
    {
        // Демонстрація використання модифікатора параметру out
        bool res;
        double x1, x2;

        // виклик методу
        res = CalcEquation(8, 3, -4, out x1, out x2);

        if (res)
        {
            Console.WriteLine("x1 = {0}", x1);
            Console.WriteLine("x2 = {0}", x2);
        }
        else
            Console.WriteLine("Рівняння не має коренів");
        return;
    }
}

Приклад 3. Розробити метод, що повертає назву цифри у рядку.

// клас, що реалізує метод, який повертає назву цифри
class Number
{ 
    // вивід назви числа на основі вхідного значення num
    public void TextNumber(int num, out string text)
    {
        switch (num)
        {
            case 1: text = "one"; break;
            case 2: text = "two"; break;
            case 3: text = "three"; break;
            case 4: text = "four"; break;
            case 5: text = "five"; break;
            case 6: text = "six"; break;
            case 7: text = "seven"; break;
            default: text = ""; break;
        } 
        return;
    }
}

Використання методу TextNumber() класу Number

Number nm = new Number(); // nm - об'єкт класу Number
string s;

nm.TextNumber(3, out s); // s = "three"
nm.TextNumber(8, out s); // s = ""
nm.TextNumber(1, out s); // s = "one"

 

5. Яка відмінність між модифікаторами ref та out?

Між модифікаторами ref та out є три взаємопов’язані відмінності:

  • 1. Параметр з модифікатором out використовується тільки для повернення значення з методу. Параметр з модифікатором ref може використовуватись і для повернення і для задавання значення в методі іншим змінним. Тому, перед викликом методу, немає потреби присвоювати якесь значення змінній, що використовується з модифікатором out.
  • 2. В методі, змінна оголошена з параметром out вважається неініціалізованою. Змінна, оголошена з ref вважається ініціалізованою. Тому, не можна використовувати out-змінну у правій частині оператора присвоювання. А ref-змінну можна.
  • 3. Якщо параметр оголошено з модифікатором out, то в тілі методу цьому параметру обов’язково має бути присвоєне якесь значення. Інакше буде помилка компіляції. Якщо параметр оголошено з модифікатором ref, то цьому параметру присвоювати значення в тілі методу не обов’язково.

 

6. Передача у функцію посилання на екземпляр класу з використанням модифікатору ref. Приклад

У функцію можна передавати не тільки значення базових типів (int, double, char і т.д.) але і значення екземплярів класу (об’єктів). Якщо при передачі у функцію об’єкту класу використовуються модифікатори ref або out, то саме посилання передається за посиланням. Це дозволяє змінювати сам об’єкт у методі.

Приклад. У прикладі реалізовано метод, який замінює один об’єкт класу на інший. Обидва об’єкти класу передаються у метод як параметри. Об’єкт який потрібно замінити передається з модифікатором ref.

using System;
using static System.Console;

namespace ConsoleApp2
{
  // Клас Point - описує точку на координатній площині
  class Point
  {
    // внутрішні поля класу
    int x;
    int y;

    // Конструктор
    public Point(int x, int y)
    {
      this.x = x;
      this.y = y;
    }

    // Метод присвоєння одного екземпляру класу іншому: 
    // point1 <= point2
    public void AssignmentPoint(ref Point point1, Point point2)
    {
      point1 = point2; // перевизначення об'єкту point1
    }

    // Метод, що виводить значення полів класу
    public void Print(string text)
    {
      Write(text);
      WriteLine("x = {0}, y = {1}", x, y); 
    }
  }

  class Program
  {
    static void Main(string[] args)
    {
      // Передача посилань на об'єкти як ref- та out-параметрів
      // 1. Оголосити екземпляри класів
      Point pt1 = new Point(1, 2);
      Point pt2 = new Point(5, 6);

      // 2. Вивести екземпляри класів
      pt1.Print("Object pt1:");
      pt2.Print("Object pt2:");

      // 3. Виклик методу AssignmentPoint() - передача об'єкту як ref-посилання
      pt1.AssignmentPoint(ref pt1, pt2); // pt1 <= pt2

      // 4. Вивести екземпляри класів після присвоєння 
      WriteLine("\nAfter AssignmentPoint");
      pt1.Print("Object pt1:");
      pt2.Print("Object pt2:");
    }
  }
}

Результат виконання програми

Object pt1:x = 1, y = 2
Object pt2:x = 5, y = 6

After AssignmentPoint
Object pt1:x = 5, y = 6
Object pt2:x = 5, y = 6

Проаналізувавши результат роботи програми можна прийти до висновку: у методі AssignmentPoint() відбулась повна заміна об’єкту pt1 на об’єкт pt2. Це можливо завдяки модифікатору ref. Якщо забрати модифікатор ref з першого параметру методу AssignmentPoint(), тоді зміни всередині методу не будуть діяти у викликаючому коді (функції main()). Щоб перевірити це, достатньо забрати модифікатор ref у методі AssignmentPoint() та у виклику цього методу.

 

7. Передача у функцію посилання на екземпляр класу з використанням модифікатору out. Приклад

Екземпляр класу може бути переданий у функцію з модифікатором out. При такій передачі можна змінювати сам екземпляр класу як і у випадку з ref-модифікатором.

Приклад. У прикладі реалізовано метод FormArray(), який формує новий об’єкт класу ArrayDouble, виділяючи для нього пам’ять та заповнюючи довільним значенням. У викликаючому коді (функції main()) метод отримує екземпляр класу зі значенням null.

using System;
using static System.Console;

namespace ConsoleApp2
{
  // Клас, що реалізує масив чисел
  class ArrayDouble
  {
    private double[] A; // масив чисел

    // Конструктор з 1 параметром
    public ArrayDouble(int length)
    {
      // виділити пам'ять для масиву та заповнити його нульовими значеннями
      A = new double[length];
      for (int i = 0; i < A.Length; i++)
      {
        A[i] = 0.0;
      }
    }

    // Методи доступу
    public double GetAi(int index)
    {
      if ((index >= 0) && (index < A.Length))
        return A[index];
      else
        return 0;
    }

    public void SetAi(int index, double value)
    {
      if ((index >= 0) && (index < A.Length))
        A[index] = value;
    }

    // Метод, що виводить масив в рядок
    public void Print(string text)
    {
      WriteLine(text);
      for (int i = 0; i < A.Length; i++)
      {
        Write("{0:f2}\t", A[i]);
      }
      WriteLine();
    }
  }

  class Program
  {
    // Метод, який заповнює масив типу ArrayDouble випадковими числами,
    // метод формує повністю новий екземпляр класу ArrayDouble
    static void FormArray(out ArrayDouble A, int length)
    {
      // 1. Ініціалізувати генератор випадкових чисел
      Random rnd_num = new Random();

      // 2. Виділити пам'ять для екземпляру A
      A = new ArrayDouble(length);

      // 3. Заповнити екземпляр A випадковими числами від 0 до 10
      for (int i = 0; i < length; i++)
      {
        A.SetAi(i, rnd_num.NextDouble() * 10);
      }
    }

    static void Main(string[] args)
    {
      ArrayDouble ad = null; // екземпляр класу ArrayDouble

      // Сформувати масив ad
      // параметр out - масив формується всередині методу FormArray()
      Program.FormArray(out ad, 5);
      ad.Print("1 - Array ad:");

      // Повторно сформувати масив ad
      FormArray(out ad, 7); // на виході з методу - новий екземпляр
      ad.Print("2 - Array ad:");
    }
  }
}

Результат виконання програми

1 - Array ad:
4.67 8.74 2.64 4.83 0.34
2 - Array ad:
6.65 6.66 4.32 1.24 8.30 9.73 3.13

 


Зв’язані теми