Методы (Руководство по программированию на C#)
Метод — это блок кода, содержащий ряд инструкций. Программа инициирует выполнение инструкций, вызывая метод и указывая все аргументы, необходимые для этого метода. В C# все инструкции выполняются в контексте метода.
Метод Main является точкой входа для каждого приложения C# и вызывается общеязыковой средой выполнения (CLR) при запуске программы. В приложении, использующем инструкции верхнего уровня, метод Main создается компилятором и содержит все инструкции верхнего уровня.
В этой статье рассматриваются названные методы. Дополнительные сведения об анонимных функциях см. в статье Лямбда-выражения.
Сигнатуры методов
Методы объявляются в классе, структуре или интерфейсе путем указания уровня доступа, такого как public или private , необязательных модификаторов, таких как abstract или sealed , возвращаемого значения, имени метода и всех параметров этого метода. Все эти части вместе представляют собой сигнатуру метода.
Тип возврата метода не является частью сигнатуры метода в целях перегрузки метода. Однако он является частью сигнатуры метода при определении совместимости между делегатом и методом, который он указывает.
Параметры метода заключаются в скобки и разделяются запятыми. Пустые скобки указывают, что параметры методу не требуются. Этот класс содержит четыре метода:
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 must implement this. public abstract double GetTopSpeed(); >
Доступ к методу
Вызов метода в объекте аналогичен доступу к полю. После имени объекта добавьте точку, имя метода и круглые скобки. Аргументы перечисляются в этих скобках и разделяются запятыми. Таким образом, методы класса Motorcycle могут вызываться, как показано в следующем примере:
class TestMotorcycle : Motorcycle < public override double GetTopSpeed() < return 108.4; >static void Main() < TestMotorcycle moto = new TestMotorcycle(); moto.StartEngine(); moto.AddGas(15); moto.Drive(5, 20); double speed = moto.GetTopSpeed(); Console.WriteLine("My top speed is ", speed); > >
Параметры и аргументы метода
Определение метода задает имена и типы всех необходимых параметров. Когда вызывающий код вызывает метод, он предоставляет конкретные значения, называемые аргументами, для каждого параметра. Аргументы должны быть совместимы с типом параметра, но имя аргумента (если есть), используемое в вызывающем коде, не обязательно должно совпадать с именем параметра, указанным в методе. Например:
public void Caller() < int numA = 4; // Call with an int variable. int productA = Square(numA); int numB = 32; // Call with another int variable. int productB = Square(numB); // Call with an integer literal. int productC = Square(12); // Call with an expression that evaluates to int. productC = Square(productA * 3); >int Square(int i) < // Store input argument in a local variable. int input = i; return input * input; >
Передача по ссылке и передача по значению
По умолчанию при передаче в метод экземпляра типа значения вместо самого этого экземпляра передается его копия. Поэтому изменения в аргументе не оказывают влияния на исходный экземпляр в вызывающем методе. Чтобы передать экземпляр типа значения по ссылке, используйте ключевое слово ref . Дополнительные сведения см. в разделе Передача параметров типа значения.
При передаче в метод объекта ссылочного типа передается ссылка на этот объект. То есть метод получает не сам объект, а аргумент, который указывает расположение объекта. При изменении члена объекта с помощью этой ссылки это изменение отражается в аргументе в вызывающем методе, даже если объект передается по значению.
Ссылочный тип создается с помощью ключевого слова class , как показано в следующем примере.
public class SampleRefType
Теперь, если передать объект, основанный на этом типе, в метод, то будет передана ссылка на объект. В следующем примере объект типа SampleRefType передается в метод ModifyObject :
public static void TestRefType() < SampleRefType rt = new SampleRefType(); rt.value = 44; ModifyObject(rt); Console.WriteLine(rt.value); >static void ModifyObject(SampleRefType obj)
В этом примере, в сущности, делается то же, что и в предыдущем примере, — аргумент по значению передается в метод. Но поскольку здесь используется ссылочный тип, результат будет другим. В данном случае в методе ModifyObject изменено поле value параметра obj , а также изменено поле value аргумента, rt в методе TestRefType . В качестве выходных данных метод TestRefType отображает 33.
Дополнительные сведения о передаче ссылочных типов по ссылке и по значению см. в разделах Передача параметров ссылочного типа и Ссылочные типы.
Возвращаемые значения
Методы могут возвращать значение вызывающему объекту. Если тип возврата, указываемый перед именем метода, не void , этот метод может возвращать значение с помощью оператора return . Инструкция с ключевым словом return , за которым следует значение, соответствующее типу возврата, будет возвращать это значение объекту, вызвавшему метод.
Значение можно вернуть вызывающему объекту по значению или по ссылке. Значения возвращаются вызывающему объекту по ссылке, если ключевое слово ref используется в сигнатуре метода и указывается после каждого ключевого слова return . Например, следующая сигнатура метода и оператор return указывают, что метод возвращает переменную с именем estDistance вызывающему объекту по ссылке.
public ref double GetEstimatedDistance()
Ключевое слове 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 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 , для сохранения значения является необязательным. Это может улучшить читаемость кода или может оказаться необходимым, если нужно сохранить исходное значение аргумента для всей области метода.
Чтобы использовать значение, возвращаемое по ссылке из метода, необходимо объявить локальную ссылочную переменную, если планируется изменение значения. Например, если метод Planet.GetEstimatedDistance возвращает значение Double по ссылке, можно определить его как локальную ссылочную переменную с использованием кода следующего вида:
ref double distance = ref Planet.GetEstimatedDistance();
Возвращать многомерный массив из метода M , который изменяет содержимое массива, необязательно, если вызывающая функция передает массив в M . В целях оптимизации можно возвращать полученный массив из M или функциональный поток значений, однако это необязательно. Это связано с тем, что C# передает все ссылочные типы по значению, а значение ссылки на массив представляет собой указатель на массив. В методе M любые изменения содержимого массива отслеживаются любым кодом, имеющим ссылку на массив, как показано в приведенном ниже примере:
static void Main(string[] args) < int[,] matrix = new int[2, 2]; FillMatrix(matrix); // matrix is now full of -1 >public static void FillMatrix(int[,] matrix) < for (int i = 0; i < matrix.GetLength(0); i++) < for (int j = 0; j < matrix.GetLength(1); j++) < matrix[i, j] = -1; >> >
Асинхронные методы
С помощью функции async можно вызывать асинхронные методы, не прибегая к использованию явных обратных вызовов или ручному разделению кода между несколькими методами или лямбда-выражениями.
Если пометить метод с помощью модификатора async, можно использовать в этом методе инструкцию await. Когда управление достигает выражения await в асинхронном методе, управление возвращается вызывающему объекту и выполнение метода приостанавливается до завершения выполнения ожидающей задачи. После завершения задачи выполнение в методе может быть возобновлено.
Асинхронный метод возвращается в вызывающий объект, когда он встречает первый ожидаемый объект, выполнение которого еще не завершено, или когда выполнение асинхронного метода доходит до конца — в зависимости от того, что происходит раньше.
В следующем примере метод Main служит примером асинхронного метода с типом возврата Task. Он переходит к методу DoSomethingAsync и, поскольку он выражается в одной строке, он может опустить ключевые слова async и await . Поскольку DoSomethingAsync является асинхронным методом, задача для вызова DoSomethingAsync должна быть ожидаемой, как показывает следующая инструкция: await DoSomethingAsync(); .
class Program < static Task Main() =>DoSomethingAsync(); static async Task DoSomethingAsync() < TaskdelayTask = DelayAsync(); int result = await delayTask; // The previous two statements may be combined into // the following statement. //int result = await DelayAsync(); Console.WriteLine($"Result: "); > static async Task DelayAsync() < await Task.Delay(100); return 5; >> // Example output: // Result: 5
Асинхронный метод не может объявить все параметры ref или out , но может вызывать методы, которые имеют такие параметры.
Определения текста выражений
Часто используются определения методов, которые просто немедленно возвращаются с результатом выражения или которые имеют единственную инструкцию в тексте метода. Для определения таких методов существует сокращенный синтаксис с использованием => :
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 метода доступа.
Итераторы
Итератор выполняет настраиваемую итерацию по коллекции, например по списку или массиву. Итератор использует инструкцию yield return для возврата всех элементов по одному. При достижении инструкции yield return текущее расположение в коде запоминается. При следующем вызове итератора выполнение возобновляется с этого места.
Итератор вызывается из клиентского кода с помощью инструкции foreach .
Дополнительные сведения см. в разделе Итераторы.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
- Руководство по программированию на C#
- Система типов C#
- Модификаторы доступа
- Статические классы и члены статических классов
- Наследование
- Абстрактные и запечатанные классы и члены классов
- params
- out
- ref;
- Параметры методов
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.
Как создать метод в c
Все, что необходимо начинающему и опытному программисту
Методы классов.
Использование и создание методов в программе на С# во многом схоже на уже известный вам подход из С++. Рассмотрим это поподробней. В C# любой метод является методом какого-либо класса. Например:
using System; class Man < public string name; public int age; public Man(string n,int a)< name = n; age = a; >public void Show() < Console.WriteLine("Имя = "+name+"\tВозраст = "+age); >> class Sample < static void Main() < try< Man obj = new Man("Fire",56); obj.Show(); >catch(Exception er) < Console.WriteLine(er.Message); >Console.Read(); > >
В данном примере у нас есть класс Man с методом Show и конструктором. Обратите внимание возле Show стоит спецификатор доступа public. Если его не указать по умолчанию будет поставлен private и вызвать его из класса Sample не получится. Для возврата значений и выхода из метода также как и в С++ используется оператор return. Принцип использования демонстрируется в примере:
using System; class SomeClass < public float x; public float y; public SomeClass(float a,float b)< x = a; y = b; >public float Summa() < return x+y; >public void Test(float t) < if(t == 0)< Console.WriteLine("Для деления был передан 0"); return; >Console.WriteLine(" Результат = ",(x+y)/t); > > class Sample < static void Main() < try< SomeClass obj = new SomeClass(2,3); Console.WriteLine("Сумма равна "+obj.y); // 1 2 >catch(Exception er) < Console.WriteLine(er.Message); >Console.Read(); > >
Как изменить значение для переменной типа int или double при передаче её внутрь метода будет показано в следующем разделе. В С#, как и в С++. можно перегружать методы. Правила при перегрузке такие же, как и в С++.
Статические методы
Статическим методом называется метод с модификатором static. Разница между обычным методом класса и таким состоит в том, что метод с static модификатором может быть вызван без создания какого-либо объекта класса. Например:
using System; class Figure < public static void Draw()< Console.WriteLine("FigureDraw"); >> class Sample < static void Main() < try< Figure.Draw(); >catch(Exception er) < Console.WriteLine(er.Message); >Console.Read(); > >
- Методы с модификатором static не имеют ссылки this.
- Метод с модификатором static, может непосредственно( без указания имени объекта ) вызывать только другой статический метод.
- Метод с модификатором static имеет только прямой доступ к static — данным.
using System; class Figure < public int a; public void Line()< Console.WriteLine("*************"); >public static void Draw() < a = 78; // ОШИБКА . a не static - член Console.WriteLine("FigureDraw"); Line(); //// ОШИБКА . Line не static - метод >public static void Draw2(Figure t) < t.Line(); // А так можно . >> class Sample < static void Main() < try< Figure.Draw(); Figure.Draw2(); >catch(Exception er) < Console.WriteLine(er.Message); >Console.Read(); > >
Библиотека программиста. 2009.
Администратор: admin@programmer-lib.ru
C#: Создание (определение) метода
Определение собственных методов значительно упрощает написание и поддержку программ. Методы позволяют объединять сложные (составные) операции в одну. Например, отправка письма на сайте — это достаточно сложный процесс, включающий в себя взаимодействие с внешними системами (интернет). Благодаря возможности определять методы, вся сложность может быть скрыта за одним простым методом:
// Место откуда берется класс с методом using MailerLib; var email = "support@hexlet.io"; var title = "Помогите"; var body = "Я написал историю успеха, как я могу получить скидку?"; // Mailer – имя класса по аналогии с Console, который мы уже многократно использовали // Один маленький вызов — и много логики внутри Mailer.Send(email, title, body);
Создадим наш первый метод. Его задача — вывести на экран текущую дату:
Today is: 2021-10-25
// Объявление класса class App < // Определение метода public static void ShowCurrentDate() < // Класс со свойством для получения текущего времени var currentDate = DateTime.Now; var text = $"Today is: "; Console.WriteLine(text); > > // Вызов метода // Обязательно указывать имя класса App.ShowCurrentDate(); // => "Today is: 09.12.2021 10:52:13"
Определение метода в C# включает в себя много страшных слов, которые мы постепенно разберем.
Объявление класса
class App < // . >
Класс объявляется через ключевое слово class , за которым следует имя создаваемого класса App . Потом идут фигурные скобки <> между которыми идет наполнение этого класса. Проще всего воспринимать класс как «контейнер» для методов. Представьте себе ящик с инструментами (класс) в котором лежат нужные инструменты (методы).
Console.WriteLine("Hello"); // Console – "контейнер" для инструментов работы с консолью // WriteLine() – "инструмент" для вывода информации в консоль
Помимо WriteLine() в Console есть и другие свойства и методы. Попробуйте найти в документации что еще можно делать через этот класс.
Определение метода в классе
class App < // Определение метода public static void ShowCurrentDate() < // . >>
Определение метода в C# включает в себя много страшных слов, которые мы постепенно разберем. В целом, их можно разделить на две группы: то, что влияет на работу самого метода и то как этот метод видим за пределами класса.
За видимость отвечает слово public, оно дает возможность вызывать методы снаружи класса, как в примере выше. Если бы мы забыли добавить слово public, то метод считался бы private – приватным. Приватные методы нельзя вызывать снаружи класса.
class App < // Определение метода. Слово public пропущено static void ShowCurrentDate() < // . >> // вызовет ошибку компиляции: // error CS0122: 'App.ShowCurrentDate()' is inaccessible due to its protection level App.ShowCurrentDate();
За работу метода отвечают static и void.
static отвязывает метод от объекта и делает возможным его вызов напрямую из класса. На протяжение этого курса, все методы, которые мы создадим, будут статичными. Поэтому просто не забывайте его ставить каждый раз, когда создаете метод.
void используется тогда, когда метод ничего не возвращает. Например такое определение у метода WriteLine() . А вот если метод возвращает какие-то данные, то вместо void будет указан тип возвращаемых данных. Подробнее об этом в следующем уроке.
В отличие от обычных данных, методы выполняют действия, поэтому их имена практически всегда должны быть глаголами: «построить что-то», «нарисовать что-то», «открыть что-то».
Всё, что описывается внутри фигурных скобок <> после имени метода, называется телом метода. Внутри тела можно описывать любой код. Считайте, что это маленькая самостоятельная программа, набор произвольных инструкций. Тело выполняется ровно в тот момент, когда запускается метод. Причём каждый вызов метода запускает тело независимо от других вызовов. Кстати, тело может быть пустым:
// Минимальное определение метода public class App < public static void Noop() < // Тут мог бы быть код, но его нет // Обратите внимание на отступы // Для читаемости, любой код внутри тела сдвигается вправо на 4 пробела >> App.Noop();
Понятие «создать метод» имеет много синонимов: «реализовать», «определить» и даже «заимплементить» (от слова implement). Все они встречаются в повседневной практике на работе.
Задание
Реализуйте класс App со статическим методом с именем PrintMotto() , который выведет на экран фразу Winter is coming. Тесты будут вызывать ваш код вот так:
App.PrintMotto(); // => Winter is coming
Чтобы мы могли вызвать этот метод снаружи, нужно его пометить не только ключевым словом static , но еще и public .
Не забудьте! В задачах, в которых нужно реализовать метод, этот метод вызывать не нужно. Вызывать метод будут автоматизированные тесты, которые проверяют его работоспособность. Пример с вызовом выше показан только для того, чтобы вы понимали, как ваш метод будет использоваться.
Упражнение не проходит проверку — что делать?
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя
Это нормально , в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Методы в C#
Метод — это блок кода, содержащий ряд инструкций. Программа инициирует выполнение инструкций, вызывая метод и указывая все аргументы, необходимые для этого метода. В C# все инструкции выполняются в контексте метода. Метод Main является точкой входа для каждого приложения C# и вызывается общеязыковой средой выполнения (CLR) при запуске программы.
В этом разделе рассматриваются названные методы. Дополнительные сведения об анонимных функциях см. в статье Лямбда-выражения.
Сигнатуры методов
Методы объявляются с помощью ограничений 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. Для того чтобы вызвать метод instance, необходимо создать экземпляр объекта и вызвать для него метод; метод instance будет применен к этому экземпляру и его данным. Вы вызываете статический метод, ссылаясь на имя типа, к которому принадлежит метод; статические методы не работают с данными экземпляра. При попытке вызвать статический метод с помощью экземпляра объекта возникает ошибка компилятора.
Вызов метода аналогичен доступу к полю. После имени объекта (при вызове метода экземпляра) или имени типа (если вы вызываете 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 ", 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. 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: ", 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: ", 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 = ", value); ModifyValue(value); Console.WriteLine("Back in Main, value = ", value); > static void ModifyValue(int i) < i = 30; Console.WriteLine("In ModifyValue, parameter value = ", 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 = ", value); ModifyValue(ref value); Console.WriteLine("Back in Main, value = ", value); > private static void ModifyValue(ref int i) < i = 30; Console.WriteLine("In ModifyValue, parameter value = ", 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 = j = ", i, j); Swap(ref i, ref j); Console.WriteLine("i = j = ", 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 , должен быть типом массива и занимать последнюю позицию в списке параметров метода.
После этого вызывающий объект можно вызвать одним из четырех способов:
- передавая массив соответствующего типа, содержащий требуемое число элементов;
- передавая в метод список отдельных аргументов соответствующего типа, разделенный запятыми;
- Путем передачи null .
- не передавая никакие аргументы в массив параметров.
В следующем примере определяется метод с именем GetVowels , возвращающий все гласные из массива параметров. Метод Main демонстрирует все четыре способа вызова метода. Вызывающие элементы не требуются для предоставления аргументов для параметров, включающих params модификатор. В этом случае параметр является пустым массивом.
static class ParamsExample < static void Main() < string fromArray = GetVowels(["apple", "banana", "pear"]); Console.WriteLine($"Vowels from array: ''"); string fromMultipleArguments = GetVowels("apple", "banana", "pear"); Console.WriteLine($"Vowels from multiple arguments: ''"); string fromNull = GetVowels(null); Console.WriteLine($"Vowels from null: ''"); string fromNoValue = GetVowels(); Console.WriteLine($"Vowels from no value: ''"); > static string GetVowels(params string[]? input) < if (input == null || input.Length == 0) < 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: ''
Необязательные параметры и аргументы
Определение метода может указать, что его параметры являются обязательными или являются необязательными. По умолчанию параметры обязательны. Для определения необязательных параметров значения параметра по умолчанию включаются в определение метода. Если при вызове метода никакие аргументы для необязательного параметры не указываются, вместо них используется значение по умолчанию.
Значение параметра по умолчанию должно быть назначено одним из следующих видов выражений:
- Константа, например, строковый литерал или число.
- Выражение, имеющее вид default(SomeType) , где SomeType может быть либо типом значения, либо ссылочным типом. Использование ссылочного типа практически эквивалентно указанию null . Вы можете использовать default литерал, так как компилятор может вывести тип из объявления параметра.
- Выражение в форме new ValType() , где ValType — это тип значения. Это вызывает неявный конструктор типа значения без параметров, который не является фактическим членом типа.
Если метод содержит как обязательные, так и необязательные параметры, необязательные параметры определяются в конце списка параметров после всех обязательных параметров.
В следующем примере определяется метод ExampleMethod , который имеет один обязательный и два необязательных параметра.
public class Options < public void ExampleMethod(int required, int optionalInt = default, string? description = default) < var msg = $": + = "; 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 , за которым следует переменная, константа или выражение, соответствующие типу возврата, будут возвращать это значение объекту, вызвавшему метод. Методы с типом возврата, отличным от void, должны использовать ключевое слово return для возврата значения. Ключевое слове return также останавливает выполнение метода.
Если тип возврата — void , инструкцию return без значения по-прежнему можно использовать для завершения выполнения метода. Без ключевого слова return этот метод будет останавливать выполнение при достижении конца блока кода.
Например, в следующих двух методах ключевое слово return используется для возврата целочисленных значений.
class SimpleMath < public int AddTwoNumbers(int number1, int number2) =>number1 + number2; public int SquareANumber(int number) => number * number; >
Чтобы использовать значение, возвращаемое из метода, вызывающий метод может применять сам вызов метода везде, где будет достаточно значения того же типа. Можно также назначить возвращаемое значение переменной. Например, следующие два примера кода достигают одной и той же цели.
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 , для сохранения значения является необязательным. Это может улучшить читаемость кода или может оказаться необходимым, если нужно сохранить исходное значение аргумента для всей области метода.
В некоторых случаях нужно, чтобы метод возвращал больше одного значения. Это можно сделать с помощью типов кортежей и кортежей. Тип кортежа определяет типы данных для элементов кортежа. Литералы кортежей предоставляют фактические значения возвращаемого кортежа. В следующем примере (string, string, string, int) определяет тип кортежа, возвращаемый методом GetPersonalInfo . Выражение (per.FirstName, per.MiddleName, per.LastName, per.Age) представляет собой литерал кортежа; метод возвращает имя, отчество и фамилию, а также возраст объекта PersonInfo .
public (string, string, string, int) GetPersonalInfo(string id)
После этого вызывающий объект может использовать возвращенный кортеж в коде следующего вида:
var person = GetPersonalInfo("111111111"); Console.WriteLine($" : age = ");
Имена могут также назначаться элементам кортежа в определении типа кортежа. В следующих примерах демонстрируется альтернативная версия метода GetPersonalInfo , в котором используются именованные элементы:
public (string FName, string MName, string LName, int Age) GetPersonalInfo(string id)
После этого предыдущий вызов метода GetPersonalInfo можно изменить следующим образом:
var person = GetPersonalInfo("111111111"); Console.WriteLine($" : 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("", value); > > public static void DoubleValues(int[] arr) < for (var ctr = 0; ctr > > // The example displays the following output: // 4 8 12 16
Методы расширения
Как правило, добавлять методы в существующий тип можно двумя способами:
- Изменение исходного кода для этого типа. Конечно, это невозможно сделать, если вы не владеете исходным кодом типа. Если при этом в поддержку метода также добавляются поля закрытых данных, это изменение становится критическим.
- Определение нового метода в производном классе. Метод нельзя добавить таким образом с помощью наследования для других типов, таких как структуры и перечисления. Кроме того, оно не позволяет «добавить» метод в запечатанный класс.
Методы расширения позволяют «добавить» метод в существующий тип, не меняя сам тип и не реализуя новый метод в наследуемом типе. Метод расширения также не должен находиться в той же сборке, что и тип, который он расширяет. Вызовите метод расширения, как будто он является определенным членом типа.
Дополнительные сведения см. в статье Методы расширения.
Асинхронные методы
С помощью функции async можно вызывать асинхронные методы, не прибегая к использованию явных обратных вызовов или ручному разделению кода между несколькими методами или лямбда-выражениями.
Если пометить метод с помощью модификатора async, можно использовать в этом методе инструкцию await. Когда элемент управления достигает await выражения в асинхронном методе, элемент управления возвращается вызывающему объекту, если ожидаемая задача не завершена, и ход выполнения метода с await помощью ключевое слово будет приостановлен до тех пор, пока ожидающая задача не завершится. После завершения задачи выполнение в методе может быть возобновлено.
Асинхронный метод возвращается в вызывающий объект, когда он встречает первый ожидаемый объект, выполнение которого еще не завершено, или когда выполнение асинхронного метода доходит до конца — в зависимости от того, что происходит раньше.
В следующем примере DelayAsync представляет собой асинхронный метод с оператором return, который возвращает целое число. Так как это асинхронный метод, его объявление метода должно иметь возвращаемый тип Task . Поскольку тип возврата — Task , вычисление выражения await в DoSomethingAsync создает целое число, как показывает следующий оператор: int result = await delayTask .
class Program < static Task Main() =>DoSomethingAsync(); static async Task DoSomethingAsync() < TaskdelayTask = DelayAsync(); int result = await delayTask; // The previous two statements may be combined into // the following statement. //int result = await DelayAsync(); Console.WriteLine($"Result: "); > static async Task DelayAsync() < await Task.Delay(100); return 5; >> // Example output: // Result: 5
Асинхронный метод не может объявлять параметры in, ref или out, но может вызывать методы, имеющие такие параметры.
Элементы, воплощающие выражение
Обычно используются определения методов, которые просто возвращаются немедленно с результатом выражения или имеют одну инструкцию в качестве текста метода. Существует сочетание синтаксиса для определения таких методов с помощью => :
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 метод доступа ключевое слово.
Итераторы
Итератор выполняет настраиваемую итерацию по коллекции, например по списку или массиву. Итератор использует инструкцию yield return для возврата всех элементов по одному. По достижении оператора yield return текущее расположение запоминается, чтобы вызывающий объект мог запросить следующий элемент в последовательности.
Дополнительные сведения см. в разделе Итераторы.
См. также
- Модификаторы доступа
- Статические классы и члены статических классов
- Наследование
- Абстрактные и запечатанные классы и члены классов
- params
- out
- ref;
- в папке
- Передача параметров
Совместная работа с нами на GitHub
Источник этого содержимого можно найти на GitHub, где также можно создавать и просматривать проблемы и запросы на вытягивание. Дополнительные сведения см. в нашем руководстве для участников.