Книга: C# 4.0: полное руководство

Использование модификаторов параметров ref и out

Использование модификаторов параметров ref и out

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

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

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

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

Использование модификатора параметра ref

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

// Использовать модификатор ref для
// передачи значения обычного типа по ссылке.
using System;
class RefTest {
// Этот метод изменяет свой аргумент. Обратите
// внимание на применение модификатора ref.
  public void Sqr(ref int i) {
    i = i * i;
  }
}
class RefDemo {
  static void Main() {
    RefTest ob = new RefTest();
    int a = 10;
    Console.WriteLine("а до вызрва: " + a);
    ob.Sqr(ref a);
    // обратите внимание на применение модификатора ref
    Console.WriteLine("а после вызова: " + а);
  }
}

Как видите, модификатор ref указывается перед объявлением параметра в самом методе и перед аргументом при вызове метода. Ниже приведен результат выполнения данной программы, который подтверждает, что значение аргумента а действительно было изменено с помощью метода Sqr().
а до вызова: 10
а после вызова: 100

Теперь, используя модификатор ref, можно написать метод, переставляющий местами значения двух своих аргументов простого типа. В качестве примера ниже приведена программа, в которой метод Swap() выполняет перестановку значений двух своих целочисленных аргументов, когда он вызывается.

// Поменять местами два значения.
using System;
class ValueSwap {
  // Этот метод меняет местами свои аргументы,
  public void Swap(ref int a, ref int b) {
    int t;
    t = a;
    a = b;
    b = t;
  }
}
class ValueSwapDemo {
  static void Main() {
    ValueSwap ob = new ValueSwap();
    int x=10, у = 20;
    Console.WriteLine("x и у до вызова: " + x + " " + у);
    ob.Swap(ref x, ref у);
    Console.WriteLine("x и у после вызова: " + x + " " + у);
  }
}

Вот к какому результату приводит выполнение этой программы.

х и у до вызова: 10 20
х и у после вызова: 20 10

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

Использование модификатора параметра out

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

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

Ниже приведен пример применения модификатора параметра out. В этом примере программы для разделения числа с плавающей точкой на целую и дробную части используется метод GetParts() из класса Decompose. Обратите внимание на то, как возвращается каждая часть исходного числа.

// Использовать модификатор параметра out.
using System;
class Decompose {
  /* Разделить числовое значение с плавающей точкой на целую и дробную части. */
  public int GetParts(double n, out double frac) {
    int whole;
    whole = (int) n;
    frac = n - whole; // передать дробную часть числа через параметр frac
    return whole;    // возвратить целую часть числа
  }
}
class UseOut {
  static void Main() {
    Decompose ob = new Decompose();
    int i;
    double f;
    i = ob.GetParts(10.125, out f) ;
    Console.WriteLine("Целая часть числа равна " + i);
    Console.WriteLine("Дробная часть числа равна " + f);
  }
}

Выполнение этой программы дает следующий результат.

Целая часть числа равна 10
Дробная часть числа равна 0.125

Метод GetParts() возвращает два фрагмента информации. Во-первых, целую часть исходного числового значения переменной n обычным образом с помощью оператора return. И во-вторых, дробную часть этого значения посредством параметра fгас типа out. Как показывает данный пример, используя модификатор параметра out, можно организовать возврат двух значений из одного и того же метода.

Разумеется, никаких ограничений на применение параметров out в одном методе не существует. С их помощью из метода можно возвратить сколько угодно фрагментов информации. Рассмотрим пример применения двух параметров out. В этом примере программы метод HasComFactor() выполняет две функции. Во-первых, он определяет общий множитель (кроме 1) для двух целых чисел, возвращая логическое значение true, если у них имеется общий множитель, а иначе — логическое значение false. И во-вторых, он возвращает посредством параметров типа out наименьший и наибольший общий множитель двух чисел, если таковые обнаруживаются.

// Использовать два параметра типа out.
using System;
class Num {
  /* Определить, имеется ли у числовых значений переменных х и v общий множитель. Если имеется, то * возвратить наименьший и наибольший множители посредством параметров типа out. */
  public bool HasComFactor(int x, int y,
          out int least, out int greatest) {
  int i;
  int max = x < у ? x : y;
  bool first = true;
  least = 1;
  greatest = 1;
  // Найти наименьший и наибольший общий множитель.
  for(i=2; i <= max/2 + 1; i++) {
    if( ((y%i)==0) & ((x%i)==0) ) {
      if (first) {
        least = i;
        first = false;
      }
      greatest = i;
    }
  }
  if(least != 1)
    return true;
  else
    return false;
  }
}
class DemoOut {
  static void Main() {
    Num ob = new Num();
    int lcf, gcf;
    if(ob.HasComFactor(231, 105, out lcf, out gcf)) {
      Console.WriteLine("Наименьший общий множитель " +
            "чисел 231 и 105 равен " + lcf) ;
      Console.WriteLine("Наибольший общий множитель " +
            "чисел 231 и 105 равен " + gcf);
    }
    else
      Console.WriteLine("Общий множитель у чисел 35 и 49 отсутствует.");
    if(ob.HasComFactor(35, 51, out lcf, out gcf)) {
      Console.WriteLine("Наименьший общий множитель " +
             "чисел 35 и 51 равен " + lcf);
      Console.WriteLine("Наибольший общий множитель " +
             "чисел 35 и 51 равен " + gcf);
    }
    else
      Console.WriteLine("Общий множитель у чисел 35 и 51 отсутствует.");
  }
}

Обратите внимание на то, что значения присваиваются переменным lcf и gcf в методе Main() до вызова метода HasComFactor(). Если бы параметры метода HasComFactor() были типа ref, а не out, это привело бы к ошибке. Данный метод возвращает логическое значение true или false, в зависимости от того, имеется ли общий множитель у двух целых чисел. Если он имеется, то посредством параметров типа out возвращаются наименьший и наибольший общий множитель этих чисел. Ниже приведен результат выполнения данной программы.

Наименьший общий множитель чисел 231 и 105 равен 3
Наибольший общий множитель чисел 231 и 105 равен 21
Общий множитель у чисел 35 и 51 отсутствует.

Использование модификаторов ref и out для ссылок на объекты

Применение модификаторов ref и out не ограничивается только передачей значений обычных типов. С их помощью можно также передавать ссылки на объекты. Если модификатор ref или out указывает на ссылку, то сама ссылка передается по ссылке. Это позволяет изменить в методе объект, на который указывает ссылка. Рассмотрим в качестве примера следующую программу, в которой ссылочные параметры типа ref служат для смены объектов, на которые указывают ссылки.

// Поменять местами две ссылки.
using System;
class RefSwap {
  int a, b;
  public RefSwap(int i, int j) {
    a = i;
    b = j;
  }
  public void Show() {
    Console.WriteLine("a: {0}, b: {1}", a, b);
  }
  // Этот метод изменяет свои аргументы.
  public void Swap(ref RefSwap ob1, ref RefSwap ob2) {
    RefSwap t;
    t = ob1;
    ob1 = ob2;
    ob2 = t;
  }
}
class RefSwapDemo {
  static void Main() {
    RefSwap x = new RefSwap(1, 2);
    RefSwap у = new RefSwap(3, 4);
    Console.Write("x до вызова: ") ;
    x.Show();
    Console.Write("у до вызова: ");
    у.Show();
    Console.WriteLine() ;
    // Смена объектов, на которые ссылаются аргументы х и у.
    x.Swap (ref x, ref у);
    Console.Write("х после вызова: ");
    x.Show();
    Console.Write("у после вызова: ") ;
    у.Show();
  }
}

При выполнении этой программы получается следующий результат.

х до вызова: а: 1, b: 2
у до вызова: а: 3, b: 4
х после вызова: а: 3, b: 4
у после вызова: а: 1, b: 2

В данном примере в методе Swap() выполняется смена объектов, на которые ссылаются два его аргумента. До вызова метода Swap() аргумент х ссылается на объект, содержащий значения 1 и 2, тогда как аргумент у ссылается на объект, содержащий значения 3 и 4. А после вызова метода Swap() аргумент х ссылается на объект, содержащий значения 3 и 4, тогда как аргумент у ссылается на объект, содержащий значения 1 и 2. Если бы не параметры типа ref, то перестановка в методе Swap() не имела бы никаких последствий за пределами этого метода. Для того чтобы убедиться в этом, исключите параметры типа ref из метода Swap().

Оглавление книги


Генерация: 1.126. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз