Обучение
Модуль
Создание методов C#, возвращающих значения - Training
Этот модуль охватывает ключевое слово возвращаемого значения и возвращает значения из методов.
Этот браузер больше не поддерживается.
Выполните обновление до Microsoft Edge, чтобы воспользоваться новейшими функциями, обновлениями для системы безопасности и технической поддержкой.
Метод — это блок кода, содержащий ряд инструкций. Программа инициирует выполнение инструкций, вызывая метод и указывая все аргументы, необходимые для этого метода. В C# все инструкции выполняются в контексте метода.
Примечание
В этом разделе рассматриваются названные методы. Дополнительные сведения об анонимных функциях см. в статье Лямбда-выражения.
Методы объявляются с помощью ограничений class
, record
или struct
, для которых указываются следующие данные.
public
или private
. Значение по умолчанию — private
.abstract
или sealed
.void
, если у метода его нет.Вместе все эти части формируют сигнатуру метода.
Важно!
Тип возврата метода не является частью сигнатуры метода в целях перегрузки метода. Однако он является частью сигнатуры метода при определении совместимости между делегатом и методом, который он указывает.
В следующем примере определяется класс с именем Motorcycle
, содержащий пять методов:
namespace MotorCycleExample
{
abstract class Motorcycle
{
// Anyone can call this.
public void StartEngine() {/* Method statements here */ }
// Only derived classes can call this.
protected void AddGas(int gallons) { /* Method statements here */ }
// Derived classes can override the base class implementation.
public virtual int Drive(int miles, int speed) { /* Method statements here */ return 1; }
// Derived classes can override the base class implementation.
public virtual int Drive(TimeSpan time, int speed) { /* Method statements here */ return 0; }
// Derived classes must implement this.
public abstract double GetTopSpeed();
}
Класс Motorcycle
включает перегруженный метод. Drive
Два метода имеют одно и то же имя, но отличаются по их типам параметров.
Можно использовать метод instance или static. Необходимо создать экземпляр объекта для вызова метода экземпляра в этом экземпляре; Метод экземпляра работает с этим экземпляром и его данными. Вы вызываете статический метод, ссылаясь на имя типа, к которому принадлежит метод; статические методы не работают с данными экземпляра. При попытке вызвать статический метод с помощью экземпляра объекта возникает ошибка компилятора.
Вызов метода аналогичен доступу к полю. После имени объекта (при вызове метода экземпляра) или имени типа (если вы вызываете static
метод), добавьте период, имя метода и круглые скобки. Аргументы перечисляются в этих скобках и разделяются запятыми.
Определение метода задает имена и типы всех необходимых параметров. Когда вызывающий код вызывает метод, он предоставляет конкретные значения, называемые аргументами, для каждого параметра. Аргументы должны быть совместимы с типом параметра, но имя аргумента, если он используется в вызывающем коде, не должен совпадать с параметром, указанным в методе. В следующем примере метод Square
имеет один параметр типа int
с именем i. Первый вызов метода передает методу Square
переменную типа int
с именем num; второй — числовую константу, а третий — выражение.
public static class SquareExample
{
public static void Main()
{
// Call with an int variable.
int num = 4;
int productA = Square(num);
// Call with an integer literal.
int productB = Square(12);
// Call with an expression that evaluates to int.
int productC = Square(productA * 3);
}
static int Square(int i)
{
// Store input argument in a local variable.
int input = i;
return input * input;
}
}
В наиболее распространенной форме вызова методов используются позиционные аргументы; они передаются в том же порядке, что и параметры метода. Таким образом, методы класса Motorcycle
могут вызываться, как показано в следующем примере. Например, вызов метода Drive
включает два аргумента, которые соответствуют двум параметрам в синтаксисе метода. Первый становится значением miles
параметра. Второй становится значением speed
параметра.
class TestMotorcycle : Motorcycle
{
public override double GetTopSpeed() => 108.4;
static void Main()
{
var moto = new TestMotorcycle();
moto.StartEngine();
moto.AddGas(15);
_ = moto.Drive(5, 20);
double speed = moto.GetTopSpeed();
Console.WriteLine("My top speed is {0}", speed);
}
}
При вызове метода вместо позиционных аргументов можно также использовать именованные аргументы. При использовании именованных аргументов необходимо указать имя параметра, двоеточие (":"), а затем аргумент. Аргументы для метода могут отображаться в любом порядке, при условии, что все обязательные аргументы присутствуют. В следующем примере для вызова метода TestMotorcycle.Drive
используются именованные аргументы. В этом примере именованные аргументы передаются из списка параметров метода в обратном порядке.
namespace NamedMotorCycle;
class TestMotorcycle : Motorcycle
{
public override int Drive(int miles, int speed) =>
(int)Math.Round((double)miles / speed, 0);
public override double GetTopSpeed() => 108.4;
static void Main()
{
var moto = new TestMotorcycle();
moto.StartEngine();
moto.AddGas(15);
int travelTime = moto.Drive(miles: 170, speed: 60);
Console.WriteLine("Travel time: approx. {0} hours", travelTime);
}
}
// The example displays the following output:
// Travel time: approx. 3 hours
Метод можно вызывать, используя и позиционные, и именованные аргументы. Однако позиционные аргументы могут следовать за именованными аргументами, только если именованные аргументы находятся в правильных позициях. В следующем примере метод TestMotorcycle.Drive
из предыдущего примера вызывается с использованием одного позиционного и одного именованного аргумента.
int travelTime = moto.Drive(170, speed: 55);
Помимо членов, определенных в нем явно, тип наследует члены, определенные в его базовых классах. Так как все типы в системе управляемых типов напрямую или косвенно наследуются из класса Object, все типы наследуют его члены, такие как Equals(Object), GetType() и ToString(). В следующем примере определяется класс Person
, который создает экземпляры двух объектов Person
и вызывает метод Person.Equals
, чтобы определить, равны ли эти объекты. Однако Equals
метод не определен в Person
классе; он наследуется от Object.
public class Person
{
public string FirstName = default!;
}
public static class ClassTypeExample
{
public static void Main()
{
Person p1 = new() { FirstName = "John" };
Person p2 = new() { FirstName = "John" };
Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
}
}
// The example displays the following output:
// p1 = p2: False
Типы могут переопределять унаследованные члены, используя ключевое слово override
и обеспечивая реализацию переопределенного метода. Подпись метода должна совпадать с переопределенным методом. Следующий пример аналогичен предыдущему за тем исключением, что переопределяет метод Equals(Object). (Он также переопределяет метод GetHashCode(), поскольку оба эти метода предназначены для получения согласованных результатов.)
namespace methods;
public class Person
{
public string FirstName = default!;
public override bool Equals(object? obj) =>
obj is Person p2 &&
FirstName.Equals(p2.FirstName);
public override int GetHashCode() => FirstName.GetHashCode();
}
public static class Example
{
public static void Main()
{
Person p1 = new() { FirstName = "John" };
Person p2 = new() { FirstName = "John" };
Console.WriteLine("p1 = p2: {0}", p1.Equals(p2));
}
}
// The example displays the following output:
// p1 = p2: True
Типы в C# делятся на типы значений и ссылочные типы. Список встроенных типов значений см. в разделе Типы. По умолчанию типы значений и ссылочные типы передаются по значению в метод.
При передаче типа значения в метод по значению вместо самого объекта передается его копия. Это значит, что изменения объекта в вызываемом методе не отражаются на исходном объекте, когда управление возвращается вызывающему объекту.
Код в следующем примере передает тип значения в метод по значению, а вызываемый метод пытается изменить значение типа значения. Он определяет переменную типа int
, который является типом значения, присваивает ему значение 20 и передает его в метод с именем ModifyValue
, который изменяет значение переменной на 30. Однако, когда метод возвращается, значение переменной остается неизменным.
public static class ByValueExample
{
public static void Main()
{
var value = 20;
Console.WriteLine("In Main, value = {0}", value);
ModifyValue(value);
Console.WriteLine("Back in Main, value = {0}", value);
}
static void ModifyValue(int i)
{
i = 30;
Console.WriteLine("In ModifyValue, parameter value = {0}", i);
return;
}
}
// The example displays the following output:
// In Main, value = 20
// In ModifyValue, parameter value = 30
// Back in Main, value = 20
Если объект ссылочного типа передается в метод по значению, ссылка на этот объект передается по значению. Это значит, что метод получает не сам объект, а аргумент, который указывает расположение объекта. Если с помощью этой ссылки в член объекта вносится изменение, это изменение отражается в объекте, даже если управление возвращается вызывающему объекту. При этом изменения в объекте, переданном в метод, не отражаются на исходном объекте, когда управление возвращается вызывающему объекту.
В следующем примере определяется класс (ссылочного типа) с именем SampleRefType
. Он создает экземпляр объекта SampleRefType
, задает в его поле value
значение 44 и передает объект в метод ModifyObject
. Этот пример по сути совпадает с предыдущим примером. Он передает аргумент по значению методу. Однако поскольку здесь используется ссылочный тип, результат будет другим. В данном случае в методе ModifyObject
изменено поле obj.value
, при этом поле value
аргумента rt
в методе Main
также изменяется на 33, как видно из результатов в предыдущем примере.
public class SampleRefType
{
public int value;
}
public static class ByRefTypeExample
{
public static void Main()
{
var rt = new SampleRefType { value = 44 };
ModifyObject(rt);
Console.WriteLine(rt.value);
}
static void ModifyObject(SampleRefType obj) => obj.value = 33;
}
Параметр передается по ссылке, когда нужно изменить значение аргумента в методе и сохранить это изменение после того, как управление вернется вызывающему методу. Для передачи параметра по ссылке используйте ключевое слово ref
или out
. Можно также передать значение по ссылке, чтобы предотвратить копирование, и при этом запретить внесение изменений с помощью ключевого слова in
.
Следующий пример идентичен предыдущему, за исключением того, что значение передается ссылкой на ModifyValue
метод. Если значение параметра в методе ModifyValue
будет изменено, при возвращении управления вызывающему объекту это изменение не сохранится.
public static class ByRefExample
{
public static void Main()
{
var value = 20;
Console.WriteLine("In Main, value = {0}", value);
ModifyValue(ref value);
Console.WriteLine("Back in Main, value = {0}", value);
}
private static void ModifyValue(ref int i)
{
i = 30;
Console.WriteLine("In ModifyValue, parameter value = {0}", i);
return;
}
}
// The example displays the following output:
// In Main, value = 20
// In ModifyValue, parameter value = 30
// Back in Main, value = 30
Общий шаблон, в котором используются параметры по ссылке, включает замену значений переменных. Когда две переменные передаются в метод по ссылке, он меняет их содержимое местами. В следующем примере меняются местами целочисленные значения.
public static class RefSwapExample
{
static void Main()
{
int i = 2, j = 3;
Console.WriteLine("i = {0} j = {1}", i, j);
Swap(ref i, ref j);
Console.WriteLine("i = {0} j = {1}", i, j);
}
static void Swap(ref int x, ref int y) =>
(y, x) = (x, y);
}
// The example displays the following output:
// i = 2 j = 3
// i = 3 j = 2
Передача параметров ссылочного типа позволяет изменить значение самой ссылки, а не отдельных ее элементов или полей.
В некоторых случаях требование об указании точного числа аргументов для метода является строгим. Используя ключевое params
слово, указывающее, что параметр является коллекцией параметров, можно вызывать метод с переменным числом аргументов. Параметр, params
помеченный ключевым словом, должен быть типом коллекции, и он должен быть последним параметром в списке параметров метода.
Вызывающий объект может вызвать метод в любом из четырех способов для params
параметра:
null
.В следующем примере определяется метод с именем GetVowels
, который возвращает все гласные из коллекции параметров. Метод Main
демонстрирует все четыре способа вызова метода. Вызывающие элементы не требуются для предоставления аргументов для параметров, включающих params
модификатор. В этом случае параметр является пустой коллекцией.
static class ParamsExample
{
static void Main()
{
string fromArray = GetVowels(["apple", "banana", "pear"]);
Console.WriteLine($"Vowels from collection expression: '{fromArray}'");
string fromMultipleArguments = GetVowels("apple", "banana", "pear");
Console.WriteLine($"Vowels from multiple arguments: '{fromMultipleArguments}'");
string fromNull = GetVowels(null);
Console.WriteLine($"Vowels from null: '{fromNull}'");
string fromNoValue = GetVowels();
Console.WriteLine($"Vowels from no value: '{fromNoValue}'");
}
static string GetVowels(params IEnumerable<string>? input)
{
if (input == null || !input.Any())
{
return string.Empty;
}
char[] vowels = ['A', 'E', 'I', 'O', 'U'];
return string.Concat(
input.SelectMany(
word => word.Where(letter => vowels.Contains(char.ToUpper(letter)))));
}
}
// The example displays the following output:
// Vowels from array: 'aeaaaea'
// Vowels from multiple arguments: 'aeaaaea'
// Vowels from null: ''
// Vowels from no value: ''
Перед C# 13 params
модификатор можно использовать только с одниммерным массивом.
Определение метода может указать, что его параметры являются обязательными или являются необязательными. По умолчанию параметры обязательны. Для определения необязательных параметров значения параметра по умолчанию включаются в определение метода. Если при вызове метода никакие аргументы для необязательного параметры не указываются, вместо них используется значение по умолчанию.
Значение по умолчанию параметра назначается одним из следующих типов выражений:
Константа, например, строковый литерал или число.
Выражение, имеющее вид default(SomeType)
, где SomeType
может быть либо типом значения, либо ссылочным типом. Использование ссылочного типа практически эквивалентно указанию null
. Вы можете использовать default
литерал, так как компилятор может вывести тип из объявления параметра.
Выражение в форме new ValType()
, где ValType
— это тип значения. Это выражение вызывает неявный конструктор типа значения без параметров, который не является фактическим членом типа.
Примечание
В C# 10 и более поздних версиях, когда выражение формы new ValType()
вызывает явно определенный конструктор без параметров типа значения, компилятор выдает ошибку, так как значение параметра по умолчанию должно быть константой времени компиляции. Используйте выражение default(ValType)
или литерал default
для предоставления значения параметра по умолчанию. Дополнительные сведения о конструкторах без параметров см. в разделе "Инициализация структуры и значения по умолчанию" статьи "Типы структур".
Если метод содержит как обязательные, так и необязательные параметры, необязательные параметры определяются в конце списка параметров после всех обязательных параметров.
В следующем примере определяется метод ExampleMethod
, который имеет один обязательный и два необязательных параметра.
public class Options
{
public void ExampleMethod(int required, int optionalInt = default,
string? description = default)
{
var msg = $"{description ?? "N/A"}: {required} + {optionalInt} = {required + optionalInt}";
Console.WriteLine(msg);
}
}
Вызывающий объект должен указать аргумент для всех необязательных параметров до последнего необязательного параметра, для которого предоставляется аргумент. ExampleMethod
Например, если вызывающий объект предоставляет аргумент для description
параметра, он также должен указать один для optionalInt
параметра. opt.ExampleMethod(2, 2, "Addition of 2 and 2");
— допустимый вызов метода; opt.ExampleMethod(2, , "Addition of 2 and 0");
вызывает ошибку компилятора "Аргумент отсутствует".
Если метод вызывается с помощью именованных аргументов или комбинации позиционных и именованных аргументов, вызывающий объект может опустить любые аргументы, следующие за последним позиционным аргументом в вызове метода.
В следующем примере метод ExampleMethod
вызывается трижды. В первых двух вызовах метода используются позиционные аргументы. В первом пропускаются оба необязательных аргумента, а во втором — последний. Третий вызов метода предоставляет позиционный аргумент для обязательного параметра, но использует именованный аргумент для передачи значения в параметр description
, в то время как аргумент optionalInt
опускается.
public static class OptionsExample
{
public static void Main()
{
var opt = new Options();
opt.ExampleMethod(10);
opt.ExampleMethod(10, 2);
opt.ExampleMethod(12, description: "Addition with zero:");
}
}
// The example displays the following output:
// N/A: 10 + 0 = 10
// N/A: 10 + 2 = 12
// Addition with zero:: 12 + 0 = 12
Использование необязательных параметров влияет на разрешение перегрузки или способ, которым компилятор C# определяет, какая перегрузка вызывается для вызова метода, как показано ниже.
Методы могут возвращать значение вызывающему объекту. Если возвращаемый тип (тип, указанный перед именем метода), не void
является, метод может возвращать значение с помощью ключевого return
слова. Оператор с ключевым словом, return
за которым следует переменная, константа или выражение, которое соответствует типу возвращаемого значения, возвращает это значение вызывающей функции метода. Методы с невоидным типом возвращаемого значения требуются для использования return
ключевого слова. Ключевое слове return
также останавливает выполнение метода.
Если тип возврата — void
, инструкцию return
без значения по-прежнему можно использовать для завершения выполнения метода. return
Без ключевого слова метод перестает выполняться, когда он достигает конца блока кода.
Например, в следующих двух методах ключевое слово return
используется для возврата целочисленных значений.
class SimpleMath
{
public int AddTwoNumbers(int number1, int number2) =>
number1 + number2;
public int SquareANumber(int number) =>
number * number;
}
Приведенные выше примеры являются элементами выражения. Члены выражения возвращают значение, возвращаемое выражением.
Вы также можете определить методы с текстом инструкции и оператором return
:
class SimpleMathExtnsion
{
public int DivideTwoNumbers(int number1, int number2)
{
return number1 / number2;
}
}
Чтобы использовать значение, возвращаемое из метода, вызывающий метод может применять сам вызов метода везде, где будет достаточно значения того же типа. Можно также назначить возвращаемое значение переменной. Например, следующие три примера кода выполняют одну и ту же задачу:
int result = obj.AddTwoNumbers(1, 2);
result = obj.SquareANumber(result);
// The result is 9.
Console.WriteLine(result);
result = obj.SquareANumber(obj.AddTwoNumbers(1, 2));
// The result is 9.
Console.WriteLine(result);
result = obj2.DivideTwoNumbers(6,2);
// The result is 3.
Console.WriteLine(result);
В некоторых случаях нужно, чтобы метод возвращал больше одного значения. Для возврата нескольких значений используются типы кортежей и кортежные литералы. Тип кортежа определяет типы данных для элементов кортежа. Литералы кортежей предоставляют фактические значения возвращаемого кортежа. В следующем примере определяет тип кортежа, (string, string, string, int)
возвращаемый методом GetPersonalInfo
. Выражение (per.FirstName, per.MiddleName, per.LastName, per.Age)
представляет собой литерал кортежа. Метод возвращает первое, среднее и семейное имя, а также возраст PersonInfo
объекта.
public (string, string, string, int) GetPersonalInfo(string id)
{
PersonInfo per = PersonInfo.RetrieveInfoById(id);
return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}
Вызывающий объект затем может использовать возвращенный кортеж с помощью следующего кода:
var person = GetPersonalInfo("111111111");
Console.WriteLine($"{person.Item1} {person.Item3}: age = {person.Item4}");
Имена могут также назначаться элементам кортежа в определении типа кортежа. В следующих примерах демонстрируется альтернативная версия метода GetPersonalInfo
, в котором используются именованные элементы:
public (string FName, string MName, string LName, int Age) GetPersonalInfo(string id)
{
PersonInfo per = PersonInfo.RetrieveInfoById(id);
return (per.FirstName, per.MiddleName, per.LastName, per.Age);
}
После этого предыдущий вызов метода GetPersonalInfo
можно изменить следующим образом:
var person = GetPersonalInfo("111111111");
Console.WriteLine($"{person.FName} {person.LName}: age = {person.Age}");
Если метод принимает массив в качестве параметра и изменяет значение отдельных элементов, для возврата массива не требуется. C# передает все ссылочные типы по значению, а значение ссылки на массив — указатель на массив. В следующем примере изменения в содержимом массива values
, сделанные в методе DoubleValues
, может отслеживать любой код, имеющий ссылку на этот массив.
public static class ArrayValueExample
{
static void Main()
{
int[] values = [2, 4, 6, 8];
DoubleValues(values);
foreach (var value in values)
{
Console.Write("{0} ", value);
}
}
public static void DoubleValues(int[] arr)
{
for (var ctr = 0; ctr <= arr.GetUpperBound(0); ctr++)
{
arr[ctr] *= 2;
}
}
}
// The example displays the following output:
// 4 8 12 16
Как правило, добавлять методы в существующий тип можно двумя способами:
Методы расширения позволяют "добавить" метод в существующий тип, не меняя сам тип и не реализуя новый метод в наследуемом типе. Метод расширения также не должен находиться в той же сборке, что и тип, который он расширяет. Вызовите метод расширения, как будто он является определенным членом типа.
Дополнительные сведения см. в статье Методы расширения.
С помощью функции async можно вызывать асинхронные методы, не прибегая к использованию явных обратных вызовов или ручному разделению кода между несколькими методами или лямбда-выражениями.
Если пометить метод с помощью модификатора async, можно использовать в этом методе инструкцию await. Когда элемент управления достигает await
выражения в асинхронном методе, элемент управления возвращается вызывающему объекту, если ожидаемая задача не завершена, и ход выполнения метода с await
ключевым словом приостановлен до завершения ожидаемой задачи. После завершения задачи выполнение в методе может быть возобновлено.
Примечание
Асинхронный метод возвращается в вызывающий объект, когда он встречает первый ожидаемый объект, выполнение которого еще не завершено, или когда выполнение асинхронного метода доходит до конца — в зависимости от того, что происходит раньше.
Асинхронный метод обычно имеет тип возвращаемого Task<TResult>значения , TaskIAsyncEnumerable<T>или void
. Тип возвращаемого значения void
в основном используется для определения обработчиков событий, где требуется возвращать тип void
. Асинхронный метод, который возвращает тип void
, не может быть ожидающим. Вызывающий объект метода, возвращающего значение типа void, не может перехватывать исключения, которые выдает этот метод. Асинхронный метод может иметь любой тип возвращаемой задачи.
В следующем примере DelayAsync
представляет собой асинхронный метод с оператором return, который возвращает целое число. Так как это асинхронный метод, его объявление метода должно иметь возвращаемый тип Task<int>
. Поскольку тип возврата — Task<int>
, вычисление выражения await
в DoSomethingAsync
создает целое число, как показывает следующий оператор: int result = await delayTask
.
class Program
{
static Task Main() => DoSomethingAsync();
static async Task DoSomethingAsync()
{
Task<int> delayTask = DelayAsync();
int result = await delayTask;
// The previous two statements may be combined into
// the following statement.
//int result = await DelayAsync();
Console.WriteLine($"Result: {result}");
}
static async Task<int> DelayAsync()
{
await Task.Delay(100);
return 5;
}
}
// Example output:
// Result: 5
Асинхронный метод не может объявлять параметры in, ref или out, но может вызывать методы, имеющие такие параметры.
Дополнительные сведения об асинхронных методах см. в разделах Асинхронное программирование с использованием ключевых слов async и await (C#) и Типы возвращаемых значений асинхронных операций.
Обычно используются определения методов, которые возвращаются немедленно с результатом выражения или имеют одну инструкцию в качестве текста метода. Существует сочетание синтаксиса для определения таких методов с помощью =>
:
public Point Move(int dx, int dy) => new Point(x + dx, y + dy);
public void Print() => Console.WriteLine(First + " " + Last);
// Works with operators, properties, and indexers too.
public static Complex operator +(Complex a, Complex b) => a.Add(b);
public string Name => First + " " + Last;
public Customer this[long id] => store.LookupCustomer(id);
Если метод возвращает void
или является асинхронным, текст этого метода должен быть выражением оператора (как и при использовании лямбда-выражений). Для свойств и индексаторов они должны быть доступны только для чтения, и вы не используете ключевое get
слово accessor.
Итератор выполняет настраиваемую итерацию по коллекции, например по списку или массиву. Итератор использует инструкцию yield return для возврата всех элементов по одному. По достижении оператора yield return
текущее расположение запоминается, чтобы вызывающий объект мог запросить следующий элемент в последовательности.
Тип возвращаемого итератора может быть IEnumerable, IEnumerable<T>, IAsyncEnumerable<T>, IEnumeratorили IEnumerator<T>.
Дополнительные сведения см. в разделе Итераторы.
Отзыв о .NET
.NET — это проект с открытым исходным кодом. Выберите ссылку, чтобы оставить отзыв:
Обучение
Модуль
Создание методов C#, возвращающих значения - Training
Этот модуль охватывает ключевое слово возвращаемого значения и возвращает значения из методов.