Передача аргументов по значению и передача по ссылке 


Мы поможем в написании ваших работ!



ЗНАЕТЕ ЛИ ВЫ?

Передача аргументов по значению и передача по ссылке



 

По умолчанию при передаче методу типа значения передаётся копия объекта, а не сам объект. Поэтому изменения в аргументе не оказывают влияния на исходную копию в вызывающем методе. Тип значения по ссылке можно передать с помощью ключевого слова ref. В C# аргументы могут быть переданы параметрам либо по значению, либо по ссылке. Передача по ссылке позволяет изменять и сохранять изменённые значения параметров членов функций, методов, свойств, индексаторов, операторов и конструкторов в вызывающей среде. Для передачи параметра по ссылке используются ключевые слова ref или out.

 

В следующем примере показано различие между значением и ссылочными параметрами:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class Program

{

static void Main()

{

int arg;

 

// Переход по значению

// Значение arg в Main не меняется

arg = 4;

squareVal(arg);

Console.WriteLine(arg);

// Выведет: 4

 

// Переход по ссылке

// Значение arg в Main меняется

arg = 4;

squareRef(ref arg);

Console.WriteLine(arg);

// Выведет: 16

Console.WriteLine("Для продолжения нажмите любую клавишу...");;

Console.ReadKey();

}

 

static void squareVal(int valParameter)

{

valParameter *= valParameter;

}

// Переход по ссылке

static void squareRef(ref int refParameter)

{

refParameter *= refParameter;

}

}

}

 

Переменная типа значения содержит данные непосредственно, в противоположность переменной ссылочного типа, которая содержит ссылку на данные. Поэтому передача переменной типа значения методу означает передачу методу копии переменной. Любые изменения параметра, выполняемые внутри метода, не влияют на исходные данные, хранимые в переменной.

 

Переменная ссылочного типа не содержит непосредственные данные; она содержит ссылку на эти данные. Если параметр ссылочного типа передаётся по значению, можно изменить данные, на которые указывает ссылка, например, значение члена класса. Однако нельзя изменить значение самой ссылки; то есть нельзя использовать одну ссылку для выделения памяти для нового класса и его создания вне заданного блока.

 

В следующем примере демонстрируется передача параметра ссылочного типа arr по значению в метод Change. Поскольку этот параметр является ссылкой на arr, можно изменить значения элементов массива. Однако попытка назначить параметр другому адресу в памяти может быть выполнена только внутри метода и не влияет на исходную переменную arr:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class Program

{

static void Change(int[] pArray)

{

pArray[0] = 888; // Это изменение влияет на исходный элемент

pArray = new int[5] { -3, -1, -2, -3, -4 }; // Это локальное изменение

Console.WriteLine("Внутри метода, первый элемент: {0}", pArray[0]);

}

 

static void Main()

{

int[] arr = { 1, 4, 5 };

Console.WriteLine("Внутри Main, перед вызовом метода, первый элемент: {0}", arr[0]);

 

Change(arr);

Console.WriteLine("Внутри Main, после вызова метода, первый элемент: {0}", arr[0]);

}

}

}

/* Выведет:

* Внутри Main, перед вызовом метода, первый элемент: 1

* Внутри метода, первый элемент: -3

* Внутри Main, после вызова метода, первый элемент: 888

*/

 

В следующем примере ключевое слово class указывает на то, что создаётся объект ссылочного типа:

 

public class SampleRefType

{

public int value;

}

 

Теперь при передаче методу объекта этого типа объект будет передаваться по ссылке. Пример:

 

public static void TestRefType()

{

SampleRefType rt = new SampleRefType();

rt.value = 44;

ModifyObject(rt);

Console.WriteLine(rt.value);

}

 

static void ModifyObject(SampleRefType obj)

{

obj.value = 33;

}

 

Здесь используется ссылочный тип. Изменения в методе ModifyObject относятся к объекту obj, созданному в методе (аргумент для экземпляра класса SampleRefType) и переданного из TestRefType. Поэтому в методе TestRefType на экран будет выведено значение 33.

 

Подробнее о модификаторе ref:

 

Модификатор параметра ref принудительно организует вызов по ссылке, а не по значению. Этот модификатор указывается как при объявлении, так и при вызове метода.

Параметры, сопровождаемые таким модификатором, называются ссылочными и применяются, когда нужно позволить методу выполнять операции и обычно также изменять значения различных элементов данных, объявляемых в вызывающем коде (например, в процедуре сортировки или обмена). Обратите внимание на следующие отличия между ссылочными и выходными параметрами:

Выходные параметры это параметры, которые не нужно инициализировать перед передачей методу. Причина в том, что метод сам должен присваивать значения выходным параметрам перед выходом.

Ссылочные параметры эти параметры нужно обязательно инициализировать перед передачей методу. Причина в том, что они подразумевают передачу ссылки на уже существующую переменную. Если первоначальное значение ей не присвоено, это будет равнозначно выполнению операции над неинициализированной локальной переменной.

Ещё один пример использования модификатора ref:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class Program

{

// Метод, изменяющий свой аргумент

static void myCh(ref char c)

{

c = 'A';

}

// Метод меняющий местами аргументы

static void Swap(ref char a, ref char b)

{

char c;

c = a;

a = b;

b = c;

}

 

static void Main()

{

char ch = 'B', s1 = 'D', s2 = 'F';

Console.WriteLine("Переменная ch до вызова метода myCh: {0}", ch);

myCh(ref ch);

Console.WriteLine("Переменная ch после вызова метода myCh: {0}", ch);

Console.WriteLine("\nПеременная s1 = {0}, переменная s2 = {1}", s1, s2);

Swap(ref s1, ref s2);

Console.WriteLine("Теперь s1 = {0}, s2 = {1}", s1, s2);

Console.WriteLine("\nДля продолжения нажмите любую клавишу...");;

Console.ReadKey();

}

}

}

 

Рис. 5. 1. Результат работы кода выше

 

В отношении модификатора ref необходимо иметь в виду следующее. Аргументу, передаваемому по ссылке с помощью этого модификатора, должно быть присвоено значение до вызова метода. Дело в том, что в методе, получающем такой аргумент в качестве параметра, предполагается, что параметр ссылается на действительное значение. Следовательно, при использовании модификатора ref в методе нельзя задать первоначальное значение аргумента.

 

Подробнее о модификаторе out:

 

Модификатор параметра out подобен модификатору ref, за одним исключением: он служит только для передачи значения за пределы метода. Поэтому переменной, используемой в качестве параметра out, не нужно (да и бесполезно) присваивать какое-то значение. Более того, в методе параметр out считается неинициализированным, т.е. предполагается, что у него отсутствует первоначальное значение. Это означает, что значение должно быть присвоено данному параметру в методе до его завершения. Следовательно, после вызова метода параметр out будет содержать некоторое значение. Пример:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class Program

{

// Метод возвращающий целую и дробную части

// числа, квадрат и корень числа

static int TrNumber(double d, out double dr, out double sqr, out double sqrt)

{

int i = (int)d;

dr = d - i;

sqr = d * d;

sqrt = Math.Sqrt(d);

return i;

}

 

static void Main()

{

int i;

double myDr, mySqr, mySqrt, myD = 12.987;

i = TrNumber(myD, out myDr, out mySqr, out mySqrt);

Console.WriteLine("Исходное число: {0}\nЦелая часть числа: {1}\nДробная часть числа: {2}\nКвадрат числа: {3}\nКвадратный корень числа: {4}", myD, i, myDr, mySqr, mySqrt);

Console.WriteLine("\nДля продолжения нажмите любую клавишу...");;

Console.ReadKey();

}

}

}

 

Рис 5. 2. Результат работы кода выше

 

Обратим внимание, что использование модификатора out в данном примере позволяет возвращать из метода сразу четыре значения.

 

Итак, подытожим. Методы могут, как принимать, так и не принимать параметров, а также возвращать или не возвращать значения вызывающей стороне. Хотя определение метода в С# выглядит довольно понятно, существует несколько ключевых слов, с помощью которых можно управлять способом передачи аргументов интересующему методу:

 

Модификатор параметра Описание
(отсутствует) Если параметр не сопровождается модификатором, предполагается, что он должен передаваться по значению, т.е. вызываемый метод должен получать копию исходных данных
out Выходные параметры должны присваиваться вызываемым методом (и, следовательно, передаваться по ссылке). Если параметрам out в вызываемом методе значения не присвоены, компилятор сообщит об ошибке
ref Это значение первоначально присваивается вызывающим кодом и при желании может повторно присваиваться в вызываемом методе (поскольку данные также передаются по ссылке). Если параметрам ref в вызываемом методе значения не присвоены, компилятор никакой ошибки генерировать не будет
params Этот модификатор позволяет передавать в виде одного логического параметра переменное количество аргументов. В каждом методе может присутствовать только один модификатор params и он должен обязательно указываться последним в списке параметров. В реальности необходимость в использовании модификатора params возникает не особо часто, однако он применяется во многих методах внутри библиотек базовых классов

 

Нередко требуется, чтобы метод оперировал теми аргументами, которые ему передаются. Характерным тому примером служит метод Swap, осуществляющий перестановку значений своих аргументов. Но поскольку аргументы простых типов передаются по значению, то, используя выбираемый в С# по умолчанию механизм вызова по значению для передачи аргумента параметру, невозможно написать метод, меняющий местами значения двух его аргументов, например типа int. Это затруднение разрешает модификатор ref.

Значение возвращается из метода вызывающей части программы с помощью оператора return. Но метод может одновременно возвратить лишь одно значение. А что, если из метода требуется возвратить два или более фрагментов информации, например, целую и дробную части числового значения с плавающей точкой? Такой метод можно написать, используя модификатор out.

 

Подробнее о модификаторе params:

 

В С# поддерживается использование массивов параметров за счет применения ключевого слова params. Ключевое слово params позволяет передавать методу переменное количество аргументов одного типа в виде единственного логического параметра. Аргументы, помеченные ключевым словом params, могут обрабатываться, если вызывающий код на их месте передает строго типизированный массив или разделенный запятыми список элементов.

Число элементов массива параметров будет равно числу аргументов, передаваемых методу. А для получения аргументов в программе организуется доступ к данному массиву:

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

 

namespace LC_Console

{

class Program

{

static void MinArr(ref int i, params int[] arr)

{

// Изначально нужно обязательно выполнить проверку

// на то, что массив не пустой

if (arr.Length == 0)

{

Console.WriteLine("Пустой массив!");

i = 0;

return;

}

else

{

if (arr.Length == 1)

{

i = arr[0];

return;

}

}

i = arr[0];

// Ищем максимум

for (int j = 1; j < arr.Length; j++)

if (arr[j] > i)

i = arr[j];

}

 

static void Main()

{

int result = 0;

int[] arr1 = new int[8];

int[] arr2 = new int[5];

Random ran = new Random();

// Инициализируем оба массива случайными числами

for (int i = 0; i < 8; i++)

arr1[i] = ran.Next(1, 20);

for (int i = 0; i < 5; i++)

arr2[i] = ran.Next(100, 200);

Console.WriteLine("Массив arr1: \n");

foreach (int i in arr1)

Console.Write("{0}\t", i);

MinArr(ref result, arr1);

Console.WriteLine("Максимум: {0}", result);

Console.WriteLine("\nМассив arr2: \n");

foreach (int i in arr2)

Console.Write("{0}\t", i);

MinArr(ref result, arr2);

Console.WriteLine("Максимум: {0}", result);

Console.WriteLine("\nДля продолжения нажмите любую клавишу...");;

Console.ReadKey();

}

}

}

 

Рис. 5. 2. Результат работы кода выше

 

В тех случаях, когда у метода имеются обычные параметры, а также параметр переменной длины типа params, он должен быть указан последним в списке параметров данного метода. Но в любом случае параметр типа params должен быть единственным.

 

Возвращаемые значения

 

Методы могут возвращать значения вызывающим их объектам. Если тип возвращаемого значения, указываемый перед именем метода, и не равен void, для возвращения значения используется ключевое слово return. В результате выполнения инструкции с ключевым словом return, после которого указано значение нужного типа, вызвавшему метод объекту будет возвращено это значение. Кроме того, ключевое слово return останавливает выполнение метода. Если тип возвращаемого значения void, инструкцию return без значения все равно можно использовать для завершения выполнения метода. Если ключевое слово return отсутствует, выполнение метода завершится, когда будет достигнут конец его блока кода. Для возврата значений методами с типом возвращаемого значения отличным от void необходимо обязательно использовать ключевое слово return. Например, в следующих двух методах ключевое слово return служит для возврата целочисленных значений:

 

class SimpleMath

{

public int AddTwoNumbers(int number1, int number2)

{

return number1 + number2;

}

 

public int SquareANumber(int number)

{

return number * number;

}

}

 

Чтобы использовать возвращаемое методом значение в вызываемом методе, вызов метода можно поместить в любое место кода, где требуется значение соответствующего типа. Кроме того, возвращаемое значение можно присвоить переменной. Например, следующие два примера кода выполняют одну и ту же задачу:

 

int result1 = obj.AddTwoNumbers(1, 2);

result1 = obj.SquareANumber(result);

// Результат: 9

Console.WriteLine(result1);

 

int result1 = 0;

result2 = obj.SquareANumber(obj.AddTwoNumbers(1, 2));

Console.WriteLine(result2);

 

Использовать локальную переменную для хранения значения (в данном случае это переменные result1 и result2) необязательно. Эта переменная может упростить читаемость кода либо быть необходимой, если необходимо сохранить исходное значение аргумента для целой области метода.



Поделиться:


Последнее изменение этой страницы: 2016-08-15; просмотров: 492; Нарушение авторского права страницы; Мы поможем в написании вашей работы!

infopedia.su Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав. Обратная связь - 3.135.198.49 (0.093 с.)