Академический Документы
Профессиональный Документы
Культура Документы
Руководство по языку C#
Приступая к работе
Как пользоваться руководством
Введение в язык C# и платформу .NET Framework
Учебники
Обзор
Введение в программирование на C#
Выберите первый урок
Здравствуй, мир
Числа в C#
Ветви и циклы
Коллекции списков
Работа в локальной среде
Настройка среды
Числа в C#
Ветви и циклы
Коллекции списков
Общие сведения о классах
Обзор C# 6
Интерполяция строк. Интерактивное руководство
Интерполяция строк. В вашей среде
Расширенные сценарии для интерполяции строки
Безопасное обновление интерфейсов с помощью методов интерфейса по
умолчанию
Создание функциональных возможностей смешения с помощью методов
интерфейса по умолчанию
Индексы и диапазоны
Использование ссылочных типов, допускающих значение NULL
Перевод приложения на ссылочные типы, допускающие значение NULL
Создание и использование асинхронных потоков
Расширение возможностей данных с помощью сопоставления шаблонов
Консольное приложение
Клиент REST
Наследование в C# и .NET
Работа с LINQ
Использование атрибутов
Обзор языка C#
Вступление
Структура программы
Типы и переменные
Выражения
Операторы
Классы и объекты
Структуры
Массивы
Интерфейсы
Перечисления
Делегаты
Атрибуты
Новые возможности C#
C# 8.0
C# 7.3
C# 7.2
C# 7.1
C# 7.0
C# 6
Журнал версий C#
Связи между языком и платформой
Соображения относительно версии и обновления
Основные понятия C#
Система типов C#
Ссылочные типы, допускающие значение null
Описание API, допускающих значение null, с помощью атрибутов и ограничений
Пространства имен
Базовые типы
Классы
Структуры
Кортежи
Деконструкция кортежей и других типов
Интерфейсы
Методы
Лямбда-выражения
Свойства
Индексаторы
Пустые переменные
Универсальные шаблоны
Iterators
Делегаты и события
Общие сведения о делегатах
System.Delegate и ключевое слово delegate
Строго типизированные делегаты
Общие шаблоны делегатов
Общие сведения о событиях
Стандартные шаблоны событий .NET
Обновленный шаблон событий .NET
Различия между делегатами и событиями
Синтаксис LINQ
Обзор LINQ
Основы выражения запроса
LINQ в C#
Создание запросов LINQ на языке C#
Запрос коллекции объектов
Возврат запроса из метода
Сохранение результатов запроса в памяти
Группировка результатов запросов
Создание вложенной группы
Вложенный запрос в операции группирования
Группирование результатов по смежным ключам
Динамическое определение фильтров предикатов во время выполнения
Выполнение внутренних соединений
Выполнение групповых соединений
Выполнение левых внешних соединений
Упорядочение результатов предложения соединения
Соединение с помощью составных ключей
Выполнение пользовательских операций соединения
Обработка значений NULL в выражениях запросов
Обработка исключений в выражениях запросов
Асинхронное программирование
Сопоставление шаблонов
Написание безопасного и эффективного кода
Деревья выражений
Введение в деревья выражений
Описание деревьев выражений
Типы платформ, поддерживающие деревья выражений
Выполнение выражений
Интерпретация выражений
Построение выражений
Преобразование выражений
Сводка
Взаимодействие на уровне машинного кода
Документирование кода
Управление версиями
Практические руководства по языку C#
Указатель раздела
Синтаксический анализ строк с помощью `String.Split`
Объединение строк
Преобразование строки в значение типа DateTime
Поиск по строкам
Изменение содержимого строк
Сравнение строк
Безопасное приведение с помощью сопоставления шаблонов, операторы is и as
Пакет SDK для платформы компилятора .NET (API-интерфейсы Roslyn)
Обзор пакета SDK для .NET Compiler Platform (API Roslyn)
Сведения о модели SDK для .NET Compiler Platform
Работа с синтаксисом
Работа с семантикой
Использование рабочей области
Изучение кода с помощью визуализатора синтаксиса
Краткие руководства
Анализ синтаксиса
Семантический анализ
Синтаксическое преобразование
Учебники
Создание средства для анализа и исправления кода
Руководство по программированию на C#
Обзор
Структура программы C#
Структура программы C#
Hello World — Создаем первую программу
Общая структура программы C#
Имена идентификаторов
Соглашения о написании кода на C#
Main() и аргументы командной строки
Обзор
Аргументы командной строки
Практическое руководство. Отображение аргументов командной строки
Значения, возвращаемые методом Main()
Основные понятия программирования
Обзор
Асинхронное программирование с использованием ключевых слов async и
await
Обзор
Асинхронная модель программирования
Пошаговое руководство. Получение доступа к Интернету с помощью
модификатора async и оператора await
Практическое руководство. Оптимизация производительности асинхронных
процедур с использованием метода Task.WhenAll
Практическое руководство. Параллельное выполнение нескольких веб-
запросов с использованием async и await
Асинхронные типы возвращаемых значений
Поток управления в асинхронных программах
Отмена задач и обработка завершенных задач
Обзор
Отмена асинхронной задачи или списка задач
Отмена асинхронных задач после определенного периода времени
Отмена оставшихся асинхронных задач после завершения одной из них
Запуск нескольких асинхронных задач и их обработка по мере завершения
Обработка повторного входа в асинхронных приложениях
Использование метода Async для доступа к файлам
Атрибуты
Обзор
Создание настраиваемых атрибутов
AttributeUsage
Обращение к атрибутам с помощью отражения
Практическое руководство. Создание объединения C/C++ с помощью
атрибутов
Общие атрибуты
Сведения о вызывающем
Коллекции
Ковариация и контрвариация
Обзор
Вариативность в универсальных интерфейсах
Создание вариативных универсальных интерфейсов
Использование расхождения в интерфейсах для универсальных коллекций
Вариативность в делегатах
Использование вариативности в делегатах
Использование вариативности в универсальных методах-делегатах Func и
Action
Деревья выражений
Обзор
Практическое руководство. Выполнение деревьев выражений
Практическое руководство. Изменение деревьев выражений
Практическое руководство. Использование деревьев выражений для
построения динамических запросов
Отладка деревьев выражений в Visual Studio
Синтаксис DebugView
Iterators
LINQ
Обзор
Приступая к работе с LINQ в C#
Введение в запросы LINQ
LINQ и универсальные типы
Основные операции запроса LINQ
Преобразование данных с помощью LINQ
Связи типов в операциях запросов LINQ
Синтаксис запросов и синтаксис методов в LINQ
Возможности C#, поддерживающие LINQ
Пошаговое руководство. Написание запросов на C# (LINQ)
Общие сведения о стандартных операторах запроса
Обзор
Синтаксис выражений запроса для стандартных операторов запроса
Классификация стандартных операторов запросов по способу выполнения
Сортировка данных
Операции над множествами
Фильтрация данных
Операции, использующие квантификаторы
Операции проецирования
Секционирование данных
Операции соединения
Группировка данных
Операции создания
Операции сравнения
Операции с элементами
Преобразование типов данных
Операции объединения
Операции агрегирования
LINQ to Objects
Обзор
LINQ и строки
Статьи с пошаговыми инструкциями
Практическое руководство. Подсчет вхождений слова в строке (LINQ)
Практическое руководство. Запрос к предложениям, содержащим
указанный набор слов (LINQ)
Практическое руководство. Запрос символов в строке (LINQ)
Практическое руководство. Объединение запросов LINQ с помощью
регулярных выражений
Практическое руководство. Нахождение разности множеств между двумя
списками (LINQ)
Практическое руководство. Сортировка или фильтрация текстовых данных
по любому слову или полю (LINQ)
Практическое руководство. Изменение порядка полей файла с
разделителями (LINQ)
Практическое руководство. Объединение и сравнение коллекций строк
(LINQ)
Практическое руководство. Заполнение коллекций объектов из
нескольких источников (LINQ)
Практическое руководство. Разделение файла на несколько файлов с
помощью групп (LINQ)
Практическое руководство. Объединение содержимого из файлов разных
форматов (LINQ)
Практическое руководство. Вычисление значений столбцов в текстовом
CSV-файле (LINQ)
LINQ и отражение
Практическое руководство. Запрос к метаданным сборки при помощи
отражения (LINQ)
LINQ и каталоги файлов
Обзор
Практическое руководство. Запрос файлов с указанным атрибутом или
именем
Практическое руководство. Группировка файлов по расширению (LINQ)
Практическое руководство. Запрос общего числа байтов в наборе папок
(LINQ)
Практическое руководство. Сравнение содержимого двух папок (LINQ)
Практическое руководство. Запрос самого большого файла или файлов в
дереве папок (LINQ)
Практическое руководство. Запрос повторяющихся файлов в дереве
папок (LINQ)
Практическое руководство. Запрос содержимого файлов в папке (LINQ)
Практическое руководство. Выполнение запроса к ArrayList с помощью
LINQ
Практическое руководство. Добавление настраиваемых методов для
запросов LINQ
LINQ to XML
Начало работы (LINQ to XML)
Общие сведения о LINQ to XML
LINQ to XML или DOM
LINQ to XML или Другие XML-технологии
Руководство по программированию (LINQ to XML)
Общие сведения о программировании в LINQ to XML
Деревья XML
Работа с пространствами имен XML
Сериализация деревьев XML
Оси LINQ to XML
Выполнение запросов деревьям XML
Изменение деревьев XML (LINQ to XML)
Производительность (LINQ to XML)
Сложные методы программирования в LINQ to XML
Безопасность LINQ to XML
Примеры XML-документов (LINQ to XML)
Ссылка (LINQ to XML)
LINQ to ADO.NET (Страница портала)
Включение источника данных для запросов LINQ
Среда разработки Visual Studio и поддержка средств для LINQ
Объектно-ориентированное программирование
Отражение
Сериализация (C#)
Обзор
Практическое руководство. Запись данных объекта в XML-файл
Практическое руководство. Чтение данных объекта из XML-файла
Пошаговое руководство. Сохранение объекта в Visual Studio
Инструкции, выражения и операторы
Обзор
Операторы
Выражения
Элементы, воплощающие выражение
Анонимные функции
Обзор
Лямбда-выражения
Практическое руководство. Использование лямбда-выражений в запросах
Равенства и сравнения на равенство
Сравнения на равенство
Практическое руководство. Определение равенства значений для типа
Практическое руководство. Проверка на ссылочное равенство
(идентичность)
Типы
Использование и определение типов
Приведение и преобразование типов
Упаковка–преобразование и распаковка–преобразование
Практическое руководство. Преобразование массива байтов в значение типа
int
Практическое руководство. Преобразование строки в число
Практическое руководство. Преобразование из шестнадцатеричных строк в
числовые типы
Использование типа dynamic
Пошаговое руководство. Создание и использование динамических объектов
(C# и Visual Basic)
Классы и структуры
Обзор
Классы
Объекты
Структуры
Обзор
Использование структур
Наследование
Полиморфизм
Обзор
Управление версиями с помощью ключевых слов Override и New
Использование ключевых слов Override и New
Практическое руководство. Переопределение метода ToString
Участники
Обзор участников
Абстрактные и запечатанные классы и члены классов
Статические классы и члены статических классов
Модификаторы доступа
Поля
Константы
Практическое руководство. Определение абстрактных свойств
Практическое руководство. Определение констант в C#
Свойства
Общие сведения о свойствах
Использование свойств
Свойства интерфейса
Ограничение доступности методов доступа
Практическое руководство. Объявление и использование свойств чтения и
записи
Автоматически реализуемые свойства
Практическое руководство. Реализации облегченного класса с автоматически
реализуемыми свойствами
Методы
Обзор методов
Локальные функции
Возвращаемые ссылочные значения и ссылочные локальные переменные
Параметры
Передача параметров
Передача параметров типа значения
Передача параметров ссылочного типа
Практическое руководство. Определение различия между передачей
структуры и ссылки класса в метод
Неявно типизированные локальные переменные
Практическое руководство. Использование явно введенных локальных
переменных и массивов в выражении запроса
Методы расширения
Практическое руководство. Реализация и вызов пользовательского метода
расширения
Практическое руководство. Создание нового метода для перечисления
Именованные и необязательные аргументы
Практическое руководство. Использование именованных и необязательных
аргументов в программировании приложений Office
Конструкторы
Обзор конструкторов
Использование конструкторов
Конструкторы экземпляров
Закрытые конструкторы
Статические конструкторы
Практическое руководство. Создание конструктора копий
Методы завершения
Инициализаторы объектов и коллекций
Практическое руководство. Инициализация объектов с помощью
инициализатора объектов
Практическое руководство. Инициализация словаря с помощью
инициализатора коллекций
Вложенные типы
Разделяемые классы и методы
Анонимные типы
Практическое руководство. Возвращение подмножества свойств элементов в
запросе
Интерфейсы
Обзор
Явная реализация интерфейса
Как выполнить явную реализацию членов интерфейса
Как выполнить явную реализацию членов двух интерфейсов
Типы перечислений
Делегаты
Обзор
Использование делегатов
Делегаты с именованными методами и Анонимные методы
Практическое руководство. Руководство по программированию на C#.
Объединение делегатов (многоадресные делегаты)
Практическое руководство. Объявление, создание и использование делегата
Массивы
Обзор
Массивы как объекты
Одномерные массивы
Многомерные массивы
Массивы массивов
Использование оператора foreach с массивами
Передача массивов в качестве аргументов
Неявно типизированные массивы
Строки
Программирование со строками
Практическое руководство. Как определить, представляет ли строка числовое
значение
Индексаторы
Обзор
Использование индексаторов
Индексаторы в интерфейсах
Сравнение свойств и индексаторов
События
Обзор
Практическое руководство. Подписка и отмена подписки на события
Как публиковать события в соответствии с рекомендациями по .NET Framework
Как создавать события базового класса в производных классах
Как реализовать события интерфейса
Практическое руководство. реализовать пользовательские методы доступа к
событиям
Универсальные шаблоны
Обзор
Параметры универсального типа
Ограничения параметров типа
Универсальные классы
Универсальные интерфейсы
Универсальные методы
Универсальные методы и массивы
Универсальные делегаты
Различия между шаблонами языка C++ и универсальными шаблонами языка
C#
Универсальные типы во время выполнения
Универсальные типы и отражение
Универсальные шаблоны и атрибуты
Пространства имен
Обзор
Использование пространств имен
Практическое руководство. Использование пространства имен My
Небезопасный код и указатели
Обзор и ограничения
Буферы фиксированного размера
типы указателей
Обзор
Преобразования указателей
Практическое руководство. Использование указателей для копирования
массива байтов
Комментарии XML-документации
Обзор
Рекомендуемые теги для комментариев документации
Обработка XML-файла
Разделители для тегов документации
Практическое руководство. Использование возможностей XML-документации
Справочник по тегам документации
<c>
<code>
Атрибут cref
<example>
<exception>
<include>
<list>
<para>
<param>
<paramref>
<permission>
<remarks>
<returns>
<see>
<seealso>
<summary>
<typeparam>
<typeparamref>
<value>
Исключения и обработка исключений
Обзор
Использование исключений
Обработка исключений
Создание и порождение исключений
Исключения, создаваемые компилятором
Практическое руководство. Обработка исключений с помощью блока try-catch
Практическое руководство. Выполнение кода очистки с использованием блока
finally
Практическое руководство. Перехват несовместимого с CLS исключения
Файловая система и реестр
Обзор
Практическое руководство. Обход дерева каталогов
Практическое руководство. Получение сведений о файлах, папках и дисках
Практическое руководство. Создание файла или папки
Практическое руководство. Копирование, удаление и перемещение файлов и
папок
Практическое руководство. Отображение диалогового окна хода выполнения
для операций с файлами
Практическое руководство. Запись в текстовый файл
Практическое руководство. Чтение из текстового файла
Практическое руководство. Построчное чтение текстового файла (Visual C#)
Практическое руководство. Создание раздела в реестре (Visual C#)
Взаимодействие
Взаимодействие с платформой .NET
Общие сведения о взаимодействии
Практическое руководство. Доступ к объектам взаимодействия Office с
помощью возможностей Visual C#
Практическое руководство. Использование индексированных свойств в
программировании COM-взаимодействия
Практическое руководство. Использование вызова неуправляемого кода для
воспроизведения звукового файла
Пошаговое руководство. Программирование для Office (C# и Visual Basic)
Пример COM-класса
Справочник по языку
Обзор
Настройка версии языка
Ключевые слова в C#
Обзор
Встроенные типы
Типы значений
Характеристики типов значений
Целочисленные типы
Числовые типы с плавающей запятой
Встроенные числовые преобразования
bool
char
enum
struct
Типы значений, допускающие значение NULL
Ссылочные типы
Характеристики ссылочных типов
Встроенные ссылочные типы
class
interface
void
var
Неуправляемые типы
Справочные таблицы по типам
Таблица встроенных типов
Таблица типов значений
Таблица значений по умолчанию
Таблица форматирования числовых результатов
Модификаторы
Модификаторы доступа
Краткий справочник
Уровни доступности
Домен доступности
Ограничения на использование уровней доступности
internal
private
protected
public
protected internal
private protected
abstract
async
const
event
extern
in (универсальный модификатор)
new (модификатор члена)
out (универсальный модификатор)
override
readonly
sealed
static
unsafe
virtual
volatile
Ключевые слова операторов
Категории операторов
Инструкции выбора
if-else
switch
Операторы итерации
do
for
foreach, in
while
Операторы перехода
break
continue
goto
return
Операторы обработки исключений
throw
try-catch
try-finally
try-catch-finally
Checked и Unchecked
Обзор
checked
unchecked
Оператор fixed
Оператор lock
Параметры методов
Передача параметров
params
in (модификатор параметров)
ref
out (модификатор параметров)
Ключевые слова, используемые для пространств имен
namespace
using
Контексты для using
Директива using
Директива using static
Оператор using
Псевдоним extern
Ключевые слова тестирования типов
is
Ключевые слова ограничений универсального типа
Ограничение new
где
Ключевые слова доступа
base
this
Ключевые слова литералов
null
true
false
default
Контекстные ключевые слова
Краткий справочник
add
get
partial (тип)
partial (метод)
remove
set
when (условие фильтра)
value
yield
Ключевые слова запроса
Краткий справочник
Предложение from
Предложение where
Предложение select
Предложение group
into
Предложение orderby
Предложение join
Предложение let
ascending
descending
on
equals
by
in
Операторы в C#
Обзор
Арифметические операторы
Логические операторы
Операторы битовых операций и сдвига
Операторы равенства
Операторы сравнения
Операторы доступа к членам
Операторы приведения и тестирования типов
Операторы пользовательского преобразования
Операторы, связанные с указателем
Операторы присваивания
+ Операторы +=
- Операторы -=
Оператор ?:
! Оператор (null-forgiving)
?? и ??= — операторы
Оператор =>
:: - оператор
оператор await
оператор по умолчанию
Оператор delegate
Оператор nameof
Оператор new
Оператор sizeof
Оператор stackalloc
Операторы true и false
Перегрузка операторов
Специальные символы C#
Обзор
$ — интерполяция строк
@ — буквальный идентификатор
Директивы препроцессора C#
Обзор
#if
#else
##elif
#endif
#define
#undef
#warning
#error
#line
#region
#endregion
#pragma
#pragma warning
#pragma checksum
Параметры компилятора C#
Обзор
Построение из командной строки с помощью csc.exe
Практическое руководство. Настройка переменных среды для командной
строки Visual Studio
Параметры компилятора C#, упорядоченные по категориям
Параметры компилятора C# в алфавитном порядке
@
-addmodule
-appconfig
-baseaddress
-bugreport
-checked
-codepage
-debug
-define
-delaysign
-deterministic
-doc
-errorreport
-filealign
-fullpaths
-help, -?
-highentropyva
-keycontainer
-keyfile
-langversion
-lib
-link
-linkresource
-main
-moduleassemblyname
-noconfig
-nologo
-nostdlib
-nowarn
-nowin32manifest
-optimize
-out
-pathmap
-pdb
-platform
-preferreduilang
-publicsign
-recurse
-reference
-refout
-refonly
-resource
-subsystemversion
-target
-target:appcontainerexe
-target:exe
-target:library
-target:module
-target:winexe
-target:winmdobj
-unsafe
-utf8output
-warn
-warnaserror
-win32icon
-win32manifest
-win32res
Ошибки компилятора C#
Предварительная спецификация C# 6.0
Предложения для C# 7.0–8.0
Пошаговые руководства
Начало работы с C#
31.10.2019 • 3 minutes to read • Edit Online
В этом разделе собраны короткие и простые руководства, которые позволяют быстро создать приложение с
помощью C# и .NET Core. Это статьи по началу работы с Visual Studio 2017 и Visual Studio Code. Для
изучения этих статей требуется некоторый опыт программирования. Если вы только начинаете заниматься
программированием, рекомендуем поработать с нашими интерактивными вводными руководствами по C# .
Рассматриваются следующие темы:
Введение в язык C# и платформу .NET Framework
Содержит общие сведения о языке C# и .NET.
Создание приложения "Hello World" на C# с помощью .NET Core в Visual Studio 2017
Visual Studio позволяет писать, компилировать, запускать, отлаживать, профилировать и публиковать
приложения с помощью интегрированной среды разработки для Windows и Mac.
Эта статья предлагает вам создать и выполнить простое приложение Hello World, а затем
преобразовать его в более интерактивный вариант. Когда вы завершите создание приложения и
выполните его, вы сможете на его примере научиться отлаживать и публиковать приложения, чтобы
выполнять их на любой платформе, поддерживаемой .NET Core.
Создание библиотеки классов с помощью C# и .NET Core в Visual Studio 2017
Библиотека классов позволяет определять типы и члены типов, которые можно вызвать из любого
приложения. В этой статье вы создадите библиотеку классов с единственным методом, который
определяет, начинается ли строка с символа верхнего регистра. Когда вы закончите создавать
библиотеку, можно разработать модульный тест и убедиться, что все работает как надо, а затем
библиотеку можно сделать доступной для приложений, в которых ее нужно использовать.
Начало работы с C# и Visual Studio Code
Visual Studio Code — это бесплатный редактор кода, оптимизированный для сборки и отладки
современных веб-приложений и облачных приложений. Он поддерживает технологию IntelliSense и
доступен для Linux, macOS и Windows.
В этой статье показано, как создать и выполнить простое приложения Hello World с помощью Visual
Studio Code и .NET Core.
Связанные разделы
Руководство по программированию на C#
Содержит сведения о понятиях программирования C# и описание выполнения различных задач на C#.
Справочник по C#
Содержит подробные справочные сведения о ключевых словах, операторах, директивах
препроцессора, параметрах компилятора и ошибках и предупреждениях компилятора в среде C#.
Пошаговые руководства
Приведены ссылки на пошаговые руководства по написанию программ, использующих C#, и дано
краткое описание каждого пошагового руководства.
См. также
Разработка на C# в Visual Studio
Введение в язык C# и .NET Framework
25.11.2019 • 9 minutes to read • Edit Online
NOTE
В документации по Visual C# предполагается, что у вас есть понимание основных концепций программирования. Если
вы еще совсем новичок, рекомендуем сначала изучить выпуск Visual C# Express, доступный в Интернете. Также для
приобретения практических навыков программирования будут полезны книги и веб-ресурсы о C#.
C# - язык
Синтаксис C# очень богат, но при этом прост и удобен в изучении. Характерные фигурные скобки C#
мгновенно узнаются всеми, кто знаком с C, C++ или Java. Разработчики, знающие любой из этих языков,
обычно очень быстро начинают эффективно работать в C#. Синтаксис C# упрощает многие сложности C++,
но при этом предоставляет отсутствующие в Java мощные функции, например обнуляемые типы значений,
перечисления, делегаты, лямбда-выражения и прямой доступ к памяти. C# поддерживает универсальные
методы и типы, которые обеспечивают более высокий уровень безопасности и производительности, а также
итераторы, позволяющие определять в классах коллекций собственное поведение итерации, которое может
легко применить в клиентском коде. Выражения LINQ создают очень удобную языковую конструкцию для
строго типизированных запросов.
C# является объектно-ориентированным языком, а значит поддерживает инкапсуляцию, наследование и
полиморфизм. Все переменные и методы, включая метод Main , представляющий собой точку входа в
приложение, инкапсулируются в определения классов. Класс наследуется непосредственно из одного
родительского класса, но может реализовывать любое число интерфейсов. Методы, которые
переопределяют виртуальные методы родительского класса, должны содержать ключевое слово override ,
чтобы исключить случайное переопределение. В языке C# структура похожа на облегченный класс: это тип,
распределяемый в стеке, реализующий интерфейсы, но не поддерживающий наследование.
Помимо этих основных принципов объектно-ориентированного программирования, C# предлагает ряд
инновационных языковых конструкций, упрощающих разработку программных компонентов.
Инкапсулированные сигнатуры методов, именуемые делегатами, которые позволяют реализовать
типобезопасные уведомления о событиях.
Свойства, выполняющие функцию акцессоров для закрытых переменных-членов.
Атрибуты, предоставляющие декларативные метаданные о типах во время выполнения.
Внутристрочные комментарии для XML -документации.
LINQ для создания запросов к различным источникам данных.
Для взаимодействия с другим программным обеспечением Windows, например с объектом COM или
собственными библиотеками DLL Win32, вы можете применить процесс C#, известный как
"Взаимодействие". Взаимодействие позволяет программам на C# делать практически все, что возможно в
приложении машинного кода C++. C# поддерживает даже указатели и понятие "небезопасного" кода для
тех случаев, в которых критически важен прямой доступ к памяти.
Процесс построения в C# проще по сравнению с C или C++, но более гибок, чем в Java. Отдельные файлы
заголовка не используются, и нет необходимости объявлять методы и типы в определенном порядке.
Исходный файл C# может определить любое число классов, структур, интерфейсов и событий.
Вот еще несколько ресурсов по языку C#.
Общая вводная информация о языке хорошо представлена в Главе 1 спецификации языка C#.
Подробные сведения о конкретных аспектах языка C# вы найдете в справочнике по C#.
Дополнительные сведения о LINQ см. в этой статье о LINQ.
См. также
Начало работы с Visual C#
Учебники по C#
05.11.2019 • 6 minutes to read • Edit Online
Приветствуем вас в разделе с руководствами по языку C#. Она начинается с интерактивных занятий, которые
можно проходить в браузере. Последующие и расширенные руководства обучают работе со средствами
разработки .NET для создания программ на C# на компьютере.
Hello world
В руководстве Hello World вы создадите самую простую программу на C#. Вы ознакомитесь с типом string
и способами работы с текстом.
Числа в C#
Из руководства Числа в C# вы узнаете, как на компьютере хранятся числа и как выполнять вычисления с
разными числовыми типами. Вы ознакомитесь с основами округления и научитесь выполнять
математические вычисления с помощью C#. Это руководство можно изучить, используя локальный
компьютер.
В этом руководстве предполагается, что вы уже прошли занятие Hello World.
Ветви и циклы
В руководстве Ветви и циклы представлены общие принципы организации ветвления кода в зависимости от
значений, хранящихся в переменных. Вы узнаете, что такое поток управления, являющийся основой
принятия решений и выбора различных действий в программах. Это руководство можно изучить, используя
локальный компьютер.
В этом руководстве предполагается, что вы уже прошли занятия Hello World и Числа в C#.
Коллекция списков
Занятие Коллекция списков содержит обзор типа "Коллекция списков", в котором хранятся
последовательности данных. Вы узнаете, как добавлять и удалять элементы, выполнять их поиск и
сортировать списки. Вы ознакомитесь с различными типами списков. Это руководство можно изучить,
используя локальный компьютер.
В этом руководстве предполагается, что вы уже прошли перечисленные выше занятия.
Знакомство с C#: работа в локальной среде
Все ознакомительные руководства, в которых используется пример приложения "Hello World", можно
проходить в локальной среде разработки. В конце каждого руководства вам предлагается на выбор
возможность пройти следующее занятие в браузере или на локальном компьютере. Чтобы настроить среду
и продолжить изучение следующего руководства на компьютере, можно воспользоваться
соответствующими ссылками.
Общие руководства
Следующие руководства позволяют создавать программы на C# с использованием .NET Core.
Консольное приложение. Демонстрирует консольный ввод-вывод, структуру консольного приложения и
модель асинхронного программирования на основе задач.
Клиент REST. Демонстрирует веб-взаимодействие, сериализацию JSON и объектно ориентированные
функции языка C#.
Inheritance in C# and .NET (Наследование в C# и .NET). Демонстрирует наследование в C#, в том числе
использование наследования для определения базовых классов, абстрактных базовых классов и
производных классов.
Working with LINQ (Работа с LINQ ). Демонстрирует множество функций LINQ и элементы языка,
которые их поддерживают.
Использование атрибутов. Описывает создание и использование атрибутов в C#.
В руководстве Интерполяция строк демонстрируется, как вставлять значения в строки. Вы узнаете, как
создать интерполированную строку с внедренными выражениями C# и как управлять текстовым
представлением результатов выражений в итоговой строке. Это руководство можно изучить, используя
локальный компьютер.
Знакомство с C#
05.11.2019 • 4 minutes to read • Edit Online
Приветствуем вас в ознакомительном разделе руководств по C#. Она начинается с интерактивных занятий,
которые можно проходить в браузере. Прежде чем приступить к интерактивным урокам, вы можете
ознакомиться с основами C#, просмотрев серию видеороликов "C# для начинающих".
В первых занятиях с помощью небольших фрагментов кода объясняются основные понятия языка C#. Вы
изучите основы синтаксиса C# и научитесь работать с такими типами данных, как строки, числа и
логические значения. Вся серия интерактивна, и уже через считанные минуты вы будете писать и запускать
собственный код. Для первых занятий не требуются какие-либо знания в области программировании или
опыт работы с языком C#.
Все ознакомительные руководства, в которых используется пример приложения "Hello World", можно
проходить в веб-браузере или в локальной среде разработки. В конце каждого руководства вам
предлагается на выбор возможность пройти следующее занятие в браузере или на локальном компьютере.
Чтобы настроить среду и продолжить изучение следующего руководства на компьютере, можно
воспользоваться соответствующими ссылками.
Hello world
В руководстве Hello World вы создадите самую простую программу на C#. Вы ознакомитесь с типом string
и способами работы с текстом.
Числа в C#
Из руководства Числа в C# вы узнаете, как на компьютере хранятся числа и как выполнять вычисления с
разными числовыми типами. Вы ознакомитесь с основами округления и научитесь выполнять
математические вычисления с помощью C#. Это руководство можно изучить, используя локальный
компьютер.
В этом руководстве предполагается, что вы уже прошли занятие Hello World.
Ветви и циклы
В руководстве Ветви и циклы представлены общие принципы организации ветвления кода в зависимости от
значений, хранящихся в переменных. Вы узнаете, что такое поток управления, являющийся основой
принятия решений и выбора различных действий в программах. Это руководство можно изучить, используя
локальный компьютер.
В этом руководстве предполагается, что вы уже прошли занятия Hello World и Числа в C#.
Коллекция списков
Занятие Коллекция списков содержит обзор типа "Коллекция списков", в котором хранятся
последовательности данных. Вы узнаете, как добавлять и удалять элементы, выполнять их поиск и
сортировать списки. Вы ознакомитесь с различными типами списков. Это руководство можно изучить,
используя локальный компьютер.
В этом руководстве предполагается, что вы уже прошли перечисленные выше занятия.
Общие сведения о классах
Это заключительное руководство можно изучить, используя только локальную среду разработки с .NET
Core. Вы создадите консольное приложение и изучите основные объектно-ориентированные функции
языка C#.
В этом руководстве предполагается, что вы уже изучили ознакомительные онлайн-руководства и
установили пакет SDK для .NET Core и Visual Studio Code.
Знакомство со средствами разработки для .NET
23.10.2019 • 3 minutes to read • Edit Online
Для выполнения этого руководства прежде всего нужно настроить на компьютере среду разработки. В
руководстве .NET по созданию программы Hello World за 10 минут содержатся инструкции по настройке
локальной среды разработки в macOS, Windows или Linux.
Кроме того, вы можете установить пакет SDK для .NET Core и Visual Studio Code.
Выполните команду dotnet build , чтобы создать исполняемый файл. Затем запустите исполняемый файла
с помощью команды dotnet run .
Выбор руководства
Вы можете начать изучение с любого из следующих руководств:
Числа в C#
Из руководства Числа в C# вы узнаете, как на компьютере хранятся числа и как выполнять вычисления с
разными числовыми типами. Вы ознакомитесь с основами округления и научитесь выполнять
математические вычисления с помощью C#.
В этом руководстве предполагается, что вы уже прошли занятие Hello World.
Ветви и циклы
В руководстве Ветви и циклы представлены общие принципы организации ветвления кода в зависимости
от значений, хранящихся в переменных. Вы узнаете, что такое поток управления, являющийся основой
принятия решений и выбора различных действий в программах.
В этом руководстве предполагается, что вы уже прошли занятия Hello World и Числа в C#.
Коллекция списков
Занятие Коллекция списков содержит обзор типа "Коллекция списков", в котором хранятся
последовательности данных. Вы узнаете, как добавлять и удалять элементы, выполнять их поиск и
сортировать списки. Вы ознакомитесь с различными типами списков.
В этом руководстве предполагается, что вы уже прошли перечисленные выше занятия.
Это руководство поможет в интерактивном изучении числовых типов в C#. Вы напишете небольшие
фрагменты кода, затем скомпилируете и выполните этот код. Руководство содержит ряд уроков, в которых
рассматриваются числа и математические операции в C#. В рамках этих занятий вы ознакомитесь с
основами языка C#.
Для работы с этим руководством вам потребуется компьютер, который можно использовать для
разработки. В руководстве .NET по созданию программы Hello World за 10 минут содержатся инструкции
по настройке локальной среды разработки в macOS, Windows или Linux. Краткий обзор команд, которые
будут здесь использоваться, и ссылки на дополнительные сведения представлены в статье, посвященной
средствам разработки для .NET.
int a = 18;
int b = 6;
int c = a + b;
Console.WriteLine(c);
Чтобы выполнить этот код, введите dotnet run в окно командной строки.
Вы увидели одну из основных математических операций с целыми числами. Тип int представляет целое
положительное или отрицательное число или ноль. Для сложения используйте символ + . Другие
стандартные математические операции с целыми числами включают:
- — вычитание;
* — умножение;
/ — деление.
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
Чтобы выполнить этот код, введите dotnet run в окно командной строки.
Можно также поэкспериментировать, выполнив несколько математических операций в одной строке.
Например, выполните c = a + b - 12 * 17; . Допускается сочетание переменных и постоянных чисел.
TIP
Вероятнее всего, при изучении C# (как и любого другого языка программирования) вы будете допускать ошибки в
коде. Компилятор найдет эти ошибки и сообщит вам о них. Если результат содержит сообщения об ошибках,
внимательно просмотрите пример кода и код в окне, чтобы понять, что нужно исправить. Это упражнение
поможет вам изучить структуру кода C#.
Вы завершили первый этап. Прежде чем перейти к следующему разделу, переместим текущий код в
отдельный метод. Это упростит начало работы с новым примером. Переименуйте метод Main в
WorkingWithIntegers и запишите новый метод Main , который вызывает WorkingWithIntegers . В результате
ваш код должен выглядеть примерно следующим образом:
using System;
namespace NumbersInCSharp
{
class Program
{
static void WorkingWithIntegers()
{
int a = 18;
int b = 6;
// addition
int c = a + b;
Console.WriteLine(c);
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
}
//WorkingWithIntegers();
// запускает комментарий в C#. Комментарии — это любой текст, который должен быть сохранен в
исходном коде, но не должен выполняться как код. Компилятор не создает исполняемый код из
комментариев.
Язык C# определяет приоритет математических операций в соответствии с правилами математики.
Умножение и деление имеют приоритет над сложением и вычитанием. Чтобы удостовериться в этом,
добавьте следующий код в метод Main и выполните команду dotnet run :
int a = 5;
int b = 4;
int c = 2;
int d = a + b * c;
Console.WriteLine(d);
d = (a + b) * c;
Console.WriteLine(d);
Поэкспериментируйте, объединяя различные операции. Добавьте в конец метода Main строки, подобные
приведенным ниже. Выполните dotnet run еще раз.
d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);
Возможно, вы заметили интересное поведение целых чисел. Деление целых чисел всегда дает результат в
виде целого числа, даже если ожидаемый результат содержит десятичную или дробную часть.
Если вы еще не видели пример такого поведения, добавьте следующий код в конец метода Main :
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
namespace NumbersInCSharp
{
class Program
{
static void WorkingWithIntegers()
{
int a = 18;
int b = 6;
// addition
int c = a + b;
Console.WriteLine(c);
// subtraction
c = a - b;
Console.WriteLine(c);
// multiplication
c = a * b;
Console.WriteLine(c);
// division
c = a / b;
Console.WriteLine(c);
}
d = (a + b) * c;
Console.WriteLine(d);
d = (a + b) - 6 * c + (12 * 4) / 3 + 12;
Console.WriteLine(d);
int e = 7;
int f = 4;
int g = 3;
int h = (e + f) / g;
Console.WriteLine(h);
}
OrderPrecedence();
}
}
}
Тип целых чисел C# характеризуется еще одним отличием от математических целых: тип int имеет
минимальные и максимальные ограничения. Добавьте следующий код в метод Main , чтобы просмотреть
эти ограничения:
Если при вычислении выводится значение вне этих пределов, возникает условие потери значимости или
переполнения. Ответ должен находиться в диапазоне от минимального до максимального значения.
Добавьте следующие две строки в метод Main , чтобы увидеть пример:
Обратите внимание, что ответ очень близок к минимальному целому числу (отрицательное значение). Он
совпадает со значением min + 2 . Оператор сложения вызвал переполнение допустимых значений для
целых чисел. Ответ является очень большим отрицательным числом, так как переполнение покрывает
диапазон от наибольшего целого числа до наименьшего.
Существуют другие числовые типы с различными ограничениями и точностью, которые можно
использовать, если тип int не соответствует вашим требованиям. Рассмотрим их.
Давайте еще раз переместим код, написанный в этом разделе, в отдельный метод. Присвойте обработчику
события имя TestLimits .
double a = 5;
double b = 4;
double c = 2;
double d = (a + b) / c;
Console.WriteLine(d);
Обратите внимание, что ответ включает десятичную долю частного. Попробуйте более сложное
выражение с типом double:
double e = 19;
double f = 23;
double g = 8;
double h = (e + f) / g;
Console.WriteLine(h);
Диапазон значений типа double гораздо больше, чем диапазон значений целых чисел. Добавьте
следующий фрагмент после написанного кода:
Обратите внимание, что диапазон меньше, чем для типа double . Вы можете убедиться в повышении
точности при использовании типа decimal, выполнив следующий код:
double a = 1.0;
double b = 3.0;
Console.WriteLine(a / b);
decimal c = 1.0M;
decimal d = 3.0M;
Console.WriteLine(c / d);
Суффикс M возле чисел указывает, что для константы должен использоваться тип decimal .
Обратите внимание, что при вычислении с использованием типа decimal справа от запятой содержится
больше цифр.
Задача
Теперь, когда вы ознакомились с разными числовыми типами, напишите код, который позволяет
вычислить площадь круга с радиусом 2,50 см. Помните, что площадь круга равна квадрату радиуса,
умноженному на число пи. Подсказка: в .NET есть константа пи Math.PI, которую можно использовать.
Вы должны получить ответ от 19 до 20. Ответ можно просмотреть в готовом примере кода в GitHub.
При желании поэкспериментируйте с другими формулами.
Вы выполнили все задачи краткого руководства по числам в C#. Теперь вы можете выполнить руководство
по ветвям и циклам в своей среде разработки.
Дополнительные сведения о числах в C# см. в следующих статьях:
Целочисленные типы
Числовые типы с плавающей запятой
Встроенные числовые преобразования
Изучение условной логики с операторами ветви
и цикла
25.11.2019 • 14 minutes to read • Edit Online
В этом руководстве объясняется, как написать код, который позволяет проверить переменные и изменить
путь выполнения на основе этих переменных. Вы напишете код C# и сможете просмотреть результаты его
компиляции и выполнения. Это руководство содержит ряд уроков, в которых рассматриваются
конструкции ветвления и циклов в C#. В рамках этих занятий вы ознакомитесь с основами языка C#.
Для работы с этим руководством вам потребуется компьютер, который можно использовать для
разработки. В руководстве .NET по созданию программы Hello World за 10 минут содержатся инструкции
по настройке локальной среды разработки в macOS, Windows или Linux. Краткий обзор команд, которые
будут здесь использоваться, и ссылки на дополнительные сведения представлены в статье, посвященной
средствам разработки для .NET.
int a = 5;
int b = 6;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10.");
Чтобы выполнить этот код, введите dotnet run в окне консоли. В консоли должно появиться сообщение
The answer is greater than 10 (Ответ больше 10).
Измените объявление b , чтобы сумма была меньше 10:
int b = 3;
Введите dotnet run еще раз. Так как ответ меньше 10, никакие данные не выводятся. Проверяемое
условие имеет значение false. У вас еще нет кода для выполнения, так как вы написали только одну из
возможных ветвей для оператора if — ветвь true.
TIP
Вероятнее всего, при изучении C# (как и любого другого языка программирования) вы будете допускать ошибки в
коде. Компилятор найдет ошибки и сообщит о них. Внимательно просмотрите выходные данные ошибки и код,
вызвавший ошибку. Как правило, сведения о причине ошибки можно найти в сообщении об ошибке компилятора.
В первом примере показаны возможности if и логические типы. Логическое значение — это
переменная, которая может иметь одно из двух значений: true или false . Логические переменные в C#
определяются особым типом — bool . Оператор if проверяет значение bool . Если значение true ,
выполняется оператор, следующий после if . В противном случае он пропускается.
Этот процесс проверки условий и выполнения операторов на основе этих условий предоставляет широкие
возможности.
int a = 5;
int b = 3;
if (a + b > 10)
Console.WriteLine("The answer is greater than 10");
else
Console.WriteLine("The answer is not greater than 10");
Оператор после ключевого слова else выполняется, только если проверяемое условие имеет значение
false . Объединив операторы if и else с логическими условиями, вы получите все необходимые
возможности для обработки условий true и false .
IMPORTANT
Отступы под операторами if и else предназначены только для удобства чтения. В языке C# необязательно
ставить отступы или пробелы. Операторы после ключевого слова if или else будут выполняться на основе
условия. Во всех строках в примерах кода, представленных в этом руководстве, отступы традиционно соответствуют
потоку управления операторов.
Так как отступ не обязателен, используйте скобки { и } , если нужно указать несколько операторов в
блоке кода, который выполняется в зависимости от условий. Программисты C# обычно используют эти
фигурные скобки во всех предложениях if и else . Следующий пример аналогичен только что
созданному. Измените код выше, чтобы он соответствовал следующему коду:
int a = 5;
int b = 3;
if (a + b > 10)
{
Console.WriteLine("The answer is greater than 10");
}
else
{
Console.WriteLine("The answer is not greater than 10");
}
TIP
Все примеры кода в следующих разделах руководства содержат фигурные скобки в соответствии с принятой
практикой.
Можно проверить более сложные условия. Добавьте следующий код в метод Main после написанного
кода:
int c = 4;
if ((a + b + c > 10) && (a == b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is equal to the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not equal to the second");
}
Измените значения a , b и c , а также переключитесь между && и || для изучения. Так вы лучше
поймете, как работают операторы && и || .
Вы завершили первый этап. Прежде чем перейти к следующему разделу, переместим текущий код в
отдельный метод. Это упростит начало работы с новым примером. Переименуйте метод Main в ExploreIf
и запишите новый метод Main , который вызывает ExploreIf . В результате ваш код должен выглядеть
примерно следующим образом:
using System;
namespace BranchesAndLoops
{
class Program
{
static void ExploreIf()
{
int a = 5;
int b = 3;
if (a + b > 10)
{
Console.WriteLine("The answer is greater than 10");
}
else
{
Console.WriteLine("The answer is not greater than 10");
}
int c = 4;
if ((a + b + c > 10) && (a > b))
{
Console.WriteLine("The answer is greater than 10");
Console.WriteLine("And the first number is greater than the second");
}
else
{
Console.WriteLine("The answer is not greater than 10");
Console.WriteLine("Or the first number is not greater than the second");
}
Закомментируйте вызов ExploreIf() . Это поможет упорядочить выходные данные в этом разделе.
//ExploreIf();
// запускает комментарий в C#. Комментарии — это любой текст, который должен быть сохранен в
исходном коде, но не должен выполняться как код. Компилятор не создает исполняемый код из
комментариев.
Оператор while проверяет условие и выполняет инструкцию или блок инструкций, следующий после
while . Проверка условия и выполнение этих операторов будут повторяться, пока условие не примет
значение false.
В этом примере представлен еще один новый оператор. Объект ++ после переменной counter
представляет собой оператор инкремента. Он добавляет 1 к значению counter и сохраняет это значение
в переменной counter .
IMPORTANT
Напишите такой код, при выполнении которого значение условия цикла while изменится на false. В противном
случае будет создан бесконечный цикл, в котором выполнение программы никогда не закончится. Это не
показано в примере, так как нужно принудительно закрыть программу, нажав клавиши CTRL+C, или другим
способом.
В цикле while условие проверяется, прежде чем выполнить код, который следует после while . А в цикле
do ... while сначала выполняется код, а потом проверяется условие. Действие во время цикла показано в
следующем примере кода.
int counter = 0;
do
{
Console.WriteLine($"Hello World! The counter is {counter}");
counter++;
} while (counter < 10);
Этот цикл работает так же, как циклы while и do , использованные ранее. Оператор for состоит из трех
частей, которые отвечают за его работу.
Первая часть — для инициализатора: int index = 0; объявляет index переменной цикла и задает для
ее начальное значение 0 .
Средняя часть — для условия: index < 10 объявляет, что этот цикл for продолжает выполняться, пока
значение счетчика меньше 10.
Последняя часть — для итератора: index++ определяет, как изменится переменная цикла после
выполнения блока, следующего после оператора for . В нашем случае определяется, что значение index
должно увеличиваться на 1 каждый раз, когда выполняется блок.
Попробуйте сделать это самостоятельно. Попытайтесь выполнить следующие задания:
Измените инициализатор, чтобы цикл начинался с другого значения.
Измените условие, чтобы цикл заканчивался другим значением.
По окончании попробуйте самостоятельно написать код, чтобы применить полученные знания.
Попробуйте самостоятельно. Затем проверьте результат. Вы должны получить ответ "63". Один из
возможных ответов можно увидеть в полном примере кода в GitHub.
Вы ознакомились с руководством по ветвям и циклам.
Теперь вы можете перейти к ознакомлению с руководством Массивы и коллекции в своей среде
разработки.
Дополнительные сведения об этих понятиях см. в следующих разделах:
if-else (справочник по C#)
while (справочник по C#)
do (справочник по C#)
for (справочник по C#)
Научитесь управлять коллекциями данных с
использованием универсального типа списка
23.10.2019 • 9 minutes to read • Edit Online
using System;
using System.Collections.Generic;
namespace list_tutorial
{
class Program
{
static void Main(string[] args)
{
var names = new List<string> { "<name>", "Ana", "Felipe" };
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
}
}
}
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
В конец списка добавлены еще два имени. При этом одно имя удалено. Сохраните файл и введите
dotnet run для тестирования.
List<T> позволяет добавлять ссылки на отдельные элементы по индексу. Поместите индекс между
токенами [ и ] после имени списка. Для первого индекса в C# используется значение 0. Добавьте
следующий код сразу после добавленного фрагмента и протестируйте его:
Доступ к индексу за пределами списка получить невозможно. Помните, что индексы начинаются с 0,
поэтому максимальный допустимый индекс меньше, чем число элементов в списке. Вы можете проверить,
как долго в списке используется свойство Count. Добавьте следующий код в конец метода Main:
Сохраните файл и еще раз введите dotnet run , чтобы просмотреть результаты.
Кроме того, можно сортировать элементы в списке. Метод Sort сортирует все элементы списка в обычном
порядке (при использовании строк — в алфавитном порядке). Добавьте следующий код в конец метода
Main :
names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
namespace list_tutorial
{
class Program
{
static void Main(string[] args)
{
WorkingWithStrings();
}
Console.WriteLine();
names.Add("Maria");
names.Add("Bill");
names.Remove("Ana");
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
names.Sort();
foreach (var name in names)
{
Console.WriteLine($"Hello {name.ToUpper()}!");
}
}
}
}
Будет создан список целых чисел. Для первых двух целых чисел будет задано значение 1. Это два первых
значения последовательности Фибоначчи. Каждое следующее число Фибоначчи — это сумма двух
предыдущих чисел. Добавьте этот код:
var previous = fibonacciNumbers[fibonacciNumbers.Count - 1];
var previous2 = fibonacciNumbers[fibonacciNumbers.Count - 2];
fibonacciNumbers.Add(previous + previous2);
TIP
Для задач этого раздела вы можете закомментировать код, который вызывает WorkingWithStrings(); . Просто
поместите два символа / перед вызовом. Например: // WorkingWithStrings(); .
Задача
Попробуйте объединить некоторые идеи из этого и предыдущих занятий. Расширьте код с числами
Фибоначчи, который вы создали. Попробуйте написать код для создания первых 20 чисел в
последовательности. Подсказка: 20-е число Фибоначчи — 6765.
Выполнение задачи
Пример решения можно просмотреть в готовом примере кода на GitHub.
При каждой итерации цикла суммируются два последних целых числа в списке. Полученное значение
добавляется в список. Цикл повторяется, пока в список не будут добавлены 20 элементов.
Поздравляем! Вы выполнили задачи в руководстве по спискам. Теперь вы можете выполнить задачи
вводного руководства по классам в своей среде разработки.
Дополнительные сведения о работе с типом List см. в разделе о коллекциях руководства по .NET. Также
в нем описаны многие другие типы коллекций.
Сведения об использовании классов и объектов в
объектно-ориентированном программировании
25.11.2019 • 13 minutes to read • Edit Online
Для работы с этим руководством вам потребуется компьютер, который можно использовать для
разработки. В руководстве .NET по созданию программы Hello World за 10 минут содержатся инструкции
по настройке локальной среды разработки в macOS, Windows или Linux. Краткий обзор команд, которые
будут здесь использоваться, и ссылки на дополнительные сведения представлены в статье, посвященной
средствам разработки для .NET.
Создание приложения
С помощью окна терминала создайте каталог с именем classes. В этом каталоге вы создадите приложение.
Откройте этот каталог и введите в окне консоли dotnet new console . При помощи этой команды создается
приложение. Откройте файл Program.cs. Он должен выглядеть так:
using System;
namespace classes
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
При работе с этим руководством вы создадите новые типы, представляющие банковский счет. Обычно
разработчики определяют каждый класс в отдельном текстовом файле. Благодаря этому программой легче
управлять, когда ее размер увеличивается. Создайте новый файл с именем BankAccount.cs в каталоге classes.
Этот файл будет содержать определение банковского счета. Средства объектно-ориентированного
программирования обеспечивают упорядочение кода. При этом создаются типы в виде классов. Классы
содержат код, который представляет отдельную сущность. Класс BankAccount представляет банковский счет.
Этот код реализует определенные операции с помощью методов и свойств. Созданный в этом кратком
руководстве банковский счет поддерживает следующий алгоритм работы:
1. Представляет собой число из 10 цифр, которое однозначно определяет банковский счет.
2. Содержит строку, в которой хранятся имена владельцев.
3. Позволяет получить данные сальдо.
4. Принимает депозиты.
5. Принимает списания.
6. Начальное сальдо должно было положительным.
7. После списания не должно оставаться отрицательное сальдо.
namespace classes
{
public class BankAccount
{
public string Number { get; }
public string Owner { get; set; }
public decimal Balance { get; }
Прежде чем продолжить, рассмотрим созданный код. Объявление namespace предоставляет способ
логического упорядочения кода. Это относительно небольшое руководство, поэтому весь код размещается
в одном пространстве имен.
public class BankAccount определяет класс или тип, который вы создаете. Весь код в скобках { и } ,
который следует за объявлением класса, определяет поведение класса. Есть пять элементов класса
BankAccount . Первые три элемента представляют собой свойства. Свойства являются элементами данных и
могут содержать код для запуска проверки или других правил. Последние два элемента являются
методами. Методы представляют собой блоки кода, которые выполняют только одну функцию. Имя
каждого элемента должно содержать достаточно информации, чтобы разработчик мог понять, какие
функции выполняет класс.
Это элемент данных. Он имеет свойство private , то есть к нему может получить доступ только код внутри
класса BankAccount . Таким образом общедоступные обязательства (например, получение номера счета)
отделяются от закрытой реализации (способ создания номеров счетов). Также он является static , то есть
совместно используется всеми объектами BankAccount . Значение нестатической переменной является
уникальным для каждого экземпляра объекта BankAccount . Добавьте две следующие строки в конструктор,
чтобы назначить номер счета:
this.Number = accountNumberSeed.ToString();
accountNumberSeed++;
using System;
namespace classes
{
public class Transaction
{
public decimal Amount { get; }
public DateTime Date { get; }
public string Notes { get; }
Теперь добавим List<T> объектов Transaction в класс BankAccount . Добавьте следующее объявление:
private List<Transaction> allTransactions = new List<Transaction>();
Класс List<T> требует импортировать другое пространство имен. Добавьте следующий код в начало файла
BankAccount.cs:
using System.Collections.Generic;
Теперь изменим способ отчета Balance . Чтобы вычислить его, нужно суммировать значения всех
транзакций. Измените объявление Balance в классе BankAccount следующим образом:
return balance;
}
}
В этом примере показан важный аспект свойств. Вы вычисляете сальдо, когда другой программист
запрашивает значение. В результате вашего вычисления выводится список всех транзакций и сумма в виде
текущего сальдо.
Теперь реализуйте методы MakeDeposit и MakeWithdrawal . Эти методы будут выполнять два последних
правила: начальное сальдо должно быть положительным, списание не должно создавать отрицательное
сальдо.
Это действие вводит понятие исключений. Чтобы определить, что метод не может выполнить свою
функцию, обычно вызывается исключение. В типе исключения и связанном с ним сообщении описывается
ошибка. В этом примере метод MakeDeposit вызывает исключение, если сумма депозита является
отрицательной. Метод MakeWithdrawal вызывает исключение, если сумма списания является отрицательной
или если при применении списания создается отрицательное сальдо:
public void MakeDeposit(decimal amount, DateTime date, string note)
{
if (amount <= 0)
{
throw new ArgumentOutOfRangeException(nameof(amount), "Amount of deposit must be positive");
}
var deposit = new Transaction(amount, date, note);
allTransactions.Add(deposit);
}
this.Owner = name;
MakeDeposit(initialBalance, DateTime.Now, "Initial balance");
}
DateTime.Now — это свойство, которое возвращает текущие дату и время. Протестируйте его, добавив
несколько депозитов и списаний в метод Main :
Теперь протестируйте обнаружение условий ошибки. Для этого создайте счет с отрицательным сальдо:
// Test that the initial balances must be positive.
try
{
var invalidAccount = new BankAccount("invalid", -55);
}
catch (ArgumentOutOfRangeException e)
{
Console.WriteLine("Exception caught creating account with negative balance");
Console.WriteLine(e.ToString());
}
С помощью инструкций try и catch пометьте блок кода, который может вызывать исключения, и
перехватывайте ожидаемые сообщения об ошибках. Этим же способом можно проверять код, который
вызывает исключение при получении отрицательного баланса:
report.AppendLine("Date\tAmount\tNote");
foreach (var item in allTransactions)
{
report.AppendLine($"{item.Date.ToShortDateString()}\t{item.Amount}\t{item.Notes}");
}
return report.ToString();
}
В этом примере используется класс StringBuilder, чтобы отформатировать строку, которая содержит одну
строку для каждой транзакции. Код форматирования строки вы уже видели в этой серии руководств. В этом
коде есть новый символ \t . Он позволяет вставить вкладку для форматирования выходных данных.
Добавьте следующую строку, чтобы проверить его в файле Program.cs:
Console.WriteLine(account.GetAccountHistory());
Следующие шаги
Если у вас возникли проблемы, изучите исходный код для этого руководства, размещенный в репозитории
GitHub.
Поздравляем, вы завершили работу с циклом вводных руководств по C#. Если вы хотите узнать больше,
обратитесь к другим руководствам.
Создание форматированных строк с помощью
интерполяции
04.11.2019 • 13 minutes to read • Edit Online
В этом руководстве объясняется, как с помощью интерполяции строк в C# вставить значения в одну
результирующую строку. Вы напишете код C# и сможете просмотреть результаты его компиляции и
выполнения. Это руководство содержит ряд уроков по вставке значений в строки и форматированию этих
значений различными способами.
Для работы с этим руководством вам потребуется компьютер, который можно использовать для разработки.
В руководстве .NET по созданию программы Hello World за 10 минут содержатся инструкции по настройке
локальной среды разработки в macOS, Windows или Linux. Вы также можете воспользоваться
интерактивной версией этого руководства в браузере.
Чтобы выполнить этот код, введите dotnet run в окне консоли. При запуске программы отображается одна
строка, которая содержит ваше имя в приветствии. Строка, включенная в вызов метода WriteLine, является
выражением интерполированной строки. Это похоже на шаблон, позволяющий создать одну строку
(называемую результирующей строкой) из строки, содержащей внедренный код. Интерполированные
строки особенно удобны при вставке значений в строку или сцеплении (объединении) строк.
Этот простой пример содержит два элемента, обязательные для каждой интерполированной строки:
Строковый литерал, который начинается с символа $ , стоящего до открывающей кавычки. Между
символом $ и знаком кавычки не должно быть пробелов. (Если вы хотите узнать, что при этом
случится, вставьте пробел после символа $ , сохраните файл и снова запустите программу, введя
dotnet run в окне консоли. Компилятор C# выводит сообщение об ошибке "Ошибка CS1056:
Недопустимый символ '$'".)
Одно или несколько интерполированных выражений. Интерполированное выражение обозначено
открывающей и закрывающей фигурной скобкой ( { и } ). Вы можете указать внутри фигурных
скобок любое выражение C#, возвращающее значение (включая null ).
Давайте рассмотрим еще несколько примеров интерполяции строк с другими типами данных.
Включение разных типов данных
В предыдущем разделе вы использовали интерполяцию строк для вставки одной строки внутрь другой. При
этом интерполированное выражение может относиться к любому типу данных. Давайте включим в
интерполированную строку значения разных типов данных.
В приведенном ниже примере сначала мы определим тип данных для класса Vegetable , обладающего
свойством Name и методом ToString . Этот метод переопределяет поведение метода Object.ToString().
Модификатор доступа public делает этот метод доступным любому клиентскому коду и позволяет
получить строковое представление экземпляра Vegetable . В нашем примере метод Vegetable.ToString
возвращает значение свойства Name , которое инициализируется в конструкторе Vegetable .
Затем создайте экземпляр класса Vegetable с именем item . Для этого воспользуйтесь оператором new и
укажите имя для конструктора Vegetable .
Наконец, переменная item включается в интерполированную строку, которая также содержит значение
DateTime, значение Decimal и значение перечисления Unit . Замените весь код C# в редакторе следующим
кодом, а затем используйте команду dotnet run , чтобы запустить его:
using System;
Задайте строку формата, указав ее после интерполированного выражения через точку с запятой. "d" — это
стандартная строка формата для даты и времени, представляющая краткий формат. "C2" — это стандартная
строка числового формата, представляющая число в виде денежной единицы с точностью два знака после
запятой.
Некоторые типы в библиотеках .NET поддерживают предопределенный набор строк формата. К ним
относятся все числовые типы, а также типы даты и времени. Полный список типов, поддерживающих строки
формата, см. в разделе Строки формата и типы библиотек классов .NET статьи Типы форматирования в .NET.
Попробуйте изменить строки формата в текстовом редакторе, возвращаясь в программу после каждого
изменения, чтобы узнать, как они влияют на форматирование даты и времени, а также числового значения.
Измените "d" в {date:d} на "t" (чтобы отобразить краткий формат времени), "y" (чтобы отобразить год и
месяц) и "yyyy" (чтобы отобразить год в виде четырехзначного числа). Измените "C2" в {price:C2} на "e"
(для экспоненциального представления) и "F3" (чтобы получить числовое значение с тремя знаками после
запятой).
Кроме форматирования, вы можете управлять шириной поля и выравниванием для форматированных строк,
включаемых в результирующую строку. В следующем разделе вы научитесь это делать.
Имена авторов выровнены по левому краю, а названия их произведений — по правому. Вы можете указать
выравнивание, добавив запятую (,) после интерполированного выражения и назначив минимальную ширину
поля. Если указанное значение является положительным числом, то поле выравнивается по правому краю.
Если оно является отрицательным числом, то поле выравнивается по левому краю.
Попробуйте удалить знаки "минус" из кода {"Author",-25} и {title.Key,-25} , а затем снова выполните
пример, как показано в следующем коде:
Console.WriteLine($"|{"Author",25}|{"Title",30}|");
foreach (var title in titles)
Console.WriteLine($"|{title.Key,25}|{title.Value,30}|");
В этом руководстве описано, как использовать интерполяцию строк для форматирования и включения
результатов выражения в строку результатов. В примерах предполагается, что вам знакомы основные
принципы C# и форматирования типов .NET. Если вы не знакомы с интерполяцией строк или
форматированием типов .NET, сначала ознакомьтесь с интерактивным учебником по интерполяции строк.
Дополнительные сведения о форматировании типов в .NET см. в разделе Типы форматирования в .NET.
NOTE
Примеры C# в этой статье выполняются во встроенном средстве выполнения кода и на площадке Try.NET. Нажмите
на кнопку Выполнить, чтобы выполнить пример в интерактивном окне. После выполнения кода вы можете
изменить его и выполнить измененный код, снова нажав на кнопку Выполнить. Либо в интерактивном окне
выполняется измененный код, либо, если компиляция завершается с ошибкой, в интерактивном окне отображаются
все сообщения об ошибках компилятора C#.
Вступление
Функция интерполяции строк создана на основе функции составного форматирования и имеет более
удобный синтаксис для включения форматированных результатов выражения в строку результатов.
Для определения строкового литерала в качестве интерполированной строки добавьте к началу символ $ .
Вы можете внедрить любое допустимое выражение C#, возвращающее значение в интерполированной
строке. В следующем примере после вычисления выражения его результат преобразуется в строку и
включается в строку результатов:
double a = 3;
double b = 4;
Console.WriteLine($"Area of the right triangle with legs of {a} and {b} is {0.5 * a * b}");
Console.WriteLine($"Length of the hypotenuse of the right triangle with legs of {a} and {b} is
{CalculateHypotenuse(a, b)}");
double CalculateHypotenuse(double leg1, double leg2) => Math.Sqrt(leg1 * leg1 + leg2 * leg2);
// Expected output:
// Area of the right triangle with legs of 3 and 4 is 6
// Length of the hypotenuse of the right triangle with legs of 3 and 4 is 5
Как показано в примере, можно включить выражение в интерполированную строку, заключив его в
фигурные скобки:
{<interpolationExpression>}
В следующем примере показано, как задать стандартные и настраиваемые строки формата для выражений,
возвращающих дату и время или числовые результаты:
// Expected output:
// On Sunday, November 25, 1731 Leonhard Euler introduced the letter e to denote 2.71828 in a letter to
Christian Goldbach.
Дополнительные сведения см. в разделе Компонент строки формата в статье Составное форматирование.
Этот раздел содержит ссылки на разделы, описывающие стандартные и настраиваемые строки формата,
поддерживаемые базовыми типами .NET.
{<interpolationExpression>,<alignment>}
Если значение alignment положительное, форматированное выражение будет выровнено по правому краю,
а если отрицательное — по левому.
Если вам нужно задать и выравнивание, и строку формата, начните с компонента выравнивания:
{<interpolationExpression>,<alignment>:<formatString>}
В следующем примере показано, как задать выравнивание. Текстовые поля разграничены символом
вертикальной черты ("|"):
double a = 3;
double b = 4;
Console.WriteLine($"Three classical Pythagorean means of {a} and {b}:");
Console.WriteLine($"|{"Arithmetic",NameAlignment}|{0.5 * (a + b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Geometric",NameAlignment}|{Math.Sqrt(a * b),ValueAlignment:F3}|");
Console.WriteLine($"|{"Harmonic",NameAlignment}|{2 / (1 / a + 1 / b),ValueAlignment:F3}|");
// Expected output:
// Three classical Pythagorean means of 3 and 4:
// |Arithmetic| 3.500|
// |Geometric| 3.464|
// |Harmonic | 3.429|
В выходных данных в примере видно, что если длина форматированного результата выражения превышает
заданную ширину поля, значение alignment игнорируется.
Дополнительные сведения см. в разделе Компонент выравнивания в статье Составное форматирование.
Как использовать escape-последовательности в
интерполированной строке
Интерполированные строки поддерживают все escape-последовательности, которые могут использоваться
в обычных строковых литералах. Дополнительные сведения см. в статье Escape-последовательности строки.
Для литеральной интерпретации escape-последовательности используйте строковый литерал verbatim.
Интерполированная строка verbatim начинается с символа $ , за которым следует символ @ . Начиная с
C# 8.0 маркеры $ и @ можно использовать в любом порядке: $@"..." и @$"..." являются допустимыми
интерполированными строками verbatim.
В строке результатов указывайте двойную фигурную скобку "{{" или "}}". Дополнительные сведения см. в
разделе escape-скобки в статье Составное форматирование.
В следующем примере показано, как включить фигурные скобки в строку результата и создать
интерполированную строку verbatim:
// Expected output:
// Find the intersection of the {1, 2, 7, 9} and {7, 9, 12} sets.
// C:\Users\Jane\Documents
// C:\Users\Jane\Documents
Как показано в примере, можно использовать один экземпляр FormattableString для создания нескольких
строк результата для различных языков и региональных параметров.
Заключение
В этом руководстве описаны распространенные сценарии использования интерполяции строк.
Дополнительные сведения об интерполяции строк см. в разделе Интерполяция строк. Дополнительные
сведения о форматировании типов в .NET см. в разделах Типы форматирования в .NET и Составное
форматирование.
См. также
String.Format
System.FormattableString
System.IFormattable
Строки
Учебник. Обновление интерфейсов с помощью
методов интерфейса по умолчанию в C# 8.0
04.11.2019 • 10 minutes to read • Edit Online
Начиная с C# 8.0 в .NET Core 3.0 можно определить реализацию при объявлении члена интерфейса.
Наиболее распространенным сценарием является безопасное добавление членов в интерфейс, который уже
выпущен и используется многочисленными клиентами.
В этом руководстве вы узнаете, как:
Безопасно расширять интерфейсы, добавляя методы с реализациями.
Создавать параметризованные реализации, которые обеспечивают большую гибкость.
Позволить разработчику реализовать более конкретную реализацию в виде переопределения.
Предварительные требования
Вам нужно настроить свой компьютер для выполнения .NET Core, включая компилятор C# 8.0. Компилятор
C# 8.0 доступен, начиная с версии 16.3 Visual Studio 2019, или в пакете SDK .NET Core 3.0.
Обзор сценария
Этот учебник начинается с первой версии библиотеки связи с клиентом. Начальное приложение можно
получить в нашем репозитории примеров на сайте GitHub. Компания, которая создала эту библиотеку,
рассчитывала, что клиенты с существующими приложениями будут ее внедрять. Она определила
минимальный интерфейс для реализации библиотеки пользователями. Вот определение интерфейса для
клиента:
На основе этих интерфейсов команда может собрать библиотеку для удобства работы клиентов своих
пользователей. Их целью было более полно взаимодействовать с существующими клиентами и повысить
уровень связи с новыми.
Пришло время перейти к библиотеке для следующего выпуска. Одна из востребованных возможностей —
добавление скидки за лояльность для клиентов, размещающих большое количество заказов. Это новая
скидка лояльности применяется каждый раз, когда клиент делает заказ. Специальная скидка является
свойством каждого клиента. Каждая реализация ICustomer может задавать разные правила для скидки
лояльности.
Наиболее удобный способ добавления этой функции — расширить ICustomer методом для применения
любых скидок лояльности. Это предложение по разработке вызвало проблемы среди опытных
разработчиков. "Интерфейсы являются неизменяемыми после выпуска! Это критическое изменение!" В C#
8.0 добавлены реализации интерфейсов по умолчанию для обновления интерфейсов. Авторы библиотеки
могут добавлять новые элементы интерфейса и реализации по умолчанию для этих элементов.
Реализации интерфейса по умолчанию позволяют разработчикам обновить интерфейс, по-прежнему
позволяя другим разработчикам переопределять эту реализацию. Пользователи библиотеки могут
принимать реализацию по умолчанию в качестве некритического изменения. Если их бизнес-правила не
совпадают, их можно переопределить.
// Version 1:
public decimal ComputeLoyaltyDiscount()
{
DateTime TwoYearsAgo = DateTime.Now.AddYears(-2);
if ((DateJoined < TwoYearsAgo) && (PreviousOrders.Count() > 10))
{
return 0.10m;
}
return 0;
}
Задайте параметризацию
Это хорошее начало. Однако реализация по умолчанию слишком строга. Другие пользователи этой системы
могут выбрать различные пороговые значения для числа покупок, разную длительность членства или другой
процент скидки. Удобство работы обновления можно повысить для нескольких клиентов сразу, предоставляя
возможность установить эти параметры. Давайте добавим статический метод, который задает эти три
параметра, контролируя реализацию по умолчанию:
// Version 2:
public static void SetLoyaltyThresholds(
TimeSpan ago,
int minimumOrders = 10,
decimal percentageDiscount = 0.10m)
{
length = ago;
orderCount = minimumOrders;
discountPercent = percentageDiscount;
}
private static TimeSpan length = new TimeSpan(365 * 2, 0,0,0); // two years
private static int orderCount = 10;
private static decimal discountPercent = 0.10m;
В этом небольшом фрагменте кода показано много новых языковых возможностей. Интерфейсы теперь
могут содержать статические члены, включая поля и методы. Также включены разные модификаторы
доступа. Дополнительные поля являются закрытыми, новый метод является открытым. Все эти
модификаторы разрешены для членов интерфейса.
Приложениям, использующим общую формулу для вычисления скидок лояльности с разными параметрами,
не нужно создавать собственные реализации; достаточно задать аргументы с помощью статического метода.
Например, следующий код задает "повышение уровня клиента", в рамках которого награждается любой
клиент более чем с месячным сроком членства:
Весь готовый код доступен в репозитории примеров на GitHub. Начальное приложение можно получить в
нашем репозитории примеров на сайте GitHub.
Эти новые функции означают, что интерфейсы могут обновляться безопасно при наличии разумной
реализации по умолчанию для этих новых элементов. Тщательно проектируйте интерфейсы для выражения
единой функциональной идеи, которую могут реализовывать несколько классов. Это упрощает обновление
этих определений интерфейса при обнаружении новых требований для этой же функциональности.
Учебник. Функциональные возможности смешения
при создании классов с помощью методов
интерфейса по умолчанию
25.11.2019 • 14 minutes to read • Edit Online
Начиная с C# 8.0 в .NET Core 3.0 можно определить реализацию при объявлении члена интерфейса. Эта
функция предоставляет новые возможности, позволяющие определить реализации по умолчанию для
компонентов, объявленных в интерфейсах. Классы могут выбирать, когда следует переопределять
функциональность, когда следует использовать функциональную возможность по умолчанию и когда не
следует объявлять поддержку отдельных функций.
В этом руководстве вы узнаете, как:
Создать интерфейсы с реализациями, описывающими отдельные функции.
Создать классы, которые используют реализации по умолчанию.
Создать классы, которые переопределяют некоторые или все реализации по умолчанию.
Предварительные требования
Вам нужно настроить свой компьютер для выполнения .NET Core, включая компилятор C# 8.0. Компилятор
C# 8.0 доступен, начиная с Visual Studio 2019, 16.3, или в пакете SDK для .NET Core 3.0 или более поздней
версии.
Разработка приложения
Рассмотрим приложение для системы домашней автоматики. Возможно, у вас есть много типов разного
освещения и индикаторов, которые можно использовать во всем доме. Каждый источник освещения должен
поддерживать интерфейсы API, позволяющие включать и выключать эти источники, а также передавать
данные о текущем состоянии. Некоторые источники освещения и индикаторы могут поддерживать другие
функции, такие как:
включение освещения, а затем его отключение по таймеру;
мигание освещения в течение определенного периода времени.
Некоторые из этих расширенных возможностей можно эмулировать на устройствах, поддерживающих
минимальный набор. Это соответствует реализации по умолчанию. Для устройств со встроенными
дополнительными возможностями программное обеспечение устройства будет использовать собственные
возможности. Для других источников освещения они могут реализовать интерфейс и использовать
реализацию по умолчанию.
Элементы интерфейса по умолчанию являются лучшим решением для этого сценария, чем методы
расширения. Авторы классов могут управлять выбором интерфейсов для реализации. Выбранные ими
интерфейсы доступны как методы. Кроме того, так как методы стандартного интерфейса являются
виртуальными по умолчанию, диспетчеризация методов всегда выбирает реализацию в классе.
Давайте создадим код для демонстрации этих различий.
Создание интерфейсов
Сначала можно создать интерфейс, который определяет поведение для всех источников освещения.
Основное средство тестирования верхнего освещения может реализовать этот интерфейс, как показано в
следующем коде.
public override string ToString() => $"The light is {isOn: \"on\", \"off\"}";
В этом учебнике код не поддерживает устройства Интернета вещей, но эмулирует такие действия, записывая
сообщения в консоль. Вы можете исследовать код без автоматизации дома.
Теперь определим интерфейс, автоматически отключающий освещение по истечении времени ожидания.
После добавления этого изменения класс OverheadLight может реализовать функцию таймера, объявляя
поддержку интерфейса.
Другой тип освещения может поддерживать более сложный протокол. Он может предоставить собственную
реализацию для TurnOnFor , как показано в следующем коде.
public override string ToString() => $"The light is {isOn: \"on\", \"off\"}";
}
Новый тип освещения LEDLight поддерживает функцию таймера и функцию мигания напрямую. Такой
стиль освещения реализует интерфейсы ITimerLight и IBlinkingLight , а также переопределяет метод Blink .
public override string ToString() => $"The light is {isOn: \"on\", \"off\"}";
}
public override string ToString() => $"The light is {isOn: \"on\", \"off\"}";
}
Диапазоны и индексы обеспечивают лаконичный синтаксис для доступа к отдельным элементам или
диапазонам в последовательности.
В этом руководстве вы узнаете, как:
Использовать этот синтаксис для диапазонов в последовательности.
Проектировать начало и конец каждой последовательности.
Составлять сценарии для типов Index и Range.
Последнее слово можно получить с помощью индекса ^1 . Добавьте следующий код после инициализации:
Диапазон указывает начало и конец диапазона. Диапазоны являются открытыми, то есть конечное
значение не включается в диапазон. Диапазон [0..^0] представляет весь диапазон так же, как
[0..sequence.Length] представляет весь диапазон.
Следующий код создает поддиапазон со словами "quick", "brown" и "fox". Он включает в себя элементы от
words[1] до words[3] . Элемент words[4] в диапазон не входит. Добавьте в тот же метод указанный ниже
код. Скопируйте и вставьте этот код в нижней части интерактивного окна.
string[] quickBrownFox = words[1..4];
foreach (var word in quickBrownFox)
Console.Write($"< {word} >");
Console.WriteLine();
Следующий код создает поддиапазон со словами "lazy" и "dog". Он включает элементы words[^2] и
words[^1] . Конечный индекс words[^0] не включен. Добавьте также следующий код:
В следующих примерах создаются диапазоны, которые должны быть открыты для начала, конца или в
обоих случаях:
Диапазоны или индексы можно также объявлять как переменные. Впоследствии такую переменную можно
использовать внутри символов [ и ] :
Следующий пример демонстрирует многие возможные причины для таких решений. Измените x , y и z
и опробуйте различные комбинации. Поэкспериментируйте со значениями, у которых x меньше y , а y
меньше, чем z для допустимых сочетаний. Добавьте в новый метод приведенный ниже код. Опробуйте
различные комбинации:
int[] numbers = Enumerable.Range(0, 100).ToArray();
int x = 12;
int y = 25;
int z = 36;
(int min, int max, double average) MovingAverage(int[] subSequence, Range range) =>
(
subSequence[range].Min(),
subSequence[range].Max(),
subSequence[range].Average()
);
В C# 8.0 вводятся ссылочные типы, допускающие значение NULL, которые дополняют ссылочные типы
таким же образом, как типы значений, допускающие значение NULL, дополняют другие типы значений.
Чтобы объявить переменную как имеющую ссылочный тип, допускающий значение null, добавьте ? к
типу. Например, string? соответствует типу string , допускающему значение null. Эти новые типы можно
использовать для более четкого выражения проектного замысла: некоторые переменные всегда должны
содержать значение, а в других они могут отсутствовать.
В этом руководстве вы узнаете, как:
Внедрять в проекты ссылочные типы, допускающие и не допускающие значение null.
Включать в коде проверку ссылочных типов, допускающих значение null.
Писать код, в котором компилятор принудительно применяет эти проектные решения.
Использовать ссылочный тип, допускающий значение null, в собственных проектах.
Предварительные требования
Вам нужно настроить свой компьютер для выполнения .NET Core, включая компилятор C# 8.0. Компилятор
C# 8.0 доступен в Visual Studio 2019 или .NET Core 3.0.
В этом руководстве предполагается, что вы знакомы с C# и .NET, включая Visual Studio или .NET Core CLI.
namespace NullableIntroduction
{
public class SurveyQuestion
{
}
}
Компилятор интерпретирует каждое объявление переменной ссылочного типа как ссылочный тип, не
допускающий значение NULL, для кода во включенном контексте заметок, допускающих значение
NULL. Первое предупреждение вы увидите после добавления свойств текста и типа вопроса, как показано в
следующем коде:
namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}
Так как вы еще не инициализировали QuestionText , компилятор выдает предупреждение о том, что
свойство, не допускающее значение null, не инициализировано. Для проекта требуется, чтобы текст вопроса
не принимал значение null, поэтому необходимо добавить конструктор, чтобы инициализировать свойство
и значение QuestionType . Законченное определение класса выглядит следующим образом:
namespace NullableIntroduction
{
public enum QuestionType
{
YesNo,
Number,
Text
}
namespace NullableIntroduction
{
public class SurveyRun
{
private List<SurveyQuestion> surveyQuestions = new List<SurveyQuestion>();
Как и раньше, необходимо инициализировать объект списка с помощью значения, отличного от null, или
компилятор выдаст предупреждение. При второй перегрузке AddQuestion не будет проверок значений null,
так как они не требуются: вы объявили, что переменная не допускает значение null. Это значение не может
быть равно null .
Переключитесь на Program.cs в редакторе и замените содержимое метода Main следующими строками
кода:
Так как весь проект находится во включенном контексте заметок, допускающих значение NULL, при
каждой передаче значения null в любой метод, который ожидает ссылочный тип, не допускающий
значение NULL, будет отображаться предупреждение. Попробуйте добавить следующую строку в Main :
surveyRun.AddQuestion(QuestionType.Text, default);
Затем добавьте метод static для создания новых участников путем генерации случайного
идентификатора:
Основная задача этого класса состоит в том, чтобы генерировать ответы участников на вопросы в опросе.
Эта задача состоит из нескольких шагов:
1. Запросите участие в опросе. Если пользователь не согласен, верните отсутствующий (или null) ответ.
2. Задайте каждый вопрос и запишите ответы. Любой ответ может отсутствовать (или иметь значение null).
Добавьте приведенный ниже код к классу SurveyResponse :
private Dictionary<int, string>? surveyResponses;
public bool AnswerSurvey(IEnumerable<SurveyQuestion> questions)
{
if (ConsentToSurvey())
{
surveyResponses = new Dictionary<int, string>();
int index = 0;
foreach (var question in questions)
{
var answer = GenerateAnswer(question);
if (answer != null)
{
surveyResponses.Add(index, answer);
}
index++;
}
}
return surveyResponses != null;
}
Хранилищем для ответов на опрос является Dictionary<int, string>? , из которого видно, что оно может
принимать значение null. Используйте новую языковую функцию, чтобы объявить намерение проекта как
компилятору, так и всем, кто будет работать с кодом позже. Если вы когда-нибудь разыменуете
surveyResponses , не проверив значения null , компилятор отобразит предупреждение. В методе
AnswerSurvey предупреждение не отображается, так как компилятор может определить, что переменной
surveyResponses ранее было присвоено значение, отличное от null.
Использование значения null для отсутствующих ответов указывает на важную особенность при работе с
допускающими значение NULL ссылочными типами: перед вами не стоит цель удалить все значения null
из программы. Скорее, вам нужно привести написанный вами код в соответствие со своим замыслом.
Отсутствующие значения — это неотъемлемая часть кода. И значение null хорошо подходит для их
выражения. Если вы попытаетесь удалить все значения null , вам придется определить другой способ,
чтобы выразить такие отсутствующие значения без использования null .
Далее необходимо написать метод PerformSurvey в классе SurveyRun . Добавьте в класс SurveyRun
следующий код:
В этом случае выбор List<SurveyResponse>? с возможностью принимать значение null указывает на то, что
ответ может отсутствовать. Это значит, что респонденты еще не участвовали в опросе. Обратите внимание,
что респонденты добавляются до тех пор, пока не будет достигнуто целевое значение.
Для запуска опроса осталось добавить вызов в конце метода Main :
surveyRun.PerformSurvey(50);
Так как surveyResponses является ссылочным типом, допускающим значение null, прежде чем отменять
ссылку на него, укажите, что проверка требуется. Метод Answer возвращает строку, не допускающую
значения null, поэтому мы должны охватить вариант отсутствующего ответа, используя оператор
объединения со значением null.
Затем добавьте эти три элемента, воплощающие выражение, в класс SurveyRun :
Элемент AllParticipants должен принимать во внимание, что переменная respondents может иметь
значение null, но возвращаемое значение не может быть равно null. Если изменить это выражение, удалив
?? и следующую пустую последовательность, компилятор выдаст предупреждение о том, что метод может
возвращать null , а его сигнатура возвращает тип, не допускающий значение null.
Наконец, добавьте следующий цикл в нижней части метода Main :
foreach (var participant in surveyRun.AllParticipants)
{
Console.WriteLine($"Participant: {participant.Id}:");
if (participant.AnsweredSurvey)
{
for (int i = 0; i < surveyRun.Questions.Count; i++)
{
var answer = participant.Answer(i);
Console.WriteLine($"\t{surveyRun.GetQuestion(i)} : {answer}");
}
}
else
Console.WriteLine("\tNo responses");
}
В этом коде не требуется никаких проверок null , так как вы разработали базовые интерфейсы так, чтобы
они возвращали ссылочные типы, не допускающие значения null.
Получите код
Вы можете получить код этого руководства из нашего репозитория samples в папке
csharp/NullableIntroduction.
Экспериментируйте, изменяя объявления типов между ссылочными типами, допускающими и не
допускающими значение null. Посмотрите, как создаются различные предупреждения, чтобы избежать
случайного разыменования null .
Следующие шаги
Дополнительные сведения о переводе существующего приложения на использование допускающих
значение NULL ссылочных типов:
Перевод приложения на использование ссылочных типов, допускающих значение NULL
Учебник. Перенос существующего кода со
ссылочными типами, допускающими значение null
25.11.2019 • 23 minutes to read • Edit Online
В C# 8 вводятся ссылочные типы, допускающие значение null, которые дополняют ссылочные типы
таким же образом, как типы значений, допускающие значение null, дополняют другие типы значений.
Чтобы объявить переменную как имеющую ссылочный тип, допускающий значение null, добавьте ?
к типу. Например, string? соответствует типу string , допускающему значение null. Эти новые типы можно
использовать для более четкого выражения проектного замысла: некоторые переменные всегда должны
содержать значение, а в других они могут отсутствовать. Любые существующие переменные
ссылочного типа будут интерпретироваться как не допускающие значение null.
В этом руководстве вы узнаете, как:
включить проверку пустых ссылок при работе с кодом;
выполнить диагностику и устранение различных предупреждений, связанных со значениями null;
управлять интерфейсом между контекстами, допускающими значение null и не допускающими его;
контролировать контекст заметок, допускающих значение null.
Предварительные требования
Вам нужно настроить свой компьютер для выполнения .NET Core, включая компилятор C# 8.0. Компилятор
C# 8 доступен, начиная с версии 16.3 Visual Studio 2019 или в пакете SDK .NET Core 3.0.
В этом руководстве предполагается, что вы знакомы с C# и .NET, включая Visual Studio или .NET Core CLI.
Переход проекта на C# 8
Сначала рекомендуется определить область задачи перехода. В первую очередь мы перейдем на
использование C# 8.0 (или более поздней версии). Добавьте элемент LangVersion в два CSPROJ -файла: веб-
проекта и проекта модульных тестов.
<LangVersion>8.0</LangVersion>
Версия языка обновляется до C# 8.0, но без включения контекста заметок, допускающих значение null, или
контекста предупреждений о допустимости значения null. Перестройте проект так, чтобы сборка
выполнялась без предупреждений.
Следующим шагом является включение контекста заметок, допускающих значение null, и просмотр
количества генерируемых предупреждений. Добавьте следующий элемент прямо под элементом
LangVersion в оба CSPROJ -файла решения:
<Nullable>enable</Nullable>
#nullable enable
public class NewsStoryViewModel
{
public DateTimeOffset Published { get; set; }
public string Title { get; set; }
public string Uri { get; set; }
}
#nullable restore
Эти два свойства вызывают ошибку CS8618 : "Non-nullable property is uninitialized" (Отменена
инициализация свойства, которое не является свойством необязательной определенности). Это очевидно:
оба свойства string при создании объекта NewsStoryViewModel имеют значение по умолчанию null . Важно
выяснить, как создаются объекты NewsStoryViewModel . Глядя на этот класс, невозможно определить то,
является ли значение null частью решения разработки, или то, устанавливаются ли для этих объектов
ненулевые значения во время их создания. Новости (newsStory) создаются в методе GetNews класса
NewsService :
В предыдущем блоке кода довольно много важного. Это приложение использует пакет NuGet AutoMapper
для создания элемента новостей на основе элемента ISyndicationItem . Видно также, что в этом одном
операторе и создаются элементы новостей, и задаются свойства. Это означает, что проектное решение
класса NewsStoryViewModel указывает, что эти свойства никогда не должны иметь значение null . Эти
свойства должны быть ссылочного типа, не допускающего значение null. Это лучше всего выражает
исходное намерение проекта. Фактически, любой объект NewsStoryViewModel правильно создается с
ненулевыми значениями. Следующий код инициализации является допустимым исправлением:
Присвоение строкам Title и Uri значения default , которое является null для типа string , не изменяет
поведение программы во время выполнения. Объекты класса NewsStoryViewModel все еще создаются с
нулевыми значениями, но теперь компилятор не выдает предупреждений. Оператором, разрешающим
значение null, является символ ! . В этом примере он стоит после выражения default и сообщает
компилятору, что предыдущее выражение не является нулевым. Эта методика может быть целесообразной,
если другие изменения вызывают гораздо большие изменения базы кода, но в этом приложении есть
лучшее и относительно быстрое решение. Оно состоит в том, чтобы сделать класс NewsStoryViewModel
неизменным типом, где все свойства устанавливаются в конструкторе. Внесите следующие изменения в
класс NewsStoryViewModel :
#nullable enable
public class NewsStoryViewModel
{
public NewsStoryViewModel(DateTimeOffset published, string title, string uri) =>
(Published, Title, Uri) = (published, title, uri);
После этого нужно обновить код, который настраивает AutoMapper так, чтобы тот использовал
конструктор, а не инициализировал свойства. Откройте файл NewsService.cs и найдите следующий код в
нижней части:
Этот код сопоставляет свойства объектов ISyndicationItem и NewsStoryViewModel . Нам нужно, чтобы
AutoMapper предоставил сопоставление, используя вместо этого конструктор. Замените приведенный выше
код на следующую конфигурацию AutoMapper:
#nullable enable
public class NewsStoryProfile : Profile
{
public NewsStoryProfile()
{
// Create the AutoMapper mapping profile between the 2 objects.
// ISyndicationItem.Id maps to NewsStoryViewModel.Uri.
CreateMap<ISyndicationItem, NewsStoryViewModel>()
.ForCtorParam("uri", opt => opt.MapFrom(src => src.Id));
}
Обратите внимание, так как этот класс небольшой и вы внимательно изучили его, необходимо включить
директиву #nullable enable над объявлением класса. Изменение в конструкторе могло нарушить какой-то
код, поэтому стоит выполнить все тесты и протестировать приложение, прежде чем двигаться дальше.
Первый набор изменений показал, как определить, что в исходном намерении для переменных не должно
устанавливаться значение null . Это методика называется правильным конструированием. Вы
объявляете, что объект и его свойства не могут иметь значение null при создании. Анализ потока
компилятором гарантирует, что для этих свойств не будет задано значение null после создания. Обратите
внимание, что конструктор вызывается внешним кодом, который не обращает внимания на
допустимость значений null. Новый синтаксис не обеспечивает проверку во время выполнения. Внешний
код может обойти анализ потока компилятором.
В других случаях структура класса предоставляет различные подсказки к пониманию намерения. Откройте
файл Error.cshtml.cs в папке Pages. Класс ErrorViewModel содержит следующий код:
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
Добавьте директиву #nullable enable перед объявлением класса и директиву #nullable restore после
объявления. Вы получите одно предупреждение о том, что RequestId не инициализирован. Посмотрев на
класс, вы должны решить, что в некоторых случаях свойство RequestId должно быть со значением null.
Наличие свойства ShowRequestId указывает, что возможны пропущенные значения. Так как значение null
является допустимым, добавьте ? в тип string , чтобы указать, что свойство RequestId является
ссылочного типа, допускающего значение null. Окончательное определение класса выглядит следующим
образом:
#nullable enable
public class ErrorModel : PageModel
{
public string? RequestId { get; set; }
Проверьте использование свойства. Вы увидите, что перед отображением свойства в исправлении оно
проверяется на значение null на соответствующей странице. Это безопасное использование ссылочного
типа, допускающего значение null, поэтому мы закончим работу с этим классом.
if (!string.IsNullOrEmpty(feedUrl))
{
try
{
NewsItems = await _newsService.GetNews(feedUrl);
}
catch (UriFormatException)
{
ErrorText = "There was a problem parsing the URL.";
return;
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.NameResolutionFailure)
{
ErrorText = "Unknown host name.";
return;
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.ProtocolError)
{
ErrorText = "Syndication feed not found.";
return;
}
catch (AggregateException ae)
{
ae.Handle((x) =>
{
if (x is XmlException)
{
ErrorText = "There was a problem parsing the feed. Are you sure that URL is a
syndication feed?";
return true;
}
return false;
});
}
}
}
}
Это изменение не будет распространяться на другой код, потому что любой доступ к свойству ErrorText
уже защищен проверками на значение null. Далее инициализируйте список NewsItems и удалите метод
задания свойств, сделав этот список свойством, доступным только для чтения:
NewsItems.AddRange(await _newsService.GetNews(feedUrl));
Использование метода AddRange вместо присваивания означает, что метод GetNews может вернуть
IEnumerable вместо List . Это экономит одно распределение. Измените сигнатуру метода и удалите вызов
ToList , как показано в следующем примере кода:
// Something else
default:
break;
}
}
}
catch (AggregateException ae)
{
throw ae.Flatten();
}
}
// Act
IEnumerable<NewsStoryViewModel> result =
await _newsService.GetNews(feedUrl);
// Assert
Assert.True(result.Any());
Параметр IMapper вводится в качестве ссылки, не допускающей значение null. Он вызывается кодом
инфраструктуры ASP.NET Core, поэтому компилятор не знает, что IMapper никогда не будет иметь
значение null. Контейнер по умолчанию для внедрения зависимостей ASP.NET Core генерирует
исключение, если не может устранить необходимую службу, чтобы код оставался правильным. Компилятор
не может проверить все вызовы ваших общедоступных API, даже если код скомпилирован с включенными
контекстами заметок, допускающих значение null. Кроме того, библиотеки могут использоваться в проектах,
которые еще не перешли на ссылочные типы, допускающие значение null. Проверяйте входные данные для
общедоступных API, даже если вы объявили их как типы, не допускающие значение null.
Получите код
Вы исправили предупреждения, полученные в начальной тестовой компиляции, поэтому теперь можете
включить контекст заметок, допускающих значение null, в обоих проектах. Перестройте проекты.
Компилятор не выдаст ни одного предупреждения. Получить код готового проекта можно в репозитории
GitHub dotnet/samples.
Новые возможности, поддерживающие ссылочные типы, допускающие значение null, помогают находить и
исправлять потенциальные ошибки в обработке значений null в коде. Включение контекста заметок,
допускающих значение null, позволяет выразить намерение проекта: некоторые переменные никогда не
должны быть нулевыми, тогда как другие могут. Используя эти возможности, проще выразить намерение
проекта. Аналогичным образом контекст предупреждений о допустимости значения null указывает
компилятору выдавать предупреждения при нарушении намерения проекта. Благодаря этим
предупреждениям вы сможете внести обновления, которые сделают код более устойчивым и уменьшат
вероятность создания исключений NullReferenceException во время выполнения. Вы можете
контролировать область этих контекстов, чтобы сосредоточиться на локальных областях кода, которые
нужно перенести, пока оставшаяся база кода остается без изменений. На практике можно сделать эту
задачу перехода частью регулярного обслуживания классов. В этом руководстве продемонстрирован
процесс перехода приложения на использование ссылочных типов, допускающих значение null. Вы можете
ознакомиться с гораздо большим реальным примером этого процесса, изучив запрос на вытягивание
Джона Скита, созданный для перехода на ссылочные типы, допускающие значение null, в NodaTime.
Учебник. Создание и использование асинхронных
потоков с использованием C# 8.0 и .NET Core 3.0
04.11.2019 • 13 minutes to read • Edit Online
C# 8.0 представляет асинхронные потоки, которые моделируют источник потоковых данных, когда можно
получить элементы в потоке данных или создать их асинхронно. Асинхронные потоки опираются на новые
интерфейсы, представленные в .NET Standard 2.1 и реализованные в .NET Core 3.0, чтобы обеспечить
естественную модель программирования для асинхронных источников потоковых данных.
В этом руководстве вы узнаете, как:
Создать источник данных, который формирует последовательность элементов данных асинхронно.
Использовать этот источник данных асинхронно.
Распознавать, когда новый интерфейс и источник данных предпочтительнее для более ранних
синхронных последовательностей данных.
Предварительные требования
Вам нужно настроить свой компьютер для выполнения .NET Core, включая компилятор C# 8.0. Компилятор
C# 8 доступен, начиная с версии 16.3 Visual Studio 2019 или в пакете SDK .NET Core 3.0.
Чтобы вы могли получить доступ к конечной точке GraphQL GitHub, необходимо создать маркер доступа
GitHub. Выберите следующие разрешения для маркеров доступа GitHub.
repo:status
public_repo
Храните маркер доступа в надежном месте, чтобы вы могли использовать его для получения доступа к
конечной точке API GitHub.
WARNING
Храните свой личный маркер доступа в безопасном месте. Любое программное обеспечение с вашим личным
маркером доступа может выполнять вызовы API GitHub с помощью ваших прав доступа.
В этом руководстве предполагается, что вы знакомы с C# и .NET, включая Visual Studio или .NET Core CLI.
try
{
var results = await runPagedQueryAsync(client, PagedIssueQuery, "docs",
cancellationSource.Token, progressReporter);
foreach(var issue in results)
Console.WriteLine(issue);
}
catch (OperationCanceledException)
{
Console.WriteLine("Work has been cancelled");
}
}
Вы можете задать переменную среды GitHubKey личному маркеру доступа или заменить последний
аргумент в вызове на GenEnvVariable с помощью личного маркера доступа. Не помещайте свой код доступа
в исходный код, если вы будете сохранять исходный код вместе с другими или помещать его в общий
репозиторий с исходным кодом.
После создания клиента GitHub код в Main создает объект отчета о ходе выполнения и маркер отмены.
После создания этих объектов Main вызывает runPagedQueryAsync , чтобы получить более 250 недавно
созданных проблем. Результаты отобразятся после выполнения этой задачи.
При запуске начального приложения вы можете обнаружить некоторые важные замечания о том, как будет
выполняться приложение. Вы увидите ход выполнения, передаваемый каждой странице, возвращенной с
GitHub. Прежде чем GitHub вернет каждую новую страницу проблем, возникает заметная пауза. Наконец,
проблемы отображаются только после того, как получены все 10 страниц с GitHub.
Изучение реализации
Реализация показывает, почему возникло поведение, обсуждавшееся в предыдущем разделе. Изучите код
для runPagedQueryAsync .
private static async Task<JArray> runPagedQueryAsync(GitHubClient client, string queryText, string repoName,
CancellationToken cancel, IProgress<int> progress)
{
var issueAndPRQuery = new GraphQLRequest
{
Query = queryText
};
issueAndPRQuery.Variables["repo_name"] = repoName;
namespace System.Collections.Generic
{
public interface IAsyncEnumerable<out T>
{
IAsyncEnumerator<T> GetAsyncEnumerator(CancellationToken cancellationToken = default);
}
ValueTask<bool> MoveNextAsync();
}
}
namespace System
{
public interface IAsyncDisposable
{
ValueTask DisposeAsync();
}
}
Большинство разработчиков C# должны знать об этих трех интерфейсах. Они ведут себя подобно своим
синхронным аналогам.
System.Collections.Generic.IEnumerable<T>
System.Collections.Generic.IEnumerator<T>
System.IDisposable
Один тип, который может быть незнаком, — System.Threading.Tasks.ValueTask. Структура ValueTask
предоставляет API, аналогичный классу System.Threading.Tasks.Task. ValueTask используется в этих
интерфейсах по причинам производительности.
В следующем коде показано, как начальный код обрабатывает каждую страницу для извлечения.
finalResults.Merge(issues(results)["nodes"]);
progress?.Report(issuesReturned);
cancel.ThrowIfCancellationRequested();
Замените эти три строки следующим кодом.
Вы также можете удалить объявление finalResults ранее в этом методе и оператор return , следующий за
измененным циклом.
Вы завершили изменения для создания асинхронного потока. Готовый метод должен напоминать код,
указанный ниже.
Затем измените код, который использует коллекцию, для асинхронного потока. Найдите следующий код в
Main , который обрабатывает коллекцию проблем.
var progressReporter = new progressStatus((num) =>
{
Console.WriteLine($"Received {num} issues in total");
});
CancellationTokenSource cancellationSource = new CancellationTokenSource();
try
{
var results = await runPagedQueryAsync(client, PagedIssueQuery, "docs",
cancellationSource.Token, progressReporter);
foreach(var issue in results)
Console.WriteLine(issue);
}
catch (OperationCanceledException)
{
Console.WriteLine("Work has been cancelled");
}
int num = 0;
await foreach (var issue in runPagedQueryAsync(client, PagedIssueQuery, "docs"))
{
Console.WriteLine(issue);
Console.WriteLine($"Received {++num} issues in total");
}
Вы можете получить код для готового руководства, используемый в репозитории dotnet/samples в папке
csharp/tutorials/AsyncStreams.
В C# 7 появились базовые функции сопоставления шаблонов. Эти функции были расширены в C# 8 за счет
новых выражений и шаблонов. Вы можете написать функции, которые работают так, будто вы расширили
типы, которые могут быть в других библиотеках. Еще один вариант использования шаблонов — создать
функции, которые требуются для приложения и не являются фундаментальными функциями для
расширяемого типа.
В этом руководстве вы узнаете, как:
распознавать случаи, в которых следует использовать сопоставление шаблонов;
использовать выражения сопоставления шаблонов для реализации поведения с учетом типов и значений
свойств;
комбинировать сопоставление шаблонов и другие методы для создания полных алгоритмов.
Предварительные требования
Вам нужно настроить свой компьютер для выполнения .NET Core, включая компилятор C# 8.0. Компилятор
C# 8 доступен, начиная с версии 16.3 Visual Studio 2019 или в пакете SDK .NET Core 3.0.
В этом руководстве предполагается, что вы знакомы с C# и .NET, включая Visual Studio или .NET Core CLI.
namespace ConsumerVehicleRegistration
{
public class Car
{
public int Passengers { get; set; }
}
}
namespace CommercialRegistration
{
public class DeliveryTruck
{
public int GrossWeightClass { get; set; }
}
}
namespace LiveryRegistration
{
public class Taxi
{
public int Fares { get; set; }
}
Скачать начальный код можно из репозитория GitHub dotnet/samples. Вы можете видеть, что классы
транспортных средств принадлежат разным системам и находятся в разных пространствах имен. Вы не
можете использовать другие базовые классы, кроме System.Object .
Создайте класс TollCalculator и реализуйте сопоставление шаблонов по типу автомобиля, чтобы получить
сумму сбора. В приведенном ниже примере кода показана начальная реализация класса TollCalculator .
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
namespace toll_calculator
{
public class TollCalculator
{
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car c => 2.00m,
Taxi t => 3.50m,
Bus b => 5.00m,
DeliveryTruck t => 10.00m,
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName:
nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
}
}
В коде предыдущего примера используется выражение switch (которое отличается от оператора switch ),
проверяющее шаблон типа. Выражение switch начинается с переменной vehicle в приведенном выше
коде, за которой следует ключевое слово switch . Далее следуют все доступные ветви switch внутри
фигурных скобок. Выражение switch вносит другие уточнения в синтаксис, который окружает оператор
switch . Ключевое слово case опущено, и результатом каждой ветви является выражение. Последние две
ветви демонстрируют новую функцию языка. Случай { } соответствует любому ненулевому объекту,
который не совпадает с предыдущей ветвью. Этот случай позволяет перехватить все неправильные типы,
переданные этому методу. Случай { } должен находиться после всех случаев, соответствующих типам
автомобилей, иначе случай { } будет иметь приоритет. Наконец, шаблон со значением null определяет,
когда этому методу передается значение null . Шаблон null может быть последним, так как шаблоны
другого типа соответствуют только ненулевому объекту правильного типа.
Этот код можно проверить с помощью следующего кода в файле Program.cs :
using System;
using CommercialRegistration;
using ConsumerVehicleRegistration;
using LiveryRegistration;
namespace toll_calculator
{
class Program
{
static void Main(string[] args)
{
var tollCalc = new TollCalculator();
try
{
tollCalc.CalculateToll("this will fail");
}
catch (ArgumentException e)
{
Console.WriteLine("Caught an argument exception when using the wrong type");
}
try
{
tollCalc.CalculateToll(null);
}
catch (ArgumentNullException e)
{
Console.WriteLine("Caught an argument exception when using null");
}
}
}
}
Этот код включен в начальный проект, но закомментирован. Удалите символы комментария, чтобы
проверить написанный код.
На этом примере можно понять, как шаблоны помогают создавать алгоритмы, в которых код и данные
разделены. Выражение switch проверяет тип и создает различные значения в зависимости от результатов.
Но это только начало.
vehicle switch
{
Car { Passengers: 0} => 2.00m + 0.50m,
Car { Passengers: 1 } => 2.0m,
Car { Passengers: 2} => 2.0m - 0.50m,
Car c => 2.00m - 1.0m,
// ...
};
Первые три случая проверяют тип как Car , а затем проверяют значение свойства Passengers . Если оба типа
совпадают, это выражение вычисляется и возвращается результат.
Необходимо также реализовать аналогичные варианты для такси:
vehicle switch
{
// ...
// ...
};
vehicle switch
{
// ...
// ...
};
В этом коде показано предложение when ветви switch. Используйте предложение when для проверки
условий для свойства, отличающихся от равенства. Завершив работу, вы получите метод, который выглядит
приблизительно так:
vehicle switch
{
Car { Passengers: 0} => 2.00m + 0.50m,
Car { Passengers: 1} => 2.0m,
Car { Passengers: 2} => 2.0m - 0.50m,
Car c => 2.00m - 1.0m,
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
Вы можете уменьшить количество повторяемых участков кода, использовав вложенные операторы switch. В
предыдущих примерах объекты Car и Taxi имеют по четыре варианта. В обоих случаях вы можете создать
шаблон типа, который передается в шаблон свойства. Пример использования этого способа показан в
следующем коде:
public decimal CalculateToll(object vehicle) =>
vehicle switch
{
Car c => c.Passengers switch
{
0 => 2.00m + 0.5m,
1 => 2.0m,
2 => 2.0m - 0.5m,
_ => 2.00m - 1.0m
},
{ } => throw new ArgumentException(message: "Not a known vehicle type", paramName: nameof(vehicle)),
null => throw new ArgumentNullException(nameof(vehicle))
};
Для этих трех переменных существует 16 разных комбинаций. Комбинируя некоторые условия, вы упростите
окончательное выражение switch.
В системе для сбора платы структура DateTime используется для определения времени сбора. Выполните
методы-члены, которые создают переменные из предыдущей таблицы. В следующей функции для
сопоставления шаблонов используется выражение switch, позволяющее определить, соответствует ли
DateTime выходным или рабочим дням недели:
Этот метод работает, но фрагменты кода повторяются. Вы можете упростить его, как показано в следующем
примере:
В предыдущем методе сопоставление шаблонов не применяется. В этом случае проще использовать каскад
операторов if . Чтобы преобразовать каждый диапазон времени в дискретную величину, добавьте
закрытое перечисление enum .
Создав эти методы, можно использовать другое выражение switch с шаблоном кортежа для вычисления
премиум-цены. Вы можете записать выражение switch сразу со всеми 16 вариантами:
Для входящего и исходящего трафика используется одинаковый множитель в дневное и ночное время в
рабочие дни. Эти четыре случая можно заменить следующими двумя строками:
После этих двух изменений код должен выглядеть как показано ниже:
Наконец, вы можете удалить два часа пик, за которые взимается обычная плата. Удалив эти ветви, можно
заменить false пустой переменной ( _ ) в последней ветви switch. У вас получится следующий законченный
метод:
Следующие шаги
Скачать готовый код можно из репозитория GitHub dotnet/samples. Изучите шаблоны самостоятельно и
используйте эту методику во время написания кода. Это позволит вам подойти к решению проблем с
другой стороны, чтобы создавать новые функции.
Консольное приложение
04.11.2019 • 20 minutes to read • Edit Online
Это руководство раскроет для вас некоторые возможности .NET Core и языка C#. Вы познакомитесь со
следующими аспектами:
работа с интерфейсом командной строки (CLI) в .NET Core;
структура консольного приложения C#;
консольный ввод-вывод;
основные сведения об интерфейсах API файлового ввода-вывода в .NET;
основные сведениях об асинхронном программировании задач в .NET.
Вам предстоит создать приложение, которое считывает текстовый файл и выводит его содержимое в
консоль. Вывод в консоль осуществляется с такой скоростью, которая позволяет читать текст вслух.
Скорость можно будет увеличивать или уменьшать клавишами "<" (меньше) и ">".
В этом руководстве описано множество функций. Давайте начнем поочередно разбирать их.
Предварительные требования
Компьютер должен быть настроен для выполнения .NET Core. Инструкции по установке см. на странице
скачиваемых файлов .NET Core. Это приложение можно запустить в ОС Windows, Linux, macOS или в
контейнере Docker. Вам потребуется редактор кода, но вы можете выбрать любой привычный для вас.
Создание приложения
Первым шагом является создание нового приложения. Откройте командную строку и создайте новый
каталог для приложения. Перейдите в этот каталог. В командной строке введите команду dotnet new console
. Эта команда создает начальный набор файлов для базового приложения Hello World.
Прежде чем вносить изменения в это приложение, давайте разберем процедуру его запуска. Когда вы
создадите приложение, наберите в командной строке команду dotnet restore . Она запускает процесс
восстановления из пакета NuGet. NuGet представляет собой диспетчер пакетов для .NET. Эта команда
загружает все отсутствующие зависимости для проекта. Поскольку мы имеем дело с новым проектом, для
него пока не существует зависимостей, поэтому при первом запуске будет загружена только платформа
.NET Core. После этого первого запуска команду dotnet restore нужно будет запускать только после
добавления новых зависимых пакетов или при обновлении версий используемых зависимостей.
NOTE
Начиная с пакета SDK для .NET Core 2.0 нет необходимости выполнять команду dotnet restore , так как она
выполняется неявно всеми командами, которые требуют восстановления, например dotnet new , dotnet build и
dotnet run . Эту команду по-прежнему можно использовать в некоторых сценариях, где необходимо явное
восстановление, например в сборках с использованием непрерывной интеграции в Azure DevOps Services или
системах сборки, где требуется явно контролировать время восстановления.
Когда завершится восстановление пакетов, запустите dotnet build . Эта команда запускает подсистему
сборки и создает исполняемый файл приложения. Теперь можно выполнить команду dotnet run , которая
запустит ваше приложение.
Весь код простого приложения Hello World размещается в файле Program.cs. Откройте этот файл в любом
текстовом редакторе. Сейчас мы внесем в него первые изменения. В верхней части файла вы видите
инструкцию using:
using System;
Эта инструкция указывает компилятору, что в области действия находятся все типы из пространства имен
System . Как и другие объектно ориентированные языки, с которыми вы могли работать ранее, C#
использует пространства имен для организации типов. В нашей программе Hello World все точно так же. Как
вы видите, программа заключена в пространство имен, имя которого соответствует имени текущего
каталога. В этом учебнике давайте изменим имя на TeleprompterConsole :
namespace TeleprompterConsole
Этот метод использует типы из двух новых пространств имен. Чтобы такая программа успешно
скомпилировалась, нужно добавить в начало файла следующие две строки:
using System.Collections.Generic;
using System.IO;
Запустите программу командой dotnet run и убедитесь в том, что все текстовые строки выводятся в
консоль.
Теперь следует изменить код обработки строк файла, добавив задержку после вывода каждого слова.
Замените инструкцию Console.WriteLine(line) в методе Main на такой блок кода:
Console.Write(line);
if (!string.IsNullOrWhiteSpace(line))
{
var pause = Task.Delay(200);
// Synchronously waiting on a task is an
// anti-pattern. This will get fixed in later
// steps.
pause.Wait();
}
Класс Task находится в пространства имен System.Threading.Tasks, поэтому эту инструкцию using нужно
добавить в верхней части файла:
using System.Threading.Tasks;
Запустите пример и проверьте выходные данные. Теперь слова появляются по одному и с задержками по
200 мс. Но пока с выводом сохраняются некоторые проблемы, поскольку в исходном текстовом файле есть
несколько строк длиной более 80 символов, и они выводятся без перевода строки. Это не очень удобно
читать с прокруткой. Но эту проблему легко исправить. Вам нужно лишь отслеживать длину каждой строки
и создавать перевод строки каждый раз, когда эта длина достигает определенного порога. После объявления
words в методе ReadFrom объявите локальную переменную для хранения длины строки:
var lineLength = 0;
Теперь добавьте следующий код после инструкции yield return word + " "; (перед закрывающей фигурной
скобкой):
lineLength += word.Length + 1;
if (lineLength > 70)
{
yield return Environment.NewLine;
lineLength = 0;
}
Асинхронные задачи
И на последнем этапе мы добавим код, который позволяет выполнять две асинхронные задачи, одна из
которых — вывод текста, а вторая — ожидание ввода от пользователя для ускорения, замедления или
прекращения вывода текста. Этот этап разделяется на несколько шагов, по завершении которых вы
получите все необходимые обновления. Первым шагом является создание асинхронной задачи (Task),
которая возвращает метод с тем кодом, который вы создали ранее для чтения и отображения файла.
Добавьте следующий метод в класс Program . Этот текст основан на тексте метода Main :
Но вы можете заметить два изменения. Во-первых, в тексте нет вызова Wait(), который в синхронном
режиме ожидает завершения задачи. Вместо него в этой версии используется ключевое слово await . Чтобы
это работало, в сигнатуру метода нужно добавить модификатор async . Этот метод возвращает Task .
Обратите внимание, что здесь нет инструкции для возвращения объекта Task . Вместо этого объект Task
создается в коде, который компилятор предоставляет в точке использования оператора await . Представьте,
что метод завершает выполнение при достижении await . Он возвращает Task в знак того, что работа еще
не завершена. Метод возобновит свою работу, когда завершится ожидаемая задача. Когда работа метода
завершится, это будет отражено в возвращаемом объекте Task . Вызывающий код может отслеживать
состояние полученного Task , чтобы определить момент завершения метода.
Теперь наш новый метод можно вызвать из метода Main :
ShowTeleprompter().Wait();
Здесь, в методе Main , код синхронно ожидает завершения. Всегда, когда это возможно, следует
использовать оператор await вместо синхронного ожидания. Но в методе Main консольного приложения
запрещено использовать оператор await . В противном случае приложение завершит работу раньше, чем
выполнит все свои задачи.
NOTE
При использовании C# 7.1 или более поздней версии консольные приложения можно создавать с помощью метода
async Main .
Теперь следует создать второй асинхронный метод, который позволяет считывать данные ввода из консоли
и реагировать на клавиши "<" (меньше), ">" (больше) и "X" или "x". Для выполнения этой задачи добавьте
такой метод:
Здесь создается лямбда-выражение, представляющее делегат Action, который считывает нажатие клавиши
из консоли и изменяет локальную переменную с длительностью задержки в том случае, если пользователь
нажал клавишу "<" (меньше) или ">" (больше). Выполнение метода делегата можно завершить, нажав
клавишу "X" или "x". Таким образом пользователь может в любой момент прекратить отображение текста.
Этот метод использует метод ReadKey(), чтобы блокировать выполнение и ожидать нажатия клавиши.
Чтобы завершить создание этой функции, нам нужна новая инструкция async Task , которая вернет метод,
запускающий обе задачи ( GetInput и ShowTeleprompter ) и управляющий обменом данными между этими
задачами.
Пришло время создать класс, который может обрабатывать совместное использование данных двумя
задачами. Этот класс содержит два открытых свойства: delay (задержка) и флаг Done , который означает, что
файл прочитан полностью:
namespace TeleprompterConsole
{
internal class TelePrompterConfig
{
public int DelayInMilliseconds { get; private set; } = 200;
Поместите этот класс в отдельный новый файл и заключите в пространство имен TeleprompterConsole , как
показано выше. Также следует добавить оператор using static , чтобы можно было ссылаться на методы
Min и Max без указания имени внешнего класса или пространства имен. Оператор using static
импортирует методы из одного класса. В этом она отличается от использованной ранее инструкции using ,
которая импортирует все классы из пространства имен.
Теперь вам нужно обновить методы ShowTeleprompter и GetInput для использования нового объекта
config . И еще одна инструкция Task , которая возвращает метод async , запускающий обе задачи и
завершающий работу после окончания первой задачи:
Новым методом здесь является WhenAny(Task[]). Этот метод создает задачу ( Task ), которая завершается
сразу, как только завершится любая из задач в списке аргументов.
Теперь вам нужно обновить методы ShowTeleprompter и GetInput , чтобы они использовали объект config
для задержки:
private static async Task ShowTeleprompter(TelePrompterConfig config)
{
var words = ReadFrom("sampleQuotes.txt");
foreach (var word in words)
{
Console.Write(word);
if (!string.IsNullOrWhiteSpace(word))
{
await Task.Delay(config.DelayInMilliseconds);
}
}
config.SetDone();
}
Новая версия метода ShowTeleprompter вызывает новый метод из класса TeleprompterConfig . Сейчас нужно
изменить метод Main , чтобы вместо ShowTeleprompter он вызывал RunTeleprompter :
RunTeleprompter().Wait();
Заключение
В этом учебнике мы продемонстрировали вам ряд функций языка C# и библиотек .NET Core, связанных с
работой в консольных приложениях. На основе полученных знаний вы сможете развивать свои
представления о языке и представленных здесь классах. Вы увидели базовые примеры использования
файлового и консольного ввода-вывода, асинхронного программирования на основе задач с блокировкой и
без блокировки. Вы получили информацию о языке C#, структуре программ на C#, а также об интерфейсе
командной строки и средствах .NET Core.
Дополнительные сведения о файловом вводе-выводе см. в статье Файловый и потоковый ввод-вывод.
Дополнительные сведения о модели асинхронного программирования, используемой в учебнике, см. в
статьях Асинхронное программирование на основе задач и Асинхронное программирование.
Клиент REST
23.10.2019 • 26 minutes to read • Edit Online
Вступление
Это руководство раскроет для вас некоторые возможности .NET Core и языка C#. Вы узнаете:
работа с интерфейсом командной строки (CLI) в .NET Core;
обзор возможностей языка C#;
управление зависимостями с помощью NuGet;
взаимодействие по протоколу HTTP;
обработка информации в формате JSON;
управление конфигурацией с помощью атрибутов.
Вам предстоит создать приложение, которое отправляет запросы HTTP к службе REST на GitHub. Вы будете
считывать данные в формате JSON и преобразовывать полученный пакет JSON в объекты C#. И наконец,
вы увидите примеры работы с объектами C#.
В этом руководстве описано множество функций. Давайте начнем поочередно разбирать их.
Если вы хотите поработать с примером окончательного кода в этом разделе, вы можете его загрузить.
Инструкции по загрузке см. в разделе Просмотр и скачивание примеров.
Предварительные требования
Компьютер должен быть настроен для выполнения .NET Core. Инструкции по установке см. на странице
скачиваемых файлов .NET Core. Это приложение можно запустить в ОС Windows, Linux, macOS или в
контейнере Docker. Вам потребуется редактор кода, но вы можете выбрать любой привычный для вас. В
примерах ниже используется кроссплатформенный редактор Visual Studio Code с открытым исходным
кодом. Вы можете заменить его на любое другое средство, с которым вам удобно работать.
Создание приложения
Первым шагом является создание нового приложения. Откройте командную строку и создайте новый
каталог для приложения. Перейдите в этот каталог. В командной строке введите команду dotnet new console .
Эта команда создает начальный набор файлов для базового приложения Hello World. Так как это новый
проект, зависимостей нет на месте, поэтому при первом запуске будет скачана платформа .NET Core,
установлен сертификат разработки и запущен диспетчер пакетов NuGet для восстановления отсутствующих
зависимостей.
Прежде чем вносить изменения, введите dotnet run (см. примечание) в командной строке, чтобы запустить
приложение. dotnet run автоматически выполняет dotnet restore при отсутствии зависимостей в вашей
среде. Он также выполнит dotnet build , если приложение необходимо пересобрать. После первоначальной
настройки будет достаточно запуска dotnet restore или dotnet build , когда это имеет смысл для вашего
проекта.
<Project Sdk="Microsoft.NET.Sdk">
<ItemGroup>
<PackageReference Include="System.Runtime.Serialization.Json" Version="4.3.0" />
</ItemGroup>
Большинство редакторов кода предлагает автозавершение для различных версий этих библиотек. Обычно
разработчики предпочитают использовать последнюю версию каждого добавляемого пакета. Но при этом
важно убедиться в том, что версии всех пакетов соответствуют друг другу и версии платформы приложений
.NET Core.
После внесения этих изменений снова запустите команду dotnet restore (см. примечание), которая
установит на компьютер нужный пакет.
Выполнение веб-запросов
Теперь вы готовы получать данные из Интернета. В этом приложении вы будете считывать информацию из
API GitHub. Давайте, например, получим информацию о проектах под зонтичным брендом .NET Foundation.
Для начала вам нужно составить запрос к API GitHub для получения информации о проектах. Используемая
конечная точка: https://api.github.com/orgs/dotnet/repos. Вы будете получать все данные об этих проектах,
поэтому используйте запрос HTTP GET. Браузер также использует запросы HTTP GET, поэтому вы можете
указать этот URL -адрес в адресной строке браузера и увидеть, какие сведения вы будете получать и
обрабатывать.
Для выполнения веб-запросов используется класс HttpClient. Как и все современные API-интерфейсы .NET,
HttpClient поддерживает для API-интерфейсов длительного выполнения только асинхронные методы.
Поэтому вам нужно создать асинхронный метод. Реализацию вы будете добавлять постепенно, по мере
создания функций приложения. Сначала откройте файл program.cs , расположенный в каталоге проекта, и
добавьте в класс Program следующий метод:
В верхней части метода Main нужно разместить инструкцию using , чтобы компилятор C# распознал тип
Task:
using System.Threading.Tasks;
Если сейчас вы выполните сборку проекта, то получите предупреждение для этого метода, поскольку он не
содержит ни одного оператора await , и поэтому будет выполняться синхронно. Пока это предупреждение
можно игнорировать. Операторы await здесь появятся по мере заполнения метода.
А пока переименуйте пространство имен, определенное в инструкции namespace , указав имя WebAPIClient
вместо имени по умолчанию ConsoleApp . Позднее в этом пространстве имен мы определим класс repo .
Теперь измените метод Main , добавив вызов этого метода. Метод ProcessRepositories возвращает задачу,
до завершения которой выполнение программы не должно прекращаться. Чтобы обеспечить это,
используйте метод Wait , который блокирует программу и ожидает завершения задачи:
Теперь у вас есть программа, которая работает в асинхронном режиме, но не выполняет никаких действий.
Давайте улучшим ее.
Для начала нужен объект, который может извлечь данные из Интернета. Для этого подойдет HttpClient. Он
будет обрабатывать запрос и ответы на него. Создайте один экземпляр этого типа в классе Program из файла
Program.cs.
namespace WebAPIClient
{
class Program
{
private static readonly HttpClient client = new HttpClient();
Чтобы эта версия успешно компилировалась, необходимо добавить две новые инструкции using в верхней
части файла:
using System.Net.Http;
using System.Net.Http.Headers;
Эта первая версия метода выполняет веб-запрос для чтения списка всех репозиториев в организации dotnet
foundation. (На сайте gitHub организация .NET Foundation имеет идентификатор dotnet.) Первые несколько
строк настраивают HttpClient для этого запроса. Сначала мы указываем, что он будет принимать ответы
GitHub JSON. Они возвращаются в простом формате JSON. Следующая строка добавляет заголовок User
Agent ко всем запросам от этого объекта. Кодом сервера GitHub проверяет эти два заголовка, и их наличие
необходимо для извлечения сведений из GitHub.
Когда вы настроите объект HttpClient, можно выполнить веб-запрос и получить ответ. В первой версии
используйте удобный метод HttpClient.GetStringAsync(String). Этот метод запускает задачу для выполнения
веб-запроса, а при получении результатов запроса он считывает поток ответа и извлекает из него
содержимое. Текст ответа возвращается в формате String. Эта строка становится доступна после завершения
задачи.
Последние две строки этого метода ожидают выполнения созданной задачи, а затем выводят ответ в
консоль. Выполните сборку приложения и запустите его. Предупреждение при сборке теперь не
отображается, поскольку в методе ProcessRepositories есть оператор await . В ответ вы получите длинный
текст в формате JSON.
using System;
namespace WebAPIClient
{
public class repo
{
public string name;
}
}
Поместите приведенный выше код в новый файл с именем repo.cs. Эта версия класса реализует простейший
способ обработки данных JSON. Имя класса и имя элемента совпадают с именами, используемыми в пакете
JSON, и не соответствуют соглашениям C# об именовании. Чтобы исправить это, мы позже добавим
некоторые атрибуты конфигурации. Этот класс демонстрирует еще одну важную возможность
сериализации и десериализации JSON — не все поля в пакете JSON входят в этот класс. Сериализатор
JSON просто игнорирует информацию, которая не входит в используемый тип класса. Такое поведение
позволяет легко создавать типы, работающие с любым подмножеством полей из пакета JSON.
Теперь давайте выполним десериализацию созданного типа. Вам потребуется создать объект
DataContractJsonSerializer. Этот объект должен знать тип среды CLR, которая ожидается для полученного
пакета JSON. Пакет, получаемый от GitHub, содержит последовательность репозиториев, и правильным
типом в этом случае будет List<repo> . Добавьте следующую строку в метод ProcessRepositories :
using System.Collections.Generic;
using System.Runtime.Serialization.Json;
Теперь вызовите сериализатор для преобразования пакета JSON в объекты C#. В методе
ProcessRepositories замените вызов GetStringAsync( String) следующими двумя строками.
var streamTask = client.GetStreamAsync("https://api.github.com/orgs/dotnet/repos");
var repositories = serializer.ReadObject(await streamTask) as List<repo>;
Скомпилируйте и запустите приложение. Вы получите имена всех репозиториев, которые являются частью
.NET Foundation.
Управление сериализацией
Прежде чем добавлять новые возможности, давайте разберемся с типом repo , чтобы он соответствовал
стандартным соглашениям C#. Для этого добавьте к объявлению типа repo атрибуты, которые управляют
работой сериализатора JSON. В вашем примере эти атрибуты будут определять сопоставление между
именами ключей в пакете JSON и именами классов и членов C#. Вам понадобятся два атрибута:
DataContractAttribute и DataMemberAttribute. По соглашению, все классы атрибутов завершаются
суффиксом Attribute . Но этот суффикс не обязательно указывать, когда вы применяете атрибуты.
Атрибуты DataContractAttribute и DataMemberAttribute атрибуты находятся в другой библиотеке, и эту
библиотеку необходимо добавить в файл проекта C# в качестве зависимости. Добавьте следующую строку в
раздел <ItemGroup> файла проекта:
Сохраните файл и выполните команду dotnet restore (см. примечание), чтобы получить этот пакет.
Теперь откройте файл repo.cs . Здесь мы хотим изменить имя, чтобы слово Repository было представлено в
нем целиком и с капитализацией в стиле Pascal. Но при этом узлы repo из JSON должны по-прежнему
сопоставляться с этим типом, так что вам нужно добавить атрибут DataContractAttribute к объявлению
класса. Для этого атрибута должно быть задано свойство Name с именем узлов JSON, которые будут
сопоставлены с этим типом:
[DataContract(Name="repo")]
public class Repository
using System.Runtime.Serialization;
Имя класса изменилось с repo на Repository , а значит аналогичные изменения нужно внести в файл
Program.cs (некоторые редакторы поддерживают рефакторинг путем переименования, при котором все
нужные изменения будут применены автоматически).
// ...
Теперь выполните такие же преобразования для поля name в классе DataMemberAttribute. В файле repo.cs
внесите следующие изменения в объявление name :
[DataMember(Name="name")]
public string Name;
Это изменение требует также изменить код в файле program.cs, который записывает имя каждого
хранилища:
Console.WriteLine(repo.Name);
Выполните dotnet build , а затем dotnet run , чтобы проверить правильность сопоставления. Результат
выполнения должен быть такой же, как прежде. Прежде чем перейти к обработке дополнительных свойств,
полученных от веб-сервера, давайте внесем еще одно изменение в класс Repository . Член Name является
общедоступным полем. Это плохо соотносится с рекомендациями по объектно-ориентированному
программированию, поэтому вместо него мы будем использовать свойство. В нашем примере пока не
требуется выполнять специальный код при получении или задании свойства, но благодаря такому
изменению нам позднее будет проще добавить такой код, не переделывая вызовы класса Repository .
Удалите определение поля и замените его автоматически реализуемым свойством:
Компилятор создает текст акцессоров get и set , а также закрытое поле для хранения имени. Этот код
будет выглядеть примерно так, как показано ниже. Вы также можете ввести такой код вручную:
Компилятор создает в качестве выходных данных объект Task<T> , поскольку этот метод обозначен
ключевым словом async . Теперь мы изменим метод Main так, чтобы он записывает эти результаты и
выводил в консоль имя каждого репозитория. Метод Main теперь выглядит следующим образом:
Доступ к свойству Result блокируется, пока не завершится созданная задача. Было бы лучше использовать
await , чтобы подождать завершения задачи, как мы сделали это в методе ProcessRepositories , но это не
допускается для метода Main .
[DataMember(Name="description")]
public string Description { get; set; }
[DataMember(Name="html_url")]
public Uri GitHubHomeUrl { get; set; }
[DataMember(Name="homepage")]
public Uri Homepage { get; set; }
[DataMember(Name="watchers")]
public int Watchers { get; set; }
Для этих свойств применяются встроенные преобразования из строкового типа (который используется в
пакетах JSON ) в целевой тип. Возможно, с типом Uri вы пока не знакомы. Он представляет универсальный
код ресурса, например URL -адрес, как в нашем примере. Если вы используете типы Uri и int , а пакет
JSON содержит данные, для которых невозможно выполнить преобразование в целевой тип, действие
сериализации создает исключение.
Добавив новые типы, включите их в метод Main :
2016-02-08T21:27:00Z
Он не соответствует ни одному из стандартных форматов .NET для типа DateTime. Поэтому нам нужен
специализированный метод для преобразования. Кроме того, нежелательно предоставлять пользователям
класса Repository доступ к необработанным строковым данным. И здесь нам снова помогут атрибуты. Во-
первых, определите свойство private , которое будет содержать строковое представление даты и времени в
классе Repository :
[DataMember(Name="pushed_at")]
private string JsonDate { get; set; }
Атрибут DataMemberAttribute сообщает сериализатору, что эти данные нужно обрабатывать, поскольку по
умолчанию обрабатываются только открытые члены. Во-вторых, создайте открытое свойство только для
чтения, которое преобразует строку в допустимый объект DateTime и возвращает этот объект DateTime.
[IgnoreDataMember]
public DateTime LastPush
{
get
{
return DateTime.ParseExact(JsonDate, "yyyy-MM-ddTHH:mm:ssZ", CultureInfo.InvariantCulture);
}
}
Вам осталось только добавить одну инструкцию вывода данных в консоль, и можно будет заново собрать и
запустить приложение:
Console.WriteLine(repo.LastPush);
Заключение
В этом руководстве вы увидели, как правильно выполнять веб-запросы, анализировать результаты и
отображать полученные в них свойства. Вы также добавили в свой проект несколько новых пакетов в
качестве зависимостей. Еще вы увидели примеры некоторых функций языка C#, которые поддерживают
объектно-ориентированный подход.
NOTE
Начиная с пакета SDK для .NET Core 2.0 нет необходимости выполнять команду dotnet restore , так как она
выполняется неявно всеми командами, которые требуют восстановления, например dotnet new , dotnet build и
dotnet run . Эту команду по-прежнему можно использовать в некоторых сценариях, где необходимо явное
восстановление, например в сборках с использованием непрерывной интеграции в Azure DevOps Services или
системах сборки, где требуется явно контролировать время восстановления.
Наследование в C# и .NET
04.11.2019 • 41 minutes to read • Edit Online
Предварительные требования
В этом руководстве предполагается, что вы уже установили пакет SDK для .NET Core. Чтобы скачать его,
перейдите на страницу скачиваемых файлов .NET Core. Также вам потребуется редактор кода. В этом
руководстве используется Visual Studio Code, но вы можете использовать любой другой редактор на свой
выбор.
Выполнение примеров
Чтобы создать и запустить примеры, представленные в этом руководстве, используйте служебную
программу dotnet, выполняемую из командной строки. Выполните следующие действия для каждого
примера.
1. Создайте каталог для хранения примера.
2. Введите в командной строке команду dotnet new console, чтобы создать новый проект .NET Core.
3. Скопируйте код примера и вставьте его в файл с помощью редактора кода.
4. Введите в командной строке команду dotnet restore, чтобы загрузить или восстановить зависимости
проекта.
NOTE
Начиная с пакета SDK для .NET Core 2.0 нет необходимости выполнять команду dotnet restore , так как она
выполняется неявно всеми командами, которые требуют восстановления, например dotnet new , dotnet build и
dotnet run . Эту команду по-прежнему можно использовать в некоторых сценариях, где необходимо явное
восстановление, например в сборках с использованием непрерывной интеграции в Azure DevOps Services или
системах сборки, где требуется явно контролировать время восстановления.
using System;
public class A
{
private int value = 10;
public class B : A
{
public int GetValue()
{
return this.value;
}
}
}
public class C : A
{
// public int GetValue()
// {
// return this.value;
// }
}
public class A
{
public void Method1()
{
// Method implementation.
}
}
public class B : A
{ }
public class A
{
public void Method1()
{
// Do something.
}
}
public class B : A
{
public override void Method1() // Generates CS0506.
{
// Do something else.
}
}
В некоторых случаях производный класс обязан переопределять реализацию базового класса. Члены
базового класса, отмеченные ключевым словом abstract (абстрактный), обязательно должны
переопределяться в производных классах. При попытке компиляции следующего примера возникнет
ошибка компилятора CS0534, "<class> не реализует наследуемый абстрактный член <member>", поскольку
класс B не предоставляет реализации для A.Method1 .
public abstract class A
{
public abstract void Method1();
}
Наследование применяется только для классов и интерфейсов. Другие категории типов (структуры, делегаты
и перечисления) не поддерживают наследование. Из-за этих правил попытка компиляции кода из
следующего примера приводит к ошибке компилятора CS0527: "Тип 'ValueType' в списке интерфейсов не
является интерфейсом". Такое сообщение об ошибке означает, что наследование не поддерживается,
несмотря на возможность определить интерфейсы, реализуемые в структуре.
using System;
Неявное наследование
Помимо тех типов, которые наследуются через механизм одиночного наследования, все типы в системе
типов .NET неявно наследуются от типа Object или его производного типа. Общие функции Object доступны
любому типу.
Чтобы продемонстрировать неявное наследование, давайте определим новый класс SimpleClass ,
определение которого будет пустым.
После этого с помощью отражения (которое позволяет проверить метаданные типа для получения сведений
о нем) мы получим список членов, принадлежащих типу SimpleClass . Выходные данные этого примера
возвращают нам девять членов класса SimpleClass , хотя мы не определяли ни один из них. Один из членов
является вызываемым без параметров конструктором по умолчанию, который автоматически
предоставляется для типа SimpleClass компилятором C#. Остальные восемь являются членами типа Object,
от которого неявным образом наследуются все классы и интерфейсы в системе типов .NET.
using System;
using System.Reflection;
}
}
}
// The example displays the following output:
// Type SimpleClass has 9 members:
// ToString (Method): Public, Declared by System.Object
// Equals (Method): Public, Declared by System.Object
// Equals (Method): Public Static, Declared by System.Object
// ReferenceEquals (Method): Public Static, Declared by System.Object
// GetHashCode (Method): Public, Declared by System.Object
// GetType (Method): Public, Declared by System.Object
// Finalize (Method): Internal, Declared by System.Object
// MemberwiseClone (Method): Internal, Declared by System.Object
// .ctor (Constructor): Public, Declared by SimpleClass
Неявное наследование от класса Object делает доступными для класса SimpleClass следующие методы.
Открытый метод ToString , который преобразует объект SimpleClass в строковое представление,
возвращает полное имя типа. В нашем примере метод ToString возвращает строку SimpleClass.
Три метода, которые проверяют равенство двух объектов: открытый метод экземпляра Equals(Object)
, открытый статический метод Equals(Object, Object) и открытый статический метод
ReferenceEquals(Object, Object) . По умолчанию эти методы проверяют ссылочное равенство. Это
означает, что две переменные, содержащие объекты, должны ссылаться на один и тот же объект,
чтобы считаться равными.
Открытый метод GetHashCode , который вычисляет значение, позволяющее использовать экземпляр
типа в хэшированных коллекциях.
Открытый метод GetType , который возвращает объект Type, представляющий тип SimpleClass .
Защищенный метод Finalize, который должен освобождать неуправляемые ресурсы перед тем, как
сборщик мусора освободит память объекта.
Защищенный метод MemberwiseClone, который создает неполную копию текущего объекта.
Неявное наследование позволяет вызвать любой наследуемый член объекта SimpleClass точно так же, как
если бы он был определен в самом классе SimpleClass . Например, следующий пример вызывает метод
SimpleClass.ToString , который SimpleClass наследует от Object.
using System;
В следующей таблице перечислены категории типов, которые можно создавать на языке C#, и указаны типы,
от которых они неявно наследуют. Каждый из базовых типов предоставляет всем типам, которые неявно
наследуют от него, разные наборы членов.
class Object
Обратите внимание, что связь "является" выражает также связь между типом и конкретным экземпляром
этого типа. В следующем примере представлен класс Automobile с тремя уникальными свойствами только
для чтения: Make определяет производителя автомобиля, Model определяет тип автомобиля, а Year — год
выпуска. Этот класс Automobile также содержит конструктор, аргументы которого назначаются значениям
свойств. Еще в нем переопределен метод Object.ToString, который теперь возвращает строку, однозначно
определяющую экземпляр Automobile , а не класс Automobile .
using System;
if (model == null)
throw new ArgumentNullException("The model cannot be null.");
else if (String.IsNullOrWhiteSpace(model))
throw new ArgumentException("model cannot be an empty string or have space characters only.");
Model = model;
Основанную на наследовании связь "является" лучше всего использовать для базовых классов и
производных классов, которые добавляют дополнительные члены или используют дополнительные
функции, отсутствующие в базовом классе.
Должны ли производные классы наследовать реализацию членов базового класса? Или же они могут
переопределить реализацию базового класса? Или они должны предоставлять реализацию?
Используйте ключевое слово abstract, чтобы производные классы принудительно предоставляли
реализацию. Чтобы производные классы могли переопределять методы базового класса, используйте
ключевое слово virtual. По умолчанию методы, определенные в базовом классе, переопределять
нельзя.
Класс Publication не имеет методов abstract , но сам класс помечен как abstract .
Будет ли производный класс последним в иерархии наследования (то есть его нельзя будет
использовать в качестве базового класса для дополнительных производных классов)? По умолчанию
любой класс можно использовать в качестве базового класса. Если указать ключевое слово sealed
(запечатан), то класс нельзя будет использовать как базовый класс для дополнительных производных
классов. При попытке наследовать запечатанный класс создается ошибка компилятора CS0509:
"нельзя наследовать от запечатанного типа <имя_типа>".
В вашем примере обозначьте производный класс как sealed .
В следующем примере представлен исходный код для класса Publication , а также перечисление
PublicationType , возвращаемое свойством Publication.PublicationType . Помимо элементов, которые он
наследует от Object, класс Publication определяет и переопределяет следующие члены.
using System;
if (title == null)
throw new ArgumentNullException("The title cannot be null.");
else if (String.IsNullOrWhiteSpace(title))
throw new ArgumentException("The title cannot consist only of white space.");
Title = title;
Type = type;
}
Конструктор
Поскольку класс Publication имеет обозначение abstract , для него нельзя напрямую создать
экземпляр из кода, как в следующем примере:
Но при этом его конструктор для создания экземпляров можно напрямую вызвать из конструкторов
производных классов, как показано в исходном коде для класса Book .
Два свойства, относящиеся к публикации
Свойство Title доступно только для чтения и имеет тип String. Его значение предоставляется путем
вызова конструктора Publication .
Свойство Pages доступно для чтения и записи и имеет тип Int32. Значение этого свойства показывает,
сколько всего страниц имеет эта публикация. Это значение хранится в скрытом поле с именем
totalPages . В качестве значения принимается положительное число. В противном случае создается
исключение ArgumentOutOfRangeException.
Члены, связанные с издателем
Два свойства только для чтения: Publisher и Type . Эти значения изначально предоставляются путем
вызова конструктора класса Publication .
Элементы, связанные с публикацией
Два метода, Publish и GetPublicationDate , которые устанавливают и возвращают дату публикации.
Метод Publish устанавливает закрытый флаг published в значение true и присваивает переданную
ему дату в качестве аргумента для закрытого поля datePublished . Метод GetPublicationDate
возвращает строку "NYP", если флаг published имеет значение false , или значение поля
datePublished , если флаг имеет значение true .
Если метод Object.ToString не переопределяется в типе, он возвращает полное имя типа, которое не
позволяет отличать экземпляры друг от друга. Класс Publication переопределяет метод
Object.ToString, чтобы он возвращал значение свойства Title .
Следующий рисунок иллюстрирует связь между базовым классом Publication и неявно унаследованным от
него классом Object.
Класс Book
Класс Book представляет книгу как специализированный тип публикации. В следующем примере показан
исходный код класса Book .
using System;
public Book(string title, string isbn, string author, string publisher) : base(title, publisher,
PublicationType.Book)
{
// isbn argument must be a 10- or 13-character numeric string without "-" characters.
// We could also determine whether the ISBN is valid by comparing its checksum digit
// with a computed checksum.
//
if (! String.IsNullOrEmpty(isbn)) {
// Determine if ISBN length is correct.
if (! (isbn.Length == 10 | isbn.Length == 13))
throw new ArgumentException("The ISBN must be a 10- or 13-character numeric string.");
ulong nISBN = 0;
if (! UInt64.TryParse(isbn, out nISBN))
throw new ArgumentException("The ISBN can consist of numeric characters only.");
}
ISBN = isbn;
Author = author;
}
if (currency.Length != 3)
throw new ArgumentException("The ISO currency symbol is a 3-character string.");
Currency = currency;
return oldValue;
}
public override string ToString() => $"{(String.IsNullOrEmpty(Author) ? "" : Author + ", ")}{Title}";
}
Помимо элементов, которые он наследует от Publication , класс Book определяет и переопределяет
следующие члены.
Два конструктора
Два конструктора Book используют три общих параметра. Два из них, header и publisher,
соответствуют параметрам конструктора Publication . Третий — это author, который хранится в
общедоступном неизменяемом свойстве Author . Один конструктор использует параметр isbn,
который хранится в автосвойстве ISBN .
Первый конструктор использует ключевое слово this для вызова второго конструктора. Создание
цепочки конструкторов — это обычный метод определения конструкторов. Конструкторы с меньшим
числом параметров используют значения по умолчанию, вызывая конструкторы с большим числом
параметров.
Второй конструктор использует ключевое слово base, чтобы передать заголовок и имя издателя в
конструктор базового класса. Если вы не используете явный вызов конструктора базового класса в
исходном коде, компилятор C# автоматически добавляет вызов конструктора по умолчанию (без
параметров) для базового класса.
Свойство ISBN , доступное только для чтения, которое возвращает международный стандартный
номер книги (уникальное 10- или 13-значное число) для объекта Book . Номер ISBN передается в
качестве аргумента одному из конструкторов Book . Он сохраняется в закрытом резервном поле,
автоматически создаваемым компилятором.
Свойство Author , доступное только для чтения. Имя автора передается в качестве аргумента обоим
конструкторам Book и сохраняется в свойстве.
Два свойства, Price и Currency , с информацией о цене, доступные только для чтения. Значения этих
свойств передаются в качестве аргументов при вызове метода SetPrice . Свойство Currency содержит
трехзначное обозначение валюты по стандарту ISO (например, USD обозначает доллар США).
Обозначение валюты по стандарту ISO можно получить из свойства ISOCurrencySymbol. Оба эти
свойства доступны для чтения извне, но их можно задать в коде в классе Book .
Метод SetPrice , который задает значения для свойств Price и Currency . Эти значения
возвращаются теми же свойствами.
Переопределение метода ToString , унаследованного от Publication , а также методов
Object.Equals(Object) и GetHashCode, унаследованных от Object.
Если метод Object.Equals(Object) не переопределен, он проверяет ссылочное равенство. Это означает,
что две объектные переменные считаются равными, если ссылаются на один и тот же объект. С
другой стороны, в классе Book два объекта Book должны считаться равными, если они имеют
одинаковые номера ISBN.
Переопределив метод Object.Equals(Object), необходимо также переопределить метод GetHashCode,
который возвращает значение, используемое средой выполнения для хранения элементов в
хэшированных коллекциях для быстрого извлечения. Возвращаемое значение хэш-кода должно
согласовываться с проверкой на равенство. Поскольку теперь новый метод Object.Equals(Object)
возвращает true , если у двух объектов Book равны свойства ISBN, при расчете хэш-кода вы будете
вызывать метод GetHashCode для строки, полученной из свойства ISBN .
Следующий рисунок иллюстрирует связь между классом Book и классом Publication , который является для
него базовым.
Теперь вы можете создавать экземпляры объекта Book , вызывать его уникальные и унаследованные члены,
а также передавать его в качестве аргумента в любой метод, принимающий параметры типа Publication
или Book , как показано в следующем примере.
using System;
using static System.Console;
var book2 = new Book("The Tempest", "Classic Works Press", "Shakespeare, William");
Write($"{book.Title} and {book2.Title} are the same publication: " +
$"{((Publication) book).Equals(book2)}");
}
Теперь вы можете создать несколько классов, производных от Shape , которые будут представлять разные
геометрические фигуры. В следующем примере определяются три класса: Triangle , Rectangle и Circle . В
каждом из них используются уникальные формулы для вычисления площади и периметра, соответствующие
типу фигуры. Также некоторые из производных классов определяют дополнительные свойства, например
Rectangle.Diagonal и Circle.Diameter , которые уникальны для фигуры, представляемой этим классом.
using System;
Следующий пример использует объекты, производные от Shape . Он создает массив объектов, производных
от Shape , и вызывает статические методы для класса Shape , которые служат оболочкой для обращения к
значениям свойств Shape . Среда выполнения извлекает значения переопределенных свойств для
производных типов. Также в этом примере каждый объект Shape из созданного массива приводится к
производному типу. Если это приведение выполняется успешно, выполняется обращение к свойствам,
определенным для конкретного подкласса базового класса Shape .
using System;
См. также
Классы и объекты
Наследование (руководство по программированию на C#)
Работа с LINQ
04.11.2019 • 26 minutes to read • Edit Online
Вступление
В этом руководстве описаны возможности .NET Core и C#. Вы узнаете:
как создать последовательности с помощью LINQ;
как написать методы, которые можно применять в запросах LINQ;
как различать упреждающее и отложенное вычисление.
Вы освоите эти методы на примере приложения, которое демонстрирует один из основных навыков любого
иллюзиониста: тасовка по методу фаро. Так называют метод тасовки, при котором колода делится ровно на
две части, а затем собирается заново так, что карты из каждой половины следуют строго поочередно.
Этот метод очень удобен для иллюзионистов, поскольку положение каждой карты после каждой тасовки
точно известно, и через несколько циклов порядок карт восстанавливается.
Здесь же он используется в качестве не слишком серьезного примера для процессов управления
последовательностями данных. Приложение, которое вы создадите, будет моделировать колоду карт и
выполнять для них серию тасовок, выводя новый порядок карт после каждой из них. Вы сможете сравнить
новый порядок карт с исходным.
Это руководство описывает несколько шагов. После каждого из них вы сможете запустить приложение и
оценить результаты. Готовый пример доступен в репозитории dotnet/samples на сайте GitHub. Инструкции
по загрузке см. в разделе Просмотр и скачивание примеров.
Предварительные требования
Компьютер должен быть настроен для выполнения .NET Core. Инструкции по установке см. на странице
Загрузка.NET Core. Это приложение можно запустить в ОС Windows, Ubuntu Linux, OS X или в контейнере
Docker. Вам потребуется редактор кода, но вы можете выбрать любой привычный для вас. В примерах ниже
используется кроссплатформенный редактор Visual Studio Code с открытым исходным кодом. Вы можете
заменить его на любое другое средство, с которым вам удобно работать.
Создание приложения
Первым шагом является создание нового приложения. Откройте командную строку и создайте новый
каталог для приложения. Перейдите в этот каталог. В командной строке введите команду dotnet new console
. Эта команда создает начальный набор файлов для базового приложения Hello World.
Если вы раньше никогда не работали с C#, изучите структуру программы C# по этому руководству. Мы
рекомендуем сначала ознакомиться с ним, а затем вернуться сюда и продолжить изучение LINQ.
Если эти три строки (инструкции using ) находятся не в верхней части файла, наша программа не будет
компилироваться.
Теперь, когда у вас есть все необходимые ссылки, посмотрите, из чего состоит колода карт. Как правило, в
колоде игральных карт четыре масти и в каждой масти по тринадцать значений. Вы можете создать класс
Card сразу же и заполнить коллекцию объектами Card вручную. С помощью LINQ колоду карт можно
создать гораздо быстрее, чем обычным способом. Вместо класса Card вы можете создать две
последовательности, представляющие масти и ранги соответственно. Вы создадите два очень простых
метода итератора, которые будут создавать ранги и масти как IEnumerable<T> строк:
// Program.cs
// The Main() method
Разместите их под методом Main в вашем файле Program.cs . Оба эти два метода используют синтаксис
yield return , создавая последовательность по мере выполнения. Компилятор создаст объект, который
реализует интерфейс IEnumerable<T>, и сохранит в него последовательность строк по мере их получения.
Теперь с помощью этих методов итератора создайте колоду карт. Поместите запрос LINQ в метод Main .
Это описано ниже:
// Program.cs
static void Main(string[] args)
{
var startingDeck = from s in Suits()
from r in Ranks()
select new { Suit = s, Rank = r };
// Display each card that we've generated and placed in startingDeck in the console
foreach (var card in startingDeck)
{
Console.WriteLine(card);
}
}
Несколько выражений from создают запрос SelectMany, который формирует одну последовательность из
сочетаний каждого элемента первой последовательности с каждым элементом второй последовательности.
Для нашего примера важен порядок последовательности. Первый элемент первой последовательности
(масти) поочередно сочетается с каждым элементом второй последовательности (ранги). В итоге мы
получаем все тринадцать карт первой масти. Этот процесс повторяется для каждого элемента первой
последовательности (масти). Конечным результатом является колода карт, упорядоченная сначала по
мастям, а затем по достоинствам.
Важно помнить, что независимо от того, будете ли вы записывать LINQ в синтаксисе запросов, показанном
выше, или вместо этого будете использовать синтаксис методов, вы всегда можете перейти от одной формы
синтаксиса к другой. Приведенный выше запрос, записанный в синтаксисе запросов, можно записать в
синтаксис метода следующим образом:
var startingDeck = Suits().SelectMany(suit => Ranks().Select(rank => new { Suit = suit, Rank = rank }));
// Program.cs
public static void Main(string[] args)
{
var startingDeck = from s in Suits()
from r in Ranks()
select new { Suit = s, Rank = r };
// 52 cards in a deck, so 52 / 2 = 26
var top = startingDeck.Take(26);
var bottom = startingDeck.Skip(26);
}
В стандартной библиотеке нет метода для тасовки, которым можно было бы воспользоваться, поэтому вам
нужно написать собственный. Метод для тасовки, который вы создадите, иллюстрирует несколько приемов,
которые вы будете использовать в программах на основе LINQ, поэтому каждая часть этого процесса будет
описана в действиях.
Чтобы добавить некоторые функции для взаимодействия с IEnumerable<T>, который будет возвращен в
запросах LINQ, вам потребуется написать особые методы, называемые методами расширения. Короче
говоря, метод расширения представляет собой специализированный статический метод, добавляющий
новые функциональные возможности в уже имеющийся тип без изменения исходного типа, в который
необходимо добавить функциональные возможности.
Переместите методы расширения в другое расположение, добавив новый файл статического класса в
программу с именем Extensions.cs , а затем приступите к разработке первого метода расширения:
// Extensions.cs
using System;
using System.Collections.Generic;
using System.Linq;
namespace LinqFaroShuffle
{
public static class Extensions
{
public static IEnumerable<T> InterleaveSequenceWith<T>(this IEnumerable<T> first, IEnumerable<T>
second)
{
// Your implementation will go here soon enough
}
}
}
Как вы видите, для первого аргумента этого метода добавлен модификатор this . Это означает, что метод
вызывается так, как если бы он был методом-членом и имел тип, указанный для первого аргумента. Такое
объявление методов соответствует стандартному принципу, по которому для входа и выхода используется
тип IEnumerable<T> . Такая практика позволяет объединять методы LINQ в цепочку, чтобы создавать более
сложные запросы.
Очевидно, что так как вы разделили колоду на две части, их необходимо соединить. В коде это означает, что
необходимо перечислить обе последовательности, полученные с помощью Take и Skip за один раз,
применяя команду interleaving к элементам и создавая одну последовательность: колода карт, которая
тасуется сейчас. Чтобы создать метод LINQ, который работает с двумя последовательностями, важно
хорошо понимать принципы работы IEnumerable<T>.
Интерфейс IEnumerable<T> содержит один метод: GetEnumerator. Этот метод GetEnumerator возвращает
объект, у которого есть метод для перехода к следующему элементу и свойство, которое возвращает
текущий элемент в последовательности. С помощью этих двух членов вы выполните перебор всей
коллекции и получение элементов. Метод Interleave будет реализован как метод итератора, поэтому вы не
будете создавать и возвращать коллекцию, а примените описанный выше синтаксис yield return .
Так выглядит реализация этого метода:
Теперь, добавив в проект этот метод, вернитесь к методу Main и один раз перетасуйте колоду:
// Program.cs
public static void Main(string[] args)
{
var startingDeck = from s in Suits()
from r in Ranks()
select new { Suit = s, Rank = r };
Сравнения
Через сколько тасовок колода снова соберется в исходном порядке? Чтобы узнать это, вам нужно написать
метод, который проверяет равенство двух последовательностей. Создав такой метод, вы поместите код
тасовки колоды в цикл, в котором будете проверять, расположены ли карты в правильном порядке.
Метод, который сравнивает две последовательности, будет очень простым. По структуре он похож на метод,
который мы создали для тасовки колоды. Но теперь вместо команды yield return , которая возвращает
элементы, вы будете сравнивать элементы каждой последовательности. Если перечисление
последовательности завершилось и все элементы попарно совпадают, то последовательности считаются
одинаковыми:
return true;
}
Здесь мы видим в действии второй принцип LINQ: терминальные методы. Они принимают
последовательность в качестве входных данных (или две последовательности, как в нашем примере) и
возвращают скалярное значение. В цепочке методов для запроса LINQ терминальные методы всегда
используются последними, отсюда и название "терминальный".
Этот принцип мы применяем при определении того, находится ли колода в исходном порядке. Поместите
код тасовки в цикл, который будет останавливаться в том случае, когда порядок последовательности
восстановлен. Для проверки примените метод SequenceEquals() . Как вы уже поняли, этот метод всегда будет
последним в любом запросе, поскольку он возвращает одиночное значение, а не последовательность:
// Program.cs
static void Main(string[] args)
{
// Query for building the deck
var times = 0;
// We can re-use the shuffle variable from earlier, or you can make a new one
shuffle = startingDeck;
do
{
shuffle = shuffle.Take(26).InterleaveSequenceWith(shuffle.Skip(26));
} while (!startingDeck.SequenceEquals(shuffle));
Console.WriteLine(times);
}
Выполните код и обратите внимание на то, как выполняется переупорядочивание колоды при каждой
тасовке. После 8 тасовок (итераций цикла do-while), колода возвращается к исходной конфигурации, в
которой она находилась при создании из начального запроса LINQ.
Оптимизация
Пример, который вы создали к этому моменту, выполняет внутреннюю тасовку, то есть первая и
последняя карты колоды сохраняют свои позиции после каждой итерации. Давайте внесем одно изменение:
вместо этого мы будем использовать внешнюю тасовку, при которой все 52 карты изменяют свои позиции.
Для этого колоду нужно собирать так, чтобы первой картой в колоде стала первая карта из нижней
половины. Тогда самой нижней картой станет последняя карта из верхней половины колоды. Это простое
изменение в одной строке кода. Обновите текущий запрос тасовки, переключив положения Take и Skip. Это
поменяет местами нижнюю и верхнюю половины колоды:
shuffle = shuffle.Skip(26).InterleaveSequenceWith(shuffle.Take(26));
Снова запустите программу, и вы увидите, что для восстановления исходного порядка теперь требуется 52
итерации. Также вы могли обратить внимание, что по мере выполнения программы она заметным образом
замедляется.
Для этого есть сразу несколько причин. Вы можете решить одну из самых существенных причин спада
производительности — неэффективное использование отложенного вычисления.
Короче говоря, отложенное вычисление означает, что вычисление инструкции не выполняется, пока не
понадобится ее значение. Запросы LINQ — это инструкции, которые обрабатываются отложенным
образом. Последовательности создаются только тогда, когда происходит обращение к их элементам.
Обычно это дает LINQ огромное преимущество. Но в некоторых программах, таких как в нашем примере,
это приводит к экспоненциальному росту времени выполнения.
Помните, что мы создали исходную колоду с помощью запроса LINQ. Каждая последующая тасовка
выполняет три запроса LINQ к колоде, полученной на предыдущем этапе. И все эти запросы выполняются
отложенно. В частности, это означает, что запросы выполняются каждый раз при обращении к
последовательности. Таким образом, пока вы доберетесь до 52-й итерации, исходная колода будет заново
создана очень много раз. Чтобы наглядно это продемонстрировать, давайте создадим журнал выполнения.
Затем вы исправите эту проблему.
В вашем файле Extensions.cs введите или скопируйте приведенный ниже метод. Этот метод расширения
создает файл с именем debug.log в каталоге проекта и записывает в файл журнала, какой запрос
выполняется в данный момент. Этот метод расширения можно добавить к любому запросу, чтобы
зафиксировать его выполнение.
return sequence;
}
Вы увидите красную волнистую линию под File , означающую, что его не существует. Он не будет
компилироваться, поскольку компилятор не знает, что такое File . Чтобы решить эту проблему, добавьте
следующую строку кода под самой первой строкой в Extensions.cs :
using System.IO;
// Program.cs
public static void Main(string[] args)
{
var startingDeck = (from s in Suits().LogQuery("Suit Generation")
from r in Ranks().LogQuery("Rank Generation")
select new { Suit = s, Rank = r }).LogQuery("Starting Deck");
Console.WriteLine();
var times = 0;
var shuffle = startingDeck;
do
{
// Out shuffle
/*
shuffle = shuffle.Take(26)
.LogQuery("Top Half")
.InterleaveSequenceWith(shuffle.Skip(26)
.LogQuery("Bottom Half"))
.LogQuery("Shuffle");
*/
// In shuffle
shuffle = shuffle.Skip(26).LogQuery("Bottom Half")
.InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top Half"))
.LogQuery("Shuffle");
times++;
Console.WriteLine(times);
} while (!startingDeck.SequenceEquals(shuffle));
Console.WriteLine(times);
}
Обратите внимание, что запись в журнал не нужно выполнять при обращении к запросу. Она выполняется
только при создании исходного запроса. Программа по-прежнему работает очень долго, но теперь вы
хорошо видите, почему. Если у вас не хватит терпения выполнять внешнюю тасовку с ведением журнала,
переключите программу обратно на внутреннюю тасовку. На ней вы также заметите влияние отложенного
вычисления. За один запуск программа выполняет 2592 запроса, если учитывать все создания мастей и
достоинств.
Вы можете повысить производительность кода, чтобы уменьшить количество выполнений. Простой способ
исправить — кэшировать результаты исходного запроса LINQ, который создает колоду карт. В настоящее
время вы выполняете запросы снова и снова каждый раз, когда цикл do-while проходит через итерацию,
повторно создавая и перетасовывая колоду карт. Чтобы кэшировать колоду карт, вы можете использовать
методы LINQ ToArray и ToList. Когда вы добавляете их в запросы, они будут выполнять те действия, которые
вы указали, но теперь они будут хранить результаты в массиве или списке в зависимости от того, какой
метод вы вызовете. Добавьте метод LINQ ToArray в оба запроса и снова запустите программу:
Console.WriteLine();
var times = 0;
var shuffle = startingDeck;
do
{
/*
shuffle = shuffle.Take(26)
.LogQuery("Top Half")
.InterleaveSequenceWith(shuffle.Skip(26).LogQuery("Bottom Half"))
.LogQuery("Shuffle")
.ToArray();
*/
shuffle = shuffle.Skip(26)
.LogQuery("Bottom Half")
.InterleaveSequenceWith(shuffle.Take(26).LogQuery("Top Half"))
.LogQuery("Shuffle")
.ToArray();
times++;
Console.WriteLine(times);
} while (!startingDeck.SequenceEquals(shuffle));
Console.WriteLine(times);
}
Теперь при внутренней тасовке выполняется всего 30 запросов. Переключите программу на внешнюю
тасовку, и вы заметите аналогичное улучшение: теперь выполняется 162 запроса.
Обратите внимание, что этот пример лишь демонстрирует варианты использования, в которых
отложенное вычисление приводит к проблемам с производительностью. Хотя очень важно знать, когда
отложенное вычисление может повлиять на производительность кода, не менее важно понимать, что не все
запросы должны выполняться упреждающе. Если не использовать ToArray, производительность снизится.
Это связано с тем, что каждое новое расположение карт вычисляется на основе предыдущего
расположения. Использование отложенного вычисления означает, что каждое расположение колоды
строится с самого начала, из исходной колоды, включая вызов кода для создания startingDeck . Это создает
огромный объем дополнительной работы.
На практике некоторые алгоритмы хорошо работают с упреждающим вычислением, а другие хорошо
выполняются с отложенным вычислением. Для ежедневного использования отложенное вычисление
обычно дает более хороший результат, если в качестве источника данных используется отдельный процесс,
например база данных. Для баз данных отложенное вычисление позволяет сложным запросам выполнять
только один круговой путь к процессу базы данных и обратно к оставшемуся коду. LINQ является гибким,
независимо от того, используете ли вы отложенное или упреждающее вычисление, поэтому измерьте
процессы и выберите тип вычислений, который обеспечивает наилучшую производительность.
Заключение
В этом проекте вы изучили:
использование запросов LINQ для агрегирования данных в осмысленную последовательность;
запись методов расширения для добавления собственных пользовательских функций в запросы LINQ;
поиск областей в коде, где могут возникнуть проблемы с производительностью наших запросов LINQ,
например снижение скорости;
упреждающее и отложенное вычисление в отношении запросов LINQ и их влияние на
производительность запросов.
Помимо LINQ вы узнали об использовании метода, который иллюзионисты используют для карточных
фокусов. Они используют тасовку по методу Фаро, потому что она позволяет хорошо контролировать
положение каждой карты в колоде. Теперь, когда вы все это знаете, не рассказывайте это остальным!
Дополнительные сведения о LINQ см. в следующих статьях:
LINQ
Введение в LINQ
Основные операции запросов LINQ (C#)
Преобразования данных с помощью LINQ (C#)
Синтаксис запросов и синтаксис методов в LINQ (C#)
Возможности C#, поддерживающие LINQ
Использование атрибутов в C#
04.11.2019 • 13 minutes to read • Edit Online
Атрибуты предоставляют возможность декларативно связать информацию с кодом. Также этот элемент
можно многократно использовать повторно для разнообразных целевых объектов.
Рассмотрим для примера атрибут [Obsolete] . Его можно применять к классам, структурам, методам,
конструкторам и т. д. Он объявляет, что соответствующий элемент является устаревшим. Компилятор C#
проверят наличие этого атрибута и выполняет некоторые действия, если он присутствует.
В этом руководстве мы покажем вам, как можно добавить атрибуты в код, как создавать и применять
собственные атрибуты, а также использовать некоторые встроенные атрибуты .NET Core.
Предварительные требования
Компьютер должен быть настроен для выполнения .NET Core. Инструкции по установке см. на странице
скачиваемых файлов .NET Core. Это приложение можно запустить в ОС Windows, Ubuntu Linux, macOS или
в контейнере Docker. Вам потребуется редактор кода, но вы можете выбрать любой привычный для вас. В
примерах ниже используется кроссплатформенный редактор Visual Studio Code с открытым исходным
кодом. Вы можете заменить его на любое другое средство, с которым вам удобно работать.
Создание приложения
Теперь, когда вы установили все нужные средства, создайте новое приложение .NET Core. Чтобы
использовать генератор из командной строки, выполните следующую команду в любой оболочке:
dotnet new console
Эта команда создает скелет файлов проекта для .NET Core. Нужно также выполнить команду dotnet restore
, чтобы восстановить зависимости, необходимые для компиляции проекта.
NOTE
Начиная с пакета SDK для .NET Core 2.0 нет необходимости выполнять команду dotnet restore , так как она
выполняется неявно всеми командами, которые требуют восстановления, например dotnet new , dotnet build и
dotnet run . Эту команду по-прежнему можно использовать в некоторых сценариях, где необходимо явное
восстановление, например в сборках с использованием непрерывной интеграции в Azure DevOps Services или
системах сборки, где требуется явно контролировать время восстановления.
Чтобы выполнить программу, используйте dotnet run . Она выведет в консоль сообщение "Hello, World".
Обратите внимание, что полное название класса — ObsoleteAttribute , но в коде достаточно указать только
[Obsolete] . Это стандартное соглашение в C#. При желании вы можете использовать полное имя
[ObsoleteAttribute] .
Когда вы отмечаете класс как устаревший, желательно предоставить некоторые сведения о том, почему он
устарел и (или) что можно использовать вместо него. Для этого передайте в атрибут Obsolete строковый
параметр.
Эта строка передается в качестве аргумента в конструктор ObsoleteAttribute , как если бы вы использовали
синтаксис var attr = new ObsoleteAttribute("some string") .
В конструкторе атрибута можно использовать в качестве параметров только простые типы и литералы
bool, int, double, string, Type, enums, etc и массивы этих типов. Нельзя использовать переменные или
выражения. Но вы можете свободно использовать позиционные или именованные параметры.
Добавив такой код, вы сможете использовать [MySpecial] (или [MySpecialAttribute] ) в качестве атрибута в
любом месте кода.
[MySpecial]
public class SomeOtherClass
{
}
}
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)]
public class MyAttributeForClassAndStructOnly : Attribute
{
Если вы попробуете применить описанный выше атрибут для сущности, которая не является классом или
структурой, вы получите ошибку компиляции такого рода:
Attribute 'MyAttributeForClassAndStructOnly' is not valid on this declaration type. It is only valid on 'class,
struct' declarations
Если у вас есть объект TypeInfo (или MemberInfo , FieldInfo и т. д.), вы можете использовать метод
GetCustomAttributes . Он возвращает коллекцию объектов Attribute . Можно также использовать
GetCustomAttribute , указав тип атрибута.
Ниже вы видите пример использования GetCustomAttributes для экземпляра MemberInfo класса MyClass (как
мы продемонстрировали ранее, он имеет атрибут [Obsolete] ).
Этот код выведет в консоль текст: Attribute on MyClass: ObsoleteAttribute . Попробуйте добавить другие
атрибуты для MyClass .
Обратите особое внимание, что к таким объектам Attribute применяется отложенное создание
экземпляров. При вызове GetCustomAttribute или GetCustomAttributes экземпляры не создаются. Кроме того,
они создаются заново при каждом обращении. Выполнив GetCustomAttributes два раза подряд, вы получите
два различных экземпляра ObsoleteAttribute .
В приведенном выше коде не обязательно использовать литеральную строку "Name" . Это помогает
предотвратить ошибки, связанные с опечатками, а также позволяет плавно выполнять рефакторинг и (или)
переименование.
Сводка
Атрибуты позволяют реализовать в C# возможности декларативного синтаксиса. Но они являются
разновидностью метаданных и сами по себе не выполняют действия.
Краткий обзор языка C#
24.10.2019 • 9 minutes to read • Edit Online
Здравствуй, мир
Для первого знакомства с языком программирования традиционно используется программа "Hello, World".
Вот ее пример на C#:
using System;
class Hello
{
static void Main()
{
Console.WriteLine("Hello, World");
}
}
Файлы исходного кода C# обычно имеют расширение .cs . Если код нашей программы Hello, World
хранится в файле hello.cs, для ее компиляции выполните такую команду из командной строки:
csc hello.cs
В результате вы получите исполняемую сборку с именем hello.exe. Это приложение при запуске выводит
следующий результат:
Hello, World
IMPORTANT
Команда csc выполняет компиляцию на полной версии платформы, и она может существовать не на всех
платформах.
Программа "Hello, World" начинается с директивы using , которая ссылается на пространство имен System .
Пространства имен позволяют иерархически упорядочивать программы и библиотеки C#. Пространства
имен содержат типы и другие пространства имен. Например, пространство имен System содержит
несколько типов (в том числе используемый в нашей программе класс Console ) и несколько других
пространств имен, таких как IO и Collections . Директива using , которая ссылается на пространство имен,
позволяет использовать типы из этого пространства имен без указания полного имени. Благодаря директиве
using в коде программы можно использовать сокращенное имя Console.WriteLine вместо полного варианта
System.Console.WriteLine .
Класс Hello , объявленный в программе "Hello, World", имеет только один член — это метод с именем Main .
Метод Main объявлен с модификатором static. Методы экземпляра могут ссылаться на конкретный
экземпляр объекта, используя ключевое слово this , а статические методы работают без ссылки на
конкретный объект. По стандартному соглашению точкой входа программы является статический метод с
именем Main .
Выходные данные программы создаются в методе WriteLine класса Console из пространства имен System .
Этот класс предоставляется библиотеками стандартных классов, ссылки на которые компилятор по
умолчанию добавляет автоматически.
Есть еще очень много важной информации о языке C#. В следующих статьях вы найдете сведения об
отдельных элементах C#. Эти краткие обзоры содержат базовые сведения обо всех элементах языка и
позволят вам лучше разобраться в том, как работает C#.
Структура программы
Организационная структура C# основывается на таких понятиях, как программы, пространства
имен, типы, члены и сборки.
Типы и переменные
Получите дополнительные сведения о типах значений, ссылочных типах и переменных в
языке C#.
Выражения
Выражения создаются из операндов и операторов. Выражения возвращают значения.
Операторы
Используйте инструкции для описания действий, выполняемых программой.
Классы и объекты
Классы являются самым важным типом в языке C#. Объекты представляют собой экземпляры
классов. Классы создаются описанием их членов, которые также описаны в этой статье.
Структуры
Структуры — это сущности для хранения данных. От классов они отличаются в первую очередь
тем, что являются типами значений.
Массивы
Массив — это структура данных, содержащая несколько переменных, доступ к которым
осуществляется по вычисляемым индексам.
Интерфейсы
Интерфейс определяет контракт, который может быть реализован классами и структурами.
Интерфейс может содержать методы, свойства, события и индексаторы. Интерфейс не
предоставляет реализацию членов, которые в нем определены. Он лишь перечисляет члены,
которые должны быть определены в классах или структурах, реализующих этот интерфейс.
Перечисления
Тип enum представляет собой тип значения с набором именованных констант.
Делегаты
Тип delegate представляет ссылки на методы с конкретным списком параметров и типом
возвращаемого значения. Делегаты позволяют использовать методы как сущности, сохраняя их в
переменные и передавая в качестве параметров. Принцип работы делегатов близок к указателям
функций из некоторых языков, но в отличие от указателей функций делегаты являются объектно-
ориентированными и строго типизированными.
Атрибуты
Атрибуты позволяют программам указывать дополнительные описательные данные о типах,
членах и других сущностях.
ВПЕРЕ Д
Структура программы
23.10.2019 • 5 minutes to read • Edit Online
Этот пример кода объявляет класс с именем Stack в пространство имен с именем Acme.Collections :
using System;
namespace Acme.Collections
{
public class Stack
{
Entry top;
public void Push(object data)
{
top = new Entry(top, data);
}
class Entry
{
public Entry next;
public object data;
public Entry(Entry next, object data)
{
this.next = next;
this.data = data;
}
}
}
}
Полное имя этого класса: Acme.Collections.Stack . Этот класс содержит несколько членов: поле с именем top
, два метода с именами Push и Pop , а также вложенный класс с именем Entry . Класс Entry , в свою
очередь, содержит три члена: поле с именем next , поле с именем data и конструктор. Если мы сохраним
исходный код этого примера в файле acme.cs , то для его компиляции нужно выполнить такую командную
строку:
Результатом компиляции будет библиотека (код без точки входа Main ), сохраненная в сборке с именем
acme.dll .
IMPORTANT
В приведенных выше примерах используется компилятор командной строки csc для C#. Он существует в виде
исполняемого файла Windows. Чтобы использовать C# на других платформах, вам понадобятся средства для .NET
Core. Экосистема .NET Core использует dotnet CLI для управления сборкой из командной строки. Она позволяет
управлять зависимостями и вызывать компилятор C#. В этом руководстве вы найдете полное описание средств для
всех платформ, поддерживаемых .NET Core.
Сборки содержат исполняемый код в виде инструкций промежуточного языка (IL ) и символьную
информацию в виде метаданных. Перед выполнением сборки ее код на языке IL автоматически
преобразуется в код для конкретного процессора. Эту задачу выполняет JIT-компилятор среды .NET CLR
(Common Language Runtime).
Cборка полностью описывает сама себя и содержит весь код и метаданные, поэтому в C# не используются
директивы #include и файлы заголовков. Чтобы использовать в программе C# открытые типы и члены,
содержащиеся в определенной сборке, вам достаточно указать ссылку на эту сборку при компиляции
программы. Например, эта программа использует класс Acme.Collections.Stack из сборки acme.dll :
using System;
using Acme.Collections;
class Example
{
static void Main()
{
Stack s = new Stack();
s.Push(1);
s.Push(10);
s.Push(100);
Console.WriteLine(s.Pop());
Console.WriteLine(s.Pop());
Console.WriteLine(s.Pop());
}
}
Если на момент компиляции example.cs программа хранится в файле example.cs , то ссылка на сборку
acme.dll с использованием параметра компилятора /r будет выглядеть так:
Эта команда создает исполняемую сборку с именем example.exe , которая при запуске возвращает такие
данные:
100
10
1
В C# исходный текст программы можно хранить в нескольких исходных файлах. При компиляции
многофайловой программы на C# все исходные файлы обрабатываются вместе, и все они могут свободно
ссылаться друг на друга. По сути обработка выполняется так, как если бы все исходные файлы были
объединены в один большой файл. В C# никогда не используются опережающие объявления, поскольку
порядок объявления, за редким исключением, не играет никакой роли. В C# нет требований объявлять
только один открытый тип в одном исходном файле, а также имя исходного файла не обязано совпадать с
типом, объявляемом в этом файле.
НА ЗА Д ВПЕРЕ Д
Типы и переменные
11.11.2019 • 10 minutes to read • Edit Online
В C# существуют две разновидности типов: ссылочные типы и типы значений. Переменные типа значений
содержат непосредственно данные, а в переменных ссылочных типов хранятся ссылки на нужные данные,
которые именуются объектами. Две переменные ссылочного типа могут ссылаться на один и тот же объект,
поэтому может случиться так, что операции над одной переменной затронут объект, на который ссылается
другая переменная. Каждая переменная типа значения имеет собственную копию данных, и операции над
одной переменной не могут затрагивать другую (за исключением переменных параметров ref и out ).
Типы значений в C# подразделяются на простые типы, типы перечисления, типы структур и типы,
допускающие значение Null. Ссылочные типы в C# подразделяются на типы классов, типы интерфейсов,
типы массивов и типы делегатов.
Далее представлены общие сведения о системе типов в C#.
Типы значений
Простые типы
Целочисленный со знаком: sbyte , short , int , long
Целочисленный без знака: byte , ushort , uint , ulong
Символы Юникода: char
Бинарный оператор IEEE с плавающей запятой: float , double
Десятичное значение с повышенной точностью и плавающей запятой: decimal
Логическое значение: bool
Типы перечисления
Пользовательские типы в формате enum E {...}
Типы структур
Пользовательские типы в формате struct S {...}
Типы значений, допускающие значение NULL
Расширения других типов значений, допускающие значение null
Ссылочные типы
Типы классов
Исходный базовым классом для всех типов: object
Строки Юникода: string
Пользовательские типы в формате class C {...}
Типы интерфейсов
Пользовательские типы в формате interface I {...}
Типы массивов
Одно- и многомерные, например, int[] и int[,]
Типы делегатов
Пользовательские типы в формате delegate int D(...)
Дополнительные сведения о числовых типах см. в разделах Целочисленные типы и Таблица типов с
плавающей запятой.
Тип bool в C# используется для представления логических значений, которые могут иметь значение true
или false .
Обработка знаков и строк в C# выполняется в кодировке Юникода. Тип char представляет элемент в
кодировке UTF -16, а тип string представляет последовательность элементов в кодировке UTF -16.
Программы C# используют объявления типов для создания новых типов. В объявлении типа указываются
имя и члены нового типа. Пять категорий типов в C# определяются пользователем: типы классов, типы
структур, типы интерфейсов, типы перечисления и типы делегатов.
Тип class определяет структуру данных, которая содержит данные-члены (поля) и функции-члены
(методы, свойства и т. д.). Классы поддерживают механизмы одиночного наследования и полиморфизма,
которые позволяют создавать производные классы, расширяющие и уточняющие определения базовых
классов.
Тип struct похож на тип класса тем, что он представляет структуру с данными-членами и функциями-
членами. Но в отличие от классов, структуры являются типами значений и обычно не требуют выделения
памяти из кучи. Типы структуры не поддерживают определяемое пользователем наследование, и все типы
структуры неявно наследуют от типа object .
Тип interface (интерфейс) определяет контракт в виде именованного набора открытых функций-членов.
Объект типа class или struct , реализующий interface , должен предоставить реализации для всех
функций-членов интерфейса. Тип interface может наследовать от нескольких базовых интерфейсов, а
class или struct могут реализовывать несколько интерфейсов.
Тип delegate (делегат) представляющий ссылки на методы с конкретным списком параметров и типом
возвращаемого значения. Делегаты позволяют использовать методы как сущности, сохраняя их в
переменные и передавая в качестве параметров. Делегаты аналогичны типам функций, которые
используются в функциональных языках. Также принцип их работы близок к указателям функций из
некоторых языков, но в отличие от указателей функций делегаты являются объектно-ориентированными и
строго типизированными.
Типы class , struct , interface и delegate поддерживают универсальные шаблоны, которые позволяют
передавать им другие типы в качестве параметров.
Тип enum является отдельным типом со списком именованных констант. Каждый тип enum имеет базовый
тип, в роли которого выступает один из восьми целочисленных типов. Набор значений типа enum
аналогичен набору значений его базового типа.
C# поддерживает одно- и многомерные массивы любого типа. В отличие от перечисленных выше типов,
типы массивов не требуется объявлять перед использованием. Типы массивов можно сформировать, просто
введя квадратные скобки после имени типа. Например, int[] является одномерным массивом значений
типа int , а int[,] — двумерным массивом значений типа int , тогда как int[][] представляет собой
одномерный массив одномерных массивов значений типа int .
Типы значений, допускающие значение Null, также не нужно отдельно объявлять перед использованием.
Для каждого обычного типа значений T , который не допускает значение Null, существует идентичный тип
T? , который отличается только тем, что может содержать значение null . Например int? — это тип,
который может содержать любое 32-разрядное целое число или значение null .
Система типов в C# унифицирована таким образом, что значение любого типа можно рассматривать как
object (объект ). Каждый тип в C# является прямо или косвенно производным от типа класса object , и этот
тип object является исходным базовым классом для всех типов. Чтобы значения ссылочного типа
обрабатывались как объекты, им просто присваивается тип object . Чтобы значения типов значений
обрабатывались как объекты, выполняются операции упаковки-преобразования и распаковки-
преобразования. В следующем примере значение int преобразуется в object , а затем обратно в int .
using System;
class BoxingExample
{
static void Main()
{
int i = 123;
object o = i; // Boxing
int j = (int)o; // Unboxing
}
}
Если значение для типа значения преобразуется в тип object , то для хранения этого значения выделяется
экземпляр object , который также называется "упаковкой", и значение копируется в эту упаковку. И
наоборот, если ссылка типа object используется для типа значения, для соответствующего object
выполняется проверка, является ли он упаковкой правильного типа. Если эта проверка завершается
успешно, копируется значение этой упаковки.
Унифицированная система типов C# фактически позволяет преобразовывать типы значений в объекты "по
требованию". Такая унификация позволяет применять универсальные библиотеки, использующие тип
object , как со ссылочными типами, так и с типами значений.
В C# существует несколько типов переменных, в том числе поля, элементы массива, локальные переменные
и параметры. Переменные представляют места хранения, и каждая переменная имеет тип, который
определяет допустимые значения для хранения в этой переменной. Примеры представлены ниже.
Тип значения, не допускающий значения Null
Значение такого типа
Тип значения, допускающий значение Null
Значение null или значение такого типа
object
Ссылка null , ссылка на объект любого ссылочного типа или ссылка на упакованное значение
любого типа значения
Тип класса
Ссылка null , ссылка на экземпляр такого типа класса или ссылка на экземпляр любого класса,
производного от такого типа класса
Тип интерфейса
Ссылка null , ссылка на экземпляр типа класса, который реализует такой тип интерфейса, или
ссылка на упакованное значение типа значения, которое реализует такой тип интерфейса
Тип массива
Ссылка null , ссылка на экземпляр такого типа массива или ссылка на экземпляр любого
совместимого типа массива
Тип делегата
Ссылка null или ссылка на экземпляр совместимого типа делегата
НА ЗА Д ВПЕРЕ Д
Выражения
24.10.2019 • 2 minutes to read • Edit Online
НА ЗА Д ВПЕРЕ Д
Операторы
23.10.2019 • 5 minutes to read • Edit Online
Операторы try ... catch позволяют перехватывать исключения, создаваемые при выполнении блока кода, а
оператор try ... finally используется для указания кода завершения, который выполняется всегда,
независимо от появления исключений.
Операторы checked и unchecked операторы позволяют управлять контекстом проверки переполнения для
целочисленных арифметических операций и преобразований.
Оператор lock позволяет создать взаимоисключающую блокировку заданного объекта перед
выполнением определенных операторов, а затем снять блокировку.
Оператор using используется для получения ресурса перед определенным оператором, и для удаления
ресурса после его завершения.
Ниже перечислены виды операторов, которые вы можете использовать, и представлены примеры для
каждого из них.
Объявление локальной переменной.
Оператор выражения.
Оператор if .
Оператор switch .
Оператор while .
static void WhileStatement(string[] args)
{
int i = 0;
while (i < args.Length)
{
Console.WriteLine(args[i]);
i++;
}
}
Оператор do .
Оператор for .
Оператор foreach .
Оператор break .
Оператор continue .
static void ContinueStatement(string[] args)
{
for (int i = 0; i < args.Length; i++)
{
if (args[i].StartsWith("/"))
continue;
Console.WriteLine(args[i]);
}
}
Оператор goto .
Оператор return .
Оператор yield .
Оператор lock .
class Account
{
decimal balance;
private readonly object sync = new object();
public void Withdraw(decimal amount)
{
lock (sync)
{
if (amount > balance)
{
throw new Exception(
"Insufficient funds");
}
balance -= amount;
}
}
}
Оператор using .
НА ЗА Д ВПЕРЕ Д
Классы и объекты
23.10.2019 • 38 minutes to read • Edit Online
Классы являются основным типом в языке C#. Класс представляет собой структуру данных, которая
объединяет в себе значения (поля) и действия (методы и другие функции-члены). Класс предоставляет
определение для динамически создаваемых экземпляров класса, которые также именуются объектами.
Классы поддерживают механизмы наследования и полиморфизма, которые позволяют создавать
производные классы, расширяющие и уточняющие определения базовых классов.
Новые классы создаются с помощью объявлений классов. Объявление класса начинается с заголовка, в
котором указаны атрибуты и модификаторы класса, имя класса, базовый класс (если есть) и интерфейсы,
реализуемые этим классом. За заголовком между разделителями { и } следует тело класса, в котором
последовательно объявляются все члены класса.
Вот простой пример объявления класса с именем Point :
Экземпляры классов создаются с помощью оператора new , который выделяет память для нового
экземпляра, вызывает конструктор для инициализации этого экземпляра и возвращает ссылку на экземпляр.
Следующие инструкции создают два объекта Point и сохраняют ссылки на них в две переменные:
Занимаемая объектом память автоматически освобождается, когда объект становится недоступен. В C# нет
ни необходимости, ни возможности освобождать память объектов явным образом.
Участники
Члены класса могут быть статическими членами или членами экземпляра. Статические члены принадлежат
классу в целом, а члены экземпляра принадлежат конкретным объектам (экземплярам классов).
Ниже перечислены виды членов, которые могут содержаться в классе.
Константы
Константные значения, связанные с классом.
Поля
Переменные класса.
Методы
Вычисления и действия, которые может выполнять класс.
Свойства
Действия, связанные с чтением и записью именованных свойств класса.
Индексаторы
Действия, реализующие индексирование экземпляров класса, чтобы обращаться к ним как к
массиву.
События
Уведомления, которые могут быть созданы этим классом.
Операторы
Поддерживаемые классом операторы преобразования и выражения.
Конструкторы
Действия, необходимые для инициализации экземпляров класса или класса в целом.
Методы завершения
Действия, выполняемые перед окончательным удалением экземпляров класса.
Типы
Вложенные типы, объявленные в классе.
Специальные возможности
Каждый член класса имеет определенный уровень доступности. Он определяет, из какой области
программы можно обращаться к этому члену. Существует шесть уровней доступности. Они кратко описаны
ниже.
public
Доступ не ограничен.
protected
Доступ возможен из этого класса и из классов, унаследованных от него.
internal
Доступ ограничен только текущей сборкой (.exe, .dll и т. д.).
protected internal
Доступ ограничен содержащим классом, классами, которые являются производными от
содержащего класса, либо классами в той же сборке
private
Доступ возможен только из этого класса.
private protected
Доступ ограничен содержащим классом или классами, которые являются производными от
содержащего типа в той же сборке
Параметры типа
Определение класса может задать набор параметров типа. Список имен параметров типа указывается в
угловых скобках после имени класса. Параметры типа можно использовать в теле класса в определениях,
описывающих члены класса. В следующем примере для класса Pair заданы параметры типа TFirst и
TSecond :
Тип класса, для которого объявлены параметры типа, называется универсальным типом класса. Типы
структуры, интерфейса и делегата также могут быть универсальными. Если вы используете универсальный
класс, необходимо указать аргумент типа для каждого параметра типа, вот так:
Универсальный тип, для которого указаны аргументы типа, как Pair<int,string> в примере выше,
называется сконструированным типом.
базовых классов;
В объявлении класса можно указать базовый класс, включив имя базового класса после имени класса и
параметров типа, и отделив его двоеточием. Если спецификация базового класса не указана, класс
наследуется от типа object . В следующем примере Point3D имеет базовый класс Point , а Point —
базовый класс object :
Класс наследует члены базового класса. Наследование означает, что класс неявно содержит все члены
своего базового класса, за исключением конструкторов экземпляра, статических конструкторов и методов
завершения базового класса. Производный класс может дополнить наследуемые элементы новыми
элементами, но он не может удалить определение для наследуемого члена. В предыдущем примере
Point3D наследует поля x и y из Point , и каждый экземпляр Point3D содержит три поля: x , y и z .
Поля
Поле является переменной, связанной с определенным классом или экземпляром класса.
Поле, объявленное с модификатором static, является статическим. Статическое поле определяет строго
одно место хранения. Независимо от того, сколько будет создано экземпляров этого класса, существует
только одна копия статического поля.
Поле, объявленное без модификатора static, является полем экземпляра. Каждый экземпляр класса
содержит отдельные копии всех полей экземпляра, определенных для этого класса.
В следующем примере каждый экземпляр класса Color содержит отдельную копию полей экземпляра r ,
g и b , но для каждого из статических полей Black , White , Red , Green и Blue существует только одна
копия:
Как показано в предыдущем примере, можно объявить поля только для чтения, используя модификатор
readonly . Присвоение значения полю readonly может происходить только при объявлении этого поля или
в конструкторе этого класса.
Методы
Метод — это член, реализующий вычисление или действие, которое может выполнять объект или класс.
Доступ к статическим методам осуществляется через класс. Доступ к методам экземпляра
осуществляется через экземпляр класса.
Для метода можно определить список параметров, которые представляют переданные методу значения
или ссылки на переменные, а также возвращаемый тип, который задает тип значения, вычисляемого и
возвращаемого методом. Если метод не возвращает значений, для него устанавливается возвращаемый тип
void .
Как и типы, методы могут иметь набор параметров типа, для которых при вызове метода необходимо
указывать аргументы типа. В отличие от типов, аргументы типа зачастую могут выводиться из аргументов
вызова метода, и тогда их не обязательно задавать явным образом.
Сигнатура метода должна быть уникальной в пределах класса, в котором объявлен этот метод. Сигнатура
метода включает имя метода, количество параметров типа, а также количество, модификаторы и типы
параметров метода. Сигнатура метода не включает возвращаемый тип.
Параметры
Параметры позволяют передать в метод значения или ссылки на переменные. Фактические значения
параметрам метода присваиваются на основе аргументов, заданных при вызове метода. Существует
четыре типа параметров: параметры значения, ссылочные параметры, параметры вывода и массивы
параметров.
Параметр значения используется для передачи входных аргументов. Параметр значения сопоставляется с
локальной переменной, которая получит начальное значение из значения аргумента, переданного в этом
параметре. Изменения параметра значения не влияют на аргумент, переданный для этого параметра.
Параметры значения можно сделать необязательными, указав для них значения по умолчанию. Тогда
соответствующие аргументы можно не указывать.
Ссылочный параметр используется для передачи аргументов по ссылке. Аргумент, передаваемый в
качестве ссылочного параметра, должен представлять собой переменную с определенным значением. При
выполнении метода ссылочный параметр указывает на то же место хранения, в котором размещена
переменная аргумента. Чтобы объявить ссылочный параметр, используйте модификатор ref . Следующий
пример кода демонстрирует использование параметров ref .
using System;
class RefExample
{
static void Swap(ref int x, ref int y)
{
int temp = x;
x = y;
y = temp;
}
public static void SwapExample()
{
int i = 1, j = 2;
Swap(ref i, ref j);
Console.WriteLine($"{i} {j}"); // Outputs "2 1"
}
}
Параметр вывода используется для передачи аргументов по ссылке. Он похож на ссылочный параметр,
однако не требует явно присваивать значение аргумента, предоставляемого вызывающим объектом. Чтобы
объявить параметр вывода, используйте модификатор out . В следующем примере показано
использование параметров out с помощью синтаксиса, появившегося в C# 7.
using System;
class OutExample
{
static void Divide(int x, int y, out int result, out int remainder)
{
result = x / y;
remainder = x % y;
}
public static void OutUsage()
{
Divide(10, 3, out int res, out int rem);
Console.WriteLine("{0} {1}", res, rem); // Outputs "3 1"
}
}
}
Массив параметров позволяет передавать в метод переменное число аргументов. Чтобы объявить массив
параметров, используйте модификатор params . Массив параметров может быть только последним
параметром в методе. Для него можно использовать только тип одномерного массива. В качестве примера
правильного использования массива параметров можно назвать методы Write и WriteLine, реализованные в
классе System.Console. Ниже представлены объявления этих методов.
Внутри метода массив параметров полностью идентичен обычному параметру типа массив. Но зато при
вызове метода, использующего массив параметров, ему можно передать либо один аргумент типа массив,
либо любое количество аргументов типа элемент для массива параметров. В последнем случае экземпляр
массива автоматически создается и инициализируется с заданными аргументами. Код из этого примера...
using System;
class Squares
{
public static void WriteSquares()
{
int i = 0;
int j;
while (i < 10)
{
j = i * i;
Console.WriteLine($"{i} x {i} = {j}");
i = i + 1;
}
}
}
C# требует, чтобы локальной переменной было явно присвоено значение, прежде чем можно будет
получить это значение. Например, если в предложенное выше объявление i не включить начальное
значение, компилятор сообщит об ошибке при последующем использовании i , поскольку для i нет явно
присвоенного значения.
Метод может использовать инструкцию return , чтобы вернуть управление вызывающему объекту. Если
метод возвращает void , в нем нельзя использовать инструкцию return с выражением. В методе, выходное
значение которого имеет любой другой тип, инструкции return должны содержать выражение, которое
вычисляет возвращаемое значение.
Статические методы и методы экземпляра
Метод, объявленный с модификатором static, является статическим методом. Статический метод не
работает с конкретным экземпляром и может напрямую обращаться только к статическим членам.
Метод, объявленный без модификатора static, является методом экземпляра. Метод экземпляра работает в
определенном экземпляре и может обращаться как к статическим методам, так и к методам этого
экземпляра. В методе можно напрямую обратиться к экземпляру, для которого этот метод был вызван,
используя дескриптор this . Использование this в статическом методе приводит к ошибке.
Следующий класс Entity содержит статические члены и члены экземпляра.
class Entity
{
static int nextSerialNo;
int serialNo;
public Entity()
{
serialNo = nextSerialNo++;
}
public int GetSerialNo()
{
return serialNo;
}
public static int GetNextSerialNo()
{
return nextSerialNo;
}
public static void SetNextSerialNo(int value)
{
nextSerialNo = value;
}
}
Каждый экземпляр Entity содержит серийный номер (и может содержать другие данные, которые здесь не
показаны). Конструктор объекта Entity (который рассматривается как метод экземпляра) задает для
нового экземпляра следующий доступный серийный номер. Поскольку конструктор является членом
экземпляра, он может обращаться как к полю экземпляра serialNo , так и к статическому полю
nextSerialNo .
using System;
class EntityExample
{
public static void Usage()
{
Entity.SetNextSerialNo(1000);
Entity e1 = new Entity();
Entity e2 = new Entity();
Console.WriteLine(e1.GetSerialNo()); // Outputs "1000"
Console.WriteLine(e2.GetSerialNo()); // Outputs "1001"
Console.WriteLine(Entity.GetNextSerialNo()); // Outputs "1002"
}
}
Обратите внимание, что статические методы SetNextSerialNo и GetNextSerialNo вызываются для класса, а
метод экземпляра GetSerialNo вызывается для экземпляров класса.
Виртуальные , переопределяющие и абстрактные методы
Если объявление метода экземпляра включает модификатор virtual , такой метод называется
виртуальным методом. Если модификатор virtual отсутствует, метод считается невиртуальным.
При вызове виртуального метода могут быть вызваны разные его реализации в зависимости от того, какой
тип среды выполнения имеет экземпляр, для которого вызван этот метод. При вызове невиртуального
метода решающим фактором является тип во время компиляции для этого экземпляра.
Виртуальный метод можно переопределить в производном классе. Если объявление метода экземпляра
содержит модификатор override, этот метод переопределяет унаследованный виртуальный метод с такой
же сигнатурой. Изначальное объявление виртуального метода создает новый метод, а переопределение
этого метода создает специализированный виртуальный метод с новой реализацией взамен
унаследованного виртуального метода.
Абстрактным методом называется виртуальный метод без реализации. Абстрактный метод объявляется
с модификатором abstract. Его можно объявить только в классе, который также объявлен абстрактным.
Абстрактный метод должен обязательно переопределяться в каждом производном классе, не являющемся
абстрактным.
Следующий пример кода объявляет абстрактный класс Expression , который представляет узел дерева
выражений, а также три производных класса: Constant , VariableReference и Operation , которые реализуют
узлы дерева выражений для констант, ссылок на переменные и арифметических операций. (Они похожи на
типы дерева выражений, но их нельзя путать.)
using System;
using System.Collections.Generic;
public abstract class Expression
{
public abstract double Evaluate(Dictionary<string,object> vars);
}
public class Constant: Expression
{
double value;
public Constant(double value)
{
this.value = value;
}
public override double Evaluate(Dictionary<string,object> vars)
{
return value;
}
}
public class VariableReference: Expression
{
string name;
public VariableReference(string name)
{
this.name = name;
}
public override double Evaluate(Dictionary<string,object> vars)
{
object value = vars[name];
if (value == null)
{
throw new Exception("Unknown variable: " + name);
}
return Convert.ToDouble(value);
}
}
public class Operation: Expression
{
Expression left;
char op;
Expression right;
public Operation(Expression left, char op, Expression right)
{
this.left = left;
this.op = op;
this.right = right;
}
public override double Evaluate(Dictionary<string,object> vars)
{
double x = left.Evaluate(vars);
double y = right.Evaluate(vars);
switch (op) {
case '+': return x + y;
case '-': return x - y;
case '*': return x * y;
case '/': return x / y;
}
throw new Exception("Unknown operator");
}
}
Четыре приведенных выше класса можно использовать для моделирования арифметических выражений.
Например, с помощью экземпляров этих классов выражение x + 3 можно представить следующим
образом.
Expression e = new Operation(
new VariableReference("x"),
'+',
new Constant(3));
Метод Evaluate экземпляра Expression вызывается для вычисления данного выражения и создает
значение double . Этот метод принимает аргумент Dictionary , который содержит имена переменных (в
качестве ключей записей) и значения переменных (в качестве значений записей). Так как Evaluate —
абстрактный метод, то в неабстрактных классах, производных от Expression , необходимо переопределить
Evaluate .
using System;
using System.Collections.Generic;
class InheritanceExample
{
public static void ExampleUsage()
{
Expression e = new Operation(
new VariableReference("x"),
'*',
new Operation(
new VariableReference("y"),
'+',
new Constant(2)
)
);
Dictionary<string,object> vars = new Dictionary<string, object>();
vars["x"] = 3;
vars["y"] = 5;
Console.WriteLine(e.Evaluate(vars)); // Outputs "21"
vars["x"] = 1.5;
vars["y"] = 9;
Console.WriteLine(e.Evaluate(vars)); // Outputs "16.5"
}
}
Перегрузка методов
Перегрузка метода позволяет использовать в одном классе несколько методов с одинаковыми именами,
если они имеют уникальные сигнатуры. Когда при компиляции встречается вызов перегруженного метода,
компилятор использует принцип разрешения перегрузки, чтобы определить, какой из методов следует
вызвать. Разрешение перегрузки выбирает из методов тот, который лучше всего соответствует
предоставленным аргументам, или возвращает ошибку, если не удается выбрать конкретный подходящий
метод. В следующем примере показано, как работает разрешение перегрузки. Комментарий к каждому
вызову метода UsageExample подсказывает, какой из методов будет вызван для этой строки.
using System;
class OverloadingExample
{
static void F()
{
Console.WriteLine("F()");
}
static void F(object x)
{
Console.WriteLine("F(object)");
}
static void F(int x)
{
Console.WriteLine("F(int)");
}
static void F(double x)
{
Console.WriteLine("F(double)");
}
static void F<T>(T x)
{
Console.WriteLine("F<T>(T)");
}
static void F(double x, double y)
{
Console.WriteLine("F(double, double)");
}
public static void UsageExample()
{
F(); // Invokes F()
F(1); // Invokes F(int)
F(1.0); // Invokes F(double)
F("abc"); // Invokes F<string>(string)
F((double)1); // Invokes F(double)
F((object)1); // Invokes F(object)
F<int>(1); // Invokes F<int>(int)
F(1, 1); // Invokes F(double, double)
}
}
Как видно из этого примера, вы всегда можете выбрать конкретный метод, явным образом приведя типы
аргументов к соответствующим типам параметров, и (или) явно предоставив аргументы нужного типа.
Другие функции-члены
Все члены класса, содержащие исполняемый код, совокупно называются функции-члены. В предыдущем
разделе мы рассмотрели основные варианты методов, используемых как функции-члены. В этом разделе
описываются другие типы функций-членов, поддерживаемые в языке C#: конструкторы, свойства,
индексаторы, события, операторы и методы завершения.
Ниже приведен универсальный класс с именем MyList<T> , который реализует расширяемый список
объектов. Этот класс содержит несколько наиболее распространенных типов функций-членов.
NOTE
В этом примере создается класс MyList , который отличается от стандартного System.Collections.Generic.List<T> в
.NET. Здесь показаны основные понятия, необходимые для этого руководства, но они не заменят собой этот класс.
// Fields
T[] items;
int count;
// Constructor
public MyList(int capacity = defaultCapacity)
{
items = new T[capacity];
}
// Properties
public int Count => count;
// Indexer
public T this[int index]
{
get
{
return items[index];
}
set
{
items[index] = value;
OnChanged();
}
}
// Methods
public void Add(T item)
{
if (count == Capacity) Capacity = count * 2;
items[count] = item;
count++;
OnChanged();
}
protected virtual void OnChanged() =>
Changed?.Invoke(this, EventArgs.Empty);
// Event
public event EventHandler Changed;
// Operators
public static bool operator ==(MyList<T> a, MyList<T> b) =>
Equals(a, b);
Конструкторы
C# поддерживает конструкторы экземпляров и статические конструкторы. Конструктор экземпляра
является членом, который реализует действия для инициализации нового экземпляра класса. Статический
конструктор является членом, который реализует действия для инициализации самого класса при
первоначальной его загрузке.
Конструктор объявляется в виде метода без возвращаемого типа, имя которого совпадает с именем класса,
в котором он определен. Если объявление конструктора содержит модификатор static, создается
статический конструктор. В противном случае это объявление считается конструктором экземпляра.
Конструкторы экземпляров можно перегружать, и для них можно указать необязательные параметры.
Например, класс MyList<T> объявляет один конструктор экземпляра с одним необязательным параметром
int . Конструкторы экземпляров вызываются с помощью оператора new . Следующий пример кода
выделяет два экземпляра MyList<string> с помощью конструкторов класса MyList : один с необязательным
аргументом, а второй — без.
Индексаторы
Индексатор является членом, позволяющим индексировать объекты так, как будто они включены в массив.
Индексатор объявляется так же, как свойство, за исключением того, что именем элемента является this , а
за этим именем следует список параметров, находящийся между разделителями [ и ] . Эти параметры
доступны в акцессорах индексатора. Как и свойства, можно объявить индексаторы для чтения и записи,
только для чтения или только для записи. Кроме того, поддерживаются виртуальные акцессоры
индексатора.
Класс MyList<T> объявляет один индексатор для чтения и записи, который принимает параметр int .
Индексатор позволяет индексировать экземпляры MyList<T> значениями с типом int . Например:
Индексаторы можно перегружать, то есть в одном классе можно объявить несколько индексаторов, если у
них различаются количество или типы параметров.
События
Событие — это член, с помощью которого класс или объект предоставляют уведомления. Объявление
события выглядит так же, как объявление поля, но содержит ключевое слово event и обязано иметь тип
делегата.
В классе, который объявляет член события, это событие действует, как обычное поле с типом делегата (если
это событие не является абстрактным и не объявляет акцессоры). Это поле хранит ссылку на делегат,
который представляет добавленные к событию обработчики событий. Если обработчики событий
отсутствуют, это поле имеет значение null .
Класс MyList<T> объявляет один член события с именем Changed , который обрабатывает добавление
нового элемента. Событие Changed вызывается виртуальным методом OnChanged , который сначала
проверяет, не имеет ли это событие значение null (это означает, что обработчики отсутствуют).
Концепция создания события в точности соответствует вызову делегата, представленного этим событием.
Это позволяет обойтись без особой языковой конструкции для создания событий.
Клиенты реагируют на события посредством обработчиков событий. Обработчики событий можно
подключать с помощью оператора += и удалять с помощью оператора -= . Следующий пример кода
подключает обработчик события Changed к событию MyList<string> .
class EventExample
{
static int changeCount;
static void ListChanged(object sender, EventArgs e)
{
changeCount++;
}
public static void Usage()
{
MyList<string> names = new MyList<string>();
names.Changed += new EventHandler(ListChanged);
names.Add("Liz");
names.Add("Martha");
names.Add("Beth");
Console.WriteLine(changeCount); // Outputs "3"
}
}
Для более сложных сценариев, требующих контроля над базовым хранилищем события, в объявлении
события можно явным образом предоставить акцессоры add и remove . Они будут действовать примерно
так же, как акцессор set для свойства.
Операторы
Оператор является членом, который определяет правила применения определенного выражения к
экземплярам класса. Вы можете определить операторы трех типов: унарные операторы, двоичные
операторы и операторы преобразования. Все операторы объявляются с модификаторами public и static .
Класс MyList<T> объявляет два оператора: operator == и operator != . Это позволяет определить новое
значение для выражений, которые применяют эти операторы к экземплярам MyList . В частности, эти
операторы определяют, что равенство двух экземпляров MyList<T> проверяется путем сравнения всех
содержащихся в них объектов с помощью определенных для них методов Equals. Следующий пример кода
использует оператор == для сравнения двух экземпляров MyList<int> .
Первый Console.WriteLine выводит True , поскольку два списка содержат одинаковое число объектов с
одинаковыми значениями в том же порядке. Если бы в MyList<T> не было определения operator == ,
первый Console.WriteLine возвращал бы False , поскольку a и b указывают на различные экземпляры
MyList<int> .
Методы завершения
Метод завершения является членом, который реализует действия для завершения существования
экземпляра класса. Методы завершения не могут иметь параметры, не могут содержать модификаторы
доступа и их нельзя вызвать явным образом. Метод завершения для экземпляра вызывается автоматически
в процессе сборки мусора.
Сборщик мусора имеет широкую степень свободы в выборе времени уничтожения объектов и вызова
методов завершения. В частности, время вызова методов завершения не является детерминированным, и
эти методы могут выполняться в любом потоке. По этим и некоторым другим причинам методы
завершения следует использовать в классах только в крайнем случае, когда невозможны другие решения.
Уничтожение объектов лучше контролировать с помощью инструкции using .
НА ЗА Д ВПЕРЕ Д
Структуры
23.10.2019 • 4 minutes to read • Edit Online
Как и классы, структуры — это сущности для хранения данных, которые могут содержать данные-члены и
функции-члены. Но в отличие от классов, структуры являются типами значений и для них не выделяется
память из кучи. Переменная типа структура напрямую хранит все свои данные, а переменная типа класс
хранит ссылку на динамически выделяемый объект. Типы структуры не поддерживают определяемое
пользователем наследование. Все типы структуры неявно наследуются от типа ValueType, который, в свою
очередь, неявно наследуется от object .
Структуры особенно удобны для небольших структур данных, имеющих семантику значений. Хорошими
примерами структур можно считать комплексные числа, точки в системе координат или словари с парами
ключ-значение. Если использовать структуры вместо классов для небольших структур данных, можно
существенно снизить количество операций по выделению памяти в приложении. Например, следующая
программа создает и инициализирует массив из 100 точек. Если реализовать Point как класс, создаются
экземпляры для 101 отдельных объектов — один для массива и еще 100 для его элементов.
struct Point
{
public int x, y;
public Point(int x, int y)
{
this.x = x;
this.y = y;
}
}
Теперь создается всего один объект (для массива), а экземпляры Point хранятся в стеке массива.
Конструкторы структур, как и конструкторы класса, вызываются с помощью оператора new . Но вместо того,
чтобы динамически выделять объект в управляемой куче и возвращать ссылку на него, конструктор
структуры возвращает само значение структуры (обычно сохраняя его во временном расположении в
стеке), и затем это значение копируется туда, где оно нужно.
При использовании классов две переменные могут ссылаться на один и тот же объект, поэтому может
случиться так, что операции над одной переменной затронут объект, на который ссылается другая
переменная. При использовании структур каждая переменная имеет собственную копию данных, и
операции над одной переменной не могут затрагивать другую. Например, выходные данные следующего
фрагмента кода зависят от того, реализованы ли точки как класс или как структура.
Point a = new Point(10, 10);
Point b = a;
a.x = 20;
Console.WriteLine(b.x);
Если Point является классом, то возвращается значение 20, поскольку a и b ссылаются на один объект.
Если Point является структурой, возвращается значение 10, поскольку при сохранении a в b создается
копия значения, и это значение не изменяется при последующем присваивании в a.x .
Предыдущий пример демонстрирует два ограничения, действующие для структур. Во-первых, копирование
структуры целиком обычно менее эффективно, чем копирование ссылки на объект, поэтому присваивание и
передача в качестве параметра потребует больше затрат для структур, чем для ссылочных типов. Во-вторых,
вы не можете создавать ссылки на структуры (за исключением параметров in , ref и out ), что в
некоторых ситуациях мешает их применять.
НА ЗА Д ВПЕРЕ Д
Массивы
23.10.2019 • 4 minutes to read • Edit Online
Массив — это структура данных, содержащая несколько переменных, доступ к которым осуществляется по
вычисляемым индексам. Содержащиеся в массиве переменные именуются элементами этого массива. Все
они имеют одинаковый тип, который называется типом элементов массива.
Сами массивы имеют ссылочный тип, и объявление переменной массива только выделяет память для
ссылки на экземпляр массива. Фактические экземпляры массива создаются динамически во время
выполнения с помощью оператора new. Операция new указывает длину нового экземпляра массива,
которая остается неизменной в течение всего времени существования этого экземпляра. Элементы массива
имеют индексы в диапазоне от 0 до Length - 1 . Оператор new автоматически инициализирует все
элементы массива значением по умолчанию. Например, для всех числовых типов устанавливается нулевое
значение, а для всех ссылочных типов — значение null .
Следующий пример кода создает массив из int элементов, затем инициализирует этот массив и выводит
содержимое массива.
using System;
class ArrayExample
{
static void Main()
{
int[] a = new int[10];
for (int i = 0; i < a.Length; i++)
{
a[i] = i * i;
}
for (int i = 0; i < a.Length; i++)
{
Console.WriteLine($"a[{i}] = {a[i]}");
}
}
}
Этот пример создает и использует одномерный массив. Кроме этого, C# поддерживает многомерные
массивы. Число измерений массива, которое именуется рангом для типа массива, всегда на единицу
больше числа запятых, включенных в квадратные скобки типа массива. Следующий пример кода
поочередно создает одномерный, двухмерный и трехмерный массивы.
Массив a1 содержит 10 элементов, массив a2 — 50 элементов (10 × 5), и наконец a3 содержит 100
элементов (10 × 5 × 2). Элементы массива могут иметь любой тип, в том числе тип массива. Массив с
элементами типа массива иногда называют ступенчатым массивом, поскольку элементы такого массива
не обязаны иметь одинаковую длину. Следующий пример создает массив массивов int .
int[][] a = new int[3][];
a[0] = new int[10];
a[1] = new int[5];
a[2] = new int[20];
В первой строке создается массив с тремя элементами, каждый из которых имеет тип int[] и начальное
значение null . В последующих строках эти три элемента инициализируются ссылками на отдельные
экземпляры массивов различной длины.
Оператор new позволяет задать начальные значения элементов массива, используя инициализатор
массива. Так называется список выражений, записанных между разделителями { и } . Следующий пример
создает и инициализирует массив int[] с тремя элементами.
Обратите внимание, что длина массива определяется по числу выражений между скобками { и }. Локальные
объявления переменных и полей можно сократить, поскольку тип массива не обязательно объявлять
повторно.
НА ЗА Д ВПЕРЕ Д
Интерфейсы
23.10.2019 • 3 minutes to read • Edit Online
Интерфейс определяет контракт, который может быть реализован классами и структурами. Интерфейс
может содержать методы, свойства, события и индексаторы. Интерфейс не предоставляет реализацию
членов, которые в нем определены. Он лишь перечисляет члены, которые должны быть определены в
классах или структурах, реализующих этот интерфейс.
Интерфейсы могут применять множественное наследование. В следующем примере интерфейс
IComboBox наследует одновременно от ITextBox и IListBox .
interface IControl
{
void Paint();
}
interface ITextBox: IControl
{
void SetText(string text);
}
interface IListBox: IControl
{
void SetItems(string[] items);
}
interface IComboBox: ITextBox, IListBox {}
Классы и структуры могут реализовывать несколько интерфейсов. В следующем примере класс EditBox
реализует одновременно IControl и IDataBound .
interface IDataBound
{
void Bind(Binder b);
}
public class EditBox: IControl, IDataBound
{
public void Paint() { }
public void Bind(Binder b) { }
}
Если класс или структура реализует конкретный интерфейс, любой экземпляр этого класса или структуры
можно неявно преобразовать в такой тип интерфейса. Пример
Если в статическом контексте невозможно достоверно знать, что экземпляр реализует определенный
интерфейс, можно использовать динамическое приведение типов. Например, следующие операторы
используют динамическое приведение типов для получения реализации интерфейсов IControl и
IDataBound для объекта. Приведения выполняются успешно, поскольку во время выполнения объект имеет
фактический тип EditBox .
object obj = new EditBox();
IControl control = (IControl)obj;
IDataBound dataBound = (IDataBound)obj;
В предыдущем примере для класса EditBox метод Paint из интерфейса IControl и метод Bind из
интерфейса IDataBound реализуются с помощью открытых членов. C# также поддерживает явные
реализации членов интерфейса, что позволяет классам и структурам не использовать открытые члены.
Явная реализация члена интерфейса записывается с использованием полного имени члена интерфейса.
Например, класс EditBox может реализовывать методы IControl.Paint и IDataBound.Bind с использованием
явной реализации членов интерфейса, как показано в следующем примере.
Обращение к явным членам интерфейса можно осуществлять только через тип интерфейса. Например,
реализация IControl.Paint , представленная в предыдущем примере класса EditBox, может вызываться
только с предварительным преобразованием ссылки EditBox к типу интерфейса IControl .
НА ЗА Д ВПЕРЕ Д
перечислениям;
23.10.2019 • 3 minutes to read • Edit Online
Тип enum представляет собой тип значения с набором именованных констант. Перечисления удобно
использовать в том случае, когда переменная может иметь только дискретные значения из определенного
набора. В качестве базового хранилища в перечислении используется один из целочисленных типов
значений. Перечисления предоставляют семантическое значение для дискретных значений.
Следующий пример кода объявляет и использует тип enum с именем Color , в котором определены три
константы: Red , Green , и Blue .
using System;
enum Color
{
Red,
Green,
Blue
}
class EnumExample
{
static void PrintColor(Color color)
{
switch (color)
{
case Color.Red:
Console.WriteLine("Red");
break;
case Color.Green:
Console.WriteLine("Green");
break;
case Color.Blue:
Console.WriteLine("Blue");
break;
default:
Console.WriteLine("Unknown color");
break;
}
}
static void Main()
{
Color c = Color.Red;
PrintColor(c);
PrintColor(Color.Blue);
}
}
Каждый тип enum соотносится с одними из целочисленных типов, который является базовым типом для
этого типа enum . Если для типа enum базовый тип не объявлен явным образом, ему присваивается базовый
тип int . Формат хранения и диапазон возможных значений для типа enum определяются его базовым
типом. Набор значений, которые может принимать enum , не ограничивается его членами enum . В
частности, любое значение базового типа enum можно привести к типу enum , и оно будет являться
допустимым дискретным значением для этого типа enum .
Следующий пример кода объявляет тип enum с именем Alignment и базовым типом sbyte .
enum Alignment: sbyte
{
Left = -1,
Center = 0,
Right = 1
}
Как показано в предыдущем примере, объявление члена enum может содержать константное выражение,
задающее значение этого члена. Константное значение для каждого члена enum должно находиться в
диапазоне, определенном для базового типа enum . Когда объявление члена enum не указывает значение
явным образом, этому члену присваивается нулевое значение (если это первый элемент в типе enum ) или
значение, на единицу превышающее значение последнего объявленного члена enum .
Значения Enum можно преобразовать к целочисленным значениям, и наоборот, используя приведение
типов. Пример:
Для любого типа enum по умолчанию устанавливается целочисленное нулевое значение, преобразованное
к типу enum . В тех случаях, когда переменная автоматически инициализируются значением по умолчанию,
переменным типов enum присваивается именно это значение. Чтобы значение по умолчанию было легко
доступно для любого типа enum , литеральное значение 0 неявным образом преобразуется к любому типу
enum . Таким образом, допустимо следующее выражение.
Color c = 0;
НА ЗА Д ВПЕРЕ Д
Делегаты
23.10.2019 • 3 minutes to read • Edit Online
Тип delegate представляет ссылки на методы с конкретным списком параметров и типом возвращаемого
значения. Делегаты позволяют использовать методы как сущности, сохраняя их в переменные и передавая в
качестве параметров. Принцип работы делегатов близок к указателям функций из некоторых языков, но в
отличие от указателей функций делегаты являются объектно-ориентированными и строго
типизированными.
Следующий пример кода объявляет и использует тип делегата с именем Function .
using System;
delegate double Function(double x);
class Multiplier
{
double factor;
public Multiplier(double factor)
{
this.factor = factor;
}
public double Multiply(double x)
{
return x * factor;
}
}
class DelegateExample
{
static double Square(double x)
{
return x * x;
}
static double[] Apply(double[] a, Function f)
{
double[] result = new double[a.Length];
for (int i = 0; i < a.Length; i++) result[i] = f(a[i]);
return result;
}
static void Main()
{
double[] a = {0.0, 0.5, 1.0};
double[] squares = Apply(a, Square);
double[] sines = Apply(a, Math.Sin);
Multiplier m = new Multiplier(2.0);
double[] doubles = Apply(a, m.Multiply);
}
}
Экземпляр Function с типом делегата может ссылаться на любой метод, который принимает аргумент
double и возвращает значение double . Метод Apply применяет заданную функцию к элементам double[]
и возвращает double[] с результатами. В методе Main используется Apply для применения трех различных
функций к double[] .
Делегат может ссылаться на статический метод (например, Square или Math.Sin в предыдущем примере)
или метод экземпляра (например, m.Multiply в предыдущем примере). Делегат, который ссылается на метод
экземпляра, также содержит ссылку на конкретный объект. Когда метод экземпляра вызывается через
делегат, этот объект превращается в this в вызове.
Делегаты могут также создаваться с использованием анонимных функций, то есть создаваемых на ходу
"встроенных методов". Анонимные функции могут использовать локальные переменные соседних методов.
Таким образом, приведенный выше пример умножения можно записать проще и без использования класса
множителя:
Также стоит упомянуть о такой интересной и полезной особенности делегата, что он не имеет информации
или ограничений в отношении того, к какому классу относится указанный в нем метод. Достаточно лишь,
чтобы указанный метод имел такие же типы параметров и возвращаемого значения, которые назначены
для делегата.
НА ЗА Д ВПЕРЕ Д
Атрибуты
23.10.2019 • 3 minutes to read • Edit Online
using System;
Все классы атрибутов являются производными от базового класса Attribute, который предоставляется в
стандартной библиотеке. Чтобы задать атрибут, его имя и возможные аргументы указываются в квадратных
скобках непосредственно перед объявлением соответствующей сущности. Если имя атрибута заканчивается
на Attribute , этот суффикс можно опускать при указании ссылки на этот атрибут. Например, атрибут с
именем HelpAttribute можно использовать так:
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes")]
public class Widget
{
[Help("https://docs.microsoft.com/dotnet/csharp/tour-of-csharp/attributes",
Topic = "Display")]
public void Display(string text) {}
}
Этот пример кода присоединяет атрибут HelpAttribute к классу Widget . Также он добавляет другой атрибут
HelpAttribute для метода Display в этом классе. Открытые конструкторы класса атрибута указывают, какие
сведения необходимо указать при назначении атрибута некоторой сущности программы. Дополнительные
сведения можно предоставить через обращения к открытым свойствам класса атрибута, доступным для
чтения и записи (например, как указанная выше ссылка на свойство Topic ).
Метаданные, определенные атрибутами, можно считывать и использовать во время выполнения с помощью
отражения. Когда с помощью этого метода выполняется запрос конкретного атрибута, вызывается
конструктор для класса атрибута с указанием сведений, представленных в исходном коде программы, а
затем возвращается созданный экземпляр атрибута. Если дополнительные сведения предоставляются через
свойства, перед возвращением экземпляра атрибута этим свойствам присваиваются указанные значения.
В следующем примере кода показано, как получить экземпляр класса HelpAttribute , связанный с классом
Widget и его методом Display .
if (widgetClassAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)widgetClassAttributes[0];
Console.WriteLine($"Widget class help URL : {attr.Url} - Related topic : {attr.Topic}");
}
if (displayMethodAttributes.Length > 0)
{
HelpAttribute attr = (HelpAttribute)displayMethodAttributes[0];
Console.WriteLine($"Display method help URL : {attr.Url} - Related topic : {attr.Topic}");
}
Console.ReadLine();
НА ЗА Д
Новые возможности C# 8.0
31.10.2019 • 26 minutes to read • Edit Online
Как и большинство структур, метод ToString() не изменяет состояние. Это можно указать, добавив
модификатор readonly в объявление ToString() :
Предыдущее изменение создает предупреждение компилятора, так как ToString обращается к свойству
Distance , которое не помечено как readonly :
warning CS8656: Call to non-readonly member 'Point.Distance.get' from a 'readonly' member results in an
implicit copy of 'this'
Компилятор выдает предупреждение, когда ему требуется создать защитную копию. Свойство Distance не
изменяет состояние, поэтому вы можете устранить это предупреждение, добавив модификатор readonly в
объявление:
Обратите внимание, что модификатор readonly необходим для свойства только для чтения. Компилятор не
предполагает, что методы доступа get не изменяют состояние; readonly необходимо объявить явно.
Автоматические реализуемые свойства являются исключением; компилятор будет рассматривать все
автоматические реализуемые методы получения как readonly, поэтому не нужно добавлять модификатор
readonly к свойствам X и Y .
Эта функция позволяет указать намерение вашего проекта, чтобы компилятор мог применить его и
выполнить оптимизацию на основе намерения. Дополнительные сведения о членах, доступных только для
чтения, см. в справочнике по языку на странице readonly .
Если для приложения определен тип RGBColor , который создается на основе компонентов R , G и B , вы
можете преобразовать значение Rainbow в RGB -значение, используя следующий метод, содержащий
выражение switch:
Шаблоны свойств
Шаблон свойств позволяет сопоставлять свойства исследуемого объекта. Рассмотрим сайт электронной
коммерции, на котором должен вычисляться налог с продаж по адресу покупателя. Это вычисление не
является основной задачей класса Address . Оно меняется со временем и, скорее всего, чаще, чем изменения
формата адреса. Сумма налога с продаж зависит от свойства State адреса. В следующем методе
используется шаблон свойства для вычисления налога с продаж по адресу и цене:
При сопоставлении шаблонов создается сокращенный синтаксис для выражения этого алгоритма.
Шаблоны кортежей
Некоторые алгоритмы зависят от нескольких наборов входных данных. Шаблоны кортежей позволяют
переключаться между несколькими значениями, выраженными как кортежи. В следующем примере кода
показано выражение switch для игры камень, ножницы, бумага:
public static string RockPaperScissors(string first, string second)
=> (first, second) switch
{
("rock", "paper") => "rock is covered by paper. Paper wins.",
("rock", "scissors") => "rock breaks scissors. Rock wins.",
("paper", "rock") => "paper covers rock. Paper wins.",
("paper", "scissors") => "paper is cut by scissors. Scissors wins.",
("scissors", "rock") => "scissors is broken by rock. Rock wins.",
("scissors", "paper") => "scissors cuts paper. Scissors wins.",
(_, _) => "tie"
};
В сообщении указан победитель. В случае отклонения представляются три комбинации, ведущие к ничьей,
или другие текстовые входные данные.
Позиционные шаблоны
Некоторые типы включают метод Deconstruct , свойства которого деконструируются на дискретные
переменные. Если метод Deconstruct доступен, можно использовать позиционные шаблоны для проверки
свойств объекта и использовать эти свойства для шаблона. Рассмотрим следующий класс Point , который
содержит метод Deconstruct для создания дискретных переменных для X и Y :
Кроме того, нужно учитывать следующее перечисление, представляющее различные позиции квадранта:
Объявления using
Объявление using — это объявление переменной, которому предшествует ключевое слово using . Оно
сообщает компилятору, что объявляемая переменная должна быть удалена в конце области видимости. Для
примера рассмотрим следующий код, в котором записывается текстовый файл:
В приведенном выше примере файл удаляется при достижении закрывающей фигурной скобки метода. Это
конец области, в котором объявляется file . Приведенный выше код эквивалентен следующему коду, в
котором используется классическая инструкция using:
В приведенном выше примере файл удаляется при достижении закрывающей фигурной скобки, связанной с
инструкцией using .
В обоих случаях компилятор создает вызов метода Dispose() . Компилятор создает ошибку, если выражение
в инструкции using нельзя удалить.
int M()
{
int y;
LocalFunction();
return y;
Следующий код содержит статическую локальную функцию. Она может быть статической, так как она не
обращается ко всем переменным в области видимости:
int M()
{
int y = 5;
int x = 7;
return Add(x, y);
Асинхронные потоки
Начиная с C# версии 8.0 можно создавать и использовать потоки асинхронно. В методе, который возвращает
асинхронный поток, есть три свойства:
1. Он объявлен с помощью модификатора async .
2. Он возвращает интерфейс IAsyncEnumerable<T>.
3. Метод содержит инструкции yield return для возвращения последовательных элементов в асинхронном
потоке.
Для использования асинхронного потока требуется добавить ключевое слово await перед ключевым
словом foreach при перечислении элементов потока. Для добавления ключевого слова await требуется,
чтобы метод, который перечисляет асинхронный поток, был объявлен с помощью модификатора async и
возвращал тип, допустимый для метода async . Обычно это означает возвращение структуры Task или
Task<TResult>. Это также может быть структура ValueTask или ValueTask<TResult>. Метод может
использовать и создавать асинхронный поток. Это означает, что будет возвращен интерфейс
IAsyncEnumerable<T>. Следующий код создает последовательность чисел от 0 до 19 с интервалом 100 мс
между генерированием каждого числа:
Индексы и диапазоны
Диапазоны и индексы обеспечивают лаконичный синтаксис для доступа к отдельным элементам или
диапазонам в последовательности.
Поддержка языков опирается на два новых типа и два новых оператора:
System.Index представляет индекс в последовательности.
Оператор ^ (индекс с конца), который указывает, что индекс указан относительно конца
последовательности.
System.Range представляет вложенный диапазон последовательности.
Оператор диапазона .. , который задает начало и конец диапазона в качестве своих операндов.
Начнем с правил для использования в индексах. Рассмотрим массив sequence . Индекс 0 совпадает с
sequence[0] . Индекс ^0 совпадает с sequence[sequence.Length] . Обратите внимание, что sequence[^0]
создает исключение так же, как и sequence[sequence.Length] . Для любого числа n индекс ^n совпадает с
sequence.Length - n .
Диапазон указывает начало и конец диапазона. Начало диапазона является включающим, но конец
диапазона является исключающим, то есть начало включается в диапазон, а конец не включается. Диапазон
[0..^0] представляет весь диапазон так же, как [0..sequence.Length] представляет весь диапазон.
Рассмотрим несколько примеров. Обратите внимание на следующий массив, который помечен индексом от
начала и от конца:
Следующий код создает поддиапазон со словами "quick", "brown" и "fox". Он включает в себя элементы от
words[1] до words[3] . Элемент words[4] в диапазон не входит.
Следующий код создает поддиапазон со словами "lazy" и "dog". Он включает элементы words[^2] и
words[^1] . Конечный индекс words[^0] не включен:
В следующих примерах создаются диапазоны, которые должны быть открыты для начала, конца или в обоих
случаях:
var allWords = words[..]; // contains "The" through "dog".
var firstPhrase = words[..4]; // contains "The" through "fox"
var lastPhrase = words[6..]; // contains "the", "lazy" and "dog"
Индексы и диапазоны поддерживаются не только массивами. Кроме того, можно использовать индексы и
диапазоны со строкой (Span<T> или ReadOnlySpan<T>). Дополнительные сведения см. в разделе
Поддержка типа для индексов и диапазонов.
Вы можете изучить сведения об индексах и диапазонах адресов в руководстве Индексы и диапазоны.
тип Coords<int> является неуправляемым в C# 8.0 и более поздних версиях. Как и для любого
неуправляемого типа, вы можете создать указатель на переменную этого типа или выделить блок памяти в
стеке для экземпляров этого типа:
Span<Coords<int>> coordinates = stackalloc[]
{
new Coords<int> { X = 0, Y = 0 },
new Coords<int> { X = 0, Y = 3 },
new Coords<int> { X = 4, Y = 0 }
};
Новые возможности в выпуске C# 7.3 можно разделить на две основные группы. Одна из них — набор
функций для повышения эффективности безопасного кода до уровня небезопасного кода. Вторая —
постепенные улучшения существующих функций. Кроме того, в этом выпуске добавлены новые параметры
компилятора.
В ту группу, которая отвечает за повышение производительности безопасного кода, входят следующие
новые возможности:
доступ к полям фиксированной ширины без закрепления;
возможность переназначать локальные переменные ref ;
возможность использовать инициализаторы для массивов stackalloc ;
возможность использовать инструкции fixed с любым типом, который поддерживает шаблон;
возможность использовать дополнительные универсальные ограничения.
Для существующих функций предоставлены следующие улучшения:
возможность проверить == и != с типами кортежа;
больше мест для использование переменных выражений;
возможность подключить атрибуты к резервному полю автоматически реализуемых свойств;
улучшенное разрешение методов, аргументы которых отличаются модификатором in ;
стало меньше неоднозначных вариантов при разрешении перегрузок.
Новые параметры компилятора:
-publicsign позволяет включить подписывание сборок как программного обеспечения с открытым
кодом;
-pathmap позволяет предоставить сопоставление для исходных каталогов.
В оставшейся части этой статьи представлены дополнительные сведения и ссылки по каждому из
перечисленных улучшений. Эти функции можно изучить в своей среде с помощью глобального средства
dotnet try :
В более ранних версиях C# переменную необходимо закрепить, чтобы получить доступ к целым числам,
входящим в myFixedField . Теперь приведенный ниже код компилируется без закрепления переменной p
внутри отдельного оператора fixed :
class C
{
static S s = new S();
Переменная p обращается к одному элементу в myFixedField . Для этого не нужно объявлять отдельную
переменную int* . Обратите внимание, что контекст unsafe по-прежнему является обязательным. В более
ранних версиях C# необходимо объявить второй фиксированный указатель:
class C
{
static S s = new S();
Дополнительные сведения см. в статье о возвращаемых значениях ref и локальных переменных ref ,а
также в статье foreach .
Массивы stackalloc поддерживают инициализаторы
Раньше вы могли задавать значения для элементов массива при его инициализации:
Теперь такой же синтаксис можно применять к массивам, в объявлении которых есть stackalloc :
int* pArr = stackalloc int[3] {1, 2, 3};
int* pArr2 = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};
Инструкция fixed ранее поддерживала лишь ограниченный набор типов. Начиная с C# 7.3 любой тип,
содержащий метод GetPinnableReference() , который возвращает ref T или ref readonly T , может иметь
инструкцию fixed . Добавление этой возможности означает, что fixed можно применять для
System.Span<T> и связанных типов.
Дополнительные сведения см. в статье об инструкции fixed в справочнике по языку.
Расширенные универсальные ограничения
Теперь вы можете указать тип System.Enum или System.Delegate в качестве ограничения базового класса для
параметра типа.
Вы также можете использовать новое ограничение unmanaged , чтобы указать, что параметр типа должен
быть неуправляемым типом.
Дополнительные сведения см. в статьях об универсальных ограничениях where и ограничениях параметров
типа.
Добавление этих ограничений в существующие типы — это несовместимое изменение. Закрытые
универсальные типы не будут соответствовать подобным ограничениям.
[field: SomeThingAboutFieldAttribute]
public int SomeProperty { get; set; }
Теперь перегрузка по значению (первая в предыдущем примере) считается лучше, чем перегрузка по
атрибуту "только для чтения". Чтобы вызвать версию со ссылочным аргументом "только для чтения",
необходимо при вызове метода указать модификатор in .
NOTE
Эта возможность реализована как исправление ошибки. Теперь эта ситуация не создает неоднозначности, даже если
установлена версия языка 7.2.
public class B
{
public B(int i, out int j)
{
j = i;
}
}
public class D : B
{
public D(int i) : base(i, out var j)
{
Console.WriteLine($"The value of 'j' is {j}");
}
}
В очередной версии C# 7.2 реализован ряд новых полезных возможностей. Одной из основных задач этого
выпуска стало повышение эффективности работы с типами значений за счет исключения избыточных
операций копирования и выделения памяти.
Остальные нововведения реализованы преимущественно для удобства.
В C# 7.2 предусмотрен элемент управления выбором версии языка, с помощью которого можно задать
версию языка компилятора.
Новые языковые функции в этом выпуске
Методы написания безопасного и эффективного кода
Ряд улучшений синтаксиса, обеспечивающих работу с типами значений с использованием
семантики ссылок.
Неконечные именованные аргументы
После именованных аргументов могут следовать позиционные аргументы.
Начальные символы подчеркивания в числовых литералах
Перед любыми печатными знаками в числовых литералах теперь могут использоваться начальные
знаки подчеркивания.
Модификатор доступа private protected
Модификатор доступа private protected разрешает доступ для производных классов в одной
сборке.
Условные выражения ref
Результат условного выражения ?: теперь может быть ссылкой.
В оставшейся части этой статьи представлены общие сведения об этих функциях. Каждая функция
сопровождается обоснованием. Вы изучите синтаксис Эти функции можно изучить в своей среде с помощью
глобального средства dotnet try :
1. Установите глобальное средство dotnet-try.
2. Клонируйте репозиторий dotnet/try-samples.
3. Для репозитория try-samples установите в качестве текущего каталога подкаталог csharp7.
4. Запустите dotnet try .
C# 7.1 — это первая доработанная версия для C#. Это означает, что новые выпуски для этого языка стали
выходить чаще. Вы сможете использовать новые возможности раньше. В идеале — как только функция
будет готова. В C# 7.1 можно настраивать компилятор в соответствии с указанной версией языка. Это
позволяет отделить решение обновить средства от решения обновить версию языка.
В C# 7.1 добавлен элемент конфигурации выбора версии языка, три новые возможности языка и новое
поведение компилятора.
Новые языковые функции в этом выпуске
async метод Main
Точка входа для приложения может иметь модификатор async .
Литеральные выражения default
Литеральные выражения по умолчанию можно использовать в выражениях значения по
умолчанию, если можно вывести тип целевого объекта.
Выводимые имена элементов кортежа
Имена элементов кортежа часто можно вывести из инициализации кортежа.
Сопоставление шаблонов в параметрах универсального типа
Выражения сопоставления шаблонов можно использовать с переменными, тип которых является
параметром универсального типа.
Наконец, у компилятора есть два параметра -refout и -refonly , которые управляют созданием базовой
сборки.
Чтобы использовать новые возможности доработанного выпуска, настройте версию языка компилятора,
выбрав необходимую.
В оставшейся части этой статьи представлены общие сведения об этих функциях. Каждая функция
сопровождается обоснованием. Вы изучите синтаксис Эти функции можно изучить в своей среде с помощью
глобального средства dotnet try :
1. Установите глобальное средство dotnet-try.
2. Клонируйте репозиторий dotnet/try-samples.
3. Для репозитория try-samples установите в качестве текущего каталога подкаталог csharp7.
4. Запустите dotnet try .
Async main
Метод async main позволяет использовать await в методе Main . Раньше пришлось бы написать:
Если программа не возвращает код выхода, объявите метод Main , возвращающий Task:
int count = 5;
string label = "Colors used in the map";
var pair = (count: count, label: label);
Имена элементов кортежа можно вывести из переменных, используемых для инициализации кортежа в C#
7.1:
int count = 5;
string label = "Colors used in the map";
var pair = (count, label); // element names are "count" and "label"
Переменные out
Существующий синтаксис, поддерживающий параметры out , в этой версии был улучшен. Переменные out
можно объявлять в списке аргументов в вызове метода, не записывая отдельный оператор объявления:
if (int.TryParse(input, out int result))
Console.WriteLine(result);
else
Console.WriteLine("Could not parse input");
Для ясности можно указать тип переменной out , как показано выше. В то же время язык поддерживает
использование неявно типизированной локальной переменной:
Кортежи
C# предоставляет расширенный синтаксис для классов и структур, который используется для объяснения
цели проекта. Однако в некоторых случаях расширенный синтаксис требует дополнительной работы с
минимальной результативностью. Зачастую требуется написание методов, которым нужна простая
структура, состоящая из более чем одного элемента данных. Для поддержки этих сценариев в C# были
добавлены кортежи. Кортежи — это упрощенные структуры данных, содержащие несколько полей для
представления элементов данных. Поля не проверяются, и собственные методы определять нельзя.
NOTE
Кортежи существовали и в версиях C#, предшествовавших версии 7.0, но были неэффективны и не имели языковой
поддержки. Это означает, что ссылки на элементы кортежа можно было задавать только в виде Item1 , Item2 и т. д.
В C# 7.0 реализуется языковая поддержка кортежей, что позволяет работать с семантическими именами полей
кортежа с использованием новых, более эффективных типов кортежей.
Можно создать кортеж путем присваивания значения каждого элемента, а также (необязательно) задать
семантические имена для каждого из элементов кортежа:
Кортеж namedLetters содержит поля, которые называются Alpha и Beta . Эти имена существуют только во
время компиляции и не сохраняются, например при проверке кортежа посредством отражения во время
выполнения.
В назначении кортежа можно также указать имена полей в правой части назначения:
В некоторых случаях элементы возвращаемого методом кортежа необходимо распаковать. С этой целью для
каждого значения в этом кортеже объявляется отдельная переменная. Такая распаковка называется
деконструкцией кортежа:
Аналогичную деконструкцию можно обеспечить для любого типа в .NET. Можно написать метод
Deconstruct в качестве члена класса. Метод Deconstruct предоставляет набор аргументов out для каждого
из свойств, которые нужно извлечь. Рассмотрим этот класс Point , предоставляющий метод deconstructor,
который извлекает координаты X и Y :
Пустые переменные
При деконструкции кортежа или вызове метода с параметрами out часто требуется определить
переменную, которую вы не планируете использовать и значение которой не важно. Для работы в таких
сценариях в C# реализована поддержка пустых переменных. Пустая переменная представляет собой
доступную только для записи переменную с именем _ (знак подчеркивания). Вы можете назначить одной
переменной все значения, которые не потребуются в дальнейшем. Пустая переменная является аналогом
неприсвоенной переменной и не может использоваться в коде где-либо, за исключением оператора
присваивания.
Пустые переменные поддерживается в следующих случаях.
При деконструкции кортежей или пользовательских типов.
При вызове методов с параметрами out.
В операции сопоставления шаблонов с выражениями is и switch.
В качестве автономного идентификатора в тех случаях, когда требуется явно идентифицировать значение
присваивания как пустую переменную.
В приведенном ниже примере определяется метод QueryCityDataForYears , который возвращает кортеж из
6 элементов, содержащий данные по городу за два разных года. В вызове метода в этом примере
учитываются только два возвращаемых методом значения population, поэтому при деконструкции кортежа
оставшиеся значения обрабатываются как пустые переменные.
using System;
using System.Collections.Generic;
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int
year2)
{
int population1 = 0, population2 = 0;
double area = 0;
Регулярные выражения
Сопоставление шаблонов — это функция, которая позволяет применять отправку метода для свойств, а не
для типа объекта. Возможно, вы уже знакомы с отправкой метода на основе типа объекта. В объектно-
ориентированном программировании виртуальные и переопределяющие методы предоставляют синтаксис
языка, позволяющий отправлять метод, исходя из типа объекта. Базовый и производный классы
предусматривают различные реализации. Выражения сопоставления шаблонов расширяют эту концепцию,
позволяя легко внедрять аналогичные шаблоны отправки для типов и элементов данных, не связанных
иерархией наследования.
Сопоставление шаблонов поддерживает выражения is и switch . Каждое из них позволяет проверять
объект и его свойства и определять, соответствует ли этот объект искомому шаблону. Для добавления правил
в шаблон используется ключевое слово when .
Выражение шаблона is позволяет использовать знакомый оператор is для запроса объекта о типе и
присваивания результата в одной инструкции. Следующий код проверяет, является ли переменная int ; если
да, добавляет ее к текущей сумме:
Можно объявить возвращаемое значение как ref и изменять это значение в матрице, как показано в
следующем коде:
Язык C# включает правила, которые защищают вас от неправильного использования локальных переменных
и возвращаемых значений ref :
Необходимо добавить ключевое слово ref в сигнатуру метода и все инструкции return в методе.
Это позволяет уточнить, что метод возвращает значение по ссылке, во всех местах.
Объект ref return может быть назначен переменной-значению или переменной ref .
Вызывающий объект определяет, копируется ли возвращаемое значение. Пропуск модификатора
ref при присваивании возвращаемого значения указывает, что вызывающий объект хочет
получить копию значения, а не ссылку на хранилище.
Присвоить локальной переменной ref стандартное возвращаемое значение метода невозможно.
Это запрещает использовать операторы вида ref int i = sequence.Count();
Переменную ref невозможно возвращать переменной, которая продолжает существовать даже после
того, как метод будет выполнен.
Это означает невозможность возвращения ссылки на локальную переменную или переменную с
аналогичной областью.
Возвращаемые значения и локальные переменные ref не могут использоваться с асинхронными
методами.
На момент, когда асинхронный метод возвращает значение, компилятору неизвестно, присвоено ли
переменной, на которую указывает ссылка, окончательное значение.
Добавление локальных переменных и возвращаемых значений ref дает возможность использовать более
эффективные алгоритмы, поскольку избавляет от необходимости многократно копировать значения или
выполнять операции разыменования.
Добавление ref для возврата значения является изменением, совместимым на уровне исходного кода.
Существующий код компилируется, но возвращаемое значение ссылочного типа копируется при
назначении. Вызывающие объекты должны изменить переменную хранилища для возвращаемого значения
на локальную переменную ref , чтобы это значение хранилось в качестве ссылки.
Дополнительные сведения см. в статье ref (Справочник по C#).
Локальные функции
Модели многих классов включают методы, вызываемые только из одного места. Эти дополнительные
закрытые методы делают каждый метод небольшим и направленным. Локальные функции позволяют
объявлять методы в контексте другого метода. Локальные функции позволяют читателям класса легче
увидеть, что локальный метод вызывается только из контекста, в котором он объявлен.
Существуют два общих варианта использования локальных функций: открытые методы итератора и
открытые асинхронные методы. Оба эти типа методов создают код, который сообщает об ошибках позднее,
чем могли ожидать программисты. В случае методов итератора исключения наблюдаются только при вызове
кода, перечисляющего возвращенную последовательность. В случае асинхронных методов исключения
наблюдаются только при ожидании возвращаемого объекта Task . В следующем примере показано
отделение проверки параметров от реализации итератора с использованием локальной функции:
return alphabetSubsetImplementation();
IEnumerable<char> alphabetSubsetImplementation()
{
for (var c = start; c < end; c++)
yield return c;
}
}
Та же технология может применяться с методами async для того, чтобы исключения, возникающие при
проверке параметров, выдавались до начала асинхронной работы:
return longRunningWorkImplementation();
NOTE
Некоторые из макетов, поддерживаемых локальными функциями, также могут выполняться с помощью лямбда-
выражений. Если вам интересно, прочтите более подробные сведения о различиях.
// Expression-bodied constructor
public ExpressionMembersExample(string label) => this.Label = label;
// Expression-bodied finalizer
~ExpressionMembersExample() => Console.Error.WriteLine("Finalized!");
NOTE
В этом примере метод завершения не требуется, он приводится для демонстрации синтаксиса. Метод завершения
следует реализовывать в классе только в том случае, если это необходимо для высвобождения неуправляемых
ресурсов. Кроме того, вместо управления неуправляемыми ресурсами напрямую можно воспользоваться классом
SafeHandle.
Новые расположения для элементов, составляющих выражение, представляют важный этап развития языка
C#. Эти функции были реализованы членами сообщества, работающими над проектом Roslyn с открытым
исходным кодом.
Изменение метода на элемент, воплощающий выражение, является совместимым на уровне двоичного кода.
Выражения throw
В C# throw всегда был оператором. Поскольку throw — оператор, а не выражение, конструкции C#
находились там, где использовать их было невозможно. Они включали условные выражения, выражения
объединения со значением NULL и некоторые лямбда-выражения. Добавление элементов, воплощающих
выражение, расширяет список мест, в которых могут пригодиться выражения throw . Для записи этих
конструкций в C# 7.0 представлены выражения throw.
Это добавление упрощает написание кода на основе выражений. Дополнительные инструкции для проверки
на наличие ошибок не требуются.
NOTE
Чтобы использовать тип ValueTask<TResult>, необходимо добавить пакет NuGet System.Threading.Tasks.Extensions .
Это улучшение особенно полезно для авторов библиотек, которые хотят избежать выделения Task в
критическом по производительности коде.
0b в начале константы означает, что число записано в двоичном формате. Двоичные числа могут быть
длинными, поэтому для удобства работы с битовыми шаблонами можно разделять разряды с помощью
символа _ , как показано выше в двоичной константе. Разделитель разрядов может находиться в любом
месте константы. В десятичных числах он обычно используется для разделения тысяч:
Суммируя вышеизложенное, числовые константы можно объявлять в гораздо более удобочитаемом виде.
Новые возможности C# 6
23.10.2019 • 16 minutes to read • Edit Online
Свойства FirstName и LastName могут задаваться только в теле конструктора того же класса:
Эта функция обеспечивает действительную поддержку создания неизменяемых типов на уровне языка и
использование более краткого и удобного синтаксиса автосвойств.
Если в результате добавления такого синтаксиса доступный метод не удаляется, такое изменение считается
совместимым на уровне двоичного кода.
Инициализаторы автосвойств
Инициализаторы автосвойств позволяют объявить начальное значение для автосвойства при объявлении
свойства.
Член Grades инициализируется там, где он объявлен. Это упрощает выполнение инициализации ровно один
раз. Инициализация является частью объявления свойства, упрощая уравнение выделения хранилища с
открытым интерфейсом для объектов Student .
Этот синтаксис также можно использовать для свойств только для чтения:
using static
Усовершенствование using static позволяет импортировать статические методы одного класса. Необходимо
указать класс, который вы используете:
Объект Math не содержит никаких элементов экземпляров. using static можно также использовать для
импорта статических методов класса для класса, который содержит как статические методы, так и методы
экземпляра. Одним из наиболее полезных примеров является String:
NOTE
В операторе static using необходимо использовать полное имя класса, System.String . Нельзя использовать вместо
него ключевое слово string .
При импорте из оператора static using методы расширения находятся в области только при вызове с
помощью синтаксиса вызова метода расширения. Это не так при вызове в качестве статического метода.
Это часто встречается в запросах LINQ. Шаблон LINQ можно импортировать путем импорта Enumerable
или Queryable.
В предыдущем примере переменная first назначается null , если объект person имеет значение null . В
противном случае назначается значение свойства FirstName . Важно то, что ?. означает, что эта строка
кода не создает NullReferenceException , если переменная person имеет значение null . Вместо этого
выполняется сокращенное вычисление, и возвращается null . Можно также использовать условный
оператор NULL для доступа к массиву или индексатору. Замените [] на ?[] в выражении индекса.
Следующее выражение возвращает string , независимо от значения person . Эта конструкция часто
используется с оператором объединения со значением NULL для присвоения значений по умолчанию, если
одно из свойств имеет значение null . В случае сокращенного вычисления выражений возвращаемое
значение null имеет тип для сопоставления с полным выражением.
?. также можно использовать для условного вызова методов. Чаще всего функции-члены с условным
оператором NULL используются для безопасного вызова делегатов (или обработчиков событий), которые
могут иметь значение null . Это делается путем вызова метода Invoke делегата с помощью оператора ?.
для доступа к члену. Вы увидите пример в статье Шаблоны делегатов.
Правила оператора ?. гарантируют, что левая часть оператора вычисляется только один раз. Это
обеспечивает поддержку многих идиом, включая пример с использованием обработчиков событий.
// preferred in C# 6:
this.SomethingHappened?.Invoke(this, eventArgs);
Обеспечение того, что левая часть вычисляется только один раз, также позволяет использовать любое
выражение, включая вызовы методов, в левой части ?.
Интерполяция строк
В C# 6 новая функция интерполяции строк позволяет внедрять выражения в строку. Просто добавьте перед
строкой префикс $ и используйте выражения между { и } вместо порядковых номеров:
В этом примере для замененных выражений используются свойства. Можно использовать любое
выражение. Например, можно вычислить средний балл учащегося как часть интерполяции:
public string GetGradePointPercentage() =>
$"Name: {LastName}, {FirstName}. G.P.A: {Grades.Average():F2}";
Предыдущая строка кода форматирует значение для Grades.Average() как число с плавающей запятой с
двумя десятичными разрядами.
Часто требуется отформатировать строку, полученную с использованием конкретного языка и региональных
параметров. Для этого положитесь на тот факт, что объект, созданный в результате интерполяции строк,
можно неявно преобразовывать в System.FormattableString. Экземпляр FormattableString содержит
составную строку формата и результаты вычисления выражений до преобразования в строки. С помощью
метода FormattableString.ToString(IFormatProvider) вы можете явно указать язык и региональные параметры
при форматировании строки. Так, приведенный ниже пример создает строку, используя немецкий язык (de-
DE ) в качестве языка и региональных параметров. (В качестве десятичного разделителя в немецком обычно
используется символ ",", а "." — в качестве разделителя тысяч.)
Чтобы начать работу с интерполяцией строк, см. интерактивный учебник Интерполяция строк в C#, статью
Интерполяция и учебник Интерполяция строк в C#.
Фильтры исключений
Фильтры исключений — это предложения, которые определяют, когда должно применяться данное
предложение catch. Если выражение, используемое для фильтра исключений, принимает значение true ,
предложение catch выполняет обычную обработку исключения. Если выражение принимает значение
false , то предложение catch пропускается. Например, эту функцию можно использовать для просмотра
сведений об исключении, чтобы определить, может ли предложение catch обработать исключение:
Выражение nameof
Результатом применения выражения nameof является имя символа. Это отличный способ заставить
инструменты работать, если вам требуется имя переменной, свойства или поля члена. Одно из наиболее
распространенных применений nameof — предоставление имени символа, который вызвал исключение:
if (IsNullOrWhiteSpace(lastName))
throw new ArgumentException(message: "Cannot be blank", paramName: nameof(lastName));
Эта функция означает, что ассоциативные контейнеры могут быть инициализированы с помощью
синтаксиса, аналогичного используемому для контейнеров последовательностей для нескольких версий.
В более ранних версиях C# вызов этого метода с использованием синтаксиса группы методов приведет к
сбою:
Task.Run(DoThings);
В этой статье содержится описание основных выпусков языка C#. Команда разработчиков C# продолжает
добавлять новые функции. Сведения о состоянии функции для отдельных языков, включая функции, которые
будут добавлены в предстоящие выпуски, см. в репозитории dotnet/roslyn на сайте GitHub.
IMPORTANT
В некоторых возможностях используются типы и методы, которые в спецификации C# определены как стандартная
библиотека. Платформа .NET предоставляет эти типы и методы в разных пакетах. например, обработка исключений.
Каждая инструкция и выражение throw проверяется, чтобы убедиться, что вызываемый объект является
производным от Exception. Аналогичным образом каждая инструкция catch проверяется, чтобы убедиться, что
перехваченный тип является производным от Exception. В каждой версии могут добавляться новые требования. Чтобы
использовать новейшие возможности языка в старой среде, может потребоваться установить определенные
библиотеки. Эти зависимости описаны на странице для каждой конкретной версии. Дополнительные сведения о связи
между языком и библиотекой, а также общие сведения о такой зависимости см. здесь.
Для использования средств сборки C# требуется последний основной выпуск версии языка по умолчанию.
Основные выпуски, описанные в других статьях этого раздела, могут чередоваться с дополнительными
выпусками. Чтобы использовать новые возможности доработанного выпуска, настройте версию языка
компилятора, выбрав необходимую. После C# 7.0 было три дополнительных выпуска:
C# 7.3.
Версия C# 7.3 появилась в Visual Studio 2017 версии 15.7 и пакете SDK для .NET Core 2.1.
C# 7.2.
Версия C# 7.2 появилась в Visual Studio 2017 версии 15.5 и пакете SDK для .NET Core 2.0.
C# 7.1.
Версия C# 7.1 появилась в Visual Studio 2017 версии 15.3 и пакете SDK для .NET Core 2.0.
C# версии 1.0
Если взглянуть на C# версии 1.0, выпущенный вместе с Visual Studio .NET 2002, станет очевидно его сходство
с Java. В число поставленных перед ECMA задач проектирования входило создание "простого и
современного объектно-ориентированного языка общего назначения". В то время схожесть с Java означала
достижение этих ранних целей проектирования.
Однако если взглянуть на C# 1.0 сейчас, вам может стать немного не по себе. В этой версии не было
встроенных асинхронных возможностей и некоторых функций для универсальных шаблонов, к которым вы
уже привыкли. Фактически, в ней вообще не было универсальных шаблонов. А LINQ? Еще нет. Для
добавления этих возможностей потребовалось несколько лет.
По современным меркам C# версии 1.0 не предоставлял практически никаких возможностей. Вам пришлось
бы писать довольно подробный код. Однако надо же было с чего-то начинать. C# версии 1.0 был достойной
альтернативой Java на платформе Windows.
Основные возможности C# 1.0 включали следующие компоненты:
Классы
Структуры
Интерфейсы
События
Свойства
Делегаты
Выражения
Операторы
Атрибуты
C# версии 2.0
Вот теперь все становится гораздо интереснее. Давайте рассмотрим некоторые основные возможности языка
C# 2.0, выпущенного в 2005 году вместе с Visual Studio 2005:
Универсальные шаблоны
Разделяемые типы
Анонимные методы
Типы значений, допускающие значение NULL
Итераторы
Ковариантность и контравариантность
Другие возможности C# 2.0 расширяли существующие возможности:
Отдельный доступ к методу получения и методу задания
Преобразования групп методов (делегаты)
Статические классы
Выведение делегата
Хотя сначала C# и был универсальным объектно-ориентированным языком, в C# версии 2.0 все кардинально
изменилось. Нащупав опору под ногами, команда уделила внимание решению серьезных проблем, с
которыми сталкивались разработчики. И они подошли к делу с огоньком.
Благодаря универсальным шаблонам вы получаете типы и методы, которые могут работать с произвольным
типом, при этом обеспечивая безопасность типа. В результате, например, наличие List<T> позволяет
использовать List<string> или List<int> и совершать типобезопасные операции с этими строками или
целыми числами, выполняя итерацию по ним. Использовать универсальные шаблоны удобнее, чем создавать
ListInt , производный от ArrayList , или выполнять приведение из Object для каждой операции.
В C# версии 2.0 появились итераторы. Если кратко, итераторы позволяют изучать все элементы в List (или
других перечислимых типов) с помощью цикла foreach . Наличие итераторов в качестве первоклассного
компонента в языке значительно улучшило удобочитаемость языка и способность воспринимать код.
А еще C# продолжил играть в салки с Java. Для Java уже были выпущены версии, включающие
универсальные шаблоны и итераторы. Но вскоре этому было суждено измениться, так как языки продолжили
развиваться независимо.
C# версии 3.0
C# версии 3.0 был выпущен в конце 2007 года вместе с Visual Studio 2008, однако существенно набор
возможностей языка пополнится только в .NET Framework версии 3.5. Эта версия ознаменовала
кардинальное изменение развития C#. Она доказала, что C# действительно является достойным языком
программирования. Давайте рассмотрим некоторые основные возможности в этой версии:
Автоматически реализуемые свойства
Анонимные типы
Выражения запросов
Лямбда-выражения
Деревья выражений
Методы расширения
Неявно типизированные локальные переменные
Разделяемые методы
Инициализаторы объектов и коллекций
Если оглянуться назад, становится ясно, что многие из этих возможностей не могли не появиться или
появиться отдельно друг от друга. Все они образуют единый и стратегически значимый набор. Принято
считать, что уникальным преимуществом C# было выражение запроса, которое также называется LINQ.
Немного углубившись, можно отметить деревья выражений, лямбда-выражения и анонимные типы как
основу для создания LINQ. Однако в любом случае в C# 3.0 была представлена революционная концепция.
Версия C# 3.0 начала закладывать фундамент для превращения C# в гибридный объектно-
ориентированный/функциональный язык.
Среди прочего, теперь вы можете создавать декларативные запросы в стиле SQL для выполнения операций
с коллекциями. Вместо создания цикла for для вычисления среднего значения из списка целых чисел теперь
вам достаточно использовать list.Average() . Сочетание выражений запросов и методов расширения
позволяет сделать работу со списком целых чисел гораздо интеллектуальнее.
Людям потребовалось некоторое время, чтобы принять эту концепцию и начать применять ее, но это у них
получилось. А теперь, спустя несколько лет, код стал гораздо более кратким, простым и функциональным.
C# версии 4.0
Разработчикам версии C# 4.0, выпущенной вместе с Visual Studio 2010, пришлось приложить серьезные
усилия, чтобы не отставать от новаторской версии 3.0. С появлением версии 3.0 язык C# смог выйти из тени
Java и занять доминирующее положение. Он быстро становился утонченным и элегантным.
Следующая версия представила ряд интересных функций:
Динамическая привязка
Именованные/дополнительные аргументы
Универсальная ковариантность и контравариантность
Внедренные типы взаимодействия
Внедренные типы взаимодействия помогли сделать развертывание более удобным. Универсальная
ковариантность и контравариантность расширяют возможности применения универсальных шаблонов,
однако эти функции несколько академичны и высоко оценены авторами, создающими платформы и
библиотеки. Именованные и дополнительные параметры позволяют избавиться от многих перегрузок
методов и сделать работу более удобной. Однако ни одна из этих функций не является кардинальным
изменением.
Значимым событием стало появление ключевого слова dynamic . Благодаря ключевому слову dynamic в C#
версии 4.0 появилась возможность переопределять компилятор при типизации во время компиляции.
Используя это ключевое слово, вы можете создавать конструкции, характерные для языков с динамической
типизацией, таких как JavaScript. Вы можете создать dynamic x = "a string" и добавить туда шестерку,
отложив решение о дальнейших действиях во времени выполнения.
Динамическое связывание подвержено ошибкам, однако открывает великолепные возможности внутри
языка.
C# версии 5.0
Версия C# 5.0, выпущенная вместе с Visual Studio 2012, была крайне целенаправленной. Практически все
нововведения в этой версии касались другой инновационной концепции: модели async и await для
асинхронного программирования. Ниже приведен список основных функций:
Асинхронные члены
Информационные атрибуты вызывающего объекта
См. также
Code Project. Информационные атрибуты вызывающего объекта в C# 5.0
Информационный атрибут вызывающего объекта позволяет легко получать сведения о контексте, в котором
выполняется работа, не прибегая к массивному стандартному коду отражения. Он находит обширное
применение в задачах диагностики и ведения журнала.
Однако настоящими звездами этого выпуска являются async и await . После появления этих функций в 2012
г. C# удалось снова взять быка за рога, первоклассным образом внедрив асинхронность в язык. Если вы когда-
либо сталкивались с длительными операциями и реализацией переплетающихся обратных вызовов, эта
возможность, скорее всего, вам понравится.
C# версии 6.0
Версии 3.0 и 5.0 добавили в объектно-ориентированный язык C# несколько впечатляющих возможностей.
Версия 6.0, выпущенная вместе с Visual Studio 2015, отступает от подхода, ориентированного на реализацию
уникальных возможностей. Здесь предоставляются менее масштабные функции, с которыми
программирование на C# становится гораздо эффективнее. Вот некоторые из них:
Статические импорты
Фильтры исключений
Инициализаторы автосвойств
Элементы, воплощающие выражение
Null-распространитель
Интерполяция строк
Оператор nameof
Инициализаторы индекса
Другие новые возможности:
Выражение Await в блоках Catch и Finally
Значения по умолчанию для свойств метода получения
Каждая из этих функций интересна сама по себе. Но если взглянуть на них вместе, заметна интересная
особенность. В этой версии C# избавился от стандартного текста, чтобы сделать код более сжатым и
удобочитаемым. Поэтому эта версия, несомненно, привлечет сторонников простого и понятного кода.
Еще одно новшество, связанное с этой версией, не является функцией языка в обычном понимании.
Состоялся выпуск компилятора Roslyn в виде службы. Теперь компилятор C# написан на C#, и вы можете
использовать его при программировании.
C# версии 7.0
Сейчас последним основным номером версии для C# является 7.0. Эта версия была выпущена вместе с
Visual Studio 2017. В этой версии получили развитие некоторые функции из версии C# 6.0, однако
компилятор в виде службы отсутствует. Ниже приведены некоторые из новых функций:
Переменные Out
Кортежи и деконструкция
Сопоставление шаблонов
Локальные функции
Расширенные элементы, воплощающие выражение
Локальные переменные и возвращаемые значения Ref
Другие возможности:
Операции удаления
Двоичные литералы и цифровые разделители
Выражения throw
Все это предоставляет разработчиком множество новых возможностей и позволяет писать более понятный
код. Важным аспектом является доработка объявления переменных путем использования ключевого слова
out и поддержки нескольких возвращаемых значений благодаря кортежу.
Но при этом сфера применения C# продолжает расширяться. Теперь .NET Core подходит для любой
операционной системы, а также уверенно движется в направлении облачных технологий и портативности.
Очевидно, что разработчики активно занимаются этими новыми возможностями наравне с добавлением
новых функций.
Статья изначально опубликована в блоге NDepend , с разрешения Эрика Дитриха (Erik Dietrich) и
Патрика Смачиа (Patrick Smacchia ).
Связи между языковыми компонентами и типами
библиотек
22.06.2018 • 3 minutes to read • Edit Online
Определение языка C# требует, чтобы стандартная библиотека имела определенные типы, а также
определенные доступные члены этих типов. Компилятор создает код, использующий эти необходимые типы
и члены для многих разных языковых компонентов. При необходимости можно использовать пакеты NuGet,
которые содержат типы, необходимые для более новых версий языка при написании кода для сред, где эти
типы или члены еще не развернуты.
Эта зависимость от функциональности стандартной библиотеки входила в состав языка C# с самой первой
версии. В эту версию были включены следующие примеры:
Exception — используется для всех исключений, создаваемых компилятором.
String — тип C# string является синонимом String.
Int32 — синоним int .
Эта первая версия была простой: компилятор и стандартная библиотека поставлялись вместе, и для каждого
из них была только одна версия.
В последующих версиях C# в число зависимостей иногда добавлялись новые типы или члены. Примеры:
INotifyCompletion, CallerFilePathAttribute и CallerMemberNameAttribute. В C# 7.0 этот процесс продолжается
путем добавления зависимости от ValueTuple для реализации кортежей.
Группа разработчиков языка стремится минимизировать контактную зону типов и членов, необходимых в
совместимой стандартной библиотеке. Эта задача уравновешивается потребностью в простой структуре,
когда новые функции библиотек легко внедряются в язык. В будущих версиях C# появятся функции,
требующие новых типов и членов в стандартной библиотеке. Важно понять, как управлять этими
зависимостями в своей работе.
Управление зависимостями
Средства компилятора C# теперь отделены от цикла выпуска библиотек .NET на поддерживаемых
платформах. В действительности различные библиотеки .NET имеют разные циклы выпуска: .NET Framework
в Windows выпускается в Центре обновления Windows, .NET Core поставляется по отдельному расписанию,
а версии библиотеки для Xamarin поставляются со средствами Xamarin для каждой целевой платформы.
Большую часть времени вы не будете замечать эти изменения. Однако при работе с более новой версией
языка, которой нужны функции, пока отсутствующие в библиотеках .NET на данной платформе, вам
потребуется сослаться на пакеты NuGet, чтобы предоставить эти новые типы. По мере обновления
поддерживаемых вашим приложением платформ вы сможете удалить лишние ссылки.
Такое разделение означает, что даже ориентируясь на компьютеры, где может не быть соответствующей
платформы, вы можете использовать новые возможности языка.
Соображения относительно версии и обновления
для разработчиков на C#
23.10.2019 • 3 minutes to read • Edit Online
Совместимость очень важна при добавлении новых функций языка C#. В большинстве случаев
существующий код сможет компилироваться в новой версии компилятора безо всяких проблем.
Особое внимание требуется в тех случаях, когда в библиотеку добавляются новые возможности языка.
Например, если вы создаете новую библиотеку с функциями из последней версии, но ее должны
использовать приложения, созданные в предыдущих версиях компилятора. Или же вы обновляете
существующую библиотеку, но многие ваши пользователи пока используют старые версии. Принимая
решения о внедрении новых функций, необходимо учесть два аспекта совместимости: на уровне исходного
кода и на уровне двоичных файлов.
Несовместимые изменения
Если изменение не является совместимым ни на уровне исходного кода, ни на уровне двоичных
файлов, для зависимых приложений и библиотек потребуется изменить исходный код и заново
скомпилировать их.
Новый код:
Изменения, совместимые на уровне исходного кода, создают новый синтаксис, который изменяет
скомпилированный код одного или нескольких открытых (public) членов таким образом, который сохраняет
его совместимость со всеми существующими вызывающими объектами. Например, если подпись метода
ранее передавала параметр по значению, а теперь содержит in с параметром по ссылке, такое изменение
будет совместимым на уровне исходного кода, но не на уровне двоичных файлов:
Исходный код:
Новый код:
Во всех статьях о новых возможностях всегда указано, является ли новая функция для открытых объявлений
совместимый на уровне исходного кода или на уровне двоичных файлов.
Типы (Руководство по программированию на C#)
07.11.2019 • 20 minutes to read • Edit Online
int a = 5;
int b = a + 2; //OK
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
NOTE
Тем, кто ранее использовал C и C++, нужно обратить внимание на то, что в C# тип bool нельзя преобразовать в int.
Компилятор внедряет сведения о типе в исполняемый файл в виде метаданных. Среда CLR использует эти
метаданные во время выполнения для дальнейшего обеспечения безопасности типа при выделении и
освобождении памяти.
Задание типов в объявлениях переменных
При объявлении переменной или константы в программе необходимо либо задать ее тип, либо использовать
ключевое слово var, чтобы дать возможность компилятору определить его. В следующем примере показаны
некоторые объявления переменных, использующие встроенные числовые типы и сложные пользовательские
типы:
// Declaration only:
float temperature;
string name;
MyClass myClass;
Типы для параметров и возвращаемых значений метода задаются в сигнатуре метода. Далее представлена
сигнатура метода, который требует значение int в качестве входного аргумента и возвращает строку:
После объявления переменной она не может быть повторно объявлена с новым типом, и ей нельзя
присвоить значение, несовместимое с ее объявленным типом. Например, нельзя объявить переменную типа
int и затем присвоить ей логическое значение true. Однако значения могут быть преобразованы в другие
типы, например при сохранении в другие переменные или при передаче в качестве аргументов метода. Если
преобразование типов не приводит к потере данных, она выполняется компилятором автоматически. Для
преобразования, которое может привести к потере данных, необходимо выполнить приведение в исходном
коде.
Дополнительные сведения см. в разделе Приведение и преобразование типов.
Встроенные типы
C# предоставляет стандартный набор встроенных числовых типов для представления целых чисел, значений с
плавающей запятой, логических выражений, текстовых символов, десятичных значений и других типов
данных. Также существуют встроенные типы string и object . Они доступны для использования в любой
программе C#. Дополнительные сведения о встроенных типах см. в Справочных таблицах по встроенным
типам.
Пользовательские типы
Вы можете создавать собственные типы, используя конструкции структура, класс, интерфейс и перечисление.
Сама библиотека классов .NET — это коллекция пользовательских типов, предоставленных корпорацией
Майкрософт, которые вы можете свободно использовать в приложениях. По умолчанию в любой программе
C# доступны наиболее часто используемые типы из библиотеки классов. Чтобы сделать доступными другие
типы, нужно явным образом добавить в проект ссылку на сборку, в которой они определены. Если
компилятору предоставлена ссылка на сборку, то вы можете объявлять в коде переменные (и константы)
любых типов, объявленных в этой сборке. См. дополнительные сведения о библиотеке классов .NET.
NOTE
Как видно, все наиболее часто используемые типы организованы в пространство имен System. Но само по себе
пространство имен, в котором размещен тип, никак не зависит от того, является ли он типом значения или ссылочным
типом.
Типы значений
Типы значений являются производными от System.ValueType, который является производным от
System.Object. Типы, производные от System.ValueType, имеют особое поведение в среде CLR. Переменные
типа значения напрямую содержат свои значения, что означает, что память выделяется в любом контексте
объявления переменной. Нет раздельного размещения в куче или накладных расходов при сборке мусора
для переменных типа значения.
Существует две категории типов значений: структура и перечисление.
Встроенные числовые типы являются структурами и имеют свойства и методы, к которым можно
обращаться.
Но объявление и присвоение значений вы выполняете для них так, как если бы они были простыми
нестатистическими типами.
Типы значений являются запечатанными, что означает, например, что нельзя наследовать тип от
System.Int32 и нельзя определить структуру для наследования от любого пользовательского класса или
структуры, поскольку структура может наследовать только от System.ValueType. Тем не менее структура
может реализовывать один или несколько интерфейсов. Вы можете привести тип структуры к любому типу
интерфейса, который она реализует. Это приведет к операции упаковки-преобразования, которая упакует
структуру внутри объекта ссылочного типа в управляемой куче. Операции упаковки-преобразования
выполняются при передаче типа значения в метод, принимающий System.Object или любой тип интерфейса в
качестве входного параметра. Дополнительные сведения см. в разделе Упаковка-преобразование и
распаковка-преобразование.
Используйте ключевое слово struct, чтобы создать собственные пользовательские типы значений. Как
правило, структура используется как контейнер для небольшого набора связанных переменных, как показано
в следующем примере:
Дополнительные сведения о структурах см. в статье Structs (Структуры). См. дополнительные сведения о
типах значений.
Еще одна категория типов значений — это перечисления. Перечисление определяет набор именованных
целочисленных констант. Например, перечисление System.IO.FileMode из библиотеки классов .NET содержит
набор именованных целочисленных констант, которые определяют правила открытия файла. В следующем
примере представлено определение этого типа:
Интерфейс должен быть инициализирован вместе с объектом класса, который его реализует. Если MyClass
реализует IMyInterface , экземпляр IMyInterface создается так, как показано в следующем примере:
При создании объекта выделяется память в управляемой куче, и переменная хранит только ссылку на
расположение объекта. Хранение типов в управляемой куче требует дополнительных действий как при
выделении памяти, так и при удалении, которое выполняется функцией автоматического управления
памятью в среде CLR, известной как сборка мусора. Сборка мусора является высоко оптимизированным
процессом и в большинстве случаев она не создает помех для производительности. Дополнительные
сведения о сборке мусора см. в статье Автоматическое управление памятью.
Все массивы являются ссылочными типами, даже если их элементы являются типами значений. Массивы
являются неявно производными от класса System.Array, но в C# их можно объявлять и использовать с
упрощенным синтаксисом, как показано в следующем примере:
Ссылочные типы полностью поддерживают наследование. Создаваемый класс может наследовать от любого
другого интерфейса или класса, который не определен как запечатанный, а другие классы могут наследовать
от этого класса и переопределять его виртуальные методы. Дополнительные сведения о создании
собственных классов см. в статье Classes and Structs (Классы и структуры). Дополнительные сведения о
наследовании в C# и виртуальных методах см. в статье Inheritance (Наследование).
универсальные типы;
Тип может быть объявлен с одним или несколькими параметрами типа, служащими в качестве
местозаполнителя для фактического типа (конкретного типа), который клиентский код предоставит при
создании экземпляра типа. Такие типы называются универсальными типами. Например, тип .NET
System.Collections.Generic.List<T> имеет один параметр типа, которому в соответствии с соглашением
присвоено имя T. При создании экземпляра этого типа необходимо указать тип объектов, которые будут
содержаться в списке. Например, так используется список строковых значений:
Использование параметра типа позволяет повторно использовать один же класс для хранения элементов
любого типа, не преобразовывая каждый элемент в объект. Универсальные классы коллекций называются
строго типизированными коллекциями, так как компилятору известен конкретный тип элементов
коллекции и он может выдать ошибку во время компиляции, если, к примеру, вы попытаетесь добавить
целое число в объект stringList , созданный в предыдущем примере. Дополнительные сведения см. в статье
Универсальные шаблоны.
Связанные разделы
Дополнительные сведения см. в следующих разделах:
Приведение и преобразование типов
Упаковка-преобразование и распаковка-преобразование
Использование типа dynamic
Типы значений
Ссылочные типы
Классы и структуры
Анонимные типы
Универсальные шаблоны
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Преобразование типов XML -данных
Целочисленные типы
Ссылочные типы, допускающие значение null
11.11.2019 • 13 minutes to read • Edit Online
string? name;
Любая переменная, где ? не добавляется к имени типа, является ссылочным типом, не допускающим
значение NULL. Сюда входят все переменные ссылочных типов в существующем коде, если вы включили
эту функцию.
Компилятор использует статический анализ, чтобы определить, что ссылка, допускающая значение NULL,
имеет значение, отличное от NULL. Компилятор выдает предупреждение, если вы разыменовываете
ссылку, допускающую значение NULL, когда она может иметь значение NULL. Это поведение можно
переопределить с помощью оператора допуска NULL ! после имени переменной. Например, если вы
знаете, что переменная name не имеет значение NULL, а компилятор выдает предупреждение, напишите
следующий код, чтобы переопределить анализ компилятора:
name!.Length;
<Nullable>enable</Nullable>
Также можно использовать директивы для задания этих контекстов в любом месте в проекте:
#nullable enable . Устанавливает контекст с заметками о допустимости значений NULL и контекст с
предупреждениями о допустимости значений NULL в режим включен.
#nullable disable . Устанавливает контекст с заметками о допустимости значений NULL и контекст с
предупреждениями о допустимости значений NULL в режим отключен.
#nullable restore . Восстанавливает контекст с заметками о допустимости значений NULL и контекст с
предупреждениями о допустимости значений NULL в соответствии с параметрами проекта.
#nullable disable warnings . Устанавливает контекст с предупреждениями о допустимости значения
NULL в режим отключен.
#nullable enable warnings . Устанавливает контекст с предупреждениями о допустимости значения NULL
в режим включен.
#nullable restore warnings . Восстанавливает контекст с предупреждениями о допустимости NULL в
соответствии с параметрами проекта.
#nullable disable annotations . Устанавливает контекст с заметками о допустимости значения NULL в
режим отключен.
#nullable enable annotations . Устанавливает контекст с заметками о допустимости значения NULL в
режим включен.
#nullable restore annotations . Восстанавливает контекст с предупреждениями о заметках в соответствии
с параметрами проекта.
По умолчанию контексты с заметками и предупреждениями о допустимости значения NULL отключены.
Это означает, что существующий код компилируется без изменений и без создания новых
предупреждений.
См. также
Ссылочные типы, допускающие значение NULL. Черновик спецификации
Учебник "Введение в ссылки, допускающие значения NULL"
Перенос существующей базы кода на ссылки, допускающие значение NULL
Пространства имен (Руководство по
программированию в C#)
23.10.2019 • 2 minutes to read • Edit Online
System.Console.WriteLine("Hello World!");
System является пространством имен, а Console — это класс в нем. Можно использовать ключевое слово
using , и тогда полное имя не потребуется. См. следующий пример:
using System;
Console.WriteLine("Hello");
Console.WriteLine("World!");
namespace SampleNamespace
{
class SampleClass
{
public void SampleMethod()
{
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
}
}
}
См. также
Руководство по программированию на C#
Использование пространств имен
Практическое руководство. Использование пространства имен My
Имена идентификаторов
Директива using
:: Оператор
Типы, переменные и значения
04.11.2019 • 11 minutes to read • Edit Online
C# является строго типизированным языком. Каждая переменная и константа имеет тип, как и каждое
выражение, результатом вычисления которого является значение. Сигнатура каждого метода задает тип для
каждого входного параметра и для возвращаемого значения. Библиотека классов платформы .NET
Framework определяет набор встроенных числовых типов, а также более сложных типов, представляющих
широкое разнообразие логических конструкций, например файловую систему, сетевые подключения,
коллекции, массивы объектов и даты. Обычная программа на C# использует типы из этой библиотеки
классов и пользовательские типы, которые моделируют уникальные концепции конкретной сферы
применения.
В типах может храниться следующая информация:
место, необходимое для хранения переменной этого типа;
максимальное и минимальное значения, которые могут быть представлены;
содержащиеся в типе члены (методы, поля, события и т. д.);
базовый тип, от которого наследует этот тип;
расположение, в котором будет выделена память для переменных во время выполнения;
разрешенные виды операций.
Компилятор использует сведения о типах, чтобы проверить, все ли операции, выполняемые в коде, являются
типобезопасными. Например, при объявлении переменной типа int компилятор позволяет в дополнение
использовать переменную и операции вычитания. При попытке выполнить эти же операции для переменной
типа bool компилятор выдаст ошибку, как показано в следующем примере:
int a = 5;
int b = a + 2; //OK
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
NOTE
Тем, кто ранее использовал C и C++, нужно обратить внимание на то, что в C# тип bool нельзя преобразовать в int.
Компилятор внедряет сведения о типе в исполняемый файл в виде метаданных. Среда CLR использует эти
метаданные во время выполнения для дальнейшего обеспечения безопасности типа при выделении и
освобождении памяти.
// Declaration only:
float temperature;
string name;
MyClass myClass;
Типы для параметров и возвращаемых значений метода задаются в сигнатуре метода. Далее представлена
сигнатура метода, который требует значение int в качестве входного аргумента и возвращает строку:
После объявления переменной она не может быть повторно объявлена с новым типом, и ей нельзя
присвоить значение, несовместимое с ее объявленным типом. Например, нельзя объявить переменную типа
int и затем присвоить ей логическое значение true. Однако значения могут быть преобразованы в другие
типы, например при сохранении в другие переменные или при передаче в качестве аргументов метода. Если
преобразование типов не приводит к потере данных, она выполняется компилятором автоматически. Для
преобразования, которое может привести к потере данных, необходимо приведение в исходном коде.
Дополнительные сведения см. в разделе Приведение и преобразование типов.
Встроенные типы
C# предоставляет стандартный набор встроенных числовых типов для представления целых чисел, значений
с плавающей запятой, логических выражений, текстовых символов, десятичных значений и других типов
данных. Существуют также встроенные типы string и object. Они доступны для использования в любой
программе C#. Дополнительные сведения о встроенных типах см. в Справочных таблицах по встроенным
типам.
Пользовательские типы
Конструкции структура, класс, интерфейс и перечисление используются для создания собственных
пользовательских типов. Сама библиотека классов платформы .NET Framework является коллекцией
пользовательских типов, предоставленных корпорацией Майкрософт, которые вы можете свободно
использовать в приложениях. По умолчанию в любой программе C# доступны наиболее часто используемые
типы из библиотеки классов. Чтобы сделать доступными другие типы, нужно явным образом добавить в
проект ссылку на сборку, в которой они определены. Если компилятору предоставлена ссылка на сборку, то
вы можете объявлять в коде переменные (и константы) любых типов, объявленных в этой сборке.
универсальные типы;
Тип может быть объявлен с одним или несколькими параметрами типа, служащими в качестве
местозаполнителя для фактического типа (конкретного типа), который клиентский код предоставит при
создании экземпляра типа. Такие типы называются универсальными типами. Например, тип платформы
.NET Framework List<T> имеет один параметр типа, которому в соответствии с соглашением присвоено имя
T. При создании экземпляра этого типа необходимо указать тип объектов, которые будут содержаться в
списке. Например, так используется список строковых значений:
Использование параметра типа позволяет повторно использовать один же класс для хранения элементов
любого типа, не преобразовывая каждый элемент в объект. Универсальные классы коллекций называются
строго типизированными коллекциями, так как компилятору известен конкретный тип элементов
коллекции и он может выдать ошибку во время компиляции, если, к примеру, вы попытаетесь добавить
целое число в объект strings , созданный в предыдущем примере. Дополнительные сведения см. в разделе
Универсальные типы.
См. также
Структуры
Классы
Классы (Руководство по программированию на C#)
23.10.2019 • 8 minutes to read • Edit Online
Ссылочные типы
Тип, который определен как класс, является ссылочным типом. Когда во время выполнения вы объявляете
переменную ссылочного типа, такая переменная будет содержать значение NULL, пока вы явным образом не
создадите экземпляр класса с помощью оператора new или не назначите его объекту совместимого типа,
созданному в другом месте, как показано в следующем примере:
//Declaring another object of the same type, assigning it the value of the first object.
MyClass mc2 = mc;
При создании объекта выделяется достаточный объем памяти для этого объекта в управляемой куче, и
переменная хранит только ссылку на расположение данного объекта. Хранение типов в управляемой куче
требует дополнительных действий как при выделении памяти, так и при удалении, которое выполняется
функцией автоматического управления памятью в среде CLR, известной как сборка мусора. Сборка мусора
является хорошо оптимизированным процессом и в большинстве случаев не создает помех для
производительности. Дополнительные сведения о сборке мусора см. в разделе Автоматическое управление
памятью и сборка мусора.
Объявление классов
Классы объявляются с помощью ключевого слова class, за которым следует уникальный идентификатор, как
показано в следующем примере:
Ключевому слову class предшествует уровень доступа. Так как в этом случае используется открытый класс
(public), любой пользователь может создавать его экземпляры. За именем класса следует ключевое слово
class . Имя класса должно быть допустимым именем идентификатора C#. Оставшаяся часть определения —
это тело класса, в котором задаются данные и поведение. Поля, свойства, методы и события в классе
собирательно называются членами класса.
Создание объектов
Несмотря на то, что они иногда взаимозаменяемы, класс и объект — разные вещи. Класс определяет тип
объекта, но не является объектом. Объект — это конкретная сущность, основанная на классе, которую иногда
называют экземпляром класса.
Объекты можно создавать с помощью ключевого слова new, за которым следует имя класса, на котором
будет основан объект, например следующим образом:
Customer object1 = new Customer();
При создании экземпляра класса ссылка на объект передается программисту. В предыдущем примере
object1 представляет собой ссылку на объект, который основан на Customer . Эта ссылка указывает на новый
объект, но не содержит данные этого объекта. Фактически, можно создать ссылку на объект без создания
собственно объекта:
Customer object2;
Создание таких ссылок, которые не указывают на объект, не рекомендуется, так как попытка доступа к
объекту по такой ссылке приведет к сбою во время выполнения. Однако такую ссылку можно сделать
указывающей на объект, создав новый объект или назначив ее существующему объекту, как показано далее:
В этом коде создаются две ссылки на объект, которые указывают на один и тот же объект. Таким образом,
любые изменения объекта, выполненные посредством object3 , отражаются при последующем
использовании object4 . Поскольку на объекты, основанные на классах, указывают ссылки, классы называют
ссылочными типами.
Наследование классов
Классы полностью поддерживают наследование, фундаментальный механизм объектно ориентированного
программирования. Создаваемый класс может наследовать от любого другого интерфейса или класса,
который не определен как запечатанный, а другие классы могут наследовать от этого класса и
переопределять его виртуальные методы.
При наследовании создается производный класс, то есть класс объявляется с помощью базового класса, от
которого он наследует данные и поведение. Базовый класс задается добавлением после имени производного
класса двоеточия и имени базового класса, как показано далее:
Когда класс объявляет базовый класс, он наследует все члены базового класса, за исключением
конструкторов. Дополнительные сведения см. в разделе Наследование.
В отличие от C++, класс в C# может только напрямую наследовать от одного базового класса. Тем не менее,
поскольку базовый класс может сам наследовать от другого класса, класс может косвенно наследовать от
нескольких базовых классов. Кроме того, класс может напрямую реализовать несколько интерфейсов.
Дополнительные сведения см. в разделе Интерфейсы.
Класс может быть объявлен абстрактным. Абстрактный класс содержит абстрактные методы, которые имеют
определение сигнатуры, но не имеют реализации. Нельзя создавать экземпляры абстрактных классов. Они
могут использоваться только через производные классы, реализующие абстрактные методы. И наоборот,
запечатанный класс не позволяет другим классам быть от него производными. Дополнительные сведения см.
в статье Абстрактные и запечатанные классы и члены классов.
Определения классов можно разделить между различными исходными файлами. Дополнительные сведения
см. в разделе Разделяемые классы и методы.
Пример
В следующем примере определяется открытый класс, содержащий автоматически реализуемое свойство,
метод и специальный метод, который называется конструктором. См. дополнительные сведения о свойствах,
методах и конструкторах. Затем создаются экземпляры этого класса с помощью ключевого слова new .
using System;
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Объектно-ориентированное программирование
Полиморфизм
Имена идентификаторов
Члены
Методы
Конструкторы
Методы завершения
Объекты
Структуры
11.11.2019 • 5 minutes to read • Edit Online
Тип struct является типом значения. При создании структуры переменная, которой назначена структура,
содержит фактические данные структуры. Если структура присваивается новой переменной, все данные
копируются. Таким образом, новая переменная и исходная переменная содержат две отдельные копии
одинаковых данных. Изменения, внесенные в одну копию, не влияют на другую.
Переменные типа значения напрямую содержат свои значения, что означает, что память выделяется в
любом контексте объявления переменной. Нет раздельного размещения в куче или накладных расходов при
сборке мусора для переменных типа значения.
Существует две категории типов значений: структура и перечисление.
Встроенные числовые типы являются структурами и имеют свойства и методы, к которым можно
обращаться.
Но объявление и присвоение значений вы выполняете для них так, как если бы они были простыми
нестатистическими типами.
Типы значений являются запечатанными, что означает, например, что нельзя наследовать тип от Int32 и
нельзя определить структуру для наследования от любого пользовательского класса или структуры,
поскольку структура может наследовать только от ValueType. Тем не менее структура может реализовывать
один или несколько интерфейсов. Можно выполнить приведение типа структуры к типу интерфейса; это
приведет к операции упаковки-преобразования, которая создаст программу-оболочку для структуры
внутри объекта ссылочного типа в управляемой куче. Операции упаковки-преобразования возникают при
передаче типа значения в метод, принимающий Object в качестве входного параметра. Дополнительные
сведения см. в разделе Упаковка-преобразование и распаковка-преобразование.
Используйте ключевое слово struct, чтобы создать собственные пользовательские типы значений. Как
правило, структура используется как контейнер для небольшого набора связанных переменных, как
показано в следующем примере:
Дополнительные сведения о типах значений в .NET Framework см. в разделе Система общих типов.
Структуры используют большую часть того же синтаксиса, что и классы, однако они более ограничены по
сравнению с ними.
В объявлении структуры поля не могут быть инициализированы до тех пор, пока они будут объявлены
как const или static .
Структура не может объявлять конструктор без параметров или метод завершения.
Структуры копируются при присваивании. При присваивании структуры к новой переменной
выполняется копирование всех данных, а любое изменение новой копии не влияет на данные в
исходной копии. Это важно помнить при работе с коллекциями типов значений, такими как
Dictionary<string, myStruct>.
Структуры являются типами значений, а классы — ссылочными типами.
В отличие от классов структуры можно создавать без использования оператора new .
Структуры могут объявлять конструкторы, имеющие параметры.
Структура не может наследовать от другой структуры или класса и не может быть базовой для класса.
Все структуры наследуют непосредственно от ValueType, который наследует от Object.
Структуры могут реализовывать интерфейсы.
См. также
Классы
Базовые типы
Типы кортежей в C#
04.11.2019 • 33 minutes to read • Edit Online
NOTE
Для новых функций кортежа требуются типы ValueTuple. Для использования на платформах, которые не
включают эти типы, необходимо добавить пакет NuGet System.ValueTuple .
Это похоже на другие функции языка, использующие типы, предоставляемые на платформе. В качестве
примеров можно привести функции async и await , использующие интерфейс INotifyCompletion , а также
LINQ на базе IEnumerable<T> . Тем не менее по мере увеличения степени независимости .NET от платформы
механизм доставки изменяется. Периодичность поставки новых выпусков .NET Framework не всегда совпадает с
выпусками обновлений для компилятора языка. Если новые функции языка используют новые типы, эти типы
будут предоставляться в виде пакетов NuGet при выпуске таких новых функций. Как только эти новые типы будут
добавлены в стандартный API-интерфейс .NET и включены в состав платформы, требование обязательно
использовать пакет NuGet будет снято.
Для начала обсудим, почему была добавлена поддержка кортежей. Методы возвращают один объект.
Кортежи позволяют легко упаковать в этот объект сразу несколько значений.
Платформа .NET Framework уже включает универсальные классы Tuple , которые, однако, имеют два
серьезных ограничения. Например, классы Tuple присваивают своим свойствам имена Item1 , Item2 и
т. д. Никакой семантической информации в этих именах нет. Эти типы Tuple не позволяют сообщить
пользователю значение каждого из свойств. Новые функции языка позволяют объявить и использовать
семантически значимые имена для элементов в кортеже.
Классы Tuple вызывают несколько проблем с производительностью, так как они имеют ссылочный тип.
При использовании типов Tuple происходит распределение объектов. В критических путях выделение
множества небольших объектов может заметно влиять на производительность приложения.
Следовательно, при поддержке языков для кортежей используются новые структуры ValueTuple .
Чтобы избежать этих недостатков, можно создать class или struct , включающие несколько
элементов. К сожалению, это означает дополнительную работу и искажает цель вашего проекта.
Создание struct или class означает, что определение типа включает и данные, и поведение. Во
многих случаях все, что вам нужно, — это сохранить в одном объекте несколько значений.
Возможности языка и универсальные структуры ValueTuple обеспечивают соблюдение правила,
запрещающего добавлять поведение (методы) в эти типы кортежей. Все типы ValueTuple представляют
собой изменяемые структуры. Все поля членов открыты, что делает их очень простыми. В то же время
это означает, что кортежи не стоит использовать в случаях, когда важна неизменность.
Кортежи — это более простые и более гибкие контейнеры данных, чем типы class и struct .
Рассмотрим эти различия.
Компилятор должен передавать созданные вами имена для кортежей, возвращаемых открытыми
методами и свойствами. В подобных случаях компилятор добавляет к методу атрибут
TupleElementNamesAttribute. Этот атрибут содержит свойство списка TransformNames, которое
содержит имена, присвоенные каждому элементу в кортеже.
NOTE
Средства разработки, такие как Visual Studio, также считывают эти метаданные и предоставляют IntelliSense и
другие возможности, используя имена полей метаданных.
Знать основы работы с новыми кортежами и типом ValueTuple необходимо для того, чтобы понимать
правила назначения именованных кортежей друг другу.
Инициализаторы проекций кортежа
Как правило, инициализаторы проекций кортежа работают с использованием переменной или имен
полей в правой части оператора инициализации кортежа. Если задано явное имя, оно имеет приоритет
над именем проекции. Например, в следующем инициализаторе элементы имеют значения
explicitFieldOne и explicitFieldTwo , а не localVariableOne и localVariableTwo :
var localVariableOne = 5;
var localVariableTwo = "some text";
Во всех полях, где не указано явное имя, проецируется применимое неявное имя. Необязательно
указывать семантические имена как явно, так и неявно. Следующий инициализатор имеет имена полей
Item1 со значением 42 и stringContent ("Ответ на все вопросы"):
Существуют два условия, при которых имена полей кандидата не проецируются в поле кортежа.
1. Имя кандидата является зарезервированным именем кортежа. Примеры: Item3 , ToString или Rest .
2. Имя кандидата является дубликатом другого имени поля кортежа, явного или неявного.
Эти условия позволяют избежать неоднозначности. Эти имена вызывали бы неоднозначность, если бы
использовались в качестве имен полей для поля в кортеже. Ни одно из этих условий не вызовет ошибки
времени компиляции. Семантические имена элементов без проецируемых имен не будут
проецироваться. Все это показано в приведенных ниже примерах.
Эти ситуации не вызывают ошибок компилятора, так как это стало бы критическим изменением кода,
написанного на C# 7.0, где проекции имен полей кортежа были недоступны.
Равенство и кортежи
Начиная с C# 7.3, типы кортежей поддерживают операторы == и != . Эти операторы работают путем
сравнения каждого элемента левого аргумента с каждым элементом правого аргумента по порядку. Это
сокращенные сравнения. Они перестанут сравнивать элементы, как только будет обнаружена неравная
пара. В следующем примере кода используется оператор == , но все правила сравнения применяются к
оператору != . В следующем примере кода показано сравнение двух пар целых чисел на равенство:
Существует несколько правил, которые упрощают тесты на равенство кортежей. Если один из кортежей
допускает значение NULL, как показано в следующем коде, функция проверки кортежей на равенство
выполняет преобразования для использования форм, допускающих значение NULL:
Функция проверки кортежей на равенство также выполняет неявное преобразование каждого элемента
обоих кортежей. К ним относятся преобразования для использования форм, допускающих значение
NULL, расширяющие преобразования и другие неявные преобразования. В следующих примерах
показано, что кортеж из 2 элементов типа "целое число" можно сравнить с кортежем из 2 элементов
типа "длинное целое" в связи с неявным преобразованием из целого числа в длинное целое:
// lifted conversions
var left = (a: 5, b: 10);
(int? a, int? b) nullableMembers = (5, 10);
Console.WriteLine(left == nullableMembers); // Also true
Имена элементов кортежей не участвуют в тестах на равенство. Тем не менее если один из операндов
является литералом кортежа с явными именами, компилятор генерирует предупреждение CS8383 в
случае несовпадения этих имен с именами второго операнда. В случае, когда оба операнда являются
литералами кортежей, предупреждение находится возле правого операнда, как показано в следующем
примере:
Наконец, кортежи могут содержать вложенные кортежи. Функция проверки кортежей на равенство
сравнивает "форму" каждого операнда по вложенным кортежам, как показано в следующем примере:
(int, (int, int)) nestedTuple = (1, (2, 3));
Console.WriteLine(nestedTuple == (1, (2, 3)) );
Это ошибка времени компиляции для сравнения двух кортежей на равенство (или неравенство) при
наличии разных фигур. Компилятор не пытается выполнить деконструкцию вложенных кортежей, чтобы
сравнить их.
Назначение и кортежи
Язык поддерживает назначение между типами кортежей с одинаковым количеством элементов, где
каждый расположенный справа элемент может быть неявно преобразован в соответствующий элемент,
расположенный слева. Другие преобразования в контексте назначений не учитываются. Это ошибки
времени компиляции для назначения одного кортежа другому при наличии различных фигур.
Компилятор не будет пытаться выполнить любую деконструкцию вложенных кортежей для их
назначения. Рассмотрим возможные виды назначений между типами кортежей.
В приведенных ниже примерах можно использовать указанные переменные:
В первых двух переменных, unnamed и anonymous , семантические имена элементам не назначены. Имена
полей — Item1 и Item2 . Последние две переменные, named и differentName , включают назначенные
элементам семантические имена. Элементы в этих двух кортежах называются по-разному.
Все четыре этих кортежа имеют одинаковое число элементов (так называемую кратность), а типы этих
элементов идентичны. Таким образом, все эти назначения работают:
unnamed = named;
named = unnamed;
// 'named' still has fields that can be referred to
// as 'answer', and 'message':
Console.WriteLine($"{named.Answer}, {named.Message}");
// unnamed to unnamed:
anonymous = unnamed;
// named tuples.
named = differentNamed;
// The field names are not assigned. 'named' still has
// fields that can be referred to as 'answer' and 'message':
Console.WriteLine($"{named.Answer}, {named.Message}");
Обратите внимание на то, что имена кортежей не назначаются. Значения элементов назначаются в
соответствии с порядком элементов в кортеже.
Кортежи различных типов или с различным количеством элементов не назначаются.
// Does not compile.
// CS0029: Cannot assign Tuple(int,int,int) to Tuple(int, string)
var differentShape = (1, 2, 3);
named = differentShape;
NOTE
Код в этих примерах вычисляет стандартное отклонение неисправленной выборки. Формула стандартного
отклонения исправленной выборки делит сумму квадратов разности со средним значением на (N-1), как и метод
расширения Average . Дополнительные сведения о различиях между этими формулами для расчета
стандартного отклонения см. в тексте по статистике.
Приведенный выше код соответствует учебной формуле для стандартного отклонения. Она позволяет
получить правильный ответ, однако эта реализация неэффективна. Этот метод перечисляет
последовательность дважды. Один раз для получения среднего значения, а второй — для получения
среднего значения квадратов разницы со средним. (Запомните, что запросы LINQ вычисляются в
отложенном режиме, поэтому разница со средним значением и среднее этих разниц вычисляются в
один прием.)
Существует альтернативная формула, которая вычисляет стандартное отклонение, используя только
одно перечисление последовательности. В результате этого вычисления выдаются два значения,
поскольку оно перечисляет последовательность: сумма всех элементов в последовательности и сумма
квадратов всех значений:
public static double StandardDeviation(IEnumerable<double> sequence)
{
double sum = 0;
double sumOfSquares = 0;
double count = 0;
Эта версия перечисляет последовательность ровно один раз. В то же время этот код нельзя повторно
использовать. По мере работы вы узнаете, что во многих статистических вычислениях используется
число элементов в последовательности, сумма последовательности и сумма квадратов
последовательности. Выполним рефакторинг этого метода и напишем служебный метод, выдающий все
три из этих значений. Все три значения могут быть возвращены в виде кортежа.
Обновим этот метод таким образом, чтобы все три значения, вычисляемые при перечислении,
сохранялись в кортеж. В результате создается следующая версия:
Поддержка рефакторинга в Visual Studio позволяет легко извлекать функции основной статистики в
закрытый метод. В результате вы получаете метод private static , возвращающий тип кортежа с тремя
значениями — Sum , SumOfSquares и Count :
public static double StandardDeviation(IEnumerable<double> sequence)
{
(int Count, double Sum, double SumOfSquares) computation = ComputeSumAndSumOfSquares(sequence);
return computation;
}
Окончательную версию можно применять к любому методу, которому требуются эти три значения, а
также к любому их подмножеству.
Язык поддерживает другие варианты управления именами элементов в методах, возвращающих
кортежи.
Имена полей можно удалить из объявления возвращаемого значения и вернуть неименованный
кортеж:
private static (double, double, int) ComputeSumAndSumOfSquares(IEnumerable<double> sequence)
{
double sum = 0;
double sumOfSquares = 0;
int count = 0;
Поля этого кортежа называются Item1 , Item2 и Item3 . Для элементов кортежей, возвращаемых из
методов, рекомендуется указывать семантические имена.
Еще одна идиома, в которой кортежи могут быть полезны, связана с созданием запросов LINQ.
Итоговый проецируемый результат часто содержит некоторые, но не все свойства выбранных объектов.
Обычно результаты запроса проецируются в последовательность объектов анонимного типа. С этим
связано множество ограничений, в первую очередь потому, что анонимным типам нельзя присваивать
удобные имена в возвращаемом типе для метода. Альтернативные варианты с типом результата object
или dynamic вызывают серьезные потери в производительности.
Вернуть последовательность с типом кортежа легко, а имена и типы элементов можно получить во
время компиляции и с помощью средств IDE. В качестве примера рассмотрим приложение ToDo.
Определите класс следующего вида — он будет представлять одну запись в списке дел ToDo:
Мобильные приложения могут поддерживать компактную форму текущих пунктов в списке дел, когда
отображается только заголовок. Этот запрос LINQ выполняет проекцию, включающую только
идентификатор и заголовок. Эту модель хорошо демонстрирует метод, возвращающий
последовательность кортежей:
Именованный кортеж может быть частью сигнатуры. Это позволяет компилятору и средствам IDE
статически проверять, правильно ли используются результаты. Именованный кортеж также несет в себе
данные статического типа, избавляя от необходимости использовать для работы с результатами
дорогостоящие функции среды выполнения, такие как отражение или динамическая привязка.
Деконструкция
Все элементы в кортеже можно распаковать, выполнив деконструкцию возвращаемого методом
кортежа. Деконструкцию кортежей можно выполнять тремя различными способами. Во-первых, можно
явно объявить тип каждого поля, заключив его в круглые скобки, чтобы создать дискретные
переменные для каждого элемента в кортеже:
Типизированные переменные для каждого поля в кортеже можно также объявить неявно, используя
ключевое слово var за скобками:
Кроме того, в объявлении одной или всех переменных можно также свободно использовать ключевое
слово var , заключив его в скобки.
Определенный тип использовать за скобками нельзя, даже если каждое поле в кортеже имеет
одинаковый тип.
Можно также выполнить деконструкцию кортежей с существующими объявлениями:
Метод deconstruct позволяет выполнять назначение из объекта Person в две строки, представляющие
свойства FirstName и LastName :
Вы можете включить деконструкцию даже для типов, которые не создавали. Метод Deconstruct может
быть методом расширения, который распаковывает доступные элементы данных в объекте. В
следующем примере показан тип Student , производный от типа Person , и метод расширения, который
разбивает Student на три переменные, представляющие FirstName , LastName и GPA :
public class Student : Person
{
public double GPA { get; }
public Student(string first, string last, double gpa) :
base(first, last)
{
GPA = gpa;
}
}
Теперь у объекта Student есть два доступных метода Deconstruct : метод расширения, объявленный для
типов Student , и элемент типа Person . Оба из них входят в область, что позволяет разбить Student
либо на две, либо на три переменные. Если учащийся назначается трем переменным, возвращается
все — имя, фамилия и GPA. Если учащийся назначается двум переменным, возвращаются только имя и
фамилия.
Метод Deconstruct может преобразовать объект Person p в кортеж, содержащий две строки, но он
неприменим в контексте проверок на равенство.
Console.WriteLine($"{pair.num}: {pair.place}");
/*
* Output:
* 345: Second
*/
Кроме того, можно использовать безымянный кортеж и ссылаться на его поля как на Item1 и Item2 :
Заключение
Добавленная в язык и библиотеку поддержка именованных кортежей значительно упрощает работу с
проектами, в которых используются структуры данных, хранящие несколько элементов, но не
определяющие поведение подобно классам или структурам. Для этих типов удобно использовать
кортежи. Они дают вам все преимущества статической поддержки типов и в то же время избавляют от
необходимости создавать типы с использованием более подробного синтаксиса class или struct . И
все же наибольшую пользу они приносят при использовании со служебными методами, имеющими
атрибут private или internal . Создавайте пользовательские типы, class или struct , если ваши
открытые методы возвращают значение с несколькими элементами.
Деконструкция кортежей и других типов
04.11.2019 • 15 minutes to read • Edit Online
Кортеж позволяет вам легко получить несколько значений при вызове метода. Но после получения кортежа
вам нужно будет обработать его отдельные элементы. Это довольно неудобно, как показано в следующем
примере. Метод QueryCityData возвращает кортеж из трех элементов, и каждый из его элементов
присваивается переменной за отдельную операцию.
using System;
Получение множества значений полей и свойств из объекта может быть столь же неудобно: необходимо
присваивать значение каждого поля или свойства отдельной переменной.
Начиная с C# 7.0 вы можете извлекать из кортежа множество элементов или получать множество значений
полей, свойств и вычисляемых значений из объекта, используя всего одну операцию деконструкции. При
деконструкции кортежа вы присваиваете его элементы отдельным переменным. При деконструкции объекта
вы присваиваете отдельным переменным выбранные значения.
Деконструкция кортежа
Язык C# имеет встроенную поддержку деконструкции кортежей, которая позволяет извлекать из кортежа
все элементы за одну операцию. Общий синтаксис деконструкции кортежа напоминает синтаксис его
определения: переменные, которым будут присвоены элементы кортежа, указываются в круглых скобках в
левой части оператора присваивания. Например, следующий оператор присваивает элементы кортежа из
четырех элементов четырем отдельным переменным:
Вы можете использовать ключевое слово var , чтобы C# определил тип каждой переменной.
Ключевое слово var помещается за пределами скобок. В следующем примере используется
определение типа при деконструкции кортежа из трех элементов, возвращаемого методом
QueryCityData .
Кроме того, вы можете использовать ключевое слово var при объявлении отдельных или всех
переменных внутри скобок.
Обратите внимание, что вы не можете указать определенный тип за скобками, даже если все поля в кортеже
имеют один и тот же тип. Это приведет к возникновению ошибки компилятора CS8136 "Форма
деконструкции 'var (...)' не разрешает использовать конкретный тип для 'var'.".
Обратите внимание, что каждый элемент кортежа необходимо присвоить переменной. Если вы пропустите
какие-то элементы, компилятор выдаст сообщение об ошибке CS8132 "Невозможно деконструировать
кортеж из "x" элементов на "y" переменных".
Обратите внимание, что нельзя смешивать объявления и назначения существующим переменным в левой
части деконструкции. Компилятор выводит ошибку CS8184: "Деконструкция не может содержать
объявления и выражения в левой части", если члены содержат новые объявленные и существующие
переменные.
Деконструкция элементов кортежа с использованием пустых
переменных
При деконструкции кортежа нас часто интересуют значения только некоторых элементов. Начиная с C# 7.0
вы можете воспользоваться поддержкой пустых переменных в C#, которые представляют собой доступные
только для записи переменные, значения которых нас не интересуют. Пустая переменная обозначается
символом нижнего подчеркивания ("_") в операторе присваивания. Вы можете сделать пустыми сколько
угодно значений. Все они будут считаться одной переменной, _ .
В следующем примере показано использование кортежей с пустыми переменными. Метод
QueryCityDataForYears возвращает кортеж из шести элементов: название города, его площадь, год,
численность населения города в этом году, другой год и численность населения в том году. В примере
показано изменение численности населения за эти два года. Из доступных в кортеже данных нас не
интересует площадь города, а название города и две даты известны нам уже на этапе разработки.
Следовательно, нас интересуют только два значения численности населения, которые хранятся в кортеже.
Остальные значения можно обработать как пустые переменные.
using System;
using System.Collections.Generic;
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int
year2)
{
int population1 = 0, population2 = 0;
double area = 0;
public void Deconstruct(out string fname, out string mname, out string lname)
Затем вы можете выполнить деконструкцию экземпляра класса Person с именем p , используя подобное
присваивание:
В следующем примере показана перегрузка метода Deconstruct для возвращения различных сочетаний
свойств объекта Person . Отдельные перегрузки возвращают следующие значения:
Имя и фамилия.
Имя, фамилия и отчество.
Имя, фамилия, название города и название штата.
using System;
public void Deconstruct(out string fname, out string mname, out string lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}
Так как метод Deconstruct может быть перегружен с учетом групп данных, которые часто извлекаются из
объекта, следует соблюдать осторожность и определять методы Deconstruct с уникальными и
однозначными сигнатурами. Различные методы Deconstruct с одинаковым количеством параметров out
или с одинаковым количеством и типом параметров out в разном порядке могут привести к путанице.
Перегруженный метод Deconstruct в следующем примере иллюстрирует один из источников возможной
путаницы. Первая перегрузка возвращает имя, отчество, фамилию и возраст объекта Person именно в таком
порядке. Вторая перегрузка возвращает только сведения об имени вместе с годовым доходом, но имя,
отчество и фамилия стоят в другом порядке. Это позволяет легко перепутать порядок аргументов при
деконструкции экземпляра Person .
using System;
public void Deconstruct(out string fname, out string mname, out string lname, out int age)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
age = DateTime.Now.Year - DateOfBirth.Year;
public void Deconstruct(out string lname, out string fname, out string mname, out decimal income)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
income = AnnualIncome;
}
}
using System;
using System.Collections.Generic;
using System.Reflection;
if (getter != null)
{
if (getter.IsPublic)
getAccessTemp = "public";
else if (getter.IsPrivate)
getAccessTemp = "private";
else if (getter.IsAssembly)
getAccessTemp = "internal";
else if (getter.IsFamily)
getAccessTemp = "protected";
getAccessTemp = "protected";
else if (getter.IsFamilyOrAssembly)
getAccessTemp = "protected internal";
}
if (setter != null)
{
if (setter.IsPublic)
setAccessTemp = "public";
else if (setter.IsPrivate)
setAccessTemp = "private";
else if (setter.IsAssembly)
setAccessTemp = "internal";
else if (setter.IsFamily)
setAccessTemp = "protected";
else if (setter.IsFamilyOrAssembly)
setAccessTemp = "protected internal";
}
if (!hasGetAndSet | sameAccess)
{
Console.WriteLine(accessibility);
}
else
{
Console.WriteLine($"\n The get accessor: {getAccessibility}");
Console.WriteLine($" The set accessor: {setAccessibility}");
}
}
}
// The example displays the following output:
// The System.DateTime.Now property:
// PropertyType: DateTime
// Static: True
// Static: True
// Read-only: True
// Indexed: False
//
// Accessibility of the System.Collections.Generic.List`1.Item property: public
См. также
Операции удаления
Кортежи
Интерфейсы (Руководство по программированию в
C#)
01.11.2019 • 7 minutes to read • Edit Online
Интерфейс содержит определения для группы связанных функциональных возможностей, которые может
реализовать неабстрактный класс или структура.
С помощью интерфейсов можно, например, включить в класс поведение из нескольких источников. Эта
возможность очень важна в C#, поскольку этот язык не поддерживает множественное наследование классов.
Кроме того, необходимо использовать интерфейс, если требуется имитировать наследование для структур,
поскольку они не могут фактически наследовать от другой структуры или класса.
Интерфейс определяется с помощью ключевого слова interface, как показано в следующем примере.
interface IEquatable<T>
{
bool Equals(T obj);
}
Имя структуры должно быть допустимым именем идентификатора C#. По соглашению имена интерфейсов
начинаются с заглавной буквы I .
Любой объект (класс или структура), реализующий интерфейс IEquatable<T>, должен содержать
определение для метода Equals, соответствующее сигнатуре, которую задает интерфейс. В результате вы
можете быть уверены, что класс, реализующий IEquatable<T> , содержит метод Equals , с помощью которого
экземпляр этого класса может определить, равен ли он другому экземпляру того же класса.
Определение IEquatable<T> не предоставляет реализацию для метода Equals . Класс или структура может
реализовывать несколько интерфейсов, но класс может наследовать только от одного класса.
Дополнительные сведения об абстрактных классах см. в разделе Абстрактные и запечатанные классы и члены
классов.
Интерфейсы могут содержать методы, свойства, события, индексаторы, а также любое сочетание этих
четырех типов членов. Ссылки на примеры см. в разделе Связанные разделы. Интерфейс не может содержать
константы, поля, операторы, конструкторы экземпляров, методы завершения или типы. Члены интерфейса
автоматически являются открытыми, и они не могут включать модификаторы доступа. Члены также не могут
быть статическими.
Для реализации члена интерфейса соответствующий член реализующего класса должен быть открытым и не
статическим, а также иметь такое же имя и сигнатуру, что и член интерфейса.
Если класс (или структура) реализует интерфейс, этот класс (или структура) должен предоставлять
реализацию для всех членов, которые определяет этот интерфейс. Сам интерфейс не предоставляет
функциональность, которую класс или структура может наследовать таким же образом, как можно
наследовать функциональность базового класса. Однако если базовый класс реализует интерфейс, то любой
класс, производный от базового класса, наследует эту реализацию.
В следующем примере показана реализация интерфейса IEquatable<T>. Реализующий класс Car должен
предоставлять реализацию метода Equals.
public class Car : IEquatable<Car>
{
public string Make {get; set;}
public string Model { get; set; }
public string Year { get; set; }
Свойства и индексаторы класса могут определять дополнительные методы доступа для свойства или
индексатора, определенного в интерфейсе. Например, интерфейс может объявлять свойство, имеющее
акцессор get. Класс, реализующий этот интерфейс, может объявлять это же свойство с обоими акцессорами (
get и set). Однако если свойство или индексатор использует явную реализацию, методы доступа должны
совпадать. Дополнительные сведения о явной реализации см. в статьях Явная реализация интерфейса и
Свойства интерфейса.
Интерфейсы могут наследовать от других интерфейсов. Класс может включать интерфейс несколько раз
через наследуемые базовые классы или через интерфейсы, которые наследуются другими интерфейсами.
Однако класс может предоставить реализацию интерфейса только однократно и только если класс объявляет
интерфейс как часть определения класса ( class ClassName : InterfaceName ). Если интерфейс наследуется,
поскольку наследуется базовый класс, реализующий этот интерфейс, то базовый класс предоставляет
реализацию членов этого интерфейса. Но производный класс может повторно реализовать любые члены
виртуального интерфейса и не использовать наследованную реализацию.
Базовый класс также может реализовывать члены интерфейса с помощью виртуальных членов. В таком
случае производный класс может изменять поведение интерфейса путем переопределения виртуальных
членов. Дополнительные сведения о виртуальных членах см. в статье Полиморфизм.
Сводка по интерфейсам
Интерфейс имеет следующие свойства.
Интерфейс подобен абстрактному базовому классу, имеющему только абстрактные члены. Любой класс
(или структура), реализующий интерфейс, должен реализовывать все его члены.
Невозможно создать экземпляр интерфейса напрямую. Его члены реализуются любым классом (или
структурой), реализующим интерфейс.
Интерфейсы могут содержать события, индексаторы, методы и свойства.
Интерфейсы не содержат реализацию методов.
Класс или структура может реализовывать несколько интерфейсов. Класс может наследовать базовому
классу и также реализовывать один или несколько интерфейсов.
Содержание раздела
Явная реализация интерфейса
В этом разделе объясняется, как создать член класса, который относится к интерфейсу.
Практическое руководство. Явная реализация элементов интерфейса
В этом разделе содержится пример явной реализации членов интерфейсов.
Практическое руководство. Явная реализация элементов двух интерфейсов
В этом разделе содержится пример явной реализации членов интерфейсов с помощью наследования.
Связанные разделы
Свойства интерфейса
Индексаторы в интерфейсах
Практическое руководство. Реализация событий интерфейса
Классы и структуры
Наследование
Методы
Полиморфизм
Абстрактные и запечатанные классы и члены классов
Свойства
События
Индексаторы
См. также
Руководство по программированию на C#
Наследование
Имена идентификаторов
Методы
04.11.2019 • 33 minutes to read • Edit Online
Метод — это блок кода, содержащий ряд инструкций. Программа инициирует выполнение инструкций,
вызывая метод и указывая все аргументы, необходимые для этого метода. В C# все инструкции выполняются
в контексте метода. Метод Main является точкой входа для каждого приложения C#, и вызывается
общеязыковой средой выполнения (CLR ) при запуске программы.
NOTE
В этом разделе рассматриваются названные методы. Сведения об анонимных функциях см. в разделе Анонимные
функции.
Сигнатуры методов
Методы объявляются в class или struct , для чего указывается следующее:
Уровень доступа (необязательно), например public или private . Значение по умолчанию — private .
Необязательные модификаторы, например abstract или sealed .
Возвращаемое значение или void , если у метода его нет.
Имя метода.
Любые параметры методов. Параметры метода заключаются в скобки и разделяются запятыми. Пустые
скобки указывают, что параметры методу не требуются.
Вместе все эти части формируют сигнатуру метода.
NOTE
Тип возврата метода не является частью сигнатуры метода в целях перегрузки метода. Однако он является частью
сигнатуры метода при определении совместимости между делегатом и методом, который он указывает.
using System;
Вызов метода
Можно использовать метод instance или static. Для того чтобы вызвать метод instance, необходимо создать
экземпляр объекта и вызвать для него метод; метод instance будет применен к этому экземпляру и его
данным. Статический метод вызывается путем ссылки на имя типа, к которому относится метод; статические
методы не работают с данными экземпляров. При попытке вызвать статический метод с помощью
экземпляра объекта возникает ошибка компилятора.
Вызов метода аналогичен доступу к полю. После имени объекта (при вызове метода экземпляра) или имени
типа (при вызове метода static ) добавьте точку, имя метода и круглые скобки. Аргументы перечисляются в
этих скобках и разделяются запятыми.
Определение метода задает имена и типы всех необходимых параметров. Когда вызывающий код вызывает
метод, он предоставляет конкретные значения, называемые аргументами, для каждого параметра.
Аргументы должны быть совместимы с типом параметра, но имя аргумента (если оно используется в
вызывающем коде) может не совпадать с именем параметра, указанным в методе. В следующем примере
метод Square имеет один параметр типа int с именем i. Первый вызов метода передает методу Square
переменную типа int с именем num; второй — числовую константу, а третий — выражение.
moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
double speed = moto.GetTopSpeed();
Console.WriteLine("My top speed is {0}", speed);
}
}
При вызове метода вместо позиционных аргументов можно также использовать именованные аргументы.
При использовании именованных аргументов необходимо указать имя параметра, двоеточие (":"), а затем
аргумент. Аргументы для метода могут отображаться в любом порядке, при условии, что все обязательные
аргументы присутствуют. В следующем примере для вызова метода TestMotorcycle.Drive используются
именованные аргументы. В этом примере именованные аргументы передаются из списка параметров
метода в обратном порядке.
using System;
Метод можно вызывать, используя и позиционные, и именованные аргументы. При этом за именованным
аргументом позиционный аргумент идти не может. В следующем примере метод TestMotorcycle.Drive из
предыдущего примера вызывается с использованием одного позиционного и одного именованного
аргумента.
using System;
Типы могут переопределять унаследованные члены, используя ключевое слово override и обеспечивая
реализацию переопределенного метода. Сигнатура метода должна быть такой же, как у переопределенного
метода. Следующий пример аналогичен предыдущему за тем исключением, что переопределяет метод
Equals(Object). (Он также переопределяет метод GetHashCode(), поскольку оба эти метода предназначены
для получения согласованных результатов.)
using System;
Передача параметров
Типы в C# делятся на типы значений и ссылочные типы. Список встроенных типов значений см. в разделе
Типы и переменные. По умолчанию и типы значений, и ссылочные типы передаются в метод по значению.
Передача параметров по значению
При передаче типа значения в метод по значению вместо самого объекта передается его копия. Это значит,
что изменения объекта в вызываемом методе не отражаются на исходном объекте, когда управление
возвращается вызывающему объекту.
Код в следующем примере передает тип значения в метод по значению, а вызываемый метод пытается
изменить значение типа значения. Он определяет переменную типа int , который является типом значения,
присваивает ему значение 20 и передает его в метод с именем ModifyValue , который изменяет значение
переменной на 30. Однако, когда метод возвращается, значение переменной остается неизменным.
using System;
Если объект ссылочного типа передается в метод по значению, ссылка на этот объект передается по
значению. Это значит, что метод получает не сам объект, а аргумент, который указывает расположение
объекта. Если с помощью этой ссылки в член объекта вносится изменение, это изменение отражается в
объекте, даже если управление возвращается вызывающему объекту. При этом изменения в объекте,
переданном в метод, не отражаются на исходном объекте, когда управление возвращается вызывающему
объекту.
В следующем примере определяется класс (ссылочного типа) с именем SampleRefType . Он создает экземпляр
объекта SampleRefType , задает в его поле value значение 44 и передает объект в метод ModifyObject . В этом
примере, в сущности, происходит то же самое, что и в предыдущем, — аргумент передается в метод по
значению. Однако поскольку здесь используется ссылочный тип, результат будет другим. В данном случае в
методе ModifyObject изменено поле obj.value , при этом поле value аргумента rt в методе Main также
изменяется на 33, как видно из результатов в предыдущем примере.
using System;
using System;
Общий шаблон, в котором используются параметры по ссылке, включает замену значений переменных.
Когда две переменные передаются в метод по ссылке, он меняет их содержимое местами. В следующем
примере меняются местами целочисленные значения.
using System;
using System;
using System.Linq;
class Example
{
static void Main()
{
string fromArray = GetVowels(new[] { "apple", "banana", "pear" });
Console.WriteLine($"Vowels from array: '{fromArray}'");
Если метод содержит как обязательные, так и необязательные параметры, необязательные параметры
определяются в конце списка параметров после всех обязательных параметров.
В следующем примере определяется метод ExampleMethod , который имеет один обязательный и два
необязательных параметра.
using System;
Возвращаемые значения
Методы могут возвращать значение вызывающему объекту. Если тип возврата, указываемый перед именем
метода, не void , этот метод может возвращать значение с помощью ключевого слова return . Инструкция с
ключевым словом return , за которым следует переменная, константа или выражение, соответствующие
типу возврата, будут возвращать это значение объекту, вызвавшему метод. Методы с типом возврата,
отличным от void, должны использовать ключевое слово return для возврата значения. Ключевое слове
return также останавливает выполнение метода.
Если тип возврата — void , инструкцию return без значения по-прежнему можно использовать для
завершения выполнения метода. Без ключевого слова return этот метод будет останавливать выполнение
при достижении конца блока кода.
Например, в следующих двух методах ключевое слово return используется для возврата целочисленных
значений.
class SimpleMath
{
public int AddTwoNumbers(int number1, int number2)
{
return number1 + number2;
}
Использование локальной переменной, в данном случае result , для сохранения значения является
необязательным. Это может улучшить читаемость кода или может оказаться необходимым, если нужно
сохранить исходное значение аргумента для всей области метода.
В некоторых случаях нужно, чтобы метод возвращал больше одного значения. Начиная с C# версии 7.0, это
легко можно сделать с помощью типов кортежей и литералов кортежей. Тип кортежа определяет типы
данных для элементов кортежа. Литералы кортежей предоставляют фактические значения возвращаемого
кортежа. В следующем примере (string, string, string, int) определяет тип кортежа, возвращаемый
методом GetPersonalInfo . Выражение (per.FirstName, per.MiddleName, per.LastName, per.Age) представляет
собой литерал кортежа; метод возвращает имя, отчество и фамилию, а также возраст объекта PersonInfo .
После этого вызывающий объект может использовать возвращенный кортеж в коде следующего вида:
Имена могут также назначаться элементам кортежа в определении типа кортежа. В следующих примерах
демонстрируется альтернативная версия метода 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);
}
После этого предыдущий вызов метода GetPersonInfo можно изменить следующим образом:
Если в качестве аргумента метод получает массив, а затем изменяет значение отдельных элементов, он
может не возвращать массив, однако при желании вы можете это изменить для соблюдения правильного
стиля или обеспечения эффективного потока передачи значений. Это связано с тем, что C# передает все
ссылочные типы по значению, а значением ссылки на массив является указатель на массив. В следующем
примере изменения в содержимом массива values , сделанные в методе DoubleValues , может отслеживать
любой код, имеющий ссылку на этот массив.
using System;
Методы расширения
Как правило, добавлять методы в существующий тип можно двумя способами:
Изменение исходного кода для этого типа. Конечно, если вы не владеете исходным кодом этого типа,
сделать это невозможно. Если при этом в поддержку метода также добавляются поля закрытых данных,
это изменение становится критическим.
Определение нового метода в производном классе. Нельзя добавить метод этим способом, используя
наследование для других типов, таких как структуры и перечисления. Кроме того, оно не позволяет
"добавить" метод в запечатанный класс.
Методы расширения позволяют "добавить" метод в существующий тип, не меняя сам тип и не реализуя
новый метод в наследуемом типе. Кроме того, метод расширения может не входить в ту же сборку, в
которую входит расширяемый им тип. Вызовите метод расширения, как будто он является определенным
членом типа.
Дополнительные сведения см. в разделе Методы расширения.
Асинхронные методы
С помощью функции async можно вызывать асинхронные методы, не прибегая к использованию явных
обратных вызовов или ручному разделению кода между несколькими методами или лямбда-выражениями.
Если пометить метод с помощью модификатора async, можно использовать в этом методе оператор await.
Если ожидаемая задача не завершена, то достигнув выражения await в асинхронном методе, управление
возвращается вызывающему объекту, а выполнение метода с ключевым словом await приостанавливается
до завершения выполнения ожидаемой задачи. После завершения задачи можно возобновить выполнение в
методе.
NOTE
Асинхронный метод возвращается в вызывающий объект, когда он встречает первый ожидаемый объект, выполнение
которого еще не завершено, или когда выполнение асинхронного метода доходит до конца — в зависимости от того,
что происходит раньше.
Асинхронный метод может иметь тип возвращаемого значения Task<TResult>, Task или void . Тип
возвращаемого значения void в основном используется для определения обработчиков событий, где
требуется возвращать тип void . Асинхронный метод, который возвращает тип void , не может быть
ожидающим. Вызывающий объект метода, возвращающего значение типа void, не может перехватывать
исключения, которые выдает этот метод. Начиная с C# 7.0 асинхронный метод может возвращать любой тип
вида задачи.
В следующем примере DelayAsync представляет собой асинхронный метод с оператором return, который
возвращает целое число. Поскольку этот метод асинхронный, его объявление должно иметь тип
возвращаемого значения Task<int> . Поскольку тип возврата — Task<int> , вычисление выражения await в
DoSomethingAsync создает целое число, как показывает следующий оператор: int result = await delayTask .
using System;
using System.Diagnostics;
using System.Threading.Tasks;
// Output:
// Result: 5
}
// The example displays the following output:
// Result: 5
Асинхронный метод не может объявлять параметры in, ref или out, но может вызывать методы, имеющие
такие параметры.
Дополнительные сведения об асинхронных методах см. в разделах Асинхронное программирование с
использованием ключевых слов async и await, Поток управления в асинхронных программах и Асинхронные
типы возврата.
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 текущее расположение запоминается, чтобы вызывающий объект мог запросить следующий
элемент в последовательности.
Тип возврата итератора может быть IEnumerable, IEnumerable<T>, IEnumeratorили IEnumerator<T>.
Дополнительные сведения см. в разделе Итераторы.
См. также
Модификаторы доступа
Статические классы и члены статических классов
Наследование
Абстрактные и запечатанные классы и члены классов
params
out
ref
in
Передача параметров
Лямбда-выражения (руководство по
программированию на C#)
25.11.2019 • 17 minutes to read • Edit Online
Используйте оператор объявления лямбда-выражения => для отделения списка параметров лямбда-
выражения от исполняемого кода. Чтобы создать лямбда-выражение, необходимо указать входные
параметры (если они есть) с левой стороны лямбда-оператора и блок выражений или операторов с другой
стороны.
Лямбда-выражение может быть преобразовано в тип делегата. Тип делегата, в который может быть
преобразовано лямбда-выражение, определяется типами его параметров и возвращаемым значением. Если
лямбда-выражение не возвращает значение, оно может быть преобразовано в один из типов делегата
Action ; в противном случае его можно преобразовать в один из типов делегатов Func . Например, лямбда-
выражение, которое имеет два параметра и не возвращает значение, можно преобразовать в делегат
Action<T1,T2>. Лямбда-выражение, которое имеет два параметра и возвращает значение, можно
преобразовать в делегат Func<T,TResult>. В следующем примере лямбда-выражение x => x * x , которое
указывает параметр с именем x и возвращает значение x в квадрате, присваивается переменной типа
делегата:
Лямбда выражений также можно преобразовать в типы дерева выражения, как показано в следующем
примере:
Лямбда-выражения можно использовать в любом коде, для которого требуются экземпляры типов делегатов
или деревьев выражений, например в качестве аргумента метода Task.Run(Action) для передачи кода,
который должен выполняться в фоновом режиме. Можно также использовать лямбда-выражения при
применении LINQ в C#, как показано в следующем примере:
int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25
При использовании синтаксиса на основе методов для вызова метода Enumerable.Select в классе
System.Linq.Enumerable (например, в LINQ to Objects и LINQ to XML ) параметром является тип делегата
System.Func<T,TResult>. При вызове метода Queryable.Select в классе System.Linq.Queryable (например, в
LINQ to SQL ) типом параметра является тип дерева выражения Expression<Func<TSource,TResult>> . В обоих
случаях можно использовать одно и то же лямбда-выражение для указания значения параметра. Поэтому
оба вызова Select выглядят одинаково, хотя на самом деле объект, созданный из лямбда-выражения, имеет
другой тип.
Выражения-лямбды
Лямбда-выражение с выражением с правой стороны оператора => называется выражением лямбда.
Выражения-лямбды широко используются при конструировании деревьев выражений. Выражения-лямбды
возвращают результат выражения и принимают следующую основную форму.
Если лямбда-выражение имеет только один входной параметр, скобки можно не ставить; во всех остальных
случаях они обязательны.
Нулевое количество входных параметры задается пустыми скобками:
Иногда компилятору не удается определить входные типы. Вы можете указать типы данных в явном виде, как
показано в следующем примере:
Для входных параметров все типы нужно задать либо в явном, либо в неявном виде. В противном случае
компилятор выдает ошибку CS0748.
Текст выражения лямбды может состоять из вызова метода. Но при создании деревьев выражений, которые
вычисляются вне контекста поддержки общеязыковой среды выполнения .NET, например в SQL Server,
вызовы методов не следует использовать в лямбда-выражениях. Эти методы не имеют смысла вне контекста
среды CLR .NET.
Лямбды операторов
Лямбда оператора напоминает выражение-лямбду, за исключением того, что оператор (или операторы)
заключается в фигурные скобки:
(input-parameters) => { <sequence-of-statements> }
Тело лямбды оператора может состоять из любого количества операторов; однако на практике обычно
используется не более двух-трех.
Асинхронные лямбда-выражения
С помощью ключевых слов async и await можно легко создавать лямбда-выражения и операторы,
включающие асинхронную обработку. Например, в следующем примере Windows Forms содержится
обработчик событий, который вызывает асинхронный метод ExampleMethodAsync и ожидает его.
Лямбда-выражения и кортежи
В C# 7.0 представлена встроенная поддержка кортежей. Кортеж можно ввести в качестве аргумента лямбда-
выражения, и лямбда-выражение также может возвращать кортеж. В некоторых случаях компилятор C#
использует определение типа для определения типов компонентов кортежа.
Кортеж определяется путем заключения в скобки списка его компонентов с разделителями-запятыми. В
следующем примере кортеж с тремя компонентами используется для передачи последовательности чисел в
лямбда-выражение. Оно удваивает каждое значение и возвращает кортеж с тремя компонентами,
содержащий результат операций умножения.
Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
// Output:
// The set (2, 3, 4) doubled: (4, 6, 8)
Как правило, поля кортежи именуются как Item1 , Item2 и т. д. Тем не менее кортеж с именованными
компонентами можно определить, как показано в следующем примере:
Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
Экземпляр этого делегата можно создать как Func<int, bool> , где int — входной параметр, а bool —
возвращаемое значение. Возвращаемое значение всегда указывается в последнем параметре типа.
Например, Func<int, string, bool> определяет делегат с двумя входными параметрами, int и string , и
типом возвращаемого значения bool . Следующий делегат Func при вызове возвращает логическое
значение, которое показывает, равен ли входной параметр 5:
Лямбда-выражения также можно использовать, когда аргумент имеет тип Expression<TDelegate>, например
в стандартных операторах запросов, которые определены в типе Queryable. При указании аргумента
Expression<TDelegate> лямбда-выражение компилируется в дерево выражения.
В этом примере используется стандартный оператор запроса Count:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");
Компилятор может вывести тип входного параметра ввода; но его также можно определить явным образом.
Данное лямбда-выражение подсчитывает указанные целые значения ( n ), которые при делении на два дают
остаток 1.
В следующем примере кода показано, как создать последовательность, которая содержит все элементы
массива numbers , предшествующие 9, так как это первое число последовательности, не удовлетворяющее
условию:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine(string.Join(" ", firstNumbersLessThanSix));
// Output:
// 5 4 1 3
В следующем примере показано, как указать несколько входных параметров путем их заключения в скобки.
Этот метод возвращает все элементы в массиве numbers до того числа, значение которого меньше его
порядкового номера в массиве:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Console.WriteLine(string.Join(" ", firstSmallNumbers));
// Output:
// 5 4
updateCapturedLocalVariable = x =>
{
j = x;
bool result = j > input;
Console.WriteLine($"{j} is greater than {input}: {result}");
};
isEqualToCapturedLocalVariable = x => x == j;
int gameInput = 5;
game.Run(gameInput);
int anotherJ = 3;
game.updateCapturedLocalVariable(anotherJ);
Спецификация языка C#
Дополнительные сведения см. в разделе Выражения анонимных функций в спецификации языка C#.
См. также
Руководство по программированию на C#
Встроенный язык запросов LINQ
Деревья выражений
Локальные функции в сравнении с лямбда-выражениями
Неявно типизированные лямбда-выражения
Примеры C# в Visual Studio 2008 (см. файлы примеров запросов LINQ и программу XQuery)
Рекурсивные лямбда-выражения
Свойства
04.11.2019 • 15 minutes to read • Edit Online
Синтаксис свойства
Синтаксис свойств является естественным расширением полей. Поле определяет место хранения:
Определение свойства содержит объявления для методов доступа get и set , которые получают и
устанавливают значение этого свойства:
Синтаксис, показанный выше, является синтаксисом автосвойств. Компилятор создает место хранения для
поля, поддерживающего свойство. Компилятор также реализует тело методов доступа get и set .
Бывает, что свойство необходимо инициализировать со значением, отличным от значения по умолчанию
для его типа. C# позволяет это сделать, указав значение после закрывающей фигурной скобки свойства. В
этом случае в качестве начального значения для свойства FirstName можно задать пустую строку, а не
null . Для этого используется следующий код:
Как вы увидите далее в этой статье, конкретная инициализация особенно полезна для свойств,
предназначенных только для чтения.
Вы можете определить хранилище самостоятельно, как показано ниже:
public class Person
{
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
private string firstName;
// remaining implementation removed from listing
}
Если реализация свойства представляет собой одиночное выражение, в качестве метода получения или
задания можно использовать элементы, воплощающие выражение.
Такой упрощенный синтаксис будет применяться в этой статье везде, где это возможно.
В примере выше определяется свойство для чтения и записи. Обратите внимание на ключевое слово value
в методе доступа set. Метод доступа set всегда имеет один параметр с именем value . Метод доступа
get должен возвращать значение, которое можно преобразовать в свойство ( string в этом примере).
Сценарии
Приведенные выше примеры демонстрируют один из простейших вариантов определения свойств:
свойство для чтения и записи без проверки. Путем написания нужного кода в методах доступа get и set
можно реализовать много разных сценариев.
Проверка
Можно написать код в методе доступа set , чтобы гарантировать, что значения, представленные
свойством, всегда будут допустимыми. Например, предположим, что одно из правил для класса Person
заключается в том, что имя не может быть пустым или содержать только пробелы. Это можно реализовать
следующим образом:
public class Person
{
public string FirstName
{
get => firstName;
set
{
if (string.IsNullOrWhiteSpace(value))
throw new ArgumentException("First name must not be blank");
firstName = value;
}
}
private string firstName;
// remaining implementation removed from listing
}
Предыдущий пример можно упростить, воспользовавшись выражением throw в рамках проверки метода
задания свойства:
В приведенном выше примере код применяет правило о том, что имя не может быть пустым или
содержать только пробелы. Если разработчик пишет
hero.FirstName = "";
Это назначение создает исключение ArgumentException . Поскольку метод доступа set свойства должен
иметь тип возвращаемого значения void, чтобы сообщить об ошибках в методе доступа set, создается
исключение.
Этот синтаксис можно расширить для любых компонентов в вашем сценарии. Можно проверить
отношения между разными свойствами или соответствие любым внешним условиям. Любые допустимые
операторы C# являются допустимыми в методе доступа свойства.
Только чтение
До этого момента все примеры определения свойств определяли свойства для чтения и записи с помощью
открытых методов доступа. Это не единственные операции доступа для свойств. Можно создать свойства,
доступные только для чтения, или назначить другой уровень доступа для методов set и get. Предположим,
ваш класс Person должен допускать изменение значения свойства FirstName только из других методов
этого класса. Вы можете предоставить методу доступа set уровень доступа private , а не public :
Также допускается размещение более строгих модификаторов для метода доступа get . Например,
свойство public может быть открытым, а метод доступа get ограничен типом private . Этот сценарий
редко реализуется на практике.
Кроме того, можно ограничить изменения в свойстве, разрешив задавать его только в конструкторе или
инициализаторе свойств. Внести соответствующие изменения в класс Person можно следующим образом:
Эта функция чаще всего используется для инициализации коллекций, которые представляются как
свойства, доступные только для чтения:
Вычисляемые свойства
Свойство не обязательно должно просто возвращать значение поля члена. Можно создать свойства,
возвращающие вычисляемое значение. Расширим объект Person так, чтобы он возвращал полное имя,
вычисляемое путем объединения имени и фамилии:
В примере выше используется функция интерполяции строк для создания форматированной строки для
полного имени.
Можно также использовать члены, воплощающие выражения, которые обеспечивают более краткий
способ создания вычисляемого свойства FullName :
public class Person
{
public string FirstName { get; set; }
Члены, воплощающие выражения, используют синтаксис лямбда -выражений для определения метода,
который содержит одно выражение. Здесь это выражение возвращает полное имя объекта person.
Свойства с вычислением в кэше
Вы можете сочетать концепцию вычисляемого свойства с хранением и созданием свойства с вычислением
в кэше. Например, можно изменить свойство FullName таким образом, чтобы форматирование строки
выполнялось только при первом доступе к нему.
Обратите внимание, что приведенный выше код содержит ошибку. Если код изменяет значение свойств
FirstName или LastName , ранее вычисленное поле fullName является недопустимым. Вам потребуется
изменить методы доступа set свойств FirstName и LastName , чтобы вычислить поле fullName еще раз:
public class Person
{
private string firstName;
public string FirstName
{
get => firstName;
set
{
firstName = value;
fullName = null;
}
}
Эта окончательная версия вычисляет свойство FullName только при необходимости. Если ранее
вычисленная версия является допустимой, используется она. Если другое изменение состояния делает
ранее вычисленную версию недействительной, она будет пересчитана. Разработчикам, использующим этот
класс, необязательно знать детали реализации. Ни одно из этих внутренних изменений не влияет на
использование объекта person. Это главная причина для использования свойств для предоставления
доступа к членам данных объекта.
Присоединение атрибутов к автоматически реализуемым свойствам
Начиная с C# 7.3 атрибуты полей можно прикреплять к созданному компилятором резервному полю в
автоматически реализуемых свойствах. Например, рассмотрим изменение класса Person , который
добавляет уникальное целочисленное свойство Id . Можно записать свойство Id с помощью
автоматически реализуемого свойства, но в вашем коде не предусмотрено сохранение свойства Id .
NonSerializedAttribute можно прикреплять только к полям, а не свойствам. Можно прикрепить
NonSerializedAttribute к резервному полю для свойства Id с помощью описателя field: в атрибуте, как
показано в следующем примере:
public class Person
{
public string FirstName { get; set; }
[field:NonSerialized]
public int Id { get; set; }
Этот способ подходит для любого атрибута, который вы прикрепляете к резервному полю в автоматически
реализуемом свойстве.
Реализация INotifyPropertyChanged
Последним сценарием, где необходимо написать код в методе доступа к свойству, является поддержка
интерфейса INotifyPropertyChanged, используемого для уведомления клиентов привязки данных об
изменении значения. При изменении значения свойства объект вызывает событие
INotifyPropertyChanged.PropertyChanged, указывающее на изменение. Библиотеки привязки данных, в
свою очередь, изменяют отображаемые элементы на основе этого изменения. В следующем примере кода
показано, как можно реализовать свойства INotifyPropertyChanged для FirstName этого класса person.
Заключение
Свойства — это своего рода интеллектуальные поля в классе или объекте. Из-за пределов объекта они
представляются полями в объекте. Однако для реализации свойства можно использовать полную палитру
функциональных возможностей C#. Вы можете предоставлять разные уровни доступа, выполнять
проверки, отложенное вычисление или любые другие требования, необходимые в вашем сценарии.
Индексаторы
25.11.2019 • 14 minutes to read • Edit Online
Синтаксис индексаторов
Для обращения к индексатору используется имя переменной и квадратные скобки. Аргументы индексатора
необходимо поместить в квадратные скобки:
Для объявления индексаторов используется ключевое слово this в качестве имени свойства, а аргументы
объявляются в квадратных скобках. Это объявление соответствует примеру использования, показанному в
предыдущем абзаце:
Наш первый пример помогает понять связь между синтаксисом свойств и индексаторов. Эта аналогия
справедлива для большей части синтаксических правил для индексаторов. Индексаторы могут иметь любые
допустимые модификаторы доступа (общедоступный, защищенный внутренний, защищенный, внутренний,
закрытый или закрытый защищенный). Они могут быть запечатанными, виртуальными или абстрактными.
Как и для свойств, вы можете указать разные модификаторы доступа для методов доступа set и get в
индексаторе. Можно также указать индексаторы только для чтения (опуская метод доступа set) или только
для записи (опуская метод доступа get).
К индексаторам можно применять практически все возможности, предусмотренные для свойств.
Единственное исключение из этого правила — автоматически реализуемые свойства. Компилятор не
всегда может создать правильное хранилище для индексатора.
Наличие аргументов для ссылки на элемент в наборе элементов отличает индексаторы от свойств. Можно
определить несколько индексаторов для типа, при условии что списки аргументов для каждого индексатора
являются уникальными. Рассмотрим различные сценарии, в которых можно использовать один или
несколько индексаторов в определении класса.
Сценарии
Вам потребуется определить индексаторы в типе, если его API моделирует некоторую коллекцию, где
определяются аргументы для этой коллекции. Индексаторы могут сопоставляться или не сопоставляться
напрямую с типами коллекций, которые являются частью основной платформы .NET. Тип может иметь
другие обязанности, помимо моделирования коллекции. Индексаторы позволяют предоставить API, который
соответствует абстракции данного типа, не раскрывая внутренних сведений о том, как хранятся или
вычисляются значения этой абстракции.
Давайте рассмотрим некоторые распространенные сценарии использования индексаторов. Вы можете
воспользоваться папкой с примерами индексаторов. Инструкции по загрузке см. в разделе Просмотр и
скачивание примеров.
Массивы и векторы
Один из наиболее распространенных сценариев для создания индексаторов — когда тип моделирует массив
или вектор. Можно создать индексатор для моделирования упорядоченного набора данных.
Преимущество создания собственного индексатора заключается в том, что вы можете определить
хранилище для этой коллекции в соответствии с потребностями. Представьте себе ситуацию, когда ваш тип
моделирует исторические данные, которые слишком велики для загрузки в память за один раз. Вам
потребуется загружать и выгружать разделы коллекции по мере использования. Следующий пример
моделирует такое поведение. Он сообщает о том, сколько точек данных существует. Он создает страницы
для хранения разделов данных по требованию. Он удаляет страницы из памяти, чтобы освободить место для
страниц, которые требуются более поздним запросам.
page[index] = value;
}
}
Вы можете следовать этой идиоме проектирования для моделирования любых типов коллекций, если у вас
имеются веские причины для того, чтобы не загружать весь набор данных в коллекции в память. Обратите
внимание, что класс Page является закрытым вложенным классом, который не является частью открытого
интерфейса. Эти подробности скрыты от пользователей этого класса.
Словари
Другим распространенным сценарием является необходимость моделирования словаря или карты. Этот
сценарий подразумевает хранение в типе значений на основе ключа, как правило, текстовых ключей. В этом
примере создается словарь, сопоставляющий аргументы командной строки с лямбда-выражениями,
управляющими этими параметрами. В следующем примере показано два класса: класс ArgsActions ,
сопоставляющий параметр командной строки делегату Action , и ArgsProcessor , использующий ArgsActions
для выполнения каждого объекта Action при обнаружении соответствующего параметра.
}
public class ArgsActions
{
readonly private Dictionary<string, Action> argsActions = new Dictionary<string, Action>();
В этом примере коллекция ArgsAction точно соответствует базовой коллекции. get определяет, настроен ли
данный параметр. Если это так, он возвращает объект Action , связанный с этим параметром. В противном
случае он возвращает объект Action , не выполняющий никаких действий. Открытый метод доступа не
включает метод доступа set . Вместо этого в проекте используется открытый метод для задания параметров.
Многомерные сопоставления
Можно создавать индексаторы, использующие несколько аргументов. Кроме того, эти аргументы не
ограничиваются одним типом. Рассмотрим два примера.
В первом примере показан класс, который создает значения для множества Мандельброта. Дополнительные
сведения о математических принципах этого множества см. в этой статье. Индексатор использует два
значения double для определения точки на плоскости X и Y. Метод доступа get вычисляет количество
итераций до определения точки, не входящей в это множество. Если достигается максимальное количество
итераций, точка находится в множестве и возвращается значение maxIterations класса. (Компьютер создает
изображения на основе заданных цветов множества Мандельброта для числа итераций, необходимых для
определения того, что точка находится за пределами множества.)
Множество Мандельброта определяет значения в каждой координате (x, y) для значений реальных чисел.
Это определяет словарь, который может содержать бесконечное количество значений. Таким образом,
множество не основано на хранилище. Напротив, этот класс вычисляет значение для каждой точки, когда код
вызывает метод доступа get . Базовое хранилище не используется.
Теперь рассмотрим один из последних примеров использования индексаторов, где индексатор принимает
несколько аргументов разных типов. Рассмотрим программу, которая управляет историческими данными о
температуре. Этот индексатор использует город и дату для задания или получения высоких и низких
температур для этого расположения:
using DateMeasurements =
System.Collections.Generic.Dictionary<System.DateTime, IndexersSamples.Common.Measurements>;
using CityDataMeasurements =
System.Collections.Generic.Dictionary<string, System.Collections.Generic.Dictionary<System.DateTime,
IndexersSamples.Common.Measurements>>;
В этом примере создается индексатор, который сопоставляет данные о погоде по двум разным аргументам:
городу (представленному string ) и дате (представленной DateTime ). Внутреннее хранилище использует два
класса Dictionary , представляющие двухмерный словарь. Открытый API больше не представляет базовое
хранилище. Вместо этого функции языка для индексаторов позволяют создать открытый интерфейс, который
представляет абстракцию, несмотря на то, что базовое хранилище должно использовать разные базовые
типы коллекции.
Этот код включает две части, которые могут быть незнакомы некоторым разработчикам. Эти два оператора
using :
создают псевдоним сконструированного универсального типа. Эти операторы позволяют коду позднее
использовать более описательные имена DateMeasurements и CityDateMeasurements вместо универсальной
конструкции Dictionary<DateTime, Measurements> и Dictionary<string, Dictionary<DateTime, Measurements> > . Эта
конструкция требует использования полных имен типов в правой части равенства = .
Второй прием — отбросить части времени любого объекта DateTime , используемого для индексации в
коллекции. Платформа .NET Framework не включает тип "только дата". Разработчики используют тип
DateTime , однако использование свойства Date для проверки того, что любой объект DateTime
принадлежит к заданной дате, равноценно.
Заключение
Индексаторы следует создавать при наличии в вашем классе элемента, подобного свойству, если такое
свойство представляет не одно значение, а коллекцию значений, где каждый отдельный элемент
определяется набором аргументов. Эти аргументы могут однозначно определять, на какой элемент в
коллекции необходимо ссылаться. Индексаторы расширяют концепцию свойств, где член обрабатывается
как элемент данных за пределами класса и как метод — в его пределах. Индексаторы позволяют аргументам
находить в свойстве один элемент, который представляет набор элементов.
Пустые переменные — руководство по языку C#
04.11.2019 • 11 minutes to read • Edit Online
Начиная с версии 7.0 язык C# поддерживает пустые переменные. Это временные фиктивные переменные,
которые намеренно не используются в коде приложения. Пустые переменные эквивалентны переменным,
которым не присвоены значения; пустые переменные не имеют значений. Так как пустая переменная по
сути всего одна и этой переменной может даже не выделяться память, пустые переменные помогают
расходовать память более экономно. Эти переменные делают код яснее, повышают его читаемость и
упрощают его поддержку.
Чтобы использовать пустую переменную, назначьте ей в качестве имени символ подчеркивания ( _ ).
Например, следующий вызов метода возвращает трехкомпонентный кортеж, в котором первое и второе
значения являются пустыми переменными, а area представляет собой ранее объявленную переменную,
задаваемую для соответствующего третьего компонента, возвращенного GetCityInformation:
private static (string, double, int, int, int, int) QueryCityDataForYears(string name, int year1, int
year2)
{
int population1 = 0, population2 = 0;
double area = 0;
public void Deconstruct(out string fname, out string mname, out string lname)
{
fname = FirstName;
mname = MiddleName;
lname = LastName;
}
// <Snippet1>
// Deconstruct the person object.
var (fName, _, city, _) = p;
Console.WriteLine($"Hello {fName} of {city}!");
// The example displays the following output:
// Hello John of Boston!
// </Snippet1>
}
}
// The example displays the following output:
// Hello John Adams of Boston, MA!
using System;
using System.Globalization;
Обратите внимание, что _ также является допустимым идентификатором. При использовании вне
поддерживаемого контекста _ считается не пустой, а действительной переменной. Если в области уже есть
идентификатор с именем _ , использование _ в качестве отдельной пустой переменной может привести к
следующим результатам:
Случайное изменение значения переменной _ в области из-за присвоения ей значения пустой
переменной. Например:
См. также
Деконструкция кортежей и других типов
Ключевое слово is
Ключевое слово switch
Универсальные шаблоны (Руководство по
программированию на C#)
30.10.2019 • 6 minutes to read • Edit Online
// constructor
public GenericList()
{
head = null;
}
В следующем примере кода показано, как клиентский код использует универсальный класс GenericList<T>
для создания списка целых чисел. Просто изменяя тип аргумента, следующий код можно легко изменить для
создания списков строк или любого другого пользовательского типа:
class TestGenericList
{
static void Main()
{
// int is the type argument
GenericList<int> list = new GenericList<int>();
Связанные разделы
Параметры универсального типа
Ограничения параметров типа
Универсальные классы
Универсальные интерфейсы
Универсальные методы
Универсальные делегаты
Различия между шаблонами языка C++ и универсальными шаблонами языка C#
Универсальные типы и отражение
Универсальные типы во время выполнения
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#.
См. также
System.Collections.Generic
Руководство по программированию на C#
Типы
<typeparam>
<typeparamref>
Универсальные шаблоны в .NET
Iterators
04.11.2019 • 9 minutes to read • Edit Online
Почти каждой написанной вами программе придется выполнять итерацию определенной коллекции. Для
этого вы напишете код, проверяющий каждый элемент в коллекции.
Кроме того, вы создадите методы итератора — методы, которые итератор создает для элементов
соответствующего класса. Их можно использовать в следующих целях:
Выполнение определенного действия с каждым элементом в коллекции.
Перечисление настраиваемой коллекции.
Расширение LINQ или других библиотек.
Создание конвейера данных, обеспечивающего эффективный поток данных через методы итератора.
В языке C# предусмотрены функции, позволяющие использовать оба сценария. В этой статье представлены
общие сведения об этих функциях.
Это руководство описывает несколько шагов. После каждого из них вы сможете запустить приложение и
оценить результаты. Вы также можете просмотреть или загрузить готовый пример для этого раздела.
Инструкции по загрузке см. в разделе Просмотр и скачивание примеров.
Больше ничего не требуется. Для итерации содержимого той или иной коллекции нужен только это
оператор foreach . При этом в работе оператора foreach нет ничего сложного. Он создает код,
необходимый для итерации коллекции, опираясь на два универсальных интерфейса, определенных в
библиотеке ядра .NET: IEnumerable<T> и IEnumerator<T> . Более подробно этот механизм рассматривается
ниже.
Оба этих интерфейса также имеют неуниверсальные аналоги: IEnumerable и IEnumerator . Универсальные
версии более предпочтительны для современного кода.
Отдельные операторы yield return в этом коде показывают, что в любом методе итератора можно
использовать сразу несколько дискретных операторов yield return . Другие языковые конструкции можно
(и нужно) включать для того, чтобы код метода итератора стал более простым. Точно такую же
последовательность чисел выдает определение метода, приведенное ниже:
Выбирать какой-то один из этих вариантов необязательно. В код можно добавлять столько операторов
yield return , сколько требуется для вашего метода:
index = 100;
while (index < 110)
yield return index++;
}
Это базовый синтаксис. Рассмотрим практический пример применения метода итератора. Допустим, вы
занимаетесь проектом IoT и имеете дело с датчиками устройств, которые создают большой поток данных.
Чтобы получить представление об этих данных, можно написать метод, формирующий выборку из каждого
N -го элемента данных. С этой задачей справится вот такой небольшой метод итератора:
Методы итератора подчиняются одному важному ограничению: в одном и том же методе не могут
одновременно присутствовать оператор return и оператор yield return . Этот код компилироваться не
будет:
Обычно это ограничение не вызывает проблем. Вы можете либо использовать в методе операторы
yield return , либо разделить исходный метод на несколько отдельных методов, одни из которых будут
включать оператор return , а другие — yield return .
Немного изменим последний метод, вставив в каждом случае оператор yield return :
var items = new int[] {100, 101, 102, 103, 104, 105, 106, 107, 108, 109 };
foreach (var item in items)
yield return item;
}
В некоторых случаях метод итератора лучше разбить на два разных метода. В одном будет использоваться
оператор return , а в другом — yield return . Допустим, вам нужно получить пустую коллекцию или первые
пять нечетных чисел, используя логический аргумент. Для этого можно написать следующие два метода:
Посмотрите на приведенные выше методы. В первом используется стандартный оператор return , который
возвращает либо пустую коллекцию, либо итератор, созданный вторым методом. Второй метод включает
оператор yield return , создающий запрошенную последовательность.
Эта конструкция представляет код, создаваемый компилятором C# версии 5 или более поздней. До выхода
версии 5 переменная item имела другую область:
// C# versions 1 through 4:
IEnumerator<int> enumerator = collection.GetEnumerator();
int item = default(int);
while (enumerator.MoveNext())
{
item = enumerator.Current;
Console.WriteLine(item.ToString());
}
Изменения были обусловлены тем, что прежнее поведение этой переменной могло вызывать с трудом
выявляемые и поддающиеся диагностике ошибки, связанные с лямбда-выражениями. Дополнительные
сведения о лямбда-выражениях см. в разделе Лямбда-выражения.
На практике компилятор создает несколько более сложный код и устраняет ситуации, когда объект,
возвращаемый методом GetEnumerator() , реализует интерфейс IDisposable . Полная версия кода выглядит
так:
{
var enumerator = collection.GetEnumerator();
try
{
while (enumerator.MoveNext())
{
var item = enumerator.Current;
Console.WriteLine(item.ToString());
}
} finally
{
// dispose of enumerator.
}
}
Способ ликвидации перечислителя зависит от характеристик типа enumerator . Как правило, предложение
finally разворачивается следующим образом:
finally
{
(enumerator as IDisposable)?.Dispose();
}
Однако если тип enumerator является запечатанным, а тип enumerator не подвергается явному
преобразованию в тип IDisposable , предложение finally разворачивается в пустой блок:
finally
{
}
Если же тип enumerator подвергается неявному преобразованию в тип IDisposable , а enumerator является
типом значения, не допускающим значение NULL, предложение finally разворачивается следующим
образом:
finally
{
((IDisposable)enumerator).Dispose();
}
К счастью, запоминать все это не нужно. Оператор foreach обрабатывает все эти нюансы в фоновом
режиме, а компилятор создает правильный код для любой из этих конструкций.
Общие сведения о делегатах
04.11.2019 • 4 minutes to read • Edit Online
Делегаты предоставляют механизм позднего связывания в .NET. Позднее связывание означает, что
создается алгоритм, где вызывающий объект также предоставляет по крайней мере один метод, который
реализует часть алгоритма.
Например, рассмотрим сортировку списка звезд в астрономическом приложении. Можно отсортировать
звезды по расстоянию от Земли, по величине или по воспринимаемой яркости.
Во всех этих случаях метод Sort() выполняет, по сути, одно и то же: упорядочивает элементы в списке на
основе некоего сравнения. Для каждого порядка сортировки используется разный код, сравнивающий две
звезды.
Такого рода решения использовались в программном обеспечении в течение полувека. Концепция
использования делегатов в языке C# обеспечивает первоклассную поддержку языка и безопасность типов.
Как вы увидите далее в этой серии статей, код C#, создаваемый для подобных алгоритмов, является строго
типизированным и использует язык и компилятор для соответствия типов аргументам и возвращаемым
типам.
Назад
В этой статье описываются классы в .NET Framework, поддерживающие делегаты, и рассматривается их
сопоставление с ключевым словом delegate .
NOTE
Не рекомендуется объявлять типы делегатов (или другие типы) непосредственно в глобальном пространстве имен.
Компилятор также создает обработчики добавления и удаления для этого нового типа, чтобы клиенты этого
класса могли добавлять и удалять методы из списка вызовов экземпляра. Компилятор будет обеспечивать
соответствие подписи добавляемого или удаляемого метода подписи, используемой при объявлении
метода.
Тип переменной — Comparison<T> , тип делегата определен ранее. Имя переменной — comparator .
В приведенном выше фрагменте кода была объявлена переменная-член в классе. Можно также объявить
переменные делегатов, локальные переменные или аргументов для методов.
Вызов делегатов
Чтобы вызвать методы, которые находятся в списке вызова делегата, нужно вызвать этот делегат. В методе
Sort() код вызовет метод сравнения, чтобы определить порядок размещения объектов:
В строке выше код вызывает метод, подключенный к делегату. Переменная считается именем метода и
вызывается с помощью обычного синтаксиса вызова метода.
Эта строка кода делает содержит небезопасное условие: Нет никакой гарантии, что целевой объект был
добавлен к делегату. Если целевые объекты не были вложены, строка выше приведет к возникновению
исключения NullReferenceException . Идиомы, используемые для решения этой проблемы, более сложны,
чем простые проверки значений NULL, и рассматриваются далее в этой серии материалов.
Метод объявляется как закрытый метод. Все правильно. Вам может быть не нужно, чтобы этот метод был
частью общедоступного интерфейса. Этот метод, присоединенный к делегату, по-прежнему можно
использовать в качестве метода сравнения. В вызывающем коде этот метод будет присоединен к целевому
списку объекта делегата и будет доступен через этот делегат.
Чтобы создать эту связь, передайте метод в метод List.Sort() :
phrases.Sort(CompareLength);
Обратите внимание, что имя метода используется без скобок. Использование метода как аргумента
указывает компилятору преобразовать ссылку на метод в ссылку, которая может применяться как целевой
объект вызова делегата, и присоединить этот метод в качестве целевого объекта вызова.
Вы также явно объявили переменную типа Comparison<string> и выполнили назначение:
Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);
Если в качестве объекта делегата используется небольшой метод, для назначения обычно применяется
синтаксис лямбда-выражения:
Назад
Из предыдущей статьи вы узнали, как создавать определенные типы делегатов с помощью ключевого слова
delegate .
Абстрактный класс делегата предоставляет инфраструктуру для неплотной привязки и вызова. Конкретные
типы делегата становятся гораздо полезнее, поскольку включают и обеспечивают безопасность типов для
методов, добавляемых в список вызовов для объекта делегата. Компилятор создает эти методы, если вы
используете ключевое слово delegate и определяете конкретный тип делегата.
В результате новые типы делегатов создаются всякий раз, когда возникает необходимость в новой сигнатуре
метода. Со временем эта работа может стать слишком громоздкой. Для каждого нового компонента
требуются новые типы делегатов.
К счастью, это необязательно. Платформа .NET Core содержит несколько типов, которые можно
использовать всякий раз, когда вам нужно делегировать типы. Эти определения универсальны, поэтому
всякий раз, когда вам нужно объявить новый метод, можно объявить настройку.
Первый из этих типов — это тип Action и несколько вариантов:
Модификатор out для аргумента универсального типа результата рассматривается в статье о ковариации.
Существуют варианты делегата Func , содержащие до 16 входных аргументов, такие как
Func<T1,T2,T3,T4,T5,T6,T7,T8,T9,T10,T11,T12,T13,T14,T15,T16,TResult>. Как правило, тип результата должен
быть последним параметром типа во всех объявлениях Func .
В качестве типа делегата, возвращающего значение, используйте один из типов Func .
Также есть специализированный тип Predicate<T>, предназначенный для делегата, возвращающего тест
одного значения:
Можно заметить, что для каждого типа Predicate существует структурно эквивалентный тип Func ,
например:
Можно подумать, что два эти типа эквивалентны. Это не так. Эти две переменные не заменяют друг друга.
Переменную одного типа нельзя назначить другому типу. В системе типов C# используются имена
определенных типов, а не структуры.
Все эти определения типов делегатов в библиотеке .NET Core должны означать, что новый тип делегата для
каждой создаваемой функции, которой требуются делегаты, создавать не нужно. Эти универсальные
определения должны предоставлять типы делегатов, необходимые в большинстве ситуаций. Можно просто
создать экземпляр одного из этих типов с необходимыми параметрами типа. Если алгоритмы можно сделать
универсальными, эти делегаты можно использовать как универсальные типы.
Это позволит сэкономить время и свести к минимуму число новых типов, которые нужно создать для работы
с делегатами.
В следующей статье представлено несколько общих шаблонов для работы с делегатами на практике.
Вперед
Общие шаблоны делегатов
04.11.2019 • 14 minutes to read • Edit Online
Назад
Делегаты предоставляют механизм, который обеспечивает проектирование программного обеспечения с
минимальной взаимозависимостью между компонентами.
Отличным примером такого проектирования является LINQ. В модели выражений запросов LINQ
делегаты применяются для обеспечения всех возможностей. Рассмотрим простой пример.
В нем из последовательности чисел отфильтровываются только числа со значением меньше 10. Метод
Where использует делегат, который определяет, какие элементы последовательности проходят через
фильтр. При создании запроса LINQ вы предоставляете реализацию делегата для этой цели.
Прототип метода Where имеет следующий вид:
Этот пример актуален для всех методов, которые относятся к LINQ. Все они используют делегаты для
управления определенными запросами. Этот конструктивный шаблон API очень эффективен, что делает
его важным для изучения и понимания.
Из этого примера видно, что делегаты почти не требуют взаимозависимости между компонентами. Не
нужно создавать класс, производный от некоторого базового класса. Не нужно реализовывать
определенный интерфейс. Единственным требованием является предоставление реализации одного
метода, на основе которого решается поставленная задача.
Первая реализация
Начнем с малого: начальная реализация будет принимать новые сообщения и записывать их с помощью
любого подключенного делегата. Можно начать с одного делегата, который записывает сообщения в
консоль.
Приведенный выше статический класс содержит только самое необходимое для работы. Нам нужно
создать единственную реализацию метода, который записывает сообщения в консоль.
Logger.WriteMessage += LoggingMethods.LogToConsole;
Рекомендации
Наш пример пока очень прост, но все же он демонстрирует некоторые важные моменты, касающиеся
проектирования с помощью делегатов.
Использование типов делегатов, определенных в Core Framework, упрощает пользователям работу с
делегатами. Вам не нужно определять новые типы, а разработчикам, использующим вашу библиотеку, не
нужно изучать новые специальные типы делегатов.
Применяются минимально необходимые, но при этом максимально гибкие интерфейсы: чтобы создать
компонент для вывода данных журнала, необходимо написать всего один метод. Это может быть
статический метод или метод экземпляра. Он может иметь любой уровень доступа.
Форматирование выхода
Давайте немного улучшим первую версию, а затем приступим к созданию других механизмов ведения
журнала.
Затем добавим в метод LogMessage() несколько аргументов, чтобы класс журнала создавал более
структурированные сообщения.
Далее используем аргумент Severity для фильтрации сообщений, отправляемых в место вывода журнала.
Рекомендации
Вы добавили новые функции в инфраструктуру ведения журналов. Так как компонент logger очень слабо
связан с любым из механизмов вывода, эти функции можно добавлять без влияния на код, в котором
реализуется делегат logger.
По мере расширения кода вы увидите дополнительные примеры того, как такая слабая взаимосвязь
обеспечивает большую гибкость в плане обновления компонентов сайта без внесения изменений в другие
его части. На самом деле в более крупном приложении классы вывода данных журнала могут находиться в
другой сборке и даже не требовать повторной сборки.
После создания этого класса можно создать его экземпляр, и он подключит свой метод LogMessage к
компоненту Logger:
Эти два метода не являются взаимоисключающими. Вы можете подключить оба метода ведения журнала,
чтобы сообщения создавались как в консоли, так и в файле.
Позднее вы можете удалить один из делегатов даже в том же приложении без каких-либо проблем для
системы.
Logger.WriteMessage -= LoggingMethods.LogToConsole;
Рекомендации
Итак, вы добавили второй обработчик вывода для подсистемы ведения журнала. Ему требуется немного
больший объем инфраструктуры для правильной поддержки файловой системы. Делегат представляет
собой метод экземпляра. Кроме того, это закрытый метод. В более высоком уровне доступности нет
необходимости, так как инфраструктура делегатов может подключать делегаты.
Во-вторых, модель на основе делегатов предоставляет несколько методов вывода без дополнительного
кода. Вам не нужно создавать дополнительную инфраструктуру для поддержки нескольких методов
вывода. Они просто добавляются в список вызова.
Обратите особое внимание на код в методе вывода данных журнала в файл. Он написан так, что не
создает никаких исключений. Хотя это не является строго обязательным, зачастую это более удобно. Если
какой-либо из методов делегата создает исключение, остальные делегаты в списке вызова не будут
вызваны.
Наконец, отметим, что компонент записи данных журнала в файл должен управлять своими ресурсами,
открывая и закрывая файл для каждого сообщения журнала. Вы можете оставить файл открытым и
реализовать интерфейс IDisposable для закрытия файла по завершении операции. У каждого метода свои
плюсы и минусы. В обоих случаях несколько усиливается взаимозависимость между классами.
Для поддержки каждого из сценариев в код класса Logger не нужно вносить никаких изменений.
Условный оператор null ( ?. ) замыкается, если левый операнд (в данном случае WriteMessage ) имеет
значение null, что означает, что попытки записать сообщение не предпринимаются.
Метод Invoke() не указан в документации по System.Delegate или System.MulticastDelegate . Компилятор
создает типобезопасный метод Invoke для любого объявленного типа делегата. В этом примере это
означает, что метод Invoke принимает один аргумент string и имеет тип возвращаемого значения void.
Сводка рекомендаций
Вы ознакомились с простейшим компонентом журнала, который можно расширять с помощью других
модулей записи и иных функций. Благодаря использованию делегатов при проектировании эти
компоненты оказываются слабо взаимосвязанными. Преимуществ несколько. Очень легко создавать
новые механизмы вывода и подключать их к системе. Этим механизмам требуется только один метод,
который записывает сообщение журнала. Такая модель очень устойчива при добавлении новых
возможностей. Обязательным требованием для каждого модуля записи является реализация одного
метода. Это может быть статический метод или метод экземпляра. Он может быть открытым, закрытым
или иметь любой иной допустимый уровень доступа.
В класс Logger можно вносить любое количество улучшений и изменений без серьезной модификации.
Открытый интерфейс API, как и любой другой класс, нельзя модифицировать без риска внесения
существенных изменений. Но так как взаимосвязь между средством ведения журнала и модулями вывода
осуществляется только посредством делегата, другие типы (например, интерфейсы или базовые классы) не
затрагиваются. Взаимосвязь минимальна.
Вперед
Общие сведения о событиях
25.11.2019 • 5 minutes to read • Edit Online
Назад
События, так же как и делегаты, представляют собой механизм позднего связывания. На самом деле
события основаны на тех же средствах языка, которые обеспечивают поддержку делегатов.
С помощью событий объект может сообщить всем компонентам системы, которым это необходимо, о том,
что что-то произошло. Любой другой компонент может подписаться на событие, чтобы получать
уведомления о его наступлении.
Возможно, вы уже пользовались событиями при программировании. Во многих графических системах есть
модель событий, которая позволяет сообщать о действиях пользователей. Такие события сообщают о
перемещениях мыши, нажатиях кнопок и иных подобных действиях. Это наиболее распространенный, но,
безусловно, не единственный вариант использования событий.
Вы можете определить события, которые должны вызываться для классов. Важным моментом при работе с
событиями является то, что для определенного события может быть не зарегистрирован ни один объект.
Код необходимо писать так, чтобы он не вызывал событий, если прослушиватели не настроены.
При подписке на событие также создается взаимосвязь между двумя объектами (источником события и
приемником событий). Если приемник событий больше не должен получать события, необходимо отменить
его подписку на источник события.
Тип события (в этом примере EventHandler<FileListArgs> ) должен быть типом делегата. При объявлении
события должен соблюдаться ряд соглашений. Как правило, тип делегата события имеет возвращаемый тип
void. Объявление события должно представлять собой глагол или глагольное словосочетание. Если событие
сообщает о том, что уже произошло, используйте прошедшее время. Для сообщения о том, что должно
произойти, используйте глагол в настоящем времени (например, Closing ). Настоящее время часто
указывает на то, что класс поддерживает какую-либо настройку. Одна из самых распространенных
ситуаций — поддержка отмены. Например, событие Closing может иметь аргумент, который указывает на
то, должна ли продолжаться операция закрытия. В других ситуациях вызывающим объектам может
предоставляться возможность изменения поведения путем изменения свойств аргументов события.
Событие может вызываться для указания действия, которое алгоритму предлагается выполнить в
следующую очередь. Обработчик событий может предписать выполнение другого действия, изменив
свойства аргумента события.
Если нужно инициировать событие, то следует вызвать соответствующий обработчик событий с помощью
синтаксиса вызова делегатов:
Как описано в разделе, посвященном делегатам, оператор ?. позволяет легко предотвратить попытки вызова
события, если на него нет подписчиков.
Подписка на событие производится с помощью оператора += :
fileLister.Progress += onProgress;
Имя метода обработчика обычно содержит имя события с префиксом On, как показано выше.
Для отмены подписки на событие служит оператор -= :
fileLister.Progress -= onProgress;
Важно отметить, что для выражения, представляющего обработчик событий, была объявлена локальная
переменная. Благодаря этому при отмене подписки обработчик удаляется. Если вместо этого
использовалось тело лямбда-выражения, то производится попытка удалить обработчик, который не был
подключен, что не приводит ни к какому результату.
В следующей статье вы узнаете больше о типичных шаблонах событий и ознакомитесь с вариантами этого
примера.
Вперед
Стандартные шаблоны событий .NET
04.11.2019 • 14 minutes to read • Edit Online
Назад
События .NET обычно следуют нескольким известным шаблонам. Стандартизация на основе этих шаблонов
означает, что разработчики могут использовать знание таких стандартных шаблонов, применяя их к любой
программе событий .NET.
Мы разберем эти стандартные шаблоны, чтобы снабдить вас знаниями, необходимыми для создания
источников стандартных событий, подписки и обработки стандартных событий в коде.
Тип возвращаемого значения — void. События основаны на делегатах и являются делегатами многоадресной
рассылки. Это обеспечивает поддержку нескольких подписчиков для любого источника событий. Одно
значение, возвращаемое из метода, не масштабируется на несколько подписчиков событий. Какое
возвращаемое значение доступно источнику события после возникновения события? Далее в этой статье
будет показано, как создавать протоколы событий, которые поддерживают подписчики на события,
передающие данные в источник событий.
Список аргументов содержит два аргумента: отправителя и аргументы события. Тип времени компиляции
sender — System.Object , даже если вам, вероятно, известен более производный тип, который всегда
является правильным. По соглашению используйте object .
Второй аргумент обычно являлся типом, производным от System.EventArgs . (В следующем разделе вы
увидите, что это соглашение больше не является обязательным.) Даже если тип события не требует
дополнительных аргументов, необходимо предоставить оба аргумента. Существует специальное значение
EventArgs.Empty , которое следует использовать для обозначения того, что событие не содержит никаких
дополнительных сведений.
Создадим класс, который перечисляет соответствующие шаблону файлы в каталоге или любом из его
подкаталогов. Этот компонент создает событие для каждого найденного файла, который соответствует
шаблону.
Использование модели событий обеспечивает некоторые преимущества разработки. Можно создать
несколько прослушивателей событий, которые выполняют разные действия при нахождении искомого
файла. Сочетание разных прослушивателей позволяет создавать более надежные алгоритмы.
Ниже показано объявление аргумента исходного события для поиска искомого файла:
public class FileFoundArgs : EventArgs
{
public string FoundFile { get; }
Несмотря на то, что этот тип выглядит как небольшой тип, содержащий только данные, вы должны
выполнить соглашение и назначить его ссылочным типом ( class ). Это означает, что объект аргумента будет
передаваться по ссылке, а любые обновления данных будут доступны всем подписчикам. Первая версия
является неизменяемым объектом. Рекомендуется сделать свойства в типе аргумента события
неизменяемыми. Таким образом, один подписчик не сможет изменить значения до того, как их увидит
другой подписчик. (Существуют исключения, как можно будет увидеть ниже.)
Затем нужно создать объявление события в классе FileSearcher. Использование типа EventHandler<T>
означает, что вам не требуется создавать еще одно определение типа. Вы просто используете
универсальную специализацию.
Заполним класс FileSearcher для поиска файлов, соответствующих шаблону, и вызова правильного события
при обнаружении совпадения.
fileLister.FileFound += onFileFound;
и обработчик remove:
fileLister.FileFound -= onFileFound;
Обратите внимание, что для обработчика используется локальная переменная. Если вы используете тело
лямбда-выражения, удаление не будет работать корректно. Будет существовать другой экземпляр делегата,
не выполняющий никаких действий.
Код вне класса не может вызывать события, а также выполнять другие операции.
Это новое поле автоматически инициализируется значением false , которое используется по умолчанию
для логических полей, чтобы не происходило случайной отмены. Единственным другим изменением в
компоненте является установка флага после вызова события для просмотра, если любой из подписчиков
запросил отмену:
Одно из преимуществ этого шаблона заключается в том, что это не является критическим изменением. Ни
один из подписчиков не запрашивал отмену ранее и не запрашивает в настоящий момент. Код подписчиков
не требует обновления, если не требуется поддержка нового протокола отмены. Они очень слабо связаны.
Изменим подписчик, чтобы он запрашивал отмену, когда обнаруживает первый исполняемый файл:
Опять же, вы можете следовать рекомендациям по созданию неизменяемого ссылочного типа для
аргументов событий.
Теперь определим событие. На этот раз будет использоваться другой синтаксис. Помимо синтаксиса полей
можно явно создать свойство c помощью обработчиков add и remove. В этом примере вы не будете
добавлять код в эти обработчики, здесь просто демонстрируется их создание.
Созданный здесь код очень похож на тот код, который компилятор создает для определения полей событий,
как было показано ранее. Для создания события используется синтаксис, очень похожий на используемый
для свойств. Обратите внимание, что обработчики имеют разные имена: add и remove . Они вызываются для
подписки на событие или отмены подписки на событие. Учтите, что вы также должны объявить закрытое
резервное поле для хранения переменной событий. Оно инициализируется значением NULL.
Теперь добавим перегрузку метода Search , который обходит подкаталоги и вызывает оба события. Для
этого проще всего использовать аргумент по умолчанию для задания поиска по всем каталогам:
public void Search(string directory, string searchPattern, bool searchSubDirs = false)
{
if (searchSubDirs)
{
var allDirectories = Directory.GetDirectories(directory, "*.*", SearchOption.AllDirectories);
var completedDirs = 0;
var totalDirs = allDirectories.Length + 1;
foreach (var dir in allDirectories)
{
directoryChanged?.Invoke(this,
new SearchDirectoryArgs(dir, totalDirs, completedDirs++));
// Search 'dir' and its subdirectories for files that match the search pattern:
SearchDirectory(dir, searchPattern);
}
// Include the Current Directory:
directoryChanged?.Invoke(this,
new SearchDirectoryArgs(directory, totalDirs, completedDirs++));
SearchDirectory(directory, searchPattern);
}
else
{
SearchDirectory(directory, searchPattern);
}
}
На этом этапе можно запустить приложение, вызывающее перегруженный метод для поиска всех вложенных
каталогов. Для нового события ChangeDirectory нет подписчиков, однако благодаря использованию идиомы
?.Invoke() мы можем гарантировать правильную работу метода.
Добавим обработчик для написания строки, показывающей ход выполнения в окне консоли.
Назад
В предыдущей статье рассматривались наиболее распространенные шаблоны событий. .NET Core имеет
менее жесткий шаблон. В этой версии определение EventHandler<TEventArgs> больше не имеет ограничения,
указывающего на то, что TEventArgs должен быть классом, производным от System.EventArgs .
В результате повышается гибкость разработки и обеспечивается обратная совместимость. Начнем с гибких
возможностей. Класс System.EventArgs представляет один метод: MemberwiseClone() , который создает
неполную копию объекта. Чтобы реализовать свою функциональность для любого класса, производного от
EventArgs , этот метод должен использовать отражение. Функциональность проще создать в определенном
производном классе. Это фактически означает, что наследование от System.EventArgs является
ограничением, которое регламентирует разработки, но не предоставляет никаких дополнительных
преимуществ. На самом деле, можно изменить определения FileFoundArgs и SearchDirectoryArgs так, чтобы
они не были производными от EventArgs . Программа будет работать точно так же.
Вместо SearchDirectoryArgs можно также использовать структуру, внеся дополнительное изменение.
Во-первых, обратите внимание на то, что обработчик помечен как асинхронный обработчик. Поскольку он
назначается типу делегата обработчика событий, он будет иметь возвращаемый тип void. Это означает, что
необходимо следовать шаблону, приведенному в обработчике, и не допускать создания исключений вне
контекста асинхронного обработчика. Поскольку он не возвращает задачу, ничто не сообщает об ошибке
при переходе в состояние сбоя. Поскольку метод является асинхронным, он просто не может создать
исключение. (Вызывающий метод продолжал выполнение, так как это async .) Фактическое поведение во
время выполнения будет определяться по-разному для разных сред. Оно может завершить поток или
процесс, владеющий потоком, или оставить процесс в неопределенном состоянии. Все эти возможные
результаты очень нежелательны.
Вот почему необходимо заключить оператор await для асинхронных задач в собственный блок try. Если он
приводит к сбою задачи, можно записать ошибку в журнал. Если это ошибка, из-за которой невозможно
восстановить приложение, можно быстро и правильно выйти из программы.
Это были основные обновления шаблона событий .NET. Затем вы увидите множество примеров
предыдущих версий в библиотеках, с которыми вы работаете. Однако также необходимо иметь
представление и о последних шаблонах.
В следующей статье этой серии материалов вы узнаете об использовании delegates и events в своих
проектах. Это относительно схожие понятия, и сведения в этой статье помогут вам принять оптимальное
решение.
Вперед
Различия между делегатами и событиями
25.11.2019 • 5 minutes to read • Edit Online
Назад
Разработчики, не имеющие опыта работы с платформой .NET Core, часто не могут решить, что следует
выбрать: структуру на основе delegates или на основе events . Это сложный вопрос, так как эти две
возможности языка очень похожи. Более того, события основаны на тех же средствах языка, которые
обеспечивают поддержку делегатов.
И те и другие обеспечивают сценарии позднего связывания, в которых взаимодействие компонента
осуществляется путем вызова метода, известного только во время выполнения. И те и другие поддерживают
методы с одним или несколькими подписчиками. Иногда это называют поддержкой одноадресности и
многоадресности. Синтаксис добавления и удаления обработчиков в обоих случаях похож. Наконец, при
вызове событий и делегатов используется абсолютно одинаковый синтаксис вызова методов. Более того,
поддерживается одинаковый синтаксис метода Invoke() для использования с оператором ?. .
Учитывая такое сходство, легко могут возникнуть проблемы с выбором подходящего механизма в той или
иной ситуации.
class LINQQueryExpressions
{
static void Main()
{
Следующие шаги
Чтобы получить дополнительные сведения о LINQ, сначала ознакомьтесь с некоторыми основным
понятиями в статье Query expression basics (Базовая информация о выражении запроса), а затем
переходите к документации по интересующей вас технологии LINQ.
XML -документы: LINQ to XML
Платформа ADO.NET Entity Framework: LINQ to Entities
Коллекции, файлы, строки и другие сущности .NET: LINQ to Objects (C#)
Чтобы глубже разобраться в базовой концепции LINQ изучите статью о LINQ в C#.
Чтобы быстрее приступить к работе с LINQ в C#, переходите к руководству Working with LINQ
(Работа с LINQ ).
Основы выражения запроса
23.10.2019 • 20 minutes to read • Edit Online
В этой статье представлены основные понятия, связанные с выражениями запроса на языке C#.
IEnumerable<int> highScoresQuery =
from score in scores
where score > 80
orderby score descending
select score;
IEnumerable<string> highScoresQuery2 =
from score in scores
where score > 80
orderby score descending
select $"The score is {score}";
int highScoreCount =
(from score in scores
where score > 80
select score)
.Count();
IEnumerable<int> highScoresQuery3 =
from score in scores
where score > 80
select score;
В предыдущем примере запрос выполняется в вызове Count , так как Count должен выполнить итерацию
результатов, чтобы определить количество элементов, возвращенных методом highScoresQuery .
В следующем примере кода показано простое выражение запроса с одним источником данных, одним
предложением фильтрации, одним предложением упорядочения и без трансформации исходных элементов.
Предложение select завершает запрос.
static void Main()
{
// Data source.
int[] scores = { 90, 71, 82, 93, 75, 82 };
// Query Expression.
IEnumerable<int> scoreQuery = //query variable
from score in scores //required
where score > 80 // optional
orderby score descending // optional
select score; //must end with select or group
В предыдущем примере scoreQuery — переменная запроса, которую иногда называют просто запросом. В
переменной запроса не хранятся фактические данные результата, которые получаются с помощью цикла
foreach . Когда выполняется оператор foreach , результаты запроса не возвращаются с помощью
переменной запроса scoreQuery . В этом случае они возвращаются с помощью переменной итерации
testScore . Итерация переменной scoreQuery может выполняться во втором цикле foreach . Результаты
будет теми же, если ни они, ни источник данных не изменяются.
В переменной запроса может храниться запрос, выраженный с помощью синтаксиса запроса или метода
запроса, или их комбинации. В следующих примерах queryMajorCities и queryMajorCities2 являются
переменными запроса.
//Query syntax
IEnumerable<City> queryMajorCities =
from city in cities
where city.Population > 100000
select city;
// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);
List<City> largeCitiesList =
(from country in countries
from city in country.Cities
where city.Population > 10000
select city)
.ToList();
Дополнительные сведения о различных способах выражения запросов см. в разделе Синтаксис запросов и
синтаксис методов в LINQ.
Явная и неявная типизация переменных запроса
В этой документации обычно явно указывается тип переменной запроса для того, чтобы
продемонстрировать типичное отношение между переменной запроса и предложением select. Однако
можно также использовать ключевое слов var, чтобы указать компилятору вывести тип переменной запроса
(или любой другой локальной переменной) во время компиляции. Например, ранее приведенный в данном
разделе пример запроса также может быть выражен путем неявной типизации:
Дополнительные сведения см. в разделах Неявно типизированные локальные переменные и Связи типов в
операциях запроса LINQ.
Начало выражения запроса
Выражение запроса должно начинаться с предложения from . Оно задает источник данных вместе с
переменной диапазона. Переменная диапазона предоставляет каждый последующий элемент в исходной
последовательности во время ее обзора. Переменная диапазона строго типизируется на основе типа
элементов в источнике данных. В следующем примере переменная диапазона типизируется как countries ,
так как Country является массивом объектов Country . Так как переменная диапазона строго типизируется,
для доступа к любым доступным элементам типа можно использовать оператор-точку.
IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 500000 //sq km
select country;
Переменная диапазона находится в области до тех пор, пока запрос не завершится с помощью точки с
запятой или предложения continuation.
Выражение запроса может содержать несколько предложений from . Используйте дополнительные
предложения from , если каждый элемент в исходной последовательности является коллекцией или
содержит коллекцию. Например, предположим, что имеется коллекция объектов Country , каждый из
которых содержит коллекцию объектов City с именем Cities . Для выполнения запросов к объектам City
в каждой коллекции Country используйте два предложения from , как показано ниже.
IEnumerable<City> cityQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
var queryCountryGroups =
from country in countries
group country by country.Name[0];
IEnumerable<Country> sortedQuery =
from country in countries
orderby country.Area
select country;
Дополнительные сведения обо всех методах использования предложения select для преобразования
исходных данных см. в разделе Предложение select.
Продолжения с использованием ключевого слова "into"
Ключевое слово into можно использовать в предложении select или group для создания временного
идентификатора, в котором хранится запрос. Это действие рекомендуется выполнять, если требуется
выполнить в запросе дополнительные операции запроса после операции группирования или выбора. В
следующем примере объекты countries группируются в соответствии с численностью населения в
диапазоны по 10 миллионов. После создания этих групп дополнительные предложения отфильтровывают
некоторые группы, а затем сортируют группы в порядке возрастания. Чтобы выполнить эти дополнительные
операции, требуется продолжение, предоставляемое с помощью countryGroup .
IEnumerable<City> queryCityPop =
from city in cities
where city.Population < 200000 && city.Population > 100000
select city;
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area, country.Population descending
select country;
Ключевое слово ascending является необязательным, так как сортировка по умолчанию происходит по
возрастанию, если не задан порядок сортировки. Дополнительные сведения см. в разделе Предложение
orderby.
Предложение join
Используйте предложение join для связи или объединения элементов из одного источника данных с
элементами из другого источника данных на основе сравнения на равенство определенных ключей в
каждом элементе. В LINQ операции объединения выполняются в последовательностях объектов, элементы
которых относятся к разным типам. После объединения двух последовательностей необходимо
использовать оператор select или group , чтобы указать элемент для сохранения в выходной
последовательности. Также можно использовать анонимный тип, чтобы объединить свойства каждого
набора связанных элементов в новый тип для выходной последовательности. В следующем примере
связываются объекты prod , свойство Category которых соответствует одной из категорий в массиве строк
categories . Продукты, свойство Category которых не соответствует ни одной строке в categories ,
отфильтровываются. Оператор select формирует новый тип, свойства которого берутся как из cat , так и
из prod .
var categoryQuery =
from cat in categories
join prod in products on cat equals prod.Category
select new { Category = cat, Name = prod.Name };
Также можно выполнить групповое соединение путем сохранения результатов операции join во
временную переменную, используя ключевое слово into. Дополнительные сведения см. в разделе
Предложение join.
Предложение let
Используйте предложение let для сохранения результата выражения, например вызов метода, в новую
переменную диапазона. В следующем примере в переменную диапазона firstName сохраняется первый
элемент массива строк, возвращенного с помощью Split .
string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" };
IEnumerable<string> queryFirstNames =
from name in names
let firstName = name.Split(' ')[0]
select firstName;
var queryGroupMax =
from student in students
group student by student.GradeLevel into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore =
(from student2 in studentGroup
select student2.Scores.Average())
.Max()
};
См. также
Руководство по программированию на C#
LINQ
Ключевые слова запросов (LINQ )
Общие сведения о стандартных операторах запросов
LINQ в C#
04.11.2019 • 2 minutes to read • Edit Online
Этот раздел включает ссылки на разделы, содержащие более подробные сведения о LINQ.
Содержание раздела
Введение в запросы LINQ
Описание трех составляющих основной операции запроса LINQ, которые являются общими для всех языков
и источников данных.
LINQ и универсальные типы
Краткое введение в универсальные типы и принципы их использования в запросах LINQ.
Преобразование данных с помощью LINQ
Описание различных способов преобразования данных, полученных в результате запросов.
Связи типов в операциях запросов LINQ
Сведения о сохранении и преобразовании типов в трех составляющих операции запроса LINQ.
Синтаксис запросов и синтаксис методов в LINQ
Сравнение синтаксиса методов и синтаксиса запросов как двух способов выражения запроса LINQ.
Возможности C#, поддерживающие LINQ
Описание языковых конструкций в C#, которые поддерживают LINQ.
Связанные разделы
Выражения запросов LINQ
Общие сведения о запросах в LINQ и ссылки на дополнительные ресурсы.
Общие сведения о стандартных операторах запросов
Представление стандартных методов, используемых в запросах LINQ.
Создание запросов LINQ на языке C#
23.10.2019 • 6 minutes to read • Edit Online
NOTE
Эти запросы обращаются к простым коллекциям в памяти, однако базовый синтаксис при этом точно такой же, как в
запросах LINQ to Entities и LINQ to XML.
// Query #1.
List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
// Query #2.
IEnumerable<int> orderingQuery =
from num in numbers
where num < 3 || num > 7
orderby num ascending
select num;
// Query #3.
string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans", "barley" };
IEnumerable<IGrouping<char, string>> queryFoodGroups =
from item in groupingQuery
group item by item[0];
Обратите внимание, что запросы имеют тип IEnumerable<T>. Все эти запросы можно написать с помощью
var , как показано в следующем примере:
// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);
Если метод имеет параметры Action или Func, они предоставляются в виде лямбда-выражения, как показано
в следующем примере:
// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
Из числа предыдущих запросов незамедлительно выполняется только запрос 4. Это связано с тем, что
возвращается одно значение, а не универсальная коллекция IEnumerable<T>. Сам метод для вычисления
значения должен использовать foreach .
Каждый из предыдущих запросов может быть написан с использованием неявной типизации по переменной
var, как показано в следующем примере:
См. также
Пошаговое руководство: Написание запросов на C#
LINQ
предложение where
Запрос коллекции объектов
23.10.2019 • 3 minutes to read • Edit Online
В этом примере показано, как выполнить простой запрос к списку объектов Student . Каждый объект
Student содержит некоторые основные сведения об учащемся, а также список, отражающий баллы,
которые он набрал по результатам четырех экзаменов.
Это приложение служит основой для многих других примеров в этом разделе, где используется тот же
источник данных students .
Пример
Следующий запрос возвращает список учащихся, набравших 90 баллов или больше на первом экзамене.
Этот запрос намеренно сделан простым, чтобы с ним можно было экспериментировать. Например, можно
добавить в предложение where дополнительные условия или отсортировать результаты с помощью
предложения orderby .
См. также
LINQ
Интерполяция строк
Практическое руководство. Возврат запроса из
метода (руководство по программированию на C#)
25.11.2019 • 3 minutes to read • Edit Online
В этом примере показан способ возврата запроса из метода в качестве возвращаемого значения и параметра
out .
Объекты запроса являются составляемыми, что означает возможность возврата запроса из метода. Объекты,
представляющие запросы, не сохраняют результирующую коллекцию, а сохраняют действия, необходимые
для получения результатов. Преимущество возвращения объектов запроса заключается в том, что их можно
комбинировать или изменять. Поэтому любое возвращаемое значение или параметр out метода, который
возвращает запрос, должны также иметь этот тип. Если метод материализует запрос в конкретный тип
List<T> или Array, считается, что он должен возвращать результаты запроса, а не сам запрос. Переменную
запроса, возвращаемую из метода, можно формировать или изменять.
Пример
В следующем примере первый метод возвращает запрос в качестве возвращаемого значения, а второй
метод возвращает запрос в качестве параметра out . Обратите внимание, что в обоих случаях возвращается
запрос, а не его результаты.
class MQ
{
// QueryMethhod1 returns a query as its value.
IEnumerable<string> QueryMethod1(ref int[] ints)
{
var intsToStrings = from i in ints
where i > 4
select i.ToString();
return intsToStrings;
}
int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
IEnumerable<string> myQuery2;
// QueryMethod2 returns a query as the value of its out parameter.
app.QueryMethod2(ref nums, out myQuery2);
См. также
LINQ
Сохранение результатов запроса в памяти
23.10.2019 • 2 minutes to read • Edit Online
Пример
class StoreQueryResults
{
static List<int> numbers = new List<int>() { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
static void Main()
{
IEnumerable<int> queryFactorsOfFour =
from num in numbers
where num % 4 == 0
select num;
См. также
LINQ
Группировка результатов запросов
23.10.2019 • 10 minutes to read • Edit Online
Пример
Во всех примерах в этом разделе используются указанные ниже вспомогательные классы и источники
данных.
Пример
В этом примере показана группировка элементов источника с помощью отдельного свойства элемента в
качестве ключа группы. В данном случае в качестве ключа используется string , фамилия учащегося. Для
ключа можно также использовать подстроку. При операции группирования используется компаратор
проверки на равенство, используемый по умолчанию для данного типа.
Вставьте приведенный ниже метод в класс StudentClass . Измените оператор вызова в методе Main на
sc.GroupBySingleProperty() .
public void GroupBySingleProperty()
{
Console.WriteLine("Group by a single property in an object:");
Пример
В этом примере показана группировка элементов источника, когда в качестве ключа группы используется не
свойство объекта. В данном случае в качестве ключа используется первая буква фамилии учащегося.
Вставьте приведенный ниже метод в класс StudentClass . Измените оператор вызова в методе Main на
sc.GroupBySubstring() .
public void GroupBySubstring()
{
Console.WriteLine("\r\nGroup by something other than a property of the object:");
var queryFirstLetters =
from student in students
group student by student.LastName[0];
Пример
В этом примере показана группировка элементов источника путем использования числового диапазона в
качестве ключа группы. Затем запрос передает результаты в анонимный тип, содержащий только имя и
фамилию, а также диапазон процентилей, к которому принадлежит студент. Использование анонимного
типа объясняется тем, что для отображения результатов не требуется использовать полный объект Student .
GetPercentile — это вспомогательная функция, которая вычисляет процент на основе средних результатов
учащегося. Метод возвращает целое число в диапазоне от 0 до 10.
Вставьте приведенный ниже метод в класс StudentClass . Измените оператор вызова в методе Main на
sc.GroupByRange() .
public void GroupByRange()
{
Console.WriteLine("\r\nGroup by numeric range and project into a new anonymous type:");
var queryNumericRange =
from student in students
let percentile = GetPercentile(student)
group new { student.FirstName, student.LastName } by percentile into percentGroup
orderby percentGroup.Key
select percentGroup;
Пример
В этом примере показана группировка элементов источника с помощью выражения логического сравнения.
В этом случае логическое выражение проверяет, превосходит ли среднее значение экзаменационного
результата учащегося 75 баллов. Как и в предыдущих примерах, результаты передаются в анонимный тип, так
как весь элемент источника не требуется. Обратите внимание на то, что свойства в анонимном типе
становятся свойствами в члене Key , а при выполнении запроса доступ к ним можно получить по имени.
Вставьте приведенный ниже метод в класс StudentClass . Измените оператор вызова в методе Main на
sc.GroupByBoolean() .
public void GroupByBoolean()
{
Console.WriteLine("\r\nGroup by a Boolean into two groups with string keys");
Console.WriteLine("\"True\" and \"False\" and project into a new anonymous type:");
var queryGroupByAverages = from student in students
group new { student.FirstName, student.LastName }
by student.ExamScores.Average() > 75 into studentGroup
select studentGroup;
Пример
В этом примере показано, как использовать анонимный тип для инкапсуляции ключа, содержащего
несколько значений. В данном случае в качестве первого значения ключа используется первая буква
фамилии учащегося. Второе значение ключа является логическим и указывает, набрал ли учащийся более
85 баллов на первом экзамене. Группы можно сортировать по любому из свойств в ключе.
Вставьте приведенный ниже метод в класс StudentClass . Измените оператор вызова в методе Main на
sc.GroupByCompositeKey() .
public void GroupByCompositeKey()
{
var queryHighScoreGroups =
from student in students
group student by new { FirstLetter = student.LastName[0],
Score = student.ExamScores[0] > 85 } into studentGroup
orderby studentGroup.Key.FirstLetter
select studentGroup;
/* Output:
Group and order by a compound key:
Name starts with A who scored more than 85
Terry Adams
Name starts with F who scored more than 85
Fadi Fakhouri
Hanying Feng
Name starts with G who scored more than 85
Cesar Garcia
Hugo Garcia
Name starts with G who scored less than 85
Debra Garcia
Name starts with M who scored more than 85
Sven Mortensen
Name starts with O who scored less than 85
Claire O'Donnell
Name starts with O who scored more than 85
Svetlana Omelchenko
Name starts with T who scored less than 85
Lance Tucker
Name starts with T who scored more than 85
Michael Tucker
Name starts with Z who scored more than 85
Eugene Zabokritski
*/
См. также
GroupBy
IGrouping<TKey,TElement>
LINQ
предложение group
Анонимные типы
Вложенный запрос в операции группирования
Создание вложенной группы
Группировка данных
Создание вложенной группы
23.10.2019 • 2 minutes to read • Edit Online
В следующем примере демонстрируется создание вложенных групп в выражении запроса LINQ. Каждая
группа, созданная в соответствии с годом обучения или курсом учащегося, затем подразделяется на группы
по именам учащихся.
Пример
NOTE
Этот пример содержит ссылки на объекты, определенные в примере кода в разделе Запрос коллекции объектов.
public void QueryNestedGroups()
{
var queryNestedGroups =
from student in students
group student by student.Year into newGroup1
from newGroup2 in
(from student in newGroup1
group student by student.LastName)
group newGroup2 by newGroup1.Key;
Обратите внимание, что для выполнения итерации по внутренним элементам вложенной группы
необходимо три вложенных цикла foreach .
См. также
LINQ
Вложенный запрос в операции группирования
23.10.2019 • 2 minutes to read • Edit Online
В этой статье показаны два разных способа создания запроса, упорядочивающего исходные данные в
группы и затем выполняющего вложенный запрос для каждой группы по отдельности. Основное действие в
каждом примере заключается в группировании исходных элементов с помощью продолжения с именем
newGroup с последующим созданием вложенного запроса для newGroup . Этот вложенный запрос
выполняется для каждой новой группы, создаваемой внешним запросом. В этом конкретном примере
следует обратить внимание на то, что в конечном итоге получается не группа, а простая последовательность
анонимных типов.
Дополнительные сведения о способах группирования см. в разделе Предложение group.
Дополнительные сведения о продолжениях см. в разделе into. В приведенном ниже примере в качестве
источника данных используется структура данных в памяти, но те же принципы действуют для любого типа
источника данных LINQ.
Пример
NOTE
Этот пример содержит ссылки на объекты, определенные в примере кода в разделе Запрос коллекции объектов.
Запрос, который приведен в фрагменте кода выше, можно записать, используя синтаксис метода. В
следующем фрагменте кода приведен семантически эквивалентный запрос, написанный с использованием
синтаксиса метода.
public void QueryMaxUsingMethodSyntax()
{
var queryGroupMax = students
.GroupBy(student => student.Year)
.Select(studentGroup => new
{
Level = studentGroup.Key,
HighestScore = studentGroup.Select(student2 => student2.ExamScores.Average()).Max()
});
См. также
LINQ
Группирование результатов по смежным ключам
23.10.2019 • 8 minutes to read • Edit Online
К ЛЮЧ ЗНАЧЕНИЕ
А Мы
А думаем,
А что
С LINQ
В является
А действительно
С известным
С !
Пример
В приведенном ниже примере показаны метод расширения и использующий его клиентский код:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
namespace ChunkIt
{
// Static class to contain the extension methods.
public static class MyExtensions
{
public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource>
source, Func<TSource, TKey> keySelector)
{
return source.ChunkBy(keySelector, EqualityComparer<TKey>.Default);
}
// Make a new Chunk (group) object that initially has one GroupItem, which is a copy of the
current source element.
current = new Chunk<TKey, TSource>(key, enumerator, value => comparer.Equals(key,
keySelector(value)));
// Return the Chunk. A Chunk is an IGrouping<TKey,TSource>, which is the return value of the
ChunkBy method.
// At this point the Chunk only has the first element in its source sequence. The remaining
elements will be
// returned only when the client code foreach's over this chunk. See Chunk.GetEnumerator for
more info.
yield return current;
// Check to see whether (a) the chunk has made a copy of all its source elements or
// (b) the iterator has reached the end of the source sequence. If the caller uses an inner
// foreach loop to iterate the chunk items, and that loop ran to completion,
// then the Chunk.GetEnumerator method will already have made
// copies of all chunk items before we get here. If the Chunk.GetEnumerator loop did not
// enumerate all elements in the chunk, we need to do it here to avoid corrupting the iterator
// for clients that may be calling us on a separate thread.
if (current.CopyAllChunkElements() == noMoreSourceElements)
{
yield break;
}
}
}
// A Chunk is a contiguous group of one or more source elements that have the same key. A Chunk
// has a key and a list of ChunkItem objects, which are copies of the elements in the source sequence.
class Chunk<TKey, TSource> : IGrouping<TKey, TSource>
{
// INVARIANT: DoneCopyingChunk == true ||
// (predicate != null && predicate(enumerator.Current) && current.Value == enumerator.Current)
// (predicate != null && predicate(enumerator.Current) && current.Value == enumerator.Current)
// A Chunk has a linked list of ChunkItems, which represent the elements in the current chunk. Each
ChunkItem
// has a reference to the next ChunkItem in the list.
class ChunkItem
{
public ChunkItem(TSource value)
{
Value = value;
}
public readonly TSource Value;
public ChunkItem Next = null;
}
// Flag to indicate the source iterator has reached the end of the source sequence.
internal bool isLastSourceElement = false;
// The end and beginning are the same until the list contains > 1 elements.
tail = head;
// Indicates that all chunk elements have been copied to the list of ChunkItems,
// and the source enumerator is either at the end, or else on an element with a new key.
// the tail of the linked list is set to null in the CopyNextChunkElement method if the
// key of the next element does not match the current chunk's key, or there are no more elements in
the source.
private bool DoneCopyingChunk => tail == null;
// If we are (a) at the end of the source, or (b) at the end of the current chunk
// If we are (a) at the end of the source, or (b) at the end of the current chunk
// then null out the enumerator and predicate for reuse with the next chunk.
if (isLastSourceElement || !predicate(enumerator.Current))
{
enumerator = null;
predicate = null;
}
else
{
tail.Next = new ChunkItem(enumerator.Current);
}
// Called after the end of the last chunk was reached. It first checks whether
// there are more elements in the source sequence. If there are, it
// Returns true if enumerator for this chunk was exhausted.
internal bool CopyAllChunkElements()
{
while (true)
{
lock (m_Lock)
{
if (DoneCopyingChunk)
{
// If isLastSourceElement is false,
// it signals to the outer iterator
// to continue iterating.
return isLastSourceElement;
}
else
{
CopyNextChunkElement();
}
}
}
}
// Invoked by the inner foreach loop. This method stays just one step ahead
// of the client requests. It adds the next element of the chunk only after
// the clients requests the last element in the list so far.
public IEnumerator<TSource> GetEnumerator()
{
//Specify the initial element to enumerate.
ChunkItem current = head;
// A simple named type is used for easier viewing in the debugger. Anonymous types
// work just as well with the ChunkBy operator.
public class KeyValPair
{
public string Key { get; set; }
public string Value { get; set; }
}
class Program
{
// The source sequence.
public static IEnumerable<KeyValPair> list;
Чтобы использовать метод расширения в своем проекте, скопируйте статический класс MyExtensions в новый
или существующий файл исходного кода и, если необходимо, добавьте директиву using для пространства
имен, в котором он находится.
См. также
LINQ
Динамическое определение фильтров предикатов
во время выполнения
23.10.2019 • 4 minutes to read • Edit Online
5. Добавьте в метод Main в классе DynamicPredicates следующую строку под объявлением ids .
QueryById(ids);
6. Запустите проект.
7. В консольном окне отобразятся следующие выходные данные:
Гарсия: 114
О'Доннелл: 112
Омельченко: 111
8. Следующим шагом станет повторный запуск проекта, на этот раз с данными, введенными в среде
выполнения, а не с массивом ids . В методе Main измените QueryByID(ids) на QueryByID(args) .
9. Запустите проект с аргументами командной строки 122 117 120 115 . Когда проект будет запущен, эти
значения станут элементами args — параметра метода Main .
10. В консольном окне отобразятся следующие выходные данные:
Адамс: 120
Фенг: 117
Гарсия: 115
Такер: 122
default:
break;
}
Console.WriteLine($"The following students are at level {year}");
foreach (Student name in studentQuery)
{
Console.WriteLine($"{name.LastName}: {name.ID}");
}
}
3. В методе Main замените вызов QueryByID на следующий вызов, который отправляет первый элемент
из массива args как аргумент: QueryByYear(args[0]) .
4. Запустите проект с помощью аргумента командной строки, выраженного целочисленным значением
от 1 до 4.
См. также
LINQ
предложение where
Выполнение внутренних соединений
23.10.2019 • 13 minutes to read • Edit Online
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
/// <summary>
/// Simple inner join.
/// </summary>
public static void InnerJoinExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
Person rui = new Person { FirstName = "Rui", LastName = "Raposo" };
Обратите внимание на то, что объект Person , параметр LastName которого имеет значение "Huff", не
отображается в результирующем наборе, поскольку нет объекта Pet , параметр Pet.Owner которого
совпадает с этим объектом Person .
class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int EmployeeID { get; set; }
}
class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int StudentID { get; set; }
}
/// <summary>
/// Performs a join operation using a composite key.
/// </summary>
public static void CompositeKeyJoinExample()
{
// Create a list of employees.
List<Employee> employees = new List<Employee> {
new Employee { FirstName = "Terry", LastName = "Adams", EmployeeID = 522459 },
new Employee { FirstName = "Charlotte", LastName = "Weiss", EmployeeID = 204467 },
new Employee { FirstName = "Magnus", LastName = "Hedland", EmployeeID = 866200 },
new Employee { FirstName = "Vernette", LastName = "Price", EmployeeID = 437139 } };
// Join the two data sources based on a composite key consisting of first and last name,
// to determine which employees are also students.
IEnumerable<string> query = from employee in employees
join student in students
on new { employee.FirstName, employee.LastName }
equals new { student.FirstName, student.LastName }
select employee.FirstName + " " + employee.LastName;
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
Dog fourwheeldrive = new Dog { Name = "Four Wheel Drive", Owner = phyllis };
Dog duke = new Dog { Name = "Duke", Owner = magnus };
Dog denim = new Dog { Name = "Denim", Owner = terry };
Dog wiley = new Dog { Name = "Wiley", Owner = charlotte };
Dog snoopy = new Dog { Name = "Snoopy", Owner = rui };
Dog snickers = new Dog { Name = "Snickers", Owner = arlene };
// The first join matches Person and Cat.Owner from the list of people and
// The first join matches Person and Cat.Owner from the list of people and
// cats, based on a common Person. The second join matches dogs whose names start
// with the same letter as the cats that have the same owner.
var query = from person in people
join cat in cats on person equals cat.Owner
join dog in dogs on
new { Owner = person, Letter = cat.Name.Substring(0, 1) }
equals new { dog.Owner, Letter = dog.Name.Substring(0, 1) }
select new { CatName = cat.Name, DogName = dog.Name };
Результат query1 эквивалентен результирующему набору, который можно было бы получить, выполнив
внутреннее соединение с помощью предложения join без предложения into . Переменная query2
демонстрирует этот эквивалентный запрос.
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
/// <summary>
/// Performs an inner join by using GroupJoin().
/// </summary>
public static void InnerGroupJoinExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
См. также
Join
GroupJoin
Выполнение групповых соединений
Выполнение левых внешних соединений
Анонимные типы
Выполнение групповых соединений
23.10.2019 • 6 minutes to read • Edit Online
Групповое соединение можно использовать для создания иерархических структур данных. Каждый элемент
из первой коллекции группируется с набором соответствующих элементов из второй коллекции.
Например, класс или таблица реляционной базы данных под названием Student может содержать два поля:
Id и Name . Второй класс или таблица реляционной базы данных под названием Course может содержать
два поля: StudentId и CourseTitle . Групповое соединение этих двух источников данных, основанное на
сравнении Student.Id и Course.StudentId , приведет к группировке каждого объекта Student с коллекцией
объектов Course (которая может быть пустой).
NOTE
Каждый элемент первой коллекции отображается в результирующем наборе группового соединения вне зависимости
от того, найдены ли соответствующие ему элементы во второй коллекции. В случае, если такие элементы не найдены,
последовательность соответствующих элементов будет пуста. Селектор результата имеет доступ к каждому элементу
первой коллекции. При этом следует учесть отличие от селектора результата в негрупповом соединении: в этом
случае селектор не имеет доступа к элементам из первой коллекции, для которых нет совпадений во второй
коллекции.
В первом примере этой статьи показано, как выполнить групповое соединение. Во втором примере — как
использовать групповое соединение для создания XML -элементов.
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
/// <summary>
/// This example performs a grouped join.
/// </summary>
public static void GroupJoinExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
/// <summary>
/// This example creates XML output from a grouped join.
/// </summary>
public static void GroupJoinXMLExample()
{
Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" };
Person terry = new Person { FirstName = "Terry", LastName = "Adams" };
Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" };
Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" };
// Create XML to display the hierarchical organization of people and their pets.
XElement ownersAndPets = new XElement("PetOwners",
from person in people
join pet in pets on person equals pet.Owner into gj
select new XElement("Person",
new XAttribute("FirstName", person.FirstName),
new XAttribute("LastName", person.LastName),
from subpet in gj
select new XElement("Pet", subpet.Name)));
Console.WriteLine(ownersAndPets);
}
Левое внешнее соединение — это соединение, при котором каждый элемент первой коллекции
возвращается независимо от наличия взаимосвязанных элементов во второй коллекции. LINQ можно
использовать для левого внешнего соединения, вызвав метод DefaultIfEmpty на основании результатов
группового соединения.
Пример
В приведенном ниже примере показано, как использовать метод DefaultIfEmpty применительно к
результатам группового соединения для выполнения левого внешнего соединения.
Первым шагом выполнения левого внешнего соединения двух коллекций является выполнение внутреннего
соединения с помощью группового соединения. (Описание этой процедуры см. в разделе Выполнение
внутренних соединений.) В этом примере список объектов Person соединен посредством внутреннего
соединения со списком объектов Pet на основе объекта Person , соответствующего Pet.Owner .
Вторым шагом является включение каждого элемента первой (левой) коллекции в набор результатов, даже
если элемент не имеет совпадений в правой коллекции. Эта процедура выполняется путем вызова метода
DefaultIfEmpty для каждой последовательности совпадающих элементов из группового соединения. В этом
примере DefaultIfEmpty вызывается для каждой последовательности совпадающих объектов Pet . Метод
возвращает коллекцию, содержащую одно значение по умолчанию, если последовательность
соответствующих объектов Pet пуста для любого объекта Person , обеспечивая представление каждого
объекта Person в коллекции результатов.
NOTE
Значение по умолчанию для ссылочного типа — null , поэтому в примере проверяется пустая ссылка перед
доступом к каждому элементу в каждой коллекции Pet .
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
}
class Pet
{
public string Name { get; set; }
public Person Owner { get; set; }
}
См. также
Join
GroupJoin
Выполнение внутренних соединений
Выполнение групповых соединений
Анонимные типы
Упорядочение результатов предложения
соединения
23.10.2019 • 2 minutes to read • Edit Online
В этом примере показано, как упорядочить результаты операции соединения. Обратите внимание на то, что
упорядочение выполняется после соединения. Несмотря на то, что предложение orderby с одним или
несколькими исходными последовательностями использовать до соединения можно, обычно это не
рекомендуется. Некоторые поставщики LINQ могут не сохранять этот порядок после соединения.
Пример
Этот запрос создает группу соединения, а затем сортирует группы по элементу категории, который остается
в области действия. В инициализаторе анонимного типа подзапрос упорядочивает все соответствующие
элементы из последовательности продуктов.
class HowToOrderJoins
{
#region Data
class Product
{
public string Name { get; set; }
public int CategoryID { get; set; }
}
class Category
{
public string Name { get; set; }
public int ID { get; set; }
}
void OrderJoin1()
{
var groupJoinQuery2 =
from category in categories
join prod in products on category.ID equals prod.CategoryID into prodGroup
orderby category.Name
select new
{
Category = category.Name,
Products = from prod2 in prodGroup
orderby prod2.Name
select prod2
};
См. также
LINQ
предложение orderby
предложение join
Соединение с помощью составных ключей
23.10.2019 • 2 minutes to read • Edit Online
В этом примере показано, как выполнить операции соединения, в которых требуется использовать более
одного ключа для определения соответствия. Для этих целей используется составной ключ. Составной ключ
создается как анонимный тип или именованный тип со значениями, которые нужно сравнить. Если
переменная запроса будет передаваться за пределы метода, необходимо использовать именованный тип,
который переопределяет Equals и GetHashCode для ключа. Имена свойств и порядок, в котором они
возникают, должны совпадать в каждом ключе.
Пример
В следующем примере демонстрируется использование составного ключа для объединения данных из трех
таблиц:
Определение типа в составных ключах зависит от имен свойств в ключах и порядка, в котором они
возникают. Если свойства в исходных последовательностях имеют другие имена, в ключах им необходимо
присвоить новые имена. Например, если в таблицах Orders и OrderDetails используются разные имена
столбцов, можно создать составные ключи, назначив одинаковые имена в анонимных типах:
См. также
LINQ
предложение join
предложение group
Выполнение пользовательских операций
соединения
23.10.2019 • 6 minutes to read • Edit Online
В этом примере показано, как выполнить операции соединения, которые нельзя осуществить с помощью
предложения join . В выражении запроса предложение join ограничено уравнивающими соединениями и
оптимизировано для них; они являются самым общим типом операций соединения. При выполнении
уравнивающего соединения использование предложения join почти всегда обеспечивает наилучшую
производительность.
Однако предложение join нельзя использовать в указанных ниже случаях.
Соединение объявлено в выражении неравенства (не уравнивающее соединение).
Соединение объявлено в нескольких выражениях равенства или неравенства.
Необходимость создания временной переменной диапазона для правосторонней (внутренней)
последовательности перед операцией соединения.
Чтобы независимо представить каждый источник данных при выполнении соединений, не являющихся
уравнивающими, можно использовать несколько предложений from . Затем к переменной диапазона для
каждого источника можно применить выражение предиката в предложении where . Выражение также
может принимать форму вызова метода.
NOTE
Не путайте этот вид пользовательской операции соединения с использованием нескольких предложений from для
доступа к внутренним коллекциям. Дополнительные сведения см. в разделе Предложение join.
Пример
Первый метод в приведенном ниже примере показывает простое перекрестное соединение. Перекрестные
соединения следует использовать с осторожностью, так как они могут возвращать очень большие наборы
результатов. Однако такие соединения могут быть полезны при создании исходных последовательностей,
относительно которых выполняются дополнительные запросы.
Результатом второго метода является последовательность всех продуктов, идентификатор категории
которых находится в списке категорий с правой стороны. Учтите необходимость использования
предложения let и метода Contains для создания временного массива. Также можно создать массив перед
запросом и удалить первое предложение from .
class CustomJoins
{
#region Data
class Product
{
public string Name { get; set; }
public int CategoryID { get; set; }
}
class Category
class Category
{
public string Name { get; set; }
public int ID { get; set; }
}
void CrossJoin()
{
var crossJoinQuery =
from c in categories
from p in products
select new { c.ID, p.Name };
void NonEquijoin()
{
var nonEquijoinQuery =
from p in products
let catIds = from c in categories
select c.ID
where catIds.Contains(p.CategoryID) == true
select new { Product = p.Name, CategoryID = p.CategoryID };
Console.WriteLine("Non-equijoin query:");
foreach (var v in nonEquijoinQuery)
{
Console.WriteLine($"{v.CategoryID,-5}{v.Product}");
}
}
}
/* Output:
/* Output:
Cross Join Query:
1 Tea
1 Mustard
1 Pickles
1 Carrots
1 Bok Choy
1 Peaches
1 Melons
1 Ice Cream
1 Mackerel
2 Tea
2 Mustard
2 Pickles
2 Carrots
2 Bok Choy
2 Peaches
2 Melons
2 Ice Cream
2 Mackerel
3 Tea
3 Mustard
3 Pickles
3 Carrots
3 Bok Choy
3 Peaches
3 Melons
3 Ice Cream
3 Mackerel
Non-equijoin query:
1 Tea
2 Mustard
2 Pickles
3 Carrots
3 Bok Choy
Press any key to exit.
*/
Пример
В приведенном ниже примере запрос должен соединять две последовательности на основе сопоставления
ключей, которые в случае с внутренней (правосторонней) последовательностью не могут быть получены до
предложения соединения. Если соединение было выполнено с предложением join , для каждого элемента
требуется вызвать метод Split . Использование нескольких предложений from позволяет запросу избежать
издержек, связанных с повторным вызовом метода. Однако поскольку предложение join оптимизировано,
в этом случае его использование может быть эффективнее нескольких предложений from . Результаты будут
зависеть в основном от затрат на вызов метода.
class MergeTwoCSVFiles
{
static void Main()
{
// See section Compiling the Code for information about the data files.
string[] names = System.IO.File.ReadAllLines(@"../../../names.csv");
string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");
class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public List<int> ExamScores { get; set; }
}
/* Output:
The average score of Omelchenko Svetlana is 82.5.
The average score of O'Donnell Claire is 72.25.
The average score of Mortensen Sven is 84.5.
The average score of Garcia Cesar is 88.25.
The average score of Garcia Debra is 67.
The average score of Fakhouri Fadi is 92.25.
The average score of Feng Hanying is 88.
The average score of Garcia Hugo is 85.75.
The average score of Tucker Lance is 81.75.
The average score of Adams Terry is 85.25.
The average score of Zabokritski Eugene is 83.
The average score of Tucker Michael is 92.
*/
См. также
LINQ
предложение join
Упорядочение результатов предложения соединения
Обработка значений NULL в выражениях запросов
11.11.2019 • 2 minutes to read • Edit Online
В этом примере показано, как обрабатывать возможные нулевые значения в исходных коллекциях.
Коллекция объектов, например IEnumerable<T>, может содержать элементы со значением NULL. Если
исходная коллекция имеет значение NULL или содержит элемент со значением NULL, а запрос не
обрабатывает такие значения, при выполнении этого запроса возникает исключение NullReferenceException.
Пример
Чтобы избежать исключения в связи с пустой ссылкой, можно составить защитный код, как показано в
следующем примере:
var query1 =
from c in categories
where c != null
join p in products on c.ID equals
p?.CategoryID
select new { Category = c.Name, Name = p.Name };
Пример
Если в предложении join тип, допускающий значение NULL, имеет только один из ключей сравнения,
остальные типы в выражении запроса можно привести к типу, допускающему значение NULL. В следующем
примере предполагается, что EmployeeID — это столбец, содержащий значения типа int? :
См. также
Nullable<T>
LINQ
Типы значений, допускающие значение NULL
Обработка исключений в выражениях запросов
23.10.2019 • 3 minutes to read • Edit Online
В контексте выражения запроса можно вызвать любой метод. Тем не менее рекомендуется избегать вызова
в выражении запроса любого метода, который может создавать побочные эффекты, например изменение
содержимого источника данных или создание исключения. В этом примере показано, как избежать создания
исключений при вызове методов в выражениях запросов без нарушения общих рекомендаций .NET
относительно обработки исключений. Эти рекомендации гласят, что можно перехватывать определенные
исключения, если вы понимаете, почему они создаются в данном контексте. Дополнительные сведения см. в
разделе Лучшие методики обработки исключений.
В последнем примере показано, как обрабатывать случаи, когда необходимо создать исключение во время
выполнения запроса.
Пример
В следующем примере показано, как переместить код обработки исключений за пределы выражения
запроса. Это возможно, только если метод не зависит от переменных, являющихся локальными для запроса.
class ExceptionsOutsideQuery
{
static void Main()
{
// DO THIS with a datasource that might
// throw an exception. It is easier to deal with
// outside of the query expression.
IEnumerable<int> dataSource;
try
{
dataSource = GetData();
}
catch (InvalidOperationException)
{
// Handle (or don't handle) the exception
// in the way that is appropriate for your application.
Console.WriteLine("Invalid operation");
goto Exit;
}
Пример
В некоторых случаях лучшей реакцией на исключение, которое вызывается в запросе, будет немедленное
прекращение выполнения запроса. В следующем примере демонстрируется обработка исключений, которые
могут быть созданы из тела запроса. Предположим, что SomeMethodThatMightThrow потенциально может
вызвать исключение, которое требует остановки выполнения запроса.
Обратите внимание, что блок try включает цикл foreach , а не сам запрос. Это вызвано тем, что цикл
foreach является точкой, в которой запрос фактически выполняется. Дополнительные сведения см. в
разделе Введение в запросы LINQ.
class QueryThatThrows
{
static void Main()
{
// Data source.
string[] files = { "fileA.txt", "fileB.txt", "fileC.txt" };
См. также
LINQ
Асинхронное программирование
04.11.2019 • 17 minutes to read • Edit Online
Для решения задач, связанных с вводом-выводом (например, для запроса данных из сети или доступа к
базе данных), желательно использовать асинхронное программирование. Если у вас есть код, ограниченный
ресурсами процессора, например выполняющий сложные вычисления, то это также подходящий сценарий
для асинхронного программирования.
В C# имеется модель асинхронного программирования, реализованная на уровне языка, которая позволяет
легко писать асинхронный код, не прибегая к обратным вызовам или библиотекам, которые поддерживают
асинхронность. Она строится на принципах асинхронной модели на основе задач (TAP ).
Вот и все! В коде выражается намерение (скачивание некоторых данных в асинхронном режиме), и при
этом не приходится выполнять запутанные операции с объектами Task.
Пример с ограниченными ресурсами процессора: выполнение вычислений для игры
Предположим, вы разрабатываете игру для мобильных устройств, в которой при нажатии кнопки может
наноситься урон множеству противников на экране. Расчет урона может потреблять много ресурсов. Если
производить его в потоке пользовательского интерфейса, то на это время игра может приостанавливаться!
Оптимальный способ — запустить фоновый поток, который выполняет задачу с помощью Task.Run , а
затем ожидает результат с помощью await . Таким образом обеспечится плавная работа пользовательского
интерфейса в процессе вычисления.
Вот и все! В этом коде четко выражается назначение события нажатия кнопки, фоновым потоком не
требуется управлять вручную, и он выполняется без блокировки.
Что происходит на внутреннем уровне
Выполнение асинхронных операций связано со множеством элементов. Если вы хотите знать внутренние
принципы работы объектов Task и Task<T> , обратитесь за дополнительными сведениями к статье
Подробный обзор асинхронного программирования.
С точки зрения C#, компилятор преобразовывает код в конечный автомат, который контролирует такие
моменты, как передача выполнения при достижении await и возобновление выполнения после
завершения фонового задания.
Если вас интересует теория, это реализация модели асинхронности на основе обещаний.
Дополнительные примеры
В приведенных ниже примерах демонстрируются различные способы написания асинхронного кода на C#.
Они охватывают несколько сценариев, с которыми вы можете столкнуться.
Извлечение данных из сети
Этот фрагмент кода скачивает код HTML с главной страницы сайта www.dotnetfoundation.org и
подсчитывает число вхождений в него строки ".NET". С помощью ASP.NET MVC определяется метод веб-
контроллера, который выполняет эту задачу, и возвращается число.
NOTE
Если вы планируете проанализировать HTML в рабочем коде, не используйте регулярные выражения. Используйте
библиотеку анализа.
[HttpGet]
[Route("DotNetCount")]
public async Task<int> GetDotNetCountAsync()
{
// Suspends GetDotNetCountAsync() to allow the caller (the web server)
// to accept another request, rather than blocking on this one.
var html = await _httpClient.GetStringAsync("https://dotnetfoundation.org");
Вот аналогичный код для универсального приложения для Windows, который выполняет ту же задачу при
нажатии кнопки:
private readonly HttpClient _httpClient = new HttpClient();
// Any other work on the UI thread can be done here, such as enabling a Progress Bar.
// This is important to do here, before the "await" call, so that the user
// sees the progress bar before execution of this method is yielded.
NetworkProgressBar.IsEnabled = true;
NetworkProgressBar.Visibility = Visibility.Visible;
NetworkProgressBar.IsEnabled = false;
NetworkProgressBar.Visibility = Visibility.Collapsed;
}
Хотя размер кода меньше, будьте осторожны при использовании LINQ в сочетании с асинхронным кодом.
Так как в LINQ используется отложенное выполнение, асинхронные вызовы будут выполняться не
немедленно, как в цикле foreach() , если только вы не производите принудительную итерацию созданной
последовательности с помощью вызова .ToList() или .ToArray() .
Другие ресурсы
В статье Подробный обзор асинхронного программирования приводятся дополнительные сведения о
принципах работы задач.
Асинхронное программирование с использованием ключевых слов Async и Await (C#)
Эпизод Six Essential Tips for Async (Шесть важных советов по асинхронному программированию) с
Лусианом Вышиком (Lucian Wischik) — это отличный ресурс по асинхронному программированию.
Сопоставление шаблонов
04.11.2019 • 22 minutes to read • Edit Online
Шаблоны проверяют, содержит ли значение определенную фигуру и может ли оно извлекать сведения из
значения с соответствующей фигурой. Сопоставление шаблонов позволяет использовать сокращенный
синтаксис для тех алгоритмов, которые вы уже применяете. Вы уже создали алгоритмы сопоставления
шаблонов с помощью существующего синтаксиса. Для проверки значений можно написать операторы if
и switch . Если операторы совпадают, данные из этого значения извлекаются и используются. Новые
элементы синтаксиса — это расширения для операторов is и switch , с которыми вы уже знакомы. В этих
расширениях объединяются проверка значения и извлечение данных.
В этой статье мы рассмотрим новый синтаксис и покажем, как с его помощью можно создавать
удобочитаемый краткий код. Сопоставление шаблонов включает идиомы, в которых данные и код
разделены, в отличие от объектно-ориентированных структур, в которых данные и методы манипуляции с
данными тесно связаны.
Чтобы проиллюстрировать новые идиомы, поработаем со структурами, представляющими геометрические
фигуры, применив операторы сопоставления шаблонов. Возможно, вы уже знаете, как строить иерархии
классов и создавать виртуальные и переопределенные методы, а также настраивать поведение объектов в
зависимости от типа их среды выполнения.
Эти методы недоступны для данных, не структурированных в иерархии классов. Если данные отделены от
методов, вам потребуются другие инструменты. Новые конструкции сопоставления шаблонов включают
более четкий синтаксис для проверки данных и манипуляций с потоком управления с учетом условий этих
данных. Вы уже писали операторы if и switch , тестирующие значение переменной, и операторы is ,
проверяющие тип переменной. Сопоставление шаблонов расширяет возможности этих операторов.
В этой статье мы создадим метод, который позволяет вычислять площадь различных геометрических фигур.
Но при этом мы не будем применять повторную сортировку в объектно-ориентированные методы и
собирать иерархию классов для разных форм. Вместо этого прибегнем к сопоставлению шаблонов.
Изучите этот пример и сравните код с его возможной структурой в виде иерархии объектов. Данные,
которые вам необходимо запрашивать и использовать при выполнении операций, не являются иерархией
классов. Сопоставление шаблонов позволяет создавать удобные структуры.
Вместо того, чтобы начать с абстрактного определения фигуры и добавления различных конкретных
классов фигур, составим для каждой геометрической фигуры определения, содержащие только простые
данные:
public class Square
{
public double Side { get; }
Приведенный выше код представляет собой классическое выражение шаблона типа: он тестирует
переменную, чтобы определить ее тип, и выполняет различные действия с учетом этого типа.
Код упрощается за счет расширений, которые добавляются в выражение is для назначения переменной в
случае успешного завершения теста:
Эти правила защищают от случайного доступа к результату выражения сопоставления шаблонов при
отсутствии соответствия.
Единственным шаблоном, который поддерживался оператором switch , был шаблон константы. Кроме
того, он ограничивался числовыми типами и типом string . Эти ограничения были устранены, и теперь
оператор switch можно записывать, используя шаблон типа:
NOTE
Операторы goto для перехода к другой метке доступны только для шаблона с константами (классический оператор
switch).
Теперь оператор switch регулируют два новых правила. Ограничения на тип переменной в выражении
switch удалены. Можно использовать любой тип, как, например, object в этом примере. Выражения case
больше не ограничиваются постоянными значениями. Устранение этого ограничения означает, что
изменение в порядке следования разделов switch может изменить поведение программы.
При ограничении постоянными значениями значению выражения switch может соответствовать только
одна метка case . В сочетании с правилом, запрещающим передавать все разделы switch в следующий
раздел, это означало, что разделы switch можно было располагать в любом порядке, не влияя на
поведение программы. Теперь, когда выражения switch стали более обобщенными, порядок разделов
имеет значение. Выражения switch вычисляются в алфавитном порядке. Выполнение переходит к первой
метке switch , соответствующей выражению switch .
Вариант default будет выполнен только при отсутствии совпадений с метками других вариантов. Вариант
default вычисляется последним, независимо от его текстуального порядка. Если варианта default нет и ни
с одним другим оператором case совпадение не обнаружено, выполнение продолжается с оператора,
следующего за оператором switch . Ни один код с меткой case не выполняется.
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}
Это изменение демонстрирует несколько важных моментов, связанных с новым синтаксисом. Во-первых, к
одному разделу switch можно применить сразу несколько меток case . Этот блок оператора выполняется,
если одна из этих меток — true . В данном случае если выражение switch представляет круг или квадрат с
нулевой площадью, метод возвращает константу 0.
В этом примере представлены две различные переменные в двух метках case для первого блока switch .
Обратите внимание на то, что операторы в этом блоке switch не включают переменные c (для круга) или
s (для квадрата). Ни одна из этих переменных не назначается в этом блоке switch явно. Разумеется, в
случае совпадения с одним из вариантов одна из переменных назначается. При этом определить, какая из
них была назначена во время компиляции, нельзя, так как при выполнении возможно совпадение с любым
из этих вариантов. В связи с этим в большинстве случаев, когда для одного и того же блока задается сразу
несколько меток case , новую переменную вводить в оператор case не следует. Иначе будет
использоваться только переменная в предложении when .
Теперь, когда мы добавили формы с нулевой площадью, добавим еще пару типов фигур — прямоугольник и
треугольник:
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Triangle t:
return t.Base * t.Height / 2;
case Rectangle r:
return r.Length * r.Height;
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}
Этот набор изменений добавляет метки case для вырожденного варианта, а также метки и блоки для
каждой из новых фигур.
Наконец, можно добавить вариант null , чтобы в качестве аргумента не использовался null :
public static double ComputeArea_Version5(object shape)
{
switch (shape)
{
case Square s when s.Side == 0:
case Circle c when c.Radius == 0:
case Triangle t when t.Base == 0 || t.Height == 0:
case Rectangle r when r.Length == 0 || r.Height == 0:
return 0;
case Square s:
return s.Side * s.Side;
case Circle c:
return c.Radius * c.Radius * Math.PI;
case Triangle t:
return t.Base * t.Height / 2;
case Rectangle r:
return r.Length * r.Height;
case null:
throw new ArgumentNullException(paramName: nameof(shape), message: "Shape must not be null");
default:
throw new ArgumentException(
message: "shape is not a recognized shape",
paramName: nameof(shape));
}
}
Особый случай с шаблоном null интересен тем, что константа null в этом шаблоне не имеет типа. Она
может быть преобразована в любой ссылочный тип или тип, допускающий значение NULL. Вместо того,
чтобы преобразовывать значение null в любой тип, в языке определяется, что значение null не будет
соответствовать какому-либо шаблону типа, независимо от типа переменной во время компиляции. Такой
подход позволяет обеспечить согласованность нового шаблона типа на основе switch с оператором is :
операторы is всегда возвращают значение false , если проверяемое значение равно null . Более того,
это проще, так как после проверки типа не требуется дополнительно выполнять проверку на значения
NULL. Это видно из того факта, что в приведенном выше примере ни в одном блоке case не выполняется
проверка на значения NULL. Это излишне, так как сопоставление с шаблоном типа уже гарантирует
отсутствие значения NULL.
Третье правило относится к случаям, когда может быть полезен вариант var . Предположим, производится
сопоставление шаблона, причем входные данные представляют собой строку и нужно найти известные
значения команд. Код может выглядеть так:
case "square":
return new Square(4);
case "large-circle":
return new Circle(12);
Вариант var соответствует null , пустой строке или любой строке, которая содержит только пробелы.
Обратите внимание на то, что в приведенном выше коде используется оператор ?. , чтобы случайно не
произошло исключение NullReferenceException. Вариант default обрабатывает все остальные строковые
значения, которые не распознает этот анализатор команд.
Это один из примеров ситуации, в которой может требоваться выражение для варианта выбора var ,
отличное от выражения default .
Выводы
Конструкции сопоставления шаблонов позволяют легко управлять потоком управления между
различными переменными и типами, не связанными иерархией наследования. Логику управления можно
настроить на использование любого условия, которое вы тестируете в переменной. Она включает шаблоны
и идиомы, которые вам потребуются при создании более распределенных приложений, в которых данные и
методы манипуляции этими данными разделены. Как видите, структуры фигур в этом примере не включают
никакие методы. Используются только свойства, доступные исключительно для чтения. Сопоставление
шаблонов работает с любым типом данных. Выражения записываются для изучения объекта и управления
потоком управления решениями, которые принимаются на основе этих условий.
Сравните код из этого примера со структурой, которая начинается с создания иерархии классов для
абстрактной фигуры Shape и ряда производных фигур, каждая из которых включает собственную
реализацию виртуального метода для вычисления площади. Выражения сопоставления шаблонов часто
полезны при работе с данными, если аспекты хранилища данных необходимо отделить от аспектов
поведения.
Написание безопасного и эффективного кода C#
25.11.2019 • 28 minutes to read • Edit Online
Следуйте этим рекомендациям, когда планируете создать неизменяемый тип значения. Улучшения
производительности являются дополнительным преимуществом. readonly struct четко выражает ваше
намерение.
Вы не хотите, чтобы вызывающие объекты изменяли источник, поэтому следует возвращать значение через
readonly ref :
Возвращение ref readonly позволяет сохранить копирование больших структур и неизменность внутренних
элементов данных.
Во время вызова вызывающие объекты выбирают использовать свойство Origin как readonly ref или как
значение:
При первом назначении в предыдущем примере кода создается и назначается копия константы Origin .
При втором назначается ссылка. Обратите внимание, что модификатор readonly должен быть частью
объявления переменной. Ссылку, на которую он ссылается, изменить невозможно. В противном случае
возникнет ошибка времени компиляции.
В объявлении originReference требуется модификатор readonly .
Компилятор применяет правило, не позволяющее вызывающему объекту изменять ссылку. Попытки
назначить значение напрямую вызывают ошибку времени компиляции. Однако компилятору не может
быть известно, изменяет ли какой-либо метод члена состояние структуры. Чтобы защитить объект от
изменений, компилятор создает копию и с ее помощью вызывает ссылки на члены. Все изменения будут
вноситься в эту защитную копию.
При добавлении модификатора in для передачи аргумента по ссылке вы объявляете о своем намерении
передавать аргументы по ссылке, чтобы избежать ненужных операций копирования. Вы не собираетесь
изменять объект, используемый в качестве этого аргумента.
Такой подход часто повышает производительность для типов значений только для чтения, размер которых
превышает IntPtr.Size. Для простых типов ( sbyte , byte , short , ushort , int , uint , long , ulong , char ,
float , double , decimal и bool и enum ) возможное повышение производительности минимально. На
самом деле, производительность может снизиться при использовании передачи по ссылке для типов,
меньше чем IntPtr.Size.
Ниже приведен пример метода, который вычисляет расстояние между двумя точками в трехмерном
пространстве.
Аргументами являются две структуры, каждая из которых содержит три типа double. double имеет размер 8
байт, поэтому каждый аргумент равен 24 байтам. Указывая модификатор in , вы, в зависимости от
архитектуры компьютера, передаете этим аргументам 4- или 8-байтовую ссылку. Разница в размере
невелика, но она может вырасти, когда приложение вызывает этот метод в непрерывном цикле с помощью
множества различных значений.
Существуют и другие способы, которыми модификатор in дополняет out и ref . Невозможно создать
перегрузки метода, которые отличаются только наличием in , out или ref . Эти новые правила
расширяют то же поведение, которое всегда действовало для параметров out и ref . Как и модификаторы
out и ref , типы значений не упаковываются, так как применяется модификатор in .
Существует несколько способов, когда компилятор гарантирует, что аргумент in доступен только для
чтения. Во-первых, вызванный метод не может быть напрямую назначен параметру in . Его невозможно
напрямую назначить полю параметра in , когда это значение имеет тип struct . Кроме того, параметр in
невозможно передать какому-либо методу, использующему модификатор ref или out . Эти правила
применяются к любому полю параметра in при условии, что данное поле имеет тип struct и параметр
имеет тип struct . На самом деле эти правила применяются к нескольким уровням доступа к членам при
условии, что все уровни доступа к членам являются structs . Компилятор принудительно указывает, что
типы struct , передаваемые в качестве аргументов in , и их члены struct являются переменными,
доступными только для чтения, когда используются в качестве аргументов для других методов.
Использование параметров in позволяет избежать потенциальных затрат на создание копий. Это не
меняет семантику ни одного вызова метода. Таким образом, указывать модификатор in в месте вызова не
нужно. Пропуск модификатора in в месте вызова сообщает компилятору, что он может сделать копию
аргумента по следующим причинам:
Выполняется неявное преобразование, но не преобразование удостоверения из типа аргумента в тип
параметра.
Аргумент является выражением, но не имеет известную переменную хранения.
Существует перегрузка, которая отличается наличием или отсутствием in . В этом случае перегрузка по
значению подходит лучше.
Эти правила полезны и при обновлении существующего кода для использования аргументов со ссылками,
доступными только для чтения. Внутри вызываемого метода можно вызвать любой метод экземпляра,
который использует параметры передачи по значению. В этих экземплярах создается копия параметра in .
Поскольку компилятор может создавать временную переменную для любого параметра in , вы можете
также указать значения по умолчанию для любого параметра in . Следующий код указывает начало
координат (точку 0,0) в качестве значения по умолчанию для второй точки:
Чтобы велеть компилятору передавать аргументы, доступные только для чтения, по ссылке, укажите
модификатор in для аргументов в месте вызова, как показано в следующем коде:
Это упрощает постепенное внедрение параметров in в больших базах кода, где возможен выигрыш по
производительности. Сначала нужно добавить модификатор in в сигнатуры методов. Затем можно
добавить модификатор in в местах вызовов и создать типы readonly struct , чтобы разрешить
компилятору не создавать защитные копии параметров in в дополнительных расположениях.
Обозначение параметра in также можно использовать со ссылочными типами или числовыми
значениями. Однако преимущества в обоих случаях минимальны (если они вообще есть).
Структура Point3D не предоставляется только для чтения. В тексте этого метода есть шесть разных вызовов
доступа к свойству. На первый взгляд может показаться, что эти доступы безопасны. В конце концов, метод
доступа get не должен изменять состояние объекта. Но нет правила языка, которое это обеспечивает. Это
просто общее соглашение. Любой тип может реализовывать метод доступа get , который изменил
внутреннее состояние. Без языковой гарантии компилятору необходимо создавать временную копию
аргумента перед вызовом любого члена. Временное хранилище создается на стеке, значения аргумента
копируются во временном хранилище, и значение копируется в стек для каждого доступа к членам как
аргумент this . Во многих случаях эти копии достаточно снижают производительность, так что передача по
значению работает быстрее, чем передача по ссылке только для чтения, если тип аргумента не
readonly struct .
Компилятор создает более эффективный код, когда вызываются члены readonly struct : Ссылка this , а не
копия приемника, всегда является параметром in , переданным по ссылке методу члена. Эта оптимизация
позволяет избежать копирования при использовании readonly struct в качестве аргумента in .
Не следует передавать тип значения, допускающий значения NULL, в качестве аргумента in . Тип
Nullable<T> не объявлен как структура только для чтения. Это означает, что компилятор должен создавать
защитные копии для любого аргумента типа, допускающего значение NULL и передаваемого в метод с
помощью модификатора in в объявлении параметра.
Вы видите пример программы, который демонстрирует разницу в производительности с помощью
BenchmarkDotNet в наших репозиториях примеров на сайте GitHub. Он сравнивает передачу изменяемых
структур по значению и по ссылке с передачей неизменяемых структур по значению и по ссылке. Быстрее
всего использовать неизменяемую структуру и передачу по ссылке.
Выводы
Использование типов значений сводит к минимуму число операций распределения:
Хранилище для типов значений выделяется в стеке для локальных переменных и аргументов метода.
Хранилище для типов значений, которые являются членами других объектов, выделяется как часть этого
объекта, а не отдельное распределение.
Хранилища возвращаемых значений типов значений выделяется в стеке.
Сравните это со ссылочными типами в таких же ситуациях:
Хранилище для ссылочных типов выделяется в куче для локальных переменных и аргументов метода.
Ссылка хранится в стеке.
Хранилище для ссылочных типов, которые являются членами других объектов, выделяется в куче
отдельно. Объект хранит ссылку.
Хранилище возвращаемых значений ссылочного типа выделяется в куче. Ссылка на это хранилище
хранится в стеке.
Чтобы свести распределения к минимуму, придется пойти на компромисс. Вы копируете больше памяти,
если размер struct больше, чем размер ссылки. Ссылка обычно является 64- или 32-разрядной и зависит от
ЦП целевого компьютера.
Эти компромиссы обычно имеют минимальное влияние на производительность. Однако для больших
структур или больших коллекций влияние на производительность возрастает. Влияние может быть большим
в плотных циклах и часто используемых путях для программ.
Эти усовершенствования языка C# предназначены для критических алгоритмов производительности, когда
минимизация распределений памяти может иметь большое значение для достижения необходимой
производительности. Может оказаться, что в создаваемом коде эти функции используются довольно редко.
Тем не менее эти усовершенствования были реализованы в .NET. Поскольку с этими функциями работает
все больше API-интерфейсов, повышение производительности приложений не останется незаметным.
См. также
Ключевое слово ref
Возвращаемые значения ref и локальные переменные ref
Деревья выражений
04.11.2019 • 3 minutes to read • Edit Online
Если вы использовали LINQ, то у вас есть опыт работы с полнофункциональной библиотекой, в которой
типы Func являются частью набора API. (Если вы не работали с LINQ, перед чтением этого раздела
рекомендуем ознакомиться с руководствами по LINQ и лямбда-выражениям.) Деревья выражений
обеспечивают более широкие возможности взаимодействия с аргументами, являющимися функциями.
Аргументы функции применяются (как правило, с помощью лямбда-выражений) при создании запросов
LINQ. В типичном запросе LINQ аргументы функции преобразуются в делегат, создаваемый компилятором.
Если требуются более широкие возможности взаимодействия, необходимо использовать деревья
выражений. Деревья выражений представляют код в виде структуры, которую можно анализировать,
изменять или выполнять. Это дает возможность управлять кодом во время выполнения. Вы можете
написать код, который анализирует выполняющиеся алгоритмы или добавляет новые возможности. В более
сложных сценариях вы можете изменять выполняющиеся алгоритмы и даже преобразовывать выражения
C# в иную форму для выполнения в другой среде.
Вероятно, вы уже писали код, в котором используются деревья выражений. Интерфейсы API LINQ
платформы Entity Framework принимают деревья выражений в качестве аргументов для модели выражений
запросов LINQ. Это позволяет платформе Entity Framework преобразовывать запросы, написанные на C#, в
код SQL, который выполняется в ядре СУБД. Другим примером является Moq — популярная платформа
прототипирования для .NET.
В дальнейших разделах этого учебника описывается, что представляют собой деревья выражений,
рассматриваются поддерживающие их классы платформы и демонстрируются способы работы с деревьями
выражений. Вы узнаете, как считывать деревья выражений, как создавать их, а также как создавать
модифицированные деревья выражений и выполнять код, представленный деревьями выражений. После
ознакомления с учебником вы будете готовы к использованию этих структур для создания эффективных
адаптивных алгоритмов.
1. Описание деревьев выражений
Описание структуры и принципов использования деревьев выражений.
2. Типы платформ, поддерживающие деревья выражений
Сведения о структурах и классах, которые служат для определения деревьев выражений и управления
ими.
3. Выполнение выражений
Сведения о том, как преобразовать дерево выражения, представленное как лямбда-выражение, в
делегат и как выполнить полученный делегат.
4. Интерпретация выражений
Сведения об обходе и анализе деревьев выражений для получения представления о том, какой код
они представляют.
5. Построение выражений
Сведения о построении узлов для дерева выражения и сборке деревьев выражений.
6. Преобразование выражений
Сведения о создании измененной копии дерева выражения или преобразовании дерева выражения в
другой формат.
7. Заключение
Сводные сведения о деревьях выражений.
Описание деревьев выражений
04.11.2019 • 8 minutes to read • Edit Online
var sum = 1 + 2;
Если анализировать ее как дерево выражения, то оно содержит несколько узлов. Внешний узел — это
оператор объявления переменной с присваиванием ( var sum = 1 + 2; ). Этот внешний узел содержит
несколько дочерних узлов: объявление переменной, оператор присваивания и выражение, представляющее
часть справа от знака равенства. Это выражение далее делится на выражения, представляющие операцию
сложения, а также ее левый и правый операнды.
Давайте немного более подробно рассмотрим выражения, составляющие часть справа от знака равенства.
Выражение имеет вид 1 + 2 . Это двоичное выражение. Если точнее, это выражение двоичного сложения.
Выражение двоичного сложения имеет два дочерних элемента, представляющих левый и правый узлы
выражения сложения. В этом случае оба узла являются константными выражениями: левый операнд
представляет значение 1 , а правый — значение 2 .
Визуально весь оператор представляет собой дерево: вы можете начать с корневого узла и переходить к
каждому узлу дерева, чтобы просмотреть код, составляющий оператор.
Оператор объявления переменной с присваиванием ( var sum = 1 + 2; )
Неявное объявление типа переменной ( var sum )
Неявное ключевое слово var ( var )
Объявление имени переменной ( sum )
Оператор присваивания ( = )
Выражение двоичного сложения ( 1 + 2 )
Левый операнд ( 1 )
Оператор сложения ( + )
Правый операнд ( 2 )
Это может показаться сложным, но это очень удобно. Тем же самым образом можно раскладывать на
составные части гораздо более сложные выражения. Рассмотрим следующее выражение:
var finalAnswer = this.SecretSauceFunction(
currentState.createInterimResult(), currentState.createSecondValue(1, 2),
decisionServer.considerFinalOptions("hello")) +
MoreSecretSauce('A', DateTime.Now, true);
if (addFive.NodeType == ExpressionType.Lambda)
{
var lambdaExp = (LambdaExpression)addFive;
Console.WriteLine(parameter.Name);
Console.WriteLine(parameter.Type);
}
На этом простом примере видно, что в создании деревьев выражений и работе с ними задействовано
множество типов. Такая сложность необходима для предоставления широких возможностей словаря C#.
Перемещение по API
Существуют типы узлов выражений, которые сопоставляются почти со всеми элементами синтаксиса языка
C#. Каждый тип имеет определенные методы для конкретного типа элементов языка. Держать в голове
приходится слишком многое. Вместо того чтобы запоминать все это, при работе с деревьями выражений
можно применять описанные ниже приемы.
1. Просмотрите элементы перечисления ExpressionType , чтобы определить возможные узлы для анализа.
Это очень полезно, если нужно выполнить обход дерева и изучить его.
2. Просмотрите статические члены класса Expression , которые служат для создания выражения. С
помощью этих методов можно создать выражение любого типа на основе набора дочерних узлов.
3. Обратите внимание на класс ExpressionVisitor , который служит для создания измененного дерева
выражения.
В каждой из этих трех областей доступны и другие возможности. Вы обязательно найдете то, что вам нужно,
если начнете с этих трех основных шагов.
Следующий раздел: "Выполнение деревьев выражений"
Выполнение деревьев выражений
04.11.2019 • 10 minutes to read • Edit Online
Обратите внимание на то, что тип делегата основан на типе выражения. Если требуется использовать объект
делегата строго типизированным образом, необходимо знать тип возвращаемого значения и список
аргументов. Метод LambdaExpression.Compile() возвращает тип Delegate . Его необходимо будет привести к
правильному типу делегата, чтобы средства времени компиляции могли проверить список аргументов или
тип возвращаемого значения.
Выполнение и время существования
Код выполняется путем вызова делегата, созданного при вызове LambdaExpression.Compile() . Это можно
увидеть в приведенном выше примере, где add.Compile() возвращает делегат. Вызов этого делегата путем
вызова func() приводит к выполнению кода.
Делегат представляет код в дереве выражения. Вы можете сохранить дескриптор делегата и вызвать его
позднее. Вам не нужно компилировать дерево выражения каждый раз, когда вы хотите выполнить
представляемый им код. (Помните, что деревья выражений являются неизменяемыми и повторная
компиляция того же дерева выражения приведет к созданию делегата, который выполняет этот же код.)
Не рекомендуется создавать более сложные механизмы перехвата с целью повышения производительности
за счет уменьшения количества вызовов компиляции. Сравнение двух произвольных деревьев выражений с
целью определить, представляют ли они один и тот же алгоритм, также требует времени. Скорее всего,
время, сэкономленное за счет уменьшения количества вызовов LambdaExpression.Compile() , будет больше
времени, затрачиваемого на выполнение кода, который определяет, приводят ли два разных дерева
выражений к созданию одного и того же исполняемого кода.
Предупреждения
Компиляция лямбда-выражения в делегат и вызов этого делегата — одна из простейших операций, которые
можно выполнять с деревом выражения. Однако даже в случае с такой простой операцией следует
учитывать ряд моментов.
Лямбда-выражения создают замыкания для всех локальных переменных, на которые в них имеются ссылки.
Необходимо обеспечить возможность использования всех переменных, которые будут входить в делегат, в
месте вызова Compile и при выполнении итогового делегата.
Как правило, эту задачу решает компилятор. Однако если выражение обращается к переменной, которая
реализует IDisposable , в коде может быть удален объект, который все еще удерживается деревом
выражения.
Например, следующий код выполняется нормально, так как int не реализует IDisposable :
Делегат захватил ссылку на локальную переменную constant . Доступ к этой переменной осуществляется
позднее, когда выполняется функция, возвращаемая CreateBoundFunc .
Однако следует рассмотреть также следующий (несколько искусственный) класс, который реализует
IDisposable :
public class Resource : IDisposable
{
private bool isDisposed = false;
public int Argument
{
get
{
if (!isDisposed)
return 5;
else throw new ObjectDisposedException("Resource");
}
}
Если он используется в выражении, как показано ниже, то при выполнении кода, к которому обращается
свойство Resource.Argument , возникнет исключение ObjectDisposedException :
Делегат, возвращенный этим методом, замкнулся на объекте constant , который был ликвидирован.
(Причиной ликвидации является то, что он был объявлен в операторе using .)
Теперь при выполнении делегата, возвращаемого этим методом, будет возникать исключение
ObjectDisposedException .
Может показаться странным, что ошибка времени выполнения представляет конструкцию времени
компиляции, но таковы особенности работы с деревьями выражений.
Эта проблема может принимать разные формы, поэтому трудно дать общую рекомендацию по тому, как ее
избежать. Проявляйте осторожность в отношении доступа к локальным переменным при определении
выражений и в отношении доступа к состоянию в текущем объекте (представленном this ) при создании
дерева выражения, которое может возвращаться открытым интерфейсом API.
Код выражения может ссылаться на методы или свойства в других сборках. Эти сборки должны быть
доступны при определении выражения, его компиляции и вызове итогового делегата. В случае их отсутствия
будет возникать исключение ReferencedAssemblyNotFoundException .
Сводка
Деревья выражений, представляющие лямбда-выражения, можно компилировать с целью создания
делегатов, которые можно выполнять. Таким образом обеспечивается механизм выполнения кода,
представленного деревом выражения.
Дерево выражения представляет код, который будет выполняться для определенной конструкции, которую
вы создаете. При условии, что среда, в которой компилируется и выполняется код, соответствует среде, в
которой создается выражение, все работает правильно. В противном случае ошибки очень предсказуемы и
выявляются при проведении первых тестов кода, в котором используются деревья выражений.
Следующий раздел: "Интерпретация выражений"
Интерпретация выражений
04.11.2019 • 19 minutes to read • Edit Online
Теперь напишем код, который будет проверять это выражение и записывать некоторые важные свойства.
Вот этот код:
Здесь не используется var для объявления этого дерева выражения, так как это невозможно, поскольку
правая часть назначения неявно типизирована. Более подробные сведения см. здесь.
LambdaExpression — это корневой узел. Для получения нужного кода в правой части оператора => нужно
найти один из дочерних элементов LambdaExpression . Это необходимо сделать со всеми выражениями в этом
разделе. Благодаря родительскому узлу можно найти возвращаемый тип LambdaExpression .
Чтобы проверить каждый узел в этом выражении, необходимо рекурсивно пройти через несколько узлов.
Вот простая первая реализация:
В примере кода выше можно заметить много повторений. Очистим код и создадим посетитель узлов
выражения более общего назначения. Для этого нужно написать рекурсивный алгоритм. Любой узел может
иметь тип с дочерними элементами. Любой узел, имеющий дочерние элементы, требует посетить эти
дочерние элементы и определить тип узла. Вот очищенная версия, где используется рекурсия для посещения
операций сложения:
// Lambda Visitor
public class LambdaVisitor : Visitor
{
private readonly LambdaExpression node;
public LambdaVisitor(LambdaExpression node) : base(node)
{
this.node = node;
}
// Parameter visitor:
public class ParameterVisitor : Visitor
{
private readonly ParameterExpression node;
public ParameterVisitor(ParameterExpression node) : base(node)
{
this.node = node;
this.node = node;
}
// Constant visitor:
public class ConstantVisitor : Visitor
{
private readonly ConstantExpression node;
public ConstantVisitor(ConstantExpression node) : base(node)
{
this.node = node;
}
Этот алгоритм является основой для алгоритма, который может посещать любой произвольный
LambdaExpression . Существует множество брешей, а именно: созданный код выполняет поиск совсем
небольшого примера возможных наборов узлов дерева выражения, который он может найти. Однако в
результатах вы по-прежнему сможете находить полезные данные. (По умолчанию при обнаружении нового
типа узла метод Visitor.CreateFromExpression выводит сообщение на консоль ошибок. Таким образом, вы
знаете, что нужно добавить новый тип выражения.)
При запуске этого посетителя в выражении сложения, показанном выше, вы получите следующие выходные
данные:
Теперь, когда вы создали более общую реализацию посетителя, можно посещать и обрабатывать гораздо
больше разных типов выражений.
Перед запуском примера в алгоритме посетителя попробуйте представить, какие выходные данные могут
быть получены. Помните, что оператор + является бинарным: он должна иметь два дочерних элемента,
представляющих левый и правый операнды. Существует несколько правильных возможных способов
построения дерева:
Здесь выполнено разделение на два возможных ответа для выделения наиболее перспективного из них.
Первый представляет правоассоциативные выражения. Второй представляет левоассоциативные
выражения. Преимущества этих двух форматов в том, что формат масштабируется до любого
произвольного числа в выражениях сложения.
При запуске этого выражения через посетитель вы увидите эти выходные данные, проверяющие, что
выражение простого сложения является левоассоциативным.
Чтобы выполнить этот пример и увидеть полное дерево выражения, пришлось внести одно изменение в
исходное дерево выражения. Если дерево выражения содержит все константы, результирующее дерево
просто содержит константное значение, равное 10 . Компилятор выполняет все операции сложения и
сокращает выражение до его простейшей формы. Для просмотра исходного дерева достаточно добавить
одну переменную в выражение:
Создайте посетитель для этой суммы и запустите его. После этого вы увидите эти выходные данные:
This expression is a/an Lambda expression type
The name of the lambda is <null>
The return type is System.Int32
The expression has 1 argument(s). They are:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The expression body is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This binary expression is a Add expression
The Left argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 1
The Right argument is:
This is an Parameter expression type
Type: System.Int32, Name: a, ByRef: False
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 3
The Right argument is:
This is an Constant expression type
The type of the constant value is System.Int32
The value of the constant value is 4
С помощью кода посетителя можно также выполнить другие примеры и просмотреть, какое дерево он
представляет. Ниже приведен пример выражения sum3 выше (с дополнительным параметром, чтобы
компилятор не смог вычислить константу):
Обратите внимание, что скобки не являются частью выходных данных. В дереве выражения нет узлов,
представляющих круглые скобки во входном выражении. Структура дерева выражения содержит всю
информацию, необходимую для сообщения о приоритете.
Расширение примера
В этом примере используются только самые элементарные деревья выражений. Код, который вы видели в
этом разделе, обрабатывает только целочисленные константы и двоичный оператор + . Обратимся к
последнему примеру и изменим посетитель для обработки более сложного выражения. Сделаем так, чтобы
он работал следующим образом:
Этот код представляет одну из возможных реализаций для математической функции — факториала. В
способе написания кода выделяется два ограничения для создания деревьев выражений путем
присваивания лямбда-выражений выражениям. Во-первых, лямбды операторов не допускаются. Это
означает, что нельзя использовать циклы, блоки, операторы if/else и другие структуры управления,
распространенные в C#. Можно использовать только выражения. Во-вторых, нельзя выполнить рекурсивный
вызов того же выражения. Это можно было бы сделать, если бы уже имелся делегат, но его нельзя вызвать в
форме дерева выражения. В разделе о построении деревьев выражений вы узнаете о том, как преодолеть
эти ограничения.
В этом выражении вы встретите узлы всех указанных далее типов:
1. Equal (двоичное выражение)
2. Multiply (двоичное выражение)
3. Conditional (выражение ? :)
4. Выражение вызова метода (вызов Range() и Aggregate() )
Одним из способов изменения алгоритма посетителя является поддержка его выполнения и запись типа
узла при каждом достижении предложения default . После нескольких повторений вы просмотрите все
возможные узлы. Итак, у вас есть все, что нужно. Результат будет выглядеть примерно следующим образом:
Создание узлов
Начнем с относительно простого варианта. Мы будем использовать выражение сложения, которое
применялось в этих разделах:
Чтобы построить дерево выражения, необходимо создать конечные узлы. Конечные узлы являются
константами, поэтому для их создания можно использовать метод Expression.Constant :
Это очень простое лямбда-выражение, так как оно не содержит аргументов. Далее в этом разделе вы
узнаете, как сопоставлять аргументы с параметрами и создавать более сложные выражения.
Для выражений, которые так же просты, как и это, можно совместить все вызовы в одну инструкцию:
И, наконец, нужно поместить вызов метода в лямбда-выражение и определить аргументы для лямбда-
выражения:
В этом более сложном примере используется несколько дополнительных приемов, зачастую необходимых
при созданий деревьев выражений.
Во-первых, необходимо создать объекты, представляющие параметры или локальные переменные, перед
их использованием. Созданные объекты можно использовать в дереве выражения там, где это необходимо.
Во-вторых, необходимо использовать подмножество API-интерфейсов отражения для создания объекта
MethodInfo , чтобы можно было построить дерево выражения для получения доступа к этому методу.
Необходимо ограничить подмножество API-интерфейсов отражения, доступных на платформе .NET Core.
Эти способы можно будет использовать для других деревьев выражений.
Обратите внимание, что было построено не дерево выражения, а просто делегат. С помощью класса
Expression нельзя создать лямбды операторов. Ниже приведен код, необходимый для формирования тех
же функциональных возможностей. Он усложняется тем фактом, что API для создания цикла while не
существует. Вместо этого необходимо создать цикл, который содержит проверку условий, и целевой объект
метки для разрыва цикла.
Код для создания дерева выражения для функции факториала немного больше, более сложен и содержит
операторы меток и прерываний и другие элементы, использования которых хотелось бы избежать при
выполнении ежедневных задач по созданию кода.
Для работы в этом разделе также обновлен код посетителя для просмотра каждого узла в этом дереве
выражения и записи сведений об узлах, созданных в этом примере. Просмотреть или скачать пример кода
можно в репозитории dotnet/docs на сайте GitHub. Поэкспериментируйте со сборкой и использованием
примеров кода. Инструкции по загрузке см. в разделе Просмотр и скачивание примеров.
Изучение API-интерфейсов
Возможности навигации по API-интерфейсам дерева выражений усложнены в .NET Core, но это нормально.
Их назначение довольно сложное — написание кода, который создает код во время выполнения. Они с
трудом обеспечивают баланс между поддержкой всех структур управления, доступных на языке C#, и
сохранением минимально возможного размера контактной зоны API-интерфейсов. Этот баланс означает,
что многие структуры управления представлены не своими конструкциями C#, а конструкциями, которые
представляют базовую логику, создаваемую компилятором из этих конструкций более высокого уровня.
Кроме того, в настоящее время не существует выражений C#, которые создаются напрямую с помощью
методов класса Expression . Как правило, это будут новые операторы и выражения, добавленные в C# 5 и
C# 6. (Например, выражения async не могут быть созданы, и новый оператор ?. нельзя создать
напрямую.)
Следующий раздел — "Преобразование выражений"
Преобразование деревьев выражений
04.11.2019 • 9 minutes to read • Edit Online
При замене исходного узла новым узлом формируется новое дерево, содержащее наши изменения. Мы
можем проверить это, скомпилировав и выполнив измененное дерево.
var one = Expression.Constant(1, typeof(int));
var two = Expression.Constant(2, typeof(int));
var addition = Expression.Add(one, two);
var sum = ReplaceNodes(addition);
var executableFunc = Expression.Lambda(sum);
Создание нового дерева представляет собой сочетание обхода узлов в существующем дереве и создания
новых узлов и вставки их в дерево.
Этот пример демонстрирует значимость неизменяемых деревьев выражений. Обратите внимание, что
созданное нами новое дерево содержит сочетание вновь созданных узлов и узлов из существующего
дерева. Это безопасно, поскольку узлы в существующем дереве изменить нельзя. Это позволяет
существенно сократить потребление памяти. Одни и те же узлы можно использовать в одном дереве или в
нескольких деревьях выражений. Поскольку узлы нельзя изменить, один узел можно при необходимости
использовать повторно.
Нам потребуется написать небольшой фрагмент кода, однако основные принципы очень просты. Этот код
обходит дочерние элементы при поиске в глубину. При обнаружении узла константы посетитель возвращает
значение этой константы. После обхода обоих дочерних элементов для них вычисляется сумма, вычисляемая
для этого поддерева. Теперь добавленный узел может вычислить свою сумму. После обхода всех узлов в
дереве выражений будут вычислена сумма. Вы можете отслеживать выполнение, запустив пример в
отладчике с трассировкой выполнения.
Мы можем упростить трассировку анализа узлов и вычисления суммы путем обхода дерева. Вот
обновленная версия метода агрегирования, который включает совсем немного данных трассировки:
private static int Aggregate(Expression exp)
{
if (exp.NodeType == ExpressionType.Constant)
{
var constantExp = (ConstantExpression)exp;
Console.Error.WriteLine($"Found Constant: {constantExp.Value}");
return (int)constantExp.Value;
}
else if (exp.NodeType == ExpressionType.Add)
{
var addExp = (BinaryExpression)exp;
Console.Error.WriteLine("Found Addition Expression");
Console.Error.WriteLine("Computing Left node");
var leftOperand = Aggregate(addExp.Left);
Console.Error.WriteLine($"Left is: {leftOperand}");
Console.Error.WriteLine("Computing Right node");
var rightOperand = Aggregate(addExp.Right);
Console.Error.WriteLine($"Right is: {rightOperand}");
var sum = leftOperand + rightOperand;
Console.Error.WriteLine($"Computed sum: {sum}");
return sum;
}
else throw new NotSupportedException("Haven't written this yet");
}
10
Found Addition Expression
Computing Left node
Found Addition Expression
Computing Left node
Found Constant: 1
Left is: 1
Computing Right node
Found Constant: 2
Right is: 2
Computed sum: 3
Left is: 3
Computing Right node
Found Addition Expression
Computing Left node
Found Constant: 3
Left is: 3
Computing Right node
Found Constant: 4
Right is: 4
Computed sum: 7
Right is: 7
Computed sum: 10
10
Проследите выходные данные по приведенному выше коду. Вы наверняка поймете, каким образом код
обходит каждый узел и вычисляет сумму по мере обхода дерева и нахождения суммы.
Теперь рассмотрим другое выполнение, где выражение задается объектом sum1 :
Хотя окончательный ответ совпадает, способ обхода дерева совершенно другой. Узлы обходятся в другом
порядке, так как дерево было создано с помощью других начальных операций.
Ограничения
Существуют некоторые новые элементы языка C#, которые не подходят для преобразования в деревья
выражений. Деревья выражений не могут содержать выражения await или лямбда-выражения async .
Многие из функций, добавленных в версии C# 6, не отображаются точно так, как они написаны, в деревьях
выражений. Новые функции будут предоставляться в деревьях выражений в эквивалентном синтаксисе
прежних версий. Однако это не так ограничивает ваши возможности, как может показаться. На самом деле
это означает, что код, который интерпретирует деревья выражений, сможет успешно обрабатывать новые
возможности языка, которые будут добавлены в будущем.
Даже с этими ограничениями деревья выражений позволяют создавать динамические алгоритмы,
основанные на интерпретации и изменении кода, представленного в виде структуры данных. Это очень
эффективный инструмент и один из компонентов экосистемы .NET, которая обеспечивает преимущества
широкого набора библиотек, таких как Entity Framework.
Взаимодействие (Руководство по
программированию в C#)
04.11.2019 • 2 minutes to read • Edit Online
Возможность взаимодействия позволяет использовать уже созданный неуправляемый код, экономя средства
на разработку. Код, который выполняется под управлением среды CLR, называется управляемым кодом, а
код, который выполняется вне среды CLR, называется неуправляемым кодом. COM, COM +, компоненты
C++, компоненты ActiveX и Microsoft Windows API являются примерами неуправляемого кода.
.NET Framework обеспечивает взаимодействие с неуправляемым кодом посредством служб вызова
неуправляемого кода (PInvoke), пространства имен System.Runtime.InteropServices, COM -взаимодействия и
взаимодействия с C++.
В этом разделе
Общие сведения о взаимодействии
Описывает способы взаимодействия между управляемым кодом C# и неуправляемым кодом.
Практическое руководство. Доступ к объектам взаимодействия Office с помощью функций языка Visual C#
Описывает возможности, представленные в Visual C#, которые упрощают программирование для Office.
Практическое руководство. Использование индексированных свойств в программировании COM -
взаимодействия
Описывает использование индексированных свойств для доступа к свойствам COM, которые имеют
параметры.
Практическое руководство. Использование вызова неуправляемого кода для воспроизведения звукового
файла
Описывает, как использовать платформу вызова служб, чтобы воспроизвести звуковой WAV -файл в
операционной системе Windows.
Пошаговое руководство. Программирование для Office
Описывает процесс создания книги Excel и документа Word со ссылкой на эту книгу.
Пример COM -класса
Предлагает пример предоставления класса C# в качестве COM -объекта.
Спецификация языка C#
Дополнительные сведения см. в разделе Основные понятия в Спецификации языка C#. Спецификация языка
является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Marshal.ReleaseComObject
Руководство по программированию на C#
Взаимодействие с неуправляемым кодом
Пошаговое руководство. Программирование для Office
Документирование кода с помощью XML-
комментариев
25.11.2019 • 34 minutes to read • Edit Online
Комментарии к XML -документации — это особый тип комментария, добавляемого перед определением
определяемого пользователем типа или члена. Их особенность в том, что компилятор может обрабатывать
их для создания XML -файла документации во время компиляции. Созданный компилятором XML -файл
можно распространять вместе со сборкой .NET, чтобы Visual Studio и другие интегрированные среды
разработки могли использовать IntelliSense для отображения кратких сведений о типах или членах. Кроме
того, XML -файл можно запускать с помощью таких средств, как DocFX и Sandcastle, и создавать веб-сайты со
справочными сведениями по API.
Комментарии к XML -документации, как и все другие комментарии, пропускаются компилятором.
Можно создать XML -файл во время компиляции, выполнив одно из следующих действий.
Если вы разрабатываете приложение с использованием .NET Core из командной строки, добавьте
элемент GenerateDocumentationFile в раздел <PropertyGroup> CSPROJ -файла проекта. Также можно
указать путь к файлу документации напрямую с помощью элемента DocumentationFile . В следующем
примере создается XML -файл в каталоге проекта с тем же именем корневого файла, что и у сборки:
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
Если вы разрабатываете приложение с помощью Visual Studio, щелкните правой кнопкой мыши
проект и выберите Свойства. В диалоговом окне свойств откройте вкладку Сборка и выберите XML -
файл документации. Можно также изменить расположение, в котором компилятор записывает
файл.
Если вы компилируете приложение .NET Framework из командной строки, добавьте параметр
компилятора -doc во время компиляции.
Для вставки комментария к XML -документации используются три символа косой черты ( /// ) и текст
комментария в формате XML. Пример:
/// <summary>
/// This class does something.
/// </summary>
public class SomeClass
{
Пошаговое руководство
Давайте разберем документирование простейшей математической библиотеки, чтобы новые разработчики
могли без труда понимать и дополнять ее, а сторонние разработчики — легко использовать ее.
Ниже приведен код для простой математической библиотеки.
/*
The main Math class
Contains all methods for performing basic math functions
*/
public class Math
{
// Adds two integers and returns the result
public static int Add(int a, int b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
return a + b;
}
Теперь вам нужна возможность создания справочного документа по API из кода для сторонних
разработчиков, которые используют библиотеку, но не имеют доступа к исходному коду. Как упоминалось
ранее, это можно сделать с помощью тегов XML -документации. Сейчас вы познакомитесь со стандартными
XML -тегами, которые поддерживает компилятор C#.
<summary>
Тег <summary> добавляет краткие сведения о типе или члене. Я продемонстрирую использование тега путем
его добавления в определение класса Math и первый метод Add . Кроме того, его можно применить к
остальной части кода.
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main Math class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
// Adds two integers and returns the result
/// <summary>
/// Adds two integers and returns the result.
/// </summary>
public static int Add(int a, int b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
}
Тег <summary> имеет очень важное значение, и его рекомендуется включать, так как его содержимое
является основным источником информации о типе или члене в IntelliSense или справочном документе по
API.
<remarks>
Тег <remarks> дополняет сведения о типах или членах, предоставляемые тегом <summary> . В этом примере вы
просто добавите его в класс.
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main Math class.
/// Contains all methods for performing basic math functions.
/// </summary>
/// <remarks>
/// This class can add, subtract, multiply and divide.
/// </remarks>
public class Math
{
<returns>
Тег <returns> описывает возвращаемое значение объявления метода. Как и ранее, в следующем примере
демонстрируется тег <returns> в первом методе Add . То же самое можно сделать и с другими методами.
return a + b;
}
<value>
Тег <value> аналогичен тегу <returns> , за исключением того, что он используется для свойств. Если бы в
библиотеке Math было статическое свойство PI , вы использовали бы этот тег следующим образом:
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main Math class.
/// Contains all methods for performing basic math functions.
/// </summary>
/// <remarks>
/// This class can add, subtract, multiply and divide.
/// These operations can be performed on both integers and doubles
/// </remarks>
public class Math
{
/// <value>Gets the value of PI.</value>
public static double PI { get; }
}
<example>
Тег <example> применяется для включения примера в XML -документацию. В этом случае нужно
использовать дочерний тег <code> .
return a + b;
}
Тег code сохраняет разрывы строк и отступы для более длинных примеров.
<para>
Тег <para> используется для форматирования содержимого в его родительском теге. <para> обычно
используется внутри тега, такого как <remarks> или <returns> , чтобы разделить текст на абзацы.
Содержимое тега <remarks> для определения класса можно отформатировать.
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main Math class.
/// Contains all methods for performing basic math functions.
/// </summary>
/// <remarks>
/// <para>This class can add, subtract, multiply and divide.</para>
/// <para>These operations can be performed on both integers and doubles.</para>
/// </remarks>
public class Math
{
<c>
Что касается форматирования, можно использовать тег <c> для пометки части текста в качестве кода. Он
подобен тегу <code> , но является встроенным. Он полезен, если требуется показать пример кода как часть
содержимого тега. Давайте обновим в документацию для класса Math .
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
<exception>
С помощью тега <exception> можно сообщить разработчикам, что метод может генерировать
определенные исключения. Если взглянуть на библиотеку Math , можно увидеть, что оба метода Add
создают исключение при выполнении определенного условия. Метод Divide с типом integer также создает
исключение (хотя и не столь очевидно), если параметр b равен нулю. Теперь добавьте в этот метод
документацию по исключению.
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Adds two integers and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Add(4, 5);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than 0.</exception>
public static int Add(int a, int b)
{
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
/// <summary>
/// Adds two doubles and returns the result.
/// </summary>
/// <returns>
/// The sum of two doubles.
/// </returns>
/// </returns>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than zero.</exception>
public static double Add(double a, double b)
{
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
/// <summary>
/// Divides an integer by another and returns the result.
/// </summary>
/// <returns>
/// The division of two integers.
/// </returns>
/// <exception cref="System.DivideByZeroException">Thrown when a division by zero occurs.</exception>
public static int Divide(int a, int b)
{
return a / b;
}
/// <summary>
/// Divides a double by another and returns the result.
/// </summary>
/// <returns>
/// The division of two doubles.
/// </returns>
/// <exception cref="System.DivideByZeroException">Thrown when a division by zero occurs.</exception>
public static double Divide(double a, double b)
{
return a / b;
}
}
Атрибут cref представляет ссылку на исключение, которое доступно из текущей среды компиляции. Это
может быть любой тип, определенный в проекте или ссылочной сборке. Компилятор выдаст
предупреждение, если его значение не может быть разрешено.
<see>
Тег <see> позволяет создать активную ссылку на страницу документации по другому элементу кода. В
следующем примере будет создана активная ссылка между двумя методами Add .
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Adds two integers and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Add(4, 5);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than 0.</exception>
/// See <see cref="Math.Add(double, double)"/> to add doubles.
public static int Add(int a, int b)
{
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
/// <summary>
/// Adds two doubles and returns the result.
/// </summary>
/// <returns>
/// The sum of two doubles.
/// </returns>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than zero.</exception>
/// See <see cref="Math.Add(int, int)"/> to add integers.
public static double Add(double a, double b)
{
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
}
Атрибут cref является обязательным и представляет ссылку на тип или его член, который доступен из
текущей среды компиляции. Это может быть любой тип, определенный в проекте или ссылочной сборке.
<seealso>
Тег <seealso> используется так же, как тег <see> . Единственное различие заключается в том, что его
содержимое обычно помещается в раздел "См. также". Мы добавим тег seealso в метод Add с типом integer
для ссылки на другие методы в классе, принимающие параметры с типом integer:
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Adds two integers and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Add(4, 5);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than 0.</exception>
/// See <see cref="Math.Add(double, double)"/> to add doubles.
/// <seealso cref="Math.Subtract(int, int)"/>
/// <seealso cref="Math.Multiply(int, int)"/>
/// <seealso cref="Math.Divide(int, int)"/>
public static int Add(int a, int b)
{
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
}
Атрибут cref представляет ссылку на тип или его член, который доступен из текущей среды компиляции.
Это может быть любой тип, определенный в проекте или ссылочной сборке.
<param>
Тег <param> используется для описания параметров метода. Ниже приведен пример метода double Add :
Параметр, описываемый тегом, указан в обязательном атрибуте name .
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Adds two doubles and returns the result.
/// </summary>
/// <returns>
/// The sum of two doubles.
/// </returns>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than zero.</exception>
/// See <see cref="Math.Add(int, int)"/> to add integers.
/// <param name="a">A double precision number.</param>
/// <param name="b">A double precision number.</param>
public static double Add(double a, double b)
{
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
}
<typeparam>
Тег <typeparam> используется так же, как тег <param> , но для объявлений универсального типа или метода он
служит для описания универсального параметра. Добавьте в класс Math быстрый универсальный метод,
чтобы проверить, что одно количество больше другого.
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Checks if an IComparable is greater than another.
/// </summary>
/// <typeparam name="T">A type that inherits from the IComparable interface.</typeparam>
public static bool GreaterThan<T>(T a, T b) where T : IComparable
{
return a.CompareTo(b) > 0;
}
}
<paramref>
Иногда в процессе описания выполнения метода в теге <summary> может потребоваться создание ссылки на
параметр. Для этого отлично подойдет тег <paramref> . Обновим сводку по методу Add с типом double. Как и
для тега <param> , имя параметра указано в обязательном атрибуте name .
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Adds two doubles <paramref name="a"/> and <paramref name="b"/> and returns the result.
/// </summary>
/// <returns>
/// The sum of two doubles.
/// </returns>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than zero.</exception>
/// See <see cref="Math.Add(int, int)"/> to add integers.
/// <param name="a">A double precision number.</param>
/// <param name="b">A double precision number.</param>
public static double Add(double a, double b)
{
if ((a == double.MaxValue && b > 0) || (b == double.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
}
<typeparamref>
Тег <typeparamref> используется так же, как тег <paramref> , но для объявлений универсального типа или
метода он служит для описания универсального параметра. Можно использовать созданные ранее
универсальный метод.
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// </summary>
public class Math
{
/// <summary>
/// Checks if an IComparable <typeparamref name="T"/> is greater than another.
/// </summary>
/// <typeparam name="T">A type that inherits from the IComparable interface.</typeparam>
public static bool GreaterThan<T>(T a, T b) where T : IComparable
{
return a.CompareTo(b) > 0;
}
}
<list>
Тег <list> используется для форматирования сведений о документации в виде упорядоченного списка,
неупорядоченного списка или таблицы. Создайте неупорядоченный список для каждой математической
операции, поддерживаемой библиотекой Math .
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// <list type="bullet">
/// <item>
/// <term>Add</term>
/// <description>Addition Operation</description>
/// </item>
/// <item>
/// <term>Subtract</term>
/// <description>Subtraction Operation</description>
/// </item>
/// <item>
/// <term>Multiply</term>
/// <description>Multiplication Operation</description>
/// </item>
/// <item>
/// <term>Divide</term>
/// <description>Division Operation</description>
/// </item>
/// </list>
/// </summary>
public class Math
{
Упорядоченный список или таблицу можно сформировать, изменив атрибут type на number или table
соответственно.
Заключение
Вы выполнили инструкции в этом учебнике и включили теги в код там, где необходимо. Теперь ваш код
должен иметь следующий вид:
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <summary>
/// The main <c>Math</c> class.
/// Contains all methods for performing basic math functions.
/// <list type="bullet">
/// <item>
/// <term>Add</term>
/// <description>Addition Operation</description>
/// </item>
/// <item>
/// <term>Subtract</term>
/// <description>Subtraction Operation</description>
/// </item>
/// <item>
/// <term>Multiply</term>
/// <description>Multiplication Operation</description>
/// </item>
/// <item>
/// <term>Divide</term>
/// <description>Division Operation</description>
/// </item>
/// </list>
/// </summary>
/// <remarks>
/// <remarks>
/// <para>This class can add, subtract, multiply and divide.</para>
/// <para>These operations can be performed on both integers and doubles.</para>
/// </remarks>
public class Math
{
// Adds two integers and returns the result
/// <summary>
/// Adds two integers <paramref name="a"/> and <paramref name="b"/> and returns the result.
/// </summary>
/// <returns>
/// The sum of two integers.
/// </returns>
/// <example>
/// <code>
/// int c = Math.Add(4, 5);
/// if (c > 10)
/// {
/// Console.WriteLine(c);
/// }
/// </code>
/// </example>
/// <exception cref="System.OverflowException">Thrown when one parameter is max
/// and the other is greater than 0.</exception>
/// See <see cref="Math.Add(double, double)"/> to add doubles.
/// <seealso cref="Math.Subtract(int, int)"/>
/// <seealso cref="Math.Multiply(int, int)"/>
/// <seealso cref="Math.Divide(int, int)"/>
/// <param name="a">An integer.</param>
/// <param name="b">An integer.</param>
public static int Add(int a, int b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
return a + b;
}
<include>
Тег <include> позволяет обращаться к комментариям в отдельном XML -файле, описывающем типы и члены
в исходном коде, а не размещать комментарии к документации непосредственно в файле исходного кода.
Переместим все XML -теги в отдельный файл XML с именем docs.xml . Файлу можно задать любое имя.
<docs>
<members name="math">
<Math>
<summary>
The main <c>Math</c> class.
Contains all methods for performing basic math functions.
</summary>
<remarks>
<para>This class can add, subtract, multiply and divide.</para>
<para>These operations can be performed on both integers and doubles.</para>
</remarks>
</Math>
<AddInt>
<summary>
Adds two integers <paramref name="a"/> and <paramref name="b"/> and returns the result.
</summary>
<returns>
<returns>
The sum of two integers.
</returns>
<example>
<code>
int c = Math.Add(4, 5);
if (c > 10)
{
Console.WriteLine(c);
}
</code>
</example>
<exception cref="System.OverflowException">Thrown when one parameter is max
and the other is greater than 0.</exception>
See <see cref="Math.Add(double, double)"/> to add doubles.
<seealso cref="Math.Subtract(int, int)"/>
<seealso cref="Math.Multiply(int, int)"/>
<seealso cref="Math.Divide(int, int)"/>
<param name="a">An integer.</param>
<param name="b">An integer.</param>
</AddInt>
<AddDouble>
<summary>
Adds two doubles <paramref name="a"/> and <paramref name="b"/> and returns the result.
</summary>
<returns>
The sum of two doubles.
</returns>
<example>
<code>
double c = Math.Add(4.5, 5.4);
if (c > 10)
{
Console.WriteLine(c);
}
</code>
</example>
<exception cref="System.OverflowException">Thrown when one parameter is max
and the other is greater than 0.</exception>
See <see cref="Math.Add(int, int)"/> to add integers.
<seealso cref="Math.Subtract(double, double)"/>
<seealso cref="Math.Multiply(double, double)"/>
<seealso cref="Math.Divide(double, double)"/>
<param name="a">A double precision number.</param>
<param name="b">A double precision number.</param>
</AddDouble>
<SubtractInt>
<summary>
Subtracts <paramref name="b"/> from <paramref name="a"/> and returns the result.
</summary>
<returns>
The difference between two integers.
</returns>
<example>
<code>
int c = Math.Subtract(4, 5);
if (c > 1)
{
Console.WriteLine(c);
}
</code>
</example>
See <see cref="Math.Subtract(double, double)"/> to subtract doubles.
<seealso cref="Math.Add(int, int)"/>
<seealso cref="Math.Multiply(int, int)"/>
<seealso cref="Math.Divide(int, int)"/>
<param name="a">An integer.</param>
<param name="b">An integer.</param>
</SubtractInt>
<SubtractDouble>
<SubtractDouble>
<summary>
Subtracts a double <paramref name="b"/> from another double <paramref name="a"/> and returns the
result.
</summary>
<returns>
The difference between two doubles.
</returns>
<example>
<code>
double c = Math.Subtract(4.5, 5.4);
if (c > 1)
{
Console.WriteLine(c);
}
</code>
</example>
See <see cref="Math.Subtract(int, int)"/> to subtract integers.
<seealso cref="Math.Add(double, double)"/>
<seealso cref="Math.Multiply(double, double)"/>
<seealso cref="Math.Divide(double, double)"/>
<param name="a">A double precision number.</param>
<param name="b">A double precision number.</param>
</SubtractDouble>
<MultiplyInt>
<summary>
Multiplies two integers <paramref name="a"/> and <paramref name="b"/> and returns the result.
</summary>
<returns>
The product of two integers.
</returns>
<example>
<code>
int c = Math.Multiply(4, 5);
if (c > 100)
{
Console.WriteLine(c);
}
</code>
</example>
See <see cref="Math.Multiply(double, double)"/> to multiply doubles.
<seealso cref="Math.Add(int, int)"/>
<seealso cref="Math.Subtract(int, int)"/>
<seealso cref="Math.Divide(int, int)"/>
<param name="a">An integer.</param>
<param name="b">An integer.</param>
</MultiplyInt>
<MultiplyDouble>
<summary>
Multiplies two doubles <paramref name="a"/> and <paramref name="b"/> and returns the result.
</summary>
<returns>
The product of two doubles.
</returns>
<example>
<code>
double c = Math.Multiply(4.5, 5.4);
if (c > 100.0)
{
Console.WriteLine(c);
}
</code>
</example>
See <see cref="Math.Multiply(int, int)"/> to multiply integers.
<seealso cref="Math.Add(double, double)"/>
<seealso cref="Math.Subtract(double, double)"/>
<seealso cref="Math.Divide(double, double)"/>
<param name="a">A double precision number.</param>
<param name="b">A double precision number.</param>
</MultiplyDouble>
</MultiplyDouble>
<DivideInt>
<summary>
Divides an integer <paramref name="a"/> by another integer <paramref name="b"/> and returns the
result.
</summary>
<returns>
The quotient of two integers.
</returns>
<example>
<code>
int c = Math.Divide(4, 5);
if (c > 1)
{
Console.WriteLine(c);
}
</code>
</example>
<exception cref="System.DivideByZeroException">Thrown when <paramref name="b"/> is equal to 0.
</exception>
See <see cref="Math.Divide(double, double)"/> to divide doubles.
<seealso cref="Math.Add(int, int)"/>
<seealso cref="Math.Subtract(int, int)"/>
<seealso cref="Math.Multiply(int, int)"/>
<param name="a">An integer dividend.</param>
<param name="b">An integer divisor.</param>
</DivideInt>
<DivideDouble>
<summary>
Divides a double <paramref name="a"/> by another double <paramref name="b"/> and returns the
result.
</summary>
<returns>
The quotient of two doubles.
</returns>
<example>
<code>
double c = Math.Divide(4.5, 5.4);
if (c > 1.0)
{
Console.WriteLine(c);
}
</code>
</example>
<exception cref="System.DivideByZeroException">Thrown when <paramref name="b"/> is equal to 0.
</exception>
See <see cref="Math.Divide(int, int)"/> to divide integers.
<seealso cref="Math.Add(double, double)"/>
<seealso cref="Math.Subtract(double, double)"/>
<seealso cref="Math.Multiply(double, double)"/>
<param name="a">A double precision dividend.</param>
<param name="b">A double precision divisor.</param>
</DivideDouble>
</members>
</docs>
/*
The main Math class
Contains all methods for performing basic math functions
*/
/// <include file='docs.xml' path='docs/members[@name="math"]/Math/*'/>
public class Math
public class Math
{
// Adds two integers and returns the result
/// <include file='docs.xml' path='docs/members[@name="math"]/AddInt/*'/>
public static int Add(int a, int b)
{
// If any parameter is equal to the max value of an integer
// and the other is greater than zero
if ((a == int.MaxValue && b > 0) || (b == int.MaxValue && a > 0))
throw new System.OverflowException();
return a + b;
}
return a + b;
}
Рекомендации
Документирование кода рекомендуется по многим причинам. Далее приводится ряд рекомендаций,
распространенные сценарии использования и вопросы, которые нужно иметь в виду при работе с тегами
XML -документации в коде C#.
Для обеспечения согласованности все открытые типы и их члены должны быть задокументированы. Если
это необходимо, задокументируйте их все.
Закрытые члены можно также задокументировать с помощью XML -комментариев. Однако это приводит
к раскрытию внутренних (возможно, конфиденциальных) процессов библиотеки.
Как минимум, типы и их члены должен иметь тег <summary> , так как его содержимое требуется для
IntelliSense.
Текст документации должны быть написан с использованием законченных предложений, в конце которых
ставится точка.
Разделяемые классы полностью поддерживаются, и данные о документации объединяются в одну запись
для этого типа.
Компилятор проверяет синтаксис тегов <exception> , <include> , <param> , <see> , <seealso> и <typeparam> .
Компилятор проверяет параметры, которые содержат пути к файлам и ссылки на другие части кода.
См. также
Комментарии к XML -документации (руководство по программированию на C#)
Рекомендуемые теги для комментариев документации (руководство по программированию на C#)
Управление версиями в C#
04.11.2019 • 9 minutes to read • Edit Online
В этом учебнике вы получите представление об управлении версиями в .NET. Кроме того, вы узнаете, какие
факторы следует учитывать при управлении версиями библиотеки и обновлении до ее новой версии.
Разработка библиотек
Если вы занимались разработкой библиотек .NET для открытого использования, то, скорее всего, вам
приходилось развертывать обновления. Способ выполнения этой процедуры важен, так как необходимо
обеспечить плавный перевод существующего кода на новую версию библиотеки. Ниже описывается ряд
моментов, которые следует учитывать при создании выпуска.
Семантическое управление версиями
Семантическое версионирование (SemVer) — это соглашение об именовании, применяемое к версиям
библиотеки для обозначения ключевых вех. В идеале сведения о версии, включаемые в библиотеку, должны
помочь разработчикам определить совместимость с проектами, в которых используются предыдущие версии
этой библиотеки.
Базовым вариантом SemVer является трехкомпонентный формат MAJOR.MINOR.PATCH , где:
MAJOR увеличивается при внесении изменений, приводящих к несовместимости API;
MINOR увеличивается при добавлении функциональных возможностей с обеспечением обратной
совместимости;
PATCH увеличивается при исправлении ошибок с обеспечением обратной совместимости.
При применении сведений о версии к библиотеке .NET возможны также и особые случаи, например, можно
указать, что версия является предварительной и т. д.
Обратная совместимость
При выпуске новых версий библиотеки обеспечение обратной совместимости с предыдущими версиями,
скорее всего, будет одной из основных задач. Новая версия библиотеки является совместимой с предыдущей
на уровне исходного кода, если код, который зависит от предыдущей версии, после перекомпиляции может
работать с новой версией. Новая версия библиотеки является совместимой на уровне двоичного кода, если
приложение, которое зависело от предыдущей версии, может работать с новой версией без
перекомпиляции.
Ниже описывается ряд моментов, которые следует учитывать при обеспечении обратной совместимости с
предыдущими версиями библиотеки.
Виртуальные методы. Когда вы делаете виртуальный метод невиртуальным в новой версии, это означает,
что проекты, в которых этот метод переопределяется, необходимо будет обновить. Это очень
существенное изменение, которое настоятельно не рекомендуется производить.
Сигнатуры методов. Если при изменении поведения метода также необходимо изменить его сигнатуру,
вместо этого следует создать перегрузку, чтобы код, вызывающий этот метод, по-прежнему работал.
Прежнюю сигнатуру метода всегда можно настроить так, чтобы она вызывала новую сигнатуру,
обеспечив тем самым согласованность реализаций.
Атрибут Obsolete. С помощью этого атрибута можно указывать в коде классы или члены классов, которые
являются нерекомендуемыми и, вероятно, будут удалены в будущих версиях. Благодаря этому
разработчики, использующие вашу библиотеку, будут лучше подготовлены к существенным изменениям.
Необязательные аргументы метода. Если вы делаете необязательные аргументы метода обязательными
или меняете их значения по умолчанию, то код, который не задает эти аргументы, потребуется обновить.
NOTE
Если обязательные аргументы становятся необязательными, то, как правило, это не должно иметь особых последствий,
особенно если при этом поведение метода не меняется.
Чем проще пользователям будет произвести обновление до новой версии библиотеки, тем скорее они это
сделают.
Файл конфигурации приложения
Как разработчик .NET вы, весьма вероятно, встречали файл app.config в большинстве типов проектов. Этот
простой файл конфигурации может очень упростить развертывание обновлений. В основном библиотеки
следует проектировать так, чтобы сведения, которые, скорее всего, будут меняться регулярно, хранились в
файле app.config . Благодаря этому при изменении таких сведений достаточно будет заменить файл
конфигурации предыдущей версии на новый, не перекомпилируя библиотеку.
Использование библиотек
Если при разработке вы используете библиотеки .NET, созданные другими разработчиками, то, скорее всего,
знаете, что новая версия библиотеки может быть не полностью совместима с вашим проектом. В таком
случае приходится обновлять код в соответствии с изменениями в библиотеке.
К счастью, экосистема C# и .NET предоставляет возможности и методы, которые позволяют обновлять
приложения для работы с новыми версиями библиотек, в которые были внесены существенные изменения.
Перенаправление привязки сборок
С помощью файла app.config можно изменить версию библиотеки, используемую приложением. Добавив
так называемое перенаправление привязки, вы можете использовать новую версию библиотеки, не
перекомпилируя приложение. В приведенном ниже примере показано, как можно изменить файл
приложения app.config так, чтобы оно использовало версию исправления 1.0.1 библиотеки
ReferencedLibrary вместо версии 1.0.0 , с которой оно изначально компилировалось.
<dependentAssembly>
<assemblyIdentity name="ReferencedLibrary" publicKeyToken="32ab4ba45e0a69a1" culture="en-us" />
<bindingRedirect oldVersion="1.0.0" newVersion="1.0.1" />
</dependentAssembly>
NOTE
Такой подход будет работать только в том случае, если новая версия библиотеки ReferencedLibrary совместима с
приложением на уровне двоичного кода. Сведения об изменениях, на которые следует обращать внимание при
определении совместимости, см. выше в подразделе Обратная совместимость.
new
Модификатор new служит для скрытия унаследованных членов базового класса. Это один из способов,
которым производные классы могут реагировать на изменения в базовом классе.
Рассмотрим следующий пример:
public class BaseClass
{
public void MyMethod()
{
Console.WriteLine("A base method");
}
}
b.MyMethod();
d.MyMethod();
}
Выходные данные
A base method
A derived method
В приведенном выше примере можно видеть, как в классе DerivedClass скрывается метод MyMethod ,
имеющийся в классе BaseClass . Таким образом, если в базовом классе в новой версии библиотеки
появляется член, который уже имеется в вашем производном классе, можно просто использовать
модификатор new для члена производного класса, чтобы скрыть соответствующий член базового класса.
Если модификатор new не указан, в производном классе будут по умолчанию скрываться конфликтующие
члены базового класса. При этом будет выводиться предупреждение компилятора, однако код будет
компилироваться. Это означает, что добавление новых членов в существующий класс делает новую версию
библиотеки совместимой с кодом, в котором она используется, на уровне как исходного, так и двоичного
кода.
override
Модификатор override означает, что производная реализация расширяет реализацию члена базового
класса, а не скрывает его. К члену базового класса должен быть применен модификатор virtual .
public class MyBaseClass
{
public virtual string MethodOne()
{
return "Method One";
}
}
Выходные данные
Модификатор override оценивается во время компиляции, и компилятор выдаст ошибку, если ему не
удастся найти виртуальный член для переопределения.
Знание рассмотренных приемов и ситуаций, в которых их следует применять, поможет вам значительно
упростить переход с одной версии библиотеки на другую.
Практическое руководство (C#)
07.11.2019 • 6 minutes to read • Edit Online
Основные понятия C#
Существует несколько общих советов и рекомендаций для разработчиков на C#.
Инициализация объектов с помощью инициализатора объектов.
Различия между передачей структуры и класса в метод.
Использование перегрузки операторов.
Реализация и вызов пользовательского метода расширения.
Даже программистам на C# может потребоваться использовать пространство имен My из VB.
Создание метода для типа enum с помощью методов расширения.
Члены класса и структуры
Классы и структуры создаются для реализации программы. При написании классов или структур часто
используются перечисленные ниже методы.
Объявление автоматически реализуемых свойств.
Объявление и использование свойств чтения и записи.
Определение констант.
Переопределение метода ToString для предоставления строковых выходных данных.
Определение абстрактных свойств.
Использование возможностей XML -документации для документирования кода.
Явная реализация членов интерфейса для обеспечения краткости открытого интерфейса.
Явная реализация членов двух интерфейсов.
Работа с коллекциями
Следующие статьи содержат сведения о работе с коллекциями данных.
Инициализация словаря с помощью инициализатора коллекций.
Работа со строками
Строки являются основным типом данных, используемым для отображения текста или работы с ним. В этих
статьях приводятся распространенные варианты использования строк.
Сравнение строк.
Изменение содержимого строки.
Определение того, представляет ли строка число.
Использование String.Split для разделения строк.
Объединение нескольких строк в одну.
Поиск текста в строке.
Преобразование типов
Может потребоваться преобразовать объект к другому типу.
Определение того, представляет ли строка число.
Преобразование строк, представляющих шестнадцатеричные значения, и числа.
Преобразование строки в DateTime .
Преобразование массива байтов в значение типа int.
Преобразование строки в число.
Использование операторов сопоставления шаблонов as и is для безопасного приведения к другому
типу.
Определение пользовательского преобразования типов.
Определение того, допускает ли тип значения NULL.
Преобразование между типами значений, допускающие значения NULL, и типами, не допускающими
значения NULL.
Обработка исключений
В программах на .NET сообщения о неудачных завершениях методов выводятся путем создания исключений.
В следующих статьях вы узнаете, как работать с исключениями.
Обработка исключений с помощью try и catch .
Очистка ресурсов с помощью предложений finally .
Восстановление в результате возникновения исключений, несовместимых с CLS.
Делегаты и события
Делегаты и события предоставляют возможность формирования стратегий использования слабосвязанных
блоков кода.
Объявление, создание и использование делегатов.
Объединение многоадресных делегатов.
События предоставляют механизм публикации уведомлений или подписки на них.
Подписка и отмена подписки на события.
Реализация событий, объявленных в интерфейсах.
Соответствие рекомендациям .NET Framework при публикации событий кодом.
Создание событий, определенных в базовых классах, из производных классов.
Реализация пользовательских методов доступа к событиям.
Рекомендации по LINQ
LINQ позволяет создавать код для запросов к любому источнику данных, который поддерживает шаблон
выражения запроса LINQ. Сведения в следующих статьях помогут понять принципы использования
шаблонов и работы с различными источниками данных.
Запрос коллекции.
Использование лямбда-выражений в запросах.
Использование var в выражениях запросов.
Возвращение поднаборов свойств элементов из запроса.
Создание запросов со сложной фильтрацией.
Сортировка элементов источника данных.
Сортировка элементов по нескольким ключам.
Управление типом проекции.
Подсчет вхождений значения в исходной последовательности.
Вычисление промежуточных значений.
Слияние данных из нескольких источников.
Нахождение разности наборов между двумя последовательностями.
Отладка пустых результатов запроса.
Добавление настраиваемых методов в запросы LINQ.
Метод String.Split создает массив подстрок, разбивая входную строку по одному или нескольким
разделителям. Часто это самый простой способ разделить строку по границам слов. Он также используется
для разбиения строк по другим конкретным символам или строкам.
NOTE
Примеры C# в этой статье выполняются во встроенном средстве выполнения кода и на площадке Try.NET. Нажмите
на кнопку Выполнить, чтобы выполнить пример в интерактивном окне. После выполнения кода вы можете
изменить его и выполнить измененный код, снова нажав на кнопку Выполнить. Либо в интерактивном окне
выполняется измененный код, либо, если компиляция завершается с ошибкой, в интерактивном окне отображаются
все сообщения об ошибках компилятора C#.
Следующий код разбивает обычную фразу на массив строк для каждого слова.
string phrase = "The quick brown fox jumps over the lazy dog.";
string[] words = phrase.Split(' ');
Каждый экземпляр знака разделения создает значение в возвращаемом массиве. Последовательные знаки
разделения создают пустую строку в виде значения в возвращаемом массиве. Это можно увидеть в
следующем примере, где в качестве разделителя используется пробел:
string phrase = "The quick brown fox jumps over the lazy dog.";
string[] words = phrase.Split(' ');
Такое поведение упрощает работу с такими форматами, как файл данных с разделителями-запятыми (CSV ),
которые представляют табличные данные. Идущие подряд запятые представляют пустой столбец.
Чтобы исключить из возвращаемого массива все пустые строки, можно передать необязательный параметр
StringSplitOptions.RemoveEmptyEntries. Для более сложной обработки возвращенной коллекции можно
использовать LINQ, чтобы управлять результирующей последовательностью.
String.Split может использовать несколько знаков разделения. В приведенном ниже примере в качестве
знаков разделения используются пробелы, запятые, точки, двоеточия и символы табуляции, которые
передаются в метод Split в виде массива. Цикл в конце кода отображает каждое из слов в возвращенном
массиве.
char[] delimiterChars = { ' ', ',', '.', ':', '\t' };
Метод String.Split может принимать массив строк (в этом случае в качестве разделителей при анализе
целевой строки используются последовательности символов, а не отдельные символы).
Вы можете оценить эти примеры, просмотрев код в нашем репозитории GitHub. Или можете загрузить
образцы в ZIP -файле.
См. также
Руководство по программированию на C#
Строки
Регулярные выражения .NET
Практическое руководство. Сцепка нескольких
строк (руководство по C#)
25.11.2019 • 5 minutes to read • Edit Online
Объединение подразумевает добавление одной строки к концу другой. Вы можете сцеплять строки с
помощью оператора + . Строковые литералы и константы сцепляются во время компиляции, а не во время
выполнения. Строковые переменные сцепляются только во время выполнения.
NOTE
Примеры C# в этой статье выполняются во встроенном средстве выполнения кода и на площадке Try.NET. Нажмите
на кнопку Выполнить, чтобы выполнить пример в интерактивном окне. После выполнения кода вы можете
изменить его и выполнить измененный код, снова нажав на кнопку Выполнить. Либо в интерактивном окне
выполняется измененный код, либо, если компиляция завершается с ошибкой, в интерактивном окне отображаются
все сообщения об ошибках компилятора C#.
Следующий пример показывает использование сцепки для разделения длинного строкового литерала на
строки меньшего размера, чтобы повысить удобочитаемость исходного кода. Эти части объединяются в
одну строку во время компиляции. Количество строк не влияет на производительность во время
выполнения.
System.Console.WriteLine(text);
Для сцепки строковых переменных вы можете использовать операторы + или += , интерполяцию строк, а
также методы String.Format, String.Concat, String.Join или StringBuilder.Append. Оператор + прост в
использовании и позволяет получить интуитивно понятный код. Даже если в одном выражении
используется несколько операторов + , содержимое строки копируется только один раз. В следующем коде
показаны примеры использования операторов + и += для сцепки строк:
В некоторых выражениях строки проще сцепить с помощью интерполяции, как показано в следующем коде:
string userName = "<Type your name here>";
string date = DateTime.Today.ToShortDateString();
NOTE
В операциях сцепки строк компилятор C# обрабатывает строки NULL так же, как пустые строки.
Другие методы сцепки строк: String.Format. Этот метод лучше использовать при создании строки из
небольшого числа строк-компонентов.
В других случаях вы можете сцеплять строки во время цикла и не знать, сколько исходных строк вы
сцепляете. При этом фактическое число исходных строк может быть довольно большим. Для этих сценариев
предназначен класс StringBuilder. В следующем коде для сцепки строк используется метод Append класса
StringBuilder.
См. дополнительные сведения о причинах для выбора сцепки строк или класса StringBuilder .
Другой вариант объединения строк из коллекции — использовать метод String.Concat. Используйте метод
String.Join, если исходные строки должны быть разделены разделителем. Следующий код объединяет
массив слов с помощью обоих методов:
string[] words = { "The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog." };
Другой вариант объединения строк из коллекции — использовать LINQ и метод Enumerable.Aggregate. Этот
метод объединяет исходные строки с помощью лямбда-выражения. Лямбда-выражение добавляет каждую
строку к существующему накоплению. Следующий пример показывает объединение массива слов путем
добавления пробелов между словами.
string[] words = { "The", "quick", "brown", "fox", "jumps", "over", "the", "lazy", "dog." };
Вы можете оценить эти примеры, просмотрев код в нашем репозитории GitHub. Или можете загрузить
образцы в ZIP -файле.
См. также
String
StringBuilder
Руководство по программированию на C#
Строки
Синтаксический анализ строк даты и времени в
.NET
04.11.2019 • 11 minutes to read • Edit Online
Чтобы анализировать строки для преобразования их в объекты DateTime, нужно указать, как значения даты
и времени представлены в текстовом формате. Для различных языков и региональных параметров значения
дня, месяца и года приводятся в разном порядке. В одних представлениях времени используется 24-часовой
формат, в других же указываются значения AM и PM. В одних приложениях требуется только дата. В других
— только время. А в некоторых нужно указывать и дату, и время. Методы преобразования строк в объекты
DateTime позволяют предоставлять подробные сведения о требуемых форматах и элементах дат и времени,
которые необходимы для вашего приложения. Есть три подзадачи для правильного преобразования текста в
DateTime:
1. Укажите требуемый формат текста, представляющего дату и время.
2. Можно указать язык и региональные параметры для формата даты и времени.
3. Можно указать, как отсутствующие в тексте компоненты задаются в значениях даты и времени.
Методы Parse и TryParse позволяют преобразовать много стандартных представлений даты и времени.
Методы ParseExactи TryParseExact позволяют преобразовать строковое представление, соответствующее
шаблону, указанному в строке формата даты и времени. (Дополнительные сведения см. в статьях Строки
стандартных форматов даты и времени и Строки настраиваемых форматов даты и времени.)
Текущий объект DateTimeFormatInfo обеспечивает более точный контроль над тем, как текст должен
интерпретироваться в качестве даты и времени. В свойствах DateTimeFormatInfo описываются разделители
даты и времени, названия месяцев, дней и эр, а также формат для обозначения AM и PM. Объект
DateTimeFormatInfo в текущем потоке представляет текущие язык и региональные параметры. Если
требуется задать определенные язык и региональные параметры или настраиваемые параметры, укажите
для метода анализа параметр IFormatProvider. Для параметра IFormatProvider следует указать объект
CultureInfo, представляющий язык и региональные параметры, или объект DateTimeFormatInfo.
В тексте, представляющем дату и время, могут отсутствовать некоторые сведения. Например, большинство
пользователей будет считать, что дата "12 марта" относится к текущему году. Аналогичным образом, "март
2018 г." представляет месяц март 2018 года. Текст, представляющий время, часто включает только часы,
минуты и обозначение AM или PM. При помощи методов анализа эти отсутствующие данные
обрабатываются с использованием обоснованных значений по умолчанию:
если указано только время, в части даты используется текущая дата;
если указана только дата, в части времени задается полночь;
если в дате не указан год, используется текущий год;
если не указано число месяца, задается первое число месяца.
Если в строке есть дата, она должна включать месяц и день или год. Если указано время, значение должно
содержать час и минуты или обозначение AM либо PM.
Чтобы переопределить эти значения по умолчанию, можно задать константу NoCurrentDateDefault. Если вы
используете эту константу, для всех отсутствующих параметров года, месяца или дня устанавливается
значение 1 . Это демонстрирует последний пример, в котором применяется Parse.
Помимо компонента даты и времени строковое представление даты и времени может содержать смещение,
которое указывает, насколько время отличается от универсального синхронизированного времени (UTC ).
Например, строка "14/02/2007 5:32:00 -7: 00" определяет время, которое на семь часов меньше, чем UTC.
Если в строковом представлении времени не задано смещение, то синтаксический анализ возвращает
объект DateTime, свойство Kind которого имеет значение DateTimeKind.Unspecified. Если смещение задано,
то синтаксический анализ возвращает объект DateTime, свойство Kind которого имеет значение
DateTimeKind.Local и значение которого установлено с учетом местного часового пояса компьютера. Это
поведение можно изменить, указав для метода анализа значение DateTimeStyles.
Поставщик формата также используется для интерпретации неоднозначных числовых дат. Например, в
строке "02/03/04" неясно, какие компоненты соответствуют месяцу, дню и году. Такие компоненты
интерпретируются согласно их порядку расположения в схожих форматах даты в поставщике формата.
Parse
Ниже приведен пример использования метода DateTime.Parse для преобразования string в DateTime. В
этом примере используются язык и региональные параметры, связанные с текущим потоком. Если класс
CultureInfo, связанный с текущим значением языка и региональных параметров, не может выполнить
синтаксический анализ исходной строки, создается исключение FormatException.
TIP
Все примеры C# в этой статье выполняются в браузере. Нажмите кнопку Выполнить, чтобы просмотреть выходные
данные. Вы можете поэкспериментировать, изменяя их значения.
NOTE
Эти примеры для C# и Visual Basic вы можете найти в репозитории документации GitHub. Вы также можете загрузить
проект в виде ZIP-файла для C# или Visual Basic.
Кроме того, вы можете явно определить язык и региональные параметры, соглашения о форматировании
для которых используются при анализе строки. Укажите один из стандартных объектов DateTimeFormatInfo,
возвращенных свойством CultureInfo.DateTimeFormat. В приведенном ниже примере поставщик формата
используется для анализа строки на немецком языке в DateTime. Для представления языка и региональных
параметров de-DE создается CultureInfo. Этот объект CultureInfo обеспечивает успешный анализ
определенной строки. Это устраняет необходимость задания каких-либо параметров в CurrentCulture
потока CurrentThread.
CultureInfo MyCultureInfo = new CultureInfo("de-DE");
string MyString = "12 Juni 2008";
DateTime MyDateTime = DateTime.Parse(MyString, MyCultureInfo);
Console.WriteLine(MyDateTime);
// The example displays the following output:
// 6/12/2008 12:00:00 AM
Для указания поставщиков пользовательских форматов можно использовать перегрузки метода Parse. Но
такой метод не поддерживает анализ нестандартных форматов. Вместо этого используйте метод ParseExact
для анализа даты и времени, выраженных в нестандартном формате.
В приведенном ниже примере перечисление DateTimeStyles указывает, что текущие значения даты и
времени не нужно добавлять в DateTime для неуказанных полей.
ParseExact
Метод DateTime.ParseExact позволяет преобразовать строку в объект DateTime, если она соответствует
одному из указанных шаблонов строк. Если в этот метод передается строка, не соответствующая ни одному
из указанных шаблонов, создается исключение FormatException. Можно задать один из стандартных
описателей формата даты и времени или сочетание пользовательских описателей формата. При
использовании пользовательских описателей формата можно сконструировать пользовательскую строку
распознавания. Сведения об описателях см. в разделах Строки стандартных форматов даты и времени и
Строки настраиваемых форматов даты и времени.
В приведенном ниже примере в метод DateTime.ParseExact передается переназначенный для анализа
строковый объект. Затем следует описатель формата, который сопровождается объектом CultureInfo. С
помощью этого метода ParseExact можно анализировать только строки, соответствующие шаблону полной
даты для языка и региональных параметров en-US .
CultureInfo MyCultureInfo = new CultureInfo("en-US");
string[] MyString = { " Friday, April 10, 2009", "Friday, April 10, 2009" };
foreach (string dateString in MyString)
{
try
{
DateTime MyDateTime = DateTime.ParseExact(dateString, "D", MyCultureInfo);
Console.WriteLine(MyDateTime);
}
catch (FormatException)
{
Console.WriteLine("Unable to parse '{0}'", dateString);
}
}
// The example displays the following output:
// Unable to parse ' Friday, April 10, 2009'
// 4/10/2009 12:00:00 AM
При каждой перегрузке методов Parse и ParseExact также используется параметр IFormatProvider, который
предоставляет сведения о языке и региональных параметрах для форматирования строки. Этот объект
IFormatProvider является объектом CultureInfo, который представляет стандартные язык и региональные
параметры, или объектом DateTimeFormatInfo, возвращаемым в свойстве CultureInfo.DateTimeFormat. Кроме
того, в методе ParseExact используется дополнительная строка или аргумент массива строк для определения
одного или нескольких настраиваемых форматов даты и времени.
См. также
Parsing Strings
Типы форматирования
Преобразование типов в .NET
Стандартные форматы даты и времени
Строки настраиваемых форматов даты и времени
Практическое руководство. Поиск по строкам
25.11.2019 • 6 minutes to read • Edit Online
Существует две основные стратегии для поиска текста в строках. Методы класса String выполняют поиск
определенного текста. Регулярные выражения используются для поиска шаблонов в тексте.
NOTE
Примеры C# в этой статье выполняются во встроенном средстве выполнения кода и на площадке Try.NET. Нажмите
на кнопку Выполнить, чтобы выполнить пример в интерактивном окне. После выполнения кода вы можете
изменить его и выполнить измененный код, снова нажав на кнопку Выполнить. Либо в интерактивном окне
выполняется измененный код, либо, если компиляция завершается с ошибкой, в интерактивном окне отображаются
все сообщения об ошибках компилятора C#.
Тип string является псевдонимом класса System.String и реализует ряд полезных методов для поиска
содержимого строк. Среди них: Contains, StartsWith, EndsWith, IndexOf, LastIndexOf. Класс
System.Text.RegularExpressions.Regex предоставляет широкие возможности словаря для поиска шаблонов в
тексте. В этой статье вы узнаете, как применять эти методы и как выбрать наилучший способ в зависимости
от ваших потребностей.
string factMessage = "Extension methods have all the capabilities of regular static methods.";
// For user input and strings that will be displayed to the end user,
// use the StringComparison parameter on methods that have it to specify how to match strings.
bool ignoreCaseSearchResult = factMessage.StartsWith("extension",
System.StringComparison.CurrentCultureIgnoreCase);
Console.WriteLine($"Starts with \"extension\"? {ignoreCaseSearchResult} (ignoring case)");
В предыдущем примере показано важное правило использования этих методов. По умолчанию поиск
выполняется с учетом регистра. Для выполнения поиска без учета регистра используйте значение
перечисления StringComparison.CurrentCultureIgnoreCase.
string factMessage = "Extension methods have all the capabilities of regular static methods.";
PATTERN ЗНАЧЕНИЕ
\s Соответствует пробелу.
string[] sentences =
{
"Put the water over there.",
"They're quite thirsty.",
"Their water bottles broke."
};
if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern,
System.Text.RegularExpressions.RegexOptions.IgnoreCase))
{
Console.WriteLine($" (match for '{sPattern}' found)");
}
else
{
Console.WriteLine();
}
}
TIP
Методы string обычно удобнее при поиске точного совпадения со строкой. Регулярные выражения больше
подходят при поиске определенных шаблонов в исходной строке.
PATTERN ЗНАЧЕНИЕ
if (System.Text.RegularExpressions.Regex.IsMatch(s, sPattern))
{
Console.WriteLine(" - valid");
}
else
{
Console.WriteLine(" - invalid");
}
}
Один шаблон поиска соответствует множеству допустимых строк. Регулярные выражения больше подходят
для поиска или проверки соответствия шаблону, а не для поиска отдельной строки текста.
Вы можете оценить эти примеры, просмотрев код в нашем репозитории GitHub. Или можете загрузить
образцы в ZIP -файле.
См. также
Руководство по программированию на C#
Строки
LINQ и строки
System.Text.RegularExpressions.Regex
Регулярные выражения в .NET Framework
Элементы языка регулярных выражений — краткий справочник
Рекомендации по использованию строк в .NET
Практическое руководство. Изменение
содержимого строки в C#
25.11.2019 • 9 minutes to read • Edit Online
В этой статье демонстрируется несколько способов получения string путем изменения существующего
объекта string . Все показанные методы возвращают результат изменений как новый объект string .
Чтобы это было очевидно, во всех примерах результат сохраняется в новой переменной. Затем вы можете
изучить исходный объект string и объект string , полученный в результате изменений при выполнении
каждого примера.
NOTE
Примеры C# в этой статье выполняются во встроенном средстве выполнения кода и на площадке Try.NET. Нажмите
на кнопку Выполнить, чтобы выполнить пример в интерактивном окне. После выполнения кода вы можете
изменить его и выполнить измененный код, снова нажав на кнопку Выполнить. Либо в интерактивном окне
выполняется измененный код, либо, если компиляция завершается с ошибкой, в интерактивном окне отображаются
все сообщения об ошибках компилятора C#.
В статье приводится несколько методов. Вы можете заменять существующий текст. Вы можете искать
шаблоны и заменять соответствующий текст другим. Вы можете рассматривать строку как
последовательность символов. Вы также можете использовать удобные методы удаления пробелов.
Выберите метод, наиболее подходящий для вашего сценария.
Замена текста
Следующий код создает новую строку, заменяя существующий текст.
В коде выше демонстрируется неизменяемое свойство строк. В примере видно, что исходная строка source
не изменяется. Метод String.Replace создает новый объект string , содержащий изменения.
Метод Replace может заменять строки или отдельные символы. В обоих случаях заменяется каждое
вхождение искомого текста. В следующем примере все символы ' ' заменяются на '_':
Удаление текста
Вы можете удалять из строки текст с помощью метода String.Remove. Этот метод удаляет определенное
число символов, начиная с указанного индекса. В следующем примере показано, как использовать
String.IndexOf с Remove для удаления текста из строки:
string phrase = "The quick brown fox jumps over the fence";
Console.WriteLine(phrase);
unsafe
{
// Compiler will store (intern)
// these strings in same location.
string helloOne = "Hello";
string helloTwo = "Hello";
Вы можете оценить эти примеры, просмотрев код в нашем репозитории GitHub. Или можете загрузить
образцы в ZIP -файле.
См. также
Регулярные выражения в .NET Framework
Элементы языка регулярных выражений — краткий справочник
Сравнение строк в C#
25.11.2019 • 16 minutes to read • Edit Online
Сравнивая строки, вы хотите ответить на один из двух вопросов: "Равны ли две эти строки?" или "В каком
порядке должны следовать эти строки при их сортировке?".
Однако эту задачу осложняют факторы, влияющие на сравнение строк:
Вы можете выбрать порядковое или лингвистическое сравнение.
Вы можете указать, учитывается ли регистр.
Вы можете выбрать сравнения для конкретного языка и региональных параметров.
Лингвистические сравнения зависят от языка и региональных параметров, а также от используемой
платформы.
NOTE
Примеры C# в этой статье выполняются во встроенном средстве выполнения кода и на площадке Try.NET. Нажмите
на кнопку Выполнить, чтобы выполнить пример в интерактивном окне. После выполнения кода вы можете
изменить его и выполнить измененный код, снова нажав на кнопку Выполнить. Либо в интерактивном окне
выполняется измененный код, либо, если компиляция завершается с ошибкой, в интерактивном окне отображаются
все сообщения об ошибках компилятора C#.
Console.WriteLine($"Using == says that <{root}> and <{root2}> are {(root == root2 ? "equal" : "not equal")}");
Console.WriteLine($"Ordinal ignore case: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");
Console.WriteLine($"Ordinal static ignore case: <{root}> and <{root2}> are {(areEqual ? "equal." : "not
equal.")}");
if (comparison < 0)
Console.WriteLine($"<{root}> is less than <{root2}>");
else if (comparison > 0)
Console.WriteLine($"<{root}> is greater than <{root2}>");
else
Console.WriteLine($"<{root}> and <{root2}> are equivalent in order");
При выполнении порядкового сравнения без учета регистра эти методы используют соглашения о регистре
для инвариантного языка и региональных параметров.
Лингвистические сравнения
Строки могут быть упорядочены с использованием лингвистических правил для текущих значений языка и
региональных параметров. Иногда это называется порядком сортировки слов. При лингвистическом
сравнении некоторые символы Юникода, отличные от алфавитно-цифровых, могут иметь особые весовые
коэффициенты. Например, дефис "-" может иметь очень низкий весовой коэффициент, чтобы слова "co-op"
и "coop" находились рядом друг с другом в порядке сортировки. Кроме того, некоторые символы Юникода
могут быть эквивалентны последовательности экземпляров Char. В следующем примере используется фраза
"Они танцуют на улице." на немецком языке с буквами "ss" (U+0073 U+0073) в одной строке и буквой "ß"
(U+00DF ) в другой. Лингвистически (в Windows) буквы "ss" равнозначны немецкому символу эсцет "ß" в
языках "en-US" и "de-DE".
string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";
showComparison(word, words);
showComparison(word, other);
showComparison(words, other);
void showComparison(string one, string two)
{
int compareLinguistic = String.Compare(one, two, StringComparison.InvariantCulture);
int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
if (compareLinguistic < 0)
Console.WriteLine($"<{one}> is less than <{two}> using invariant culture");
else if (compareLinguistic > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using invariant culture");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order using invariant culture");
if (compareOrdinal < 0)
Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
else if (compareOrdinal > 0)
Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
else
Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
}
В Windows порядок сортировки "cop", "coop" и "co-op" изменяется при переходе от лингвистического
сравнения к порядковому. Два предложения на немецком языке также сравниваются по-разному при
использовании разных типов сравнения.
Сравнения с учетом языка и региональных параметров обычно используются для сравнения и сортировки
строк, вводимых пользователями. Символы и правила сортировки этих строк могут различаться в
зависимости от языкового стандарта компьютера пользователя. Даже строки, содержащие идентичные
символы, могут быть отсортированы по-разному, в зависимости от языка и региональных параметров
текущего потока. Кроме того, попробуйте запустить этот пример кода локально на компьютере с Windows, и
вы получите следующие результаты:
Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}
Console.WriteLine("\n\rSorted order:");
После сортировки массива можно выполнить поиск записей с помощью двоичного поиска. Двоичный поиск
начинается с середины коллекции, чтобы определить, какая половина коллекции содержит искомую строку.
Каждое последующее сравнение делит оставшуюся часть коллекции пополам. Массив сортируется с
использованием StringComparer.CurrentCulture. Локальная функция ShowWhere отображает сведения о том,
где была найдена строка. Если строка не найдена, возвращаемое значение указывает, где бы оно находилось,
если было бы найдено.
string[] lines = new string[]
{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
Array.Sort(lines, StringComparer.CurrentCulture);
if (index == 0)
Console.Write("beginning of sequence and ");
else
Console.Write($"{array[index - 1]} and ");
if (index == array.Length)
Console.WriteLine("end of sequence.");
else
Console.WriteLine($"{array[index]}.");
}
else
{
Console.WriteLine($"Found at index {index}.");
}
}
Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
Console.WriteLine($" {s}");
}
Console.WriteLine("\n\rSorted order:");
После сортировки по списку строк можно осуществлять двоичный поиск. Приведенный ниже пример
описывает поиск в отсортированном списке с использованием той же функции сравнения. Локальная
функция ShowWhere показывает, где находится или находился бы искомый текст:
List<string> lines = new List<string>
{
@"c:\public\textfile.txt",
@"c:\public\textFile.TXT",
@"c:\public\Text.txt",
@"c:\public\testfile2.txt"
};
lines.Sort((left, right) => left.CompareTo(right));
if (index == 0)
Console.Write("beginning of sequence and ");
else
Console.Write($"{collection[index - 1]} and ");
if (index == collection.Count)
Console.WriteLine("end of sequence.");
else
Console.WriteLine($"{collection[index]}.");
}
else
{
Console.WriteLine($"Found at index {index}.");
}
}
Всегда используйте один и тот же тип сравнения для сортировки и поиска. Использование разных типов
сравнения приводит к неожиданным результатам.
Классы коллекций, такие как System.Collections.Hashtable,
System.Collections.Generic.Dictionary<TKey,TValue>, и System.Collections.Generic.List<T>, имеют
конструкторы, принимающие параметр System.StringComparer, если типом элементов или ключей является
string . В целом, по возможности следует использовать эти конструкторы и задавать либо
StringComparer.Ordinal, либо StringComparer.OrdinalIgnoreCase.
if (String.ReferenceEquals(a, b))
Console.WriteLine("a and b are interned.");
else
Console.WriteLine("a and b are not interned.");
string c = String.Copy(a);
if (String.ReferenceEquals(a, c))
Console.WriteLine("a and c are interned.");
else
Console.WriteLine("a and c are not interned.");
NOTE
При проверке строк на равенство нужно использовать методы, которые явно указывают, какой вид сравнения
следует выполнить. Это делает код намного более понятным и удобочитаемым. Используйте перегрузки методов
классов System.String и System.Array, которые принимают параметр перечисления StringComparison. Это позволяет
указать тип выполняемого сравнения. Старайтесь избегать использования операторов == и != при проверке на
равенство. Методы экземпляра String.CompareTo всегда выполняют порядковое сравнение с учетом регистра. Они
предназначены, прежде всего, для упорядочивания строк в алфавитном порядке.
Вы можете интернировать строку или получить ссылку на существующую интернированную строку, вызвав
метод String.Intern. Чтобы определить, является ли строка интернированной, вызовите метод
String.IsInterned.
См. также
System.Globalization.CultureInfo
System.StringComparer
Строки
Сравнение строк в .NET Framework
Глобализация и локализация приложений
Практическое руководство. Безопасное
приведение с помощью сопоставления шаблонов,
а также операторов is и as
25.11.2019 • 6 minutes to read • Edit Online
В связи с полиморфизмом объектов переменная типа базового класса может содержать производный тип.
Для доступа к членам экземпляра производного типа необходимо привести значение обратно к
производному типу. Однако приведение создает риск возникновения исключения InvalidCastException. C#
предоставляет операторы сопоставления шаблонов, которые выполняют приведение условно только в
случае успеха. C# также предоставляет операторы is и as для проверки определенного типа значения.
В следующем коде показан оператор сопоставления шаблонов is . Он содержит методы, которые
проверяют аргумент метода, чтобы определить, является ли он одним из возможных наборов производных
типов.
class Animal
{
public void Eat() { Console.WriteLine("Eating."); }
public override string ToString()
{
return "I am an animal.";
}
}
class Mammal : Animal { }
class Giraffe : Mammal { }
class SuperNova { }
class Program
{
static void Main(string[] args)
{
Giraffe g = new Giraffe();
FeedMammals(g);
TestForMammals(g);
int? j = null;
PatternMatchingNullable(j);
double d = 9.78654;
PatternMatchingNullable(d);
PatternMatchingSwitch(i);
PatternMatchingSwitch(j);
PatternMatchingSwitch(d);
}
class Animal
{
public void Eat() { Console.WriteLine("Eating."); }
public override string ToString()
{
return "I am an animal.";
}
}
class Mammal : Animal { }
class Giraffe : Mammal { }
class SuperNova { }
class Program
{
static void Main(string[] args)
{
// Use the is operator to verify the type.
// before performing a cast.
Giraffe g = new Giraffe();
UseIsOperator(g);
double d = 9.78654;
UseAsWithNullable(d);
}
Как видно при сравнении этого кода с кодом сопоставления шаблонов, синтаксис сопоставления шаблонов
предоставляет более надежные функции, поскольку сочетает в себе проверку и назначение в одном
операторе. Используйте синтаксис сопоставления шаблонов во всех подходящих случаях.
Вы можете оценить эти примеры, просмотрев код в нашем репозитории GitHub. Или можете загрузить
образцы в ZIP -файле.
Пакет SDK для .NET Compiler Platform
23.10.2019 • 11 minutes to read • Edit Online
Следующие шаги
Пакет SDK для .NET Compiler Platform включает новейшие объектные модели языков для создания, анализа
и оптимизации кода. Этот раздел содержит обзор основных преимуществ пакета SDK для .NET Compiler
Platform. Дополнительные сведения можно найти в кратких руководствах, примерах и другой документации.
Дополнительные сведения об основных преимуществах пакета SDK для .NET Compiler Platform см. в
следующих пяти разделах:
Изучение кода с помощью визуализатора синтаксиса
Understand the .NET Compiler Platform SDK model (Сведения о модели SDK для .NET Compiler Platform)
Work with syntax (Работа с синтаксисом)
Work with semantics (Работа с семантикой)
Work with a workspace (Использование рабочей области)
Чтобы приступить к работе, потребуется установить пакет SDK для .NET Compiler Platform:
Компиляторы обрабатывают код, который вы пишете, следуя структурированным правилам, которые часто
отличаются от того, как читают и воспринимают код люди. Понимание принципов работы модели,
используемой компиляторами, важно для изучения API, используемых при создании средств на основе
Roslyn.
Каждый этап конвейера — это отдельный компонент. Сначала на этапе синтаксического анализа
выполняется расстановка токенов и анализ текста исходного кода с учетом синтаксиса, а затем и грамматики
языка. Потом на этапе объявления анализируется источник и импортированные метаданные для
формирования именованных символов. Далее на этапе привязки идентификаторы в коде сопоставляются с
символами. Наконец, на этапе порождения создается сборка со всеми сведениями, сформированными
компилятором.
В соответствии с каждым из этих этапов пакет SDK для .NET Compiler Platform предоставляет объектную
модель, обеспечивающую доступ к информации на этом этапе. Этап анализа предоставляет дерево
синтаксиса, этап объявления предоставляет таблицу иерархических символов, этап привязки предоставляет
результат семантического анализа компилятора, а этап порождения — это API, создающий коды байтов IL.
Каждой компилятор сочетает в себе эти компоненты, образуя единый процесс.
Эти API также используются в Visual Studio. Например, функции структурирования и форматирования кода
используют деревья синтаксиса, обозреватель объектов и функции навигации — таблицу символов,
рефакторинг и операции "Перейти к определению" — семантическую модель, а операция "Изменить и
продолжить" использует их все, включая API порождения.
Слои API
Пакет SDK компилятора .NET состоит из двух основных слоев API: API компиляторов и API рабочих
областей.
API компилятора
Слой компилятора содержит объектные модели, которые соответствуют сведениям, представляемым на
каждом этапе конвейера компилятора, как синтаксическим, так и семантическим. Слой компилятора также
содержит неизменяемый моментальный снимок однократного вызова компилятора, включая ссылки на
сборки, параметры компилятора и файлы исходного кода. Существует два отдельных API, представляющих
язык C# и Visual Basic. Эти два API похожи по форме, но для обеспечения высокой точности привязаны к
конкретному языку. Этот слой не имеет зависимостей от компонентов Visual Studio.
API диагностики
В процессе анализа компилятор может создать набор данных диагностики, охватывающий все — от
синтаксиса, семантики и явных ошибок присваивания до различных предупреждений и информационных
диагностических сообщений. Слой API компилятора предоставляет диагностику посредством расширяемого
API, который разрешает подключать пользовательские анализаторы к процессу компиляции. Она позволяет
создавать заданные пользователем диагностические данные, например, формируемые такими
программами, как StyleCop или FxCop, параллельно с диагностическими данными, определенными
компилятором. Такое формирование данных диагностики дает преимущество естественной интеграции со
средствами, такими как MSBuild и Visual Studio, которые зависят от диагностики при применении таких
процедур, как прерывание сборки на основе политики, отображение динамических волнистых линий в
редакторе и предложение исправлений кода.
API скриптов
API размещения и скриптов являются частью слоя компилятора. Их можно использовать для выполнения
фрагментов кода и накопления контекста выполнения во время выполнения. Интерактивная среда REPL
(read-evaluate-print loop) C# использует эти API. REPL позволяет использовать C# в качестве скриптового
языка C#, выполняя код в интерактивном режиме по мере его написания.
API рабочих областей
Слой рабочих областей содержит соответствующий API, который является отправной точкой для анализа
кода и рефакторинга в рамках целых решений. Он помогает упорядочить всю информацию о проектах в
решении в рамках одной объектной модели, предоставляя вам прямой доступ к объектным моделям слоя
компилятора без необходимости анализировать файлы, настраивать параметры или управлять
зависимостями между проектами.
Кроме того, слой рабочих областей предоставляет набор API, используемый реализацией средств анализа
кода и рефакторинга, которые работают в среде размещения, таких как Visual Studio IDE. В качестве
примеров можно привести API "Найти все ссылки", "Форматирование" и "Создание кода".
Этот слой не имеет зависимостей от компонентов Visual Studio.
Работа с синтаксисом
31.10.2019 • 14 minutes to read • Edit Online
Дерево синтаксиса представляет собой базовую структуру данных, предоставляемую API компилятора.
Эти деревья представляют лексическую и синтаксическую структуру исходного кода. Они служат для двух
важных целей:
1. Чтобы позволить средствам, таким как интегрированная среда разработки, надстройки, средства анализа
кода и рефакторинг, просматривать и обрабатывать синтаксической структуры исходного кода в проекте
пользователя.
2. Чтобы позволить средствам, таким как рефакторинг и интегрированная среда разработки, создавать,
изменять и переупорядочивать исходный код естественным образом без прямых изменений текста.
Создавая деревья и управляя ими, средства легко могут создавать и переупорядочивать исходный код.
Деревья синтаксиса
Деревья синтаксиса образуют первичную структуру, используемую для компиляции, анализа кода,
привязки, рефакторинга, функций интегрированной среды разработки и создания кода. Любая часть
исходного кода становится понятной только после того, как будет идентифицирована и классифицирована
по одному из многих известных структурных элементов языка.
Деревья синтаксиса имеют три ключевых атрибута. Первым атрибутом является то, что деревья синтаксиса
содержат всех сведения об источнике с полной точностью. Это означает, что дерево синтаксиса содержит
всю информацию, отраженную в исходном тексте, каждую грамматическую конструкцию, каждую лексему
и все промежуточные элементы, включая пробелы, комментарии и директивы препроцессора. Например,
каждый упомянутый в исходном коде литерал представлен точно так, как был указан. Деревья синтаксиса
также представляют ошибки в исходном коде, когда программа является неполной или неправильно
сформированной, в виде пропущенных или отсутствующих токенов.
Это позволяет реализовать второй атрибут деревьев синтаксиса. Дерево синтаксиса, предоставленное
синтаксическим анализатором, позволяет воспроизвести точный текст, на основе которого оно было
получено. С любого синтаксического узла можно получить текстовое представление поддерева,
являющегося корнем в этом узле. Это означает, что деревья синтаксиса можно использовать для создания и
изменения текста исходного кода. Создавая дерево, вы косвенно создаете эквивалентный текст, а изменяя
дерево синтаксиса, то есть получая новое дерево на основе изменений существующего, вы фактически
редактируете текст.
Третий атрибут деревьев синтаксиса заключается в том, что они неизменяемы и потокобезопасны. Это
означает, что после получения дерево представляет собой моментальный снимок текущего состояния кода
и никогда не меняется. Это позволяет нескольким пользователям одновременно взаимодействовать с одним
деревом синтаксиса в разных потоках без блокировки или дублирования. Так как деревья являются
неизменяемыми, то есть напрямую в них невозможно внести никакие изменения, фабричные методы
помогают создавать и изменять деревья синтаксиса путем создания их дополнительных снимков. Деревья
эффективно используют базовые узлы повторно, поэтому новую версию можно перестроить быстро и с
небольшими затратами памяти.
Дерево синтаксиса фактически является древовидной структурой данных, где нетерминальные структурные
элементы являются родительскими для других элементов. Каждое дерево синтаксиса состоит из узлов,
токенов и дополнительной синтаксической информации (trivia).
Синтаксические узлы
Синтаксические узлы являются одним из основных элементов деревьев синтаксиса. Они представляют такие
синтаксические конструкции, как объявления, операторы, предложения и выражения. Каждая категория
синтаксических узлов представлена отдельным классом, производным от
Microsoft.CodeAnalysis.SyntaxNode. Набор классов узлов не является расширяемым.
Все синтаксические узлы являются нетерминальными узлами в дереве синтаксиса, значит, всегда имеют
дочерние элементы в виде других узлов и токенов. Как дочерний элемент другого узла каждый узел имеет
родительский узел, к которому можно обратиться с помощью свойства SyntaxNode.Parent. Так как узлы и
деревья являются неизменяемыми, родительский элемент узла никогда не меняется. Корень дерева имеет
родительский элемент null.
Каждый узел имеет метод SyntaxNode.ChildNodes(), возвращающий список дочерних узлов в
последовательном порядке с учетом их позиции в тексте исходного кода. Этот список не содержит токены.
Каждый узел также имеет методы для изучения дочерних объектов, такие как DescendantNodes,
DescendantTokens или DescendantTrivia, которые представляют список всех узлов, токенов или
дополнительной синтаксической информации, которые присутствуют в поддереве, являющемся корнем
этого узла.
Кроме того, каждый подкласс синтаксических узлов предоставляет те же дочерние элементы посредством
строго типизированных свойств. Например, класс узлов BinaryExpressionSyntax имеет три дополнительные
свойства для отдельных бинарных операторов: Left, OperatorToken и Right. Типом Left и Right является
ExpressionSyntax, а типом OperatorToken — SyntaxToken.
Некоторые синтаксические узлы имеют дополнительные дочерние элементы. Например, IfStatementSyntax
имеет дополнительный ElseClauseSyntax. Если дочерний элемент не указан, свойство возвращает значение
null.
Синтаксические токены
Синтаксические токены являются терминалами грамматики языка, представляющими наименьшие
синтаксические фрагменты кода. Они никогда не бывают родителями других узлов или токенов.
Синтаксические токены состоят из ключевых слов, идентификаторов, литералов и знаков препинания.
Для повышения эффективности тип SyntaxToken является типом значений среды CLR. Таким образом, в
отличие от синтаксических узлов существует всего одна структура для всех видов токенов с набором
свойств, значение которых зависит от типа представленного токена.
Например, токен целочисленного литерала представляет числовое значение. Кроме необработанного текста
исходного кода, охватываемого токеном, токен литерала имеет свойство Value, сообщающее точное
декодированное целочисленное значение. Это свойство имеет тип Object, так как может быть одним из
многих типов-примитивов.
Свойство ValueText сообщает те же сведения, что и свойство Value, однако оно всегда имеет тип String.
Идентификатор в тексте исходного кода C# может включать escape-символы Юникода, при этом синтаксис
самой escape-последовательности не считается частью имени идентификатора. Поэтому хотя охватываемый
токеном необработанный текст и включает escape-последовательность, свойство ValueText ее не содержит.
Вместо этого оно включает в себя символы Юникода, определяемые этой escape-последовательностью.
Например, если текст исходного кода содержит идентификатор, записанный как \u03C0 , то свойство
ValueText для этого токена возвратит π .
Диапазоны
Каждому узлу, токену или элементу trivia известно его место в тексте исходного кода и число символов, из
которых он состоит. Положение в тексте представлено в виде 32-разрядного целого числа, являющегося
отсчитываемым от нуля индексом char . Объект TextSpan обозначает начальное положение и количество
символов, представленные в виде целых чисел. Если TextSpan имеет нулевую длину, он обозначает
расположение между двумя символами.
У каждого узла есть два свойства TextSpan: Span и FullSpan.
Свойство Span является текстовым диапазоном от начала первого токена в поддереве узла до конца
последнего токена. Этот диапазон не включает в себя никакие начальные или конечные элементы trivia.
Свойство FullSpan является текстовым диапазоном, включающим в себя обычный диапазон узла, а также
диапазон любых начальных или конечных элементов trivia.
Например:
if (x > 3)
{
|| // this is bad
|throw new Exception("Not right.");| // better exception?||
}
Узел оператора внутри блока имеет диапазон, обозначаемый отдельными вертикальными чертами (|). Он
включает символы throw new Exception("Not right."); . Полный диапазон обозначается двойными
вертикальными чертами (||). Он содержит те же символы, что и обычный диапазон, а также символы,
связанные с начальными или конечными элементами trivia.
Типы
Каждый узел, токен или элемент trivia имеет свойство SyntaxNode.RawKind типа System.Int32,
определяющее конкретный представленный элемент синтаксиса. Это значение можно привести к
перечислению конкретного языка. Каждый язык, C# или VB имеет одно перечисление SyntaxKind
(Microsoft.CodeAnalysis.CSharp.SyntaxKind и Microsoft.CodeAnalysis.VisualBasic.SyntaxKind, соответственно),
содержащее все возможные узлы, токены и элементы trivia в грамматике. Это преобразование может
выполняться автоматически с помощью методов расширения CSharpExtensions.Kind или
VisualBasicExtensions.Kind.
Свойство RawKind позволяет легко устранить неоднозначность типов синтаксических узлов, которые
используют один класс узлов. Для токенов и элементов trivia это свойство является единственным способом
отличить один тип элемента от другого.
Например, один класс BinaryExpressionSyntax имеет дочерние элементы Left, OperatorToken и Right.
Свойство Kind позволяет определить, имеет ли этот синтаксический узел тип AddExpression,
SubtractExpression или MultiplyExpression.
Ошибки
Даже если текст исходного кода содержит синтаксические ошибки, предоставляется полное дерево
синтаксиса, которое обеспечивает круговой путь до источника. Когда синтаксический анализатор
обнаруживает код, который не соответствует заданному синтаксису языка, для создания дерева синтаксиса
используется один из двух способов.
Первый: если синтаксический анализатор ожидает определенный тип токена, но не находит его, он может
вставить отсутствующий токен в дерево синтаксиса в том расположении, где он ожидался. Отсутствующий
токен представляет фактический ожидаемый токен, однако он имеет пустой диапазон, а его свойство
SyntaxNode.IsMissing возвращает true .
Второй: синтаксический анализатор может пропускать токены, пока не найдет такой, с которого можно
продолжить анализ. В этом случае пропущенные токены добавляются в виде узла trivia типа
SkippedTokensTrivia.
Работа с семантикой
23.10.2019 • 6 minutes to read • Edit Online
Деревья синтаксиса представляют лексическую и синтаксическую структуру исходного кода. Хотя одной
этой информации достаточно для описания всех объявлений и логики в источнике, ее недостаточно для
определения того, на что указывает ссылка. Имя может представлять:
тип;
поле;
метод;
локальную переменную.
Хотя каждый из этих элементов имеет уникальные отличия, чтобы определить, на какой из них ссылается
идентификатор, часто требуется хорошо разбираться в правилах языка.
В исходном коде представлены программные элементы, а программы также могут ссылаться на ранее
скомпилированные библиотеки, упакованные в файлы сборки. Хотя исходный код, а, следовательно, и
синтаксические узлы и деревья синтаксиса для сборок недоступны, программы по-прежнему могут
ссылаться на элементы внутри них.
Для этих задач требуется семантическая модель.
Дополняя синтаксическую модель исходного кода, семантическая модель инкапсулирует правила языка,
позволяя вам легко соотнести идентификаторы с программным элементом, на который указывает ссылка.
Компиляция
Компиляция является представлением всего, что необходимо для компиляции программы C# или Visual
Basic, куда входят все ссылки на сборки, параметры компилятора и исходные файлы.
Так как эти сведения находятся в одном месте, элементы, содержащиеся в исходном коде, можно описать
более подробно. Компиляция представляет каждый объявленный тип, член или переменную в виде
символа. Компиляция содержит различные методы, помогающие найти и соотнести символы, которые были
объявлены в исходном коде или импортированы в виде метаданных из сборки.
Аналогично деревьям синтаксиса компиляции являются неизменяемыми. После создания компиляция не
может быть изменена ни вами, ни другим человеком, совместно с которым вы ее используете. Однако вы
можете создать компиляцию из существующей компиляции, указав при этом некоторое изменение.
Например, можно создать компиляцию, которая во всем аналогична существующей компиляции, за
исключением того, что она может включать дополнительный исходный файл или ссылку на сборку.
Символы
Символ представляет отдельный элемент, объявленный исходным кодом или импортированный из сборки в
виде метаданных. Символом представлены все пространства имен, типы, методы, свойства, поля, события,
параметры или локальные переменные.
Различные методы и свойства в типе Compilation помогают вам искать символы. Например, можно найти
символ для объявленного типа по его общему имени метаданных. Можно также получить доступ к целой
таблице символов в виде дерева, корнем которого является глобальное пространство имен.
Символы также содержат дополнительную информацию, которую компилятор определяет из источника
или метаданных, например другие символы, на которые указывает ссылка. Каждый тип символа представлен
отдельным интерфейсом, производным от ISymbol, а также имеет собственные методы и свойства,
описывающие собранные компилятором сведения. Многие из этих свойств напрямую ссылаются на другие
символы. Например, свойство IMethodSymbol.ReturnType сообщает вам фактический символ типа, на
который ссылается объявление метода.
Символы представляют общее представление пространств имен, типов и членов между исходным кодом и
метаданными. Например, метод, который был объявлен в исходном коде, и метод, который был
импортирован из метаданных, оба представлены IMethodSymbol с одинаковыми свойствами.
По своей сути символы похожи на систему типов среды CLR, представленную API System.Reflection, однако
их возможности шире, так как они моделируют не только типы. Пространства имен, локальные переменные
и метки — все это символы. Кроме того, символы являются представлением концепций языка, а не
концепций среды CLR. Есть много похожего, но имеется и большое количество значимых различий.
Например, метод итератора в C# или Visual Basic является отдельным символом. Однако когда метод
итератора преобразуется в метаданные среды CLR, он превращается в тип и несколько методов.
Семантическая модель
Семантическая модель представляет всю семантическую информацию для одного исходного файла. Ее
можно использовать для обнаружения:
символов, на которые указывает ссылка в определенном месте в исходном коде;
результирующего типа выражения;
всех данных диагностики, относящихся к ошибкам и предупреждениям;
перемещений переменных в регионы исходного кода и из них;
ответов на более отвлеченные вопросы.
Использование рабочей области
22.06.2018 • 5 minutes to read • Edit Online
Слой Рабочие области является отправной точкой для анализа кода и рефакторинга в рамках целых
решений. В этом слое API рабочей области помогает упорядочить всю информацию о проектах в решении в
рамках одной объектной модели, предоставляя вам прямой доступ к объектным моделям слоя компилятора,
например тексту исходного кода, деревьям синтаксиса, семантическим моделям и компиляциям, без
необходимости анализировать файлы, настраивать параметры или управлять зависимостями между
проектами.
Среды размещения, такие как интегрированная среда разработки, предоставляют рабочую область,
соответствующую открытому решению. Эту модель можно использовать и вне интегрированной среды
разработки, просто загрузив файл решения.
Рабочая область
Рабочая область является активным представлением вашего решения в виде коллекции проектов, каждый
из которых содержит коллекцию документов. Рабочая область обычно привязывается к среде размещения,
которая постоянно изменяется по мере того, как пользователь вводит или использует свойства.
Workspace предоставляет доступ к текущей модели решения. При возникновении изменения в среде
размещения рабочая область выдает соответствующие события, после чего обновляется свойство
Workspace.CurrentSolution. Например, когда пользовательские типы в текстовом редакторе соответствуют
одному из исходных документов, рабочая область использует событие, чтобы сообщить всей модели об
изменении решения и измененном документе. После этого вы можете отреагировать на такие изменения,
проанализировав новую модель на корректность, выделив значимые области или внеся предложение по
изменению кода.
Кроме того, вы можете создать автономные рабочие области, которые отключены от среды размещения или
используются в приложении без такой среды.
Сводка
Roslyn содержит набор API компиляторов и API рабочих областей, предоставляющий подробные сведения
об исходном коде и обеспечивающий полную точность для языков C# и Visual Basic. Пакет SDK для .NET
Compiler Platform значительно сокращает требования к созданию средств и приложений, ориентированных
на код. Он предоставляет множество возможностей для инноваций в таких областях, как
метапрограммирование, создание и преобразование кода, интерактивное использование языков C# и Visual
Basic, а также их внедрение в доменные языки.
Изучение кода с помощью визуализатора
синтаксиса Roslyn в Visual Studio
23.10.2019 • 16 minutes to read • Edit Online
Эта статья содержит обзор средства визуализатора синтаксиса, входящего в состав пакета SDK для .NET
Compiler Platform ("Roslyn"). Визуализатор синтаксиса — окно инструментов, помогающее просматривать и
изучать деревья синтаксиса. Это средство играет важную роль, так как понимает понять модели для кода,
который требуется проанализировать. Оно также помогает выполнять отладку при разработке приложений
с помощью пакета SDK для .NET Compiler Platform ("Roslyn"). Откройте это средство, чтобы создать свои
первые анализаторы. Визуализатор помогает понять модели, используемые API-интерфейсами. Вы также
можете использовать такие средства, как SharpLab или LINQPad, для просмотра кода и изучения деревьев
синтаксиса.
Закрепите это окно инструментов в удобном месте среды Visual Studio, например слева. Визуализатор
отображает сведения о текущем файле кода.
Создайте проект с помощью команды Файл > Создать проект. Можно создать проект VB или C#. Когда
Visual Studio открывает основной файл кода для этого проекта, визуализатор отображает для него дерево
синтаксиса. Можно открыть любой существующий файл C#/VB в этом экземпляре Visual Studio, и
визуализатор отобразит для него дерево синтаксиса. Если в Visual Studio открыто несколько файлов кода,
визуализатор отображает дерево синтаксиса для активного файла (на который установлен фокус ввода).
C#
Visual Basic
Как показано на рисунках выше, окно инструментов визуализатора отображает дерево синтаксиса сверху и
сетку свойств снизу. Сетка свойств содержит свойства элемента, выбранного в дереве, включая Тип .NET и
Вид (SyntaxKind) этого элемента.
Деревья синтаксиса состоят из трех типов элементов — узлов, токенов и дополнительной информации.
Подробнее об этих типах см. в статье Работа с синтаксисом. Элементы каждого типа выделены
определенным цветом. Нажмите кнопку "Условные обозначения", чтобы просмотреть используемые цвета.
Каждый элемент в дереве также отображает свой диапазон. Диапазон — это индексы (начальное и
конечное положение) узла в текстовом файле. В предыдущем примере C# выбранный токен "UsingKeyword
[0..5)" имеет диапазон шириной пять символов — [0..5). Нотация "[..)" означает, что начальный индекс
входит в диапазон, а конечный — нет.
Перемещаться по дереву можно двумя способами:
Разверните или щелкните элементы в дереве. Визуализатор автоматически выбирает текст,
соответствующий диапазону этого элемента в редакторе кода.
Щелкните или выберите текст в редакторе кода. Если в предыдущем примере VB выбрать в редакторе
кода строку, содержащую "Module Module1", визуализатор автоматически переходит к
соответствующему узлу ModuleStatement в дереве.
Визуализатор выделяет в дереве элемент, диапазон которого лучше всего соответствует диапазону текста,
выделенного в редакторе.
Визуализатор обновляет дерево, чтобы отразить изменения в активном файле кода. Добавьте вызов
Console.WriteLine() внутрь Main() . По мере ввода визуализатор обновляет дерево.
Введя Console. , остановитесь. Некоторые элементы в дереве выделены розовым цветом. На данном этапе
введенный код содержит ошибки (это также называется диагностикой). Эти ошибки присоединяются к
узлам, токенам и дополнительной информации в дереве синтаксиса. Визуализатор показывает, к каким
элементам присоединены ошибки, выделяя их фон розовым цветом. Вы можете просмотреть ошибки для
любого элемента, выделенного розовым цветом, наведя на него указатель мыши. Визуализатор отображает
только синтаксические ошибки (связанные с синтаксисом введенного кода); семантические ошибки он не
показывает.
Графы синтаксиса
Щелкните правой кнопкой мыши любой элемент в дереве и выберите команду View Directed Syntax Graph
(Просмотреть направленный граф синтаксиса).
C#
Visual Basic
Визуализатор отображает графическое представление поддерева, корнем которого является выбранный
элемент. Повторите эти шаги для узла MethodDeclaration, соответствующего методу Main() в примере C#.
Визуализатор отображает граф синтаксиса, который выглядит следующим образом:
Средство просмотра графов синтаксиса имеет параметр для отображения условных обозначений для
цветовой схемы. Кроме того, можно навести указатель мыши на отдельные элементы графа синтаксиса,
чтобы просмотреть соответствующие им свойства.
Можно многократно просматривать графы синтаксиса для разных элементов в дереве многократно, и эти
графы всегда будут отображаться в одном и том же окне Visual Studio. Это окно можно закрепить в удобном
месте среды Visual Studio, чтобы не нужно было переключаться между вкладками для просмотра нового
графа синтаксиса. Обычно это удобнее всего сделать в нижней части под окнами редактора кода.
Ниже представлен макет закрепления для окна инструментов визуализатора и окна графа синтаксиса:
При работе с двумя мониторами можно также вывести окно графа синтаксиса на второй монитор.
Изучение семантики
Визуализатор синтаксиса обладает простейшими функциями для просмотра символов и семантической
информации. Введите double x = 1 + 1; внутри Main() в примере C#. Выберите выражение 1 + 1 в окне
редактора кода. Визуализатор выделяет узел AddExpression. Щелкните правой кнопкой мыши узел
AddExpression и выберите команду View Symbol (if any) (Просмотреть символ (при наличии)). Обратите
внимание, что большинство пунктов меню имеют квалификатор "if any" (при наличии). Визуализатор
синтаксиса проверяет свойства узла, включая свойства, которые могут присутствовать не для всех узлов.
Сетка свойств в визуализаторе обновляется, как показано на рисунке ниже. Символом для выражения
является SynthesizedIntrinsicOperatorSymbol, у которого Вид = Метод.
Попробуйте выбрать команду View TypeSymbol (if any) (Просмотреть символ типа (при наличии)) для того
же узла AddExpression. Сетка свойств в визуализаторе обновляется, как показано на рисунке ниже,
указывая, что выбранное выражение имеет тип Int32 .
Попробуйте выбрать команду View Converted TypeSymbol (if any) (Просмотреть преобразованный
символ типа (при наличии)) для того же узла AddExpression. Сетка свойств обновляется, указывая, что, хотя
выражение имеет тип Int32 , преобразованным типом выражения является Double , как показано на рисунке
ниже. Этот узел включает сведения о преобразованном символе типа, так как выражение Int32 находится в
контексте, где его необходимо преобразовать в Double . Это преобразование удовлетворяет типу Double ,
указанному для переменной x в левой части оператора присваивания.
Наконец, попробуйте выбрать команду View Constant Value (if any) (Просмотреть постоянное значение
(при наличии)) для того же узла AddExpression. Сетка свойств показывает, что значением выражения
является константа времени компиляции со значением 2 .
Предыдущий пример также можно воспроизвести в Visual Basic. Введите Dim x As Double = 1 + 1 в файле
VB. Выберите выражение 1 + 1 в окне редактора кода. Визуализатор выделяет соответствующий узел
AddExpression. Повторите предыдущие шаги для данного узла AddExpression, полученные результаты
должны быть теми же самыми.
Изучите дополнительный код в VB. Измените основной файл VB, используя следующий код:
Imports C = System.Console
Module Program
Sub Main(args As String())
C.WriteLine()
End Sub
End Module
Этот код представляет псевдоним C , сопоставляемый с типом System.Console в начале файла, и использует
его внутри Main() . Выберите использование этого псевдонима — C в C.WriteLine() — внутри метода
Main() . Визуализатор выбирает соответствующий узел IdentifierName. Щелкните этот узел правой кнопкой
мыши и выберите команду View Symbol (if any) (Просмотреть символ, если есть). Сетка свойств указывает,
что этот идентификатор привязан к типу System.Console , как показано на следующем рисунке:
Попробуйте выбрать команду View AliasSymbol (if any) (Просмотреть символ псевдонима (при наличии))
для того же узла IdentifierName. Сетка свойств указывает, что этот идентификатор является псевдонимом с
именем C , который привязан к целевому объекту System.Console . Другими словами, сетка свойств
предоставляет сведения о AliasSymbol, соответствующем идентификатору C .
Изучите символ, соответствующей любому объявленному типу, методу, свойству. Выберите
соответствующий узел в визуализаторе и щелкните элемент View Symbol (if any) (Просмотреть символ (при
наличии)). Выберите метод Sub Main() , включая его тело. Щелкните View Symbol (if any) (Просмотреть
символ (при наличии)) для соответствующего узла SubBlock в визуализаторе. Сетка свойств показывает, что
MethodSymbol для этого SubBlock имеет имя Main с типом возвращаемого значения Void .
Приведенные выше примеры VB можно легко воспроизвести в C#. Введите using C = System.Console; вместо
Imports C = System.Console для псевдонима. Выполнение описанных выше действий в C# приводит к
аналогичным результатам в окне визуализатора.
Операции семантической проверки доступны только на узлах. Они недоступны для токенов или
дополнительной информации. Не все узлы имеют значимые семантические сведения для изучения. Если узел
не содержит значимой семантической информации, при выборе элемента View * Symbol (if any)
(Просмотреть символ (при наличии) отображается пустая сетка свойств.
Подробнее об API для выполнения семантического анализа см. в обзорном документе Работа с семантикой.
using System;
using System.Collections.Generic;
using System.Linq;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello World!");
}
}
}
Посмотрите на текст предыдущей программы. Вы узнаете знакомые элементы. Весь текст представляет один
исходный файл, или блок компиляции. Первые три строки этого исходного файла являются директивами
using. Остальной исходный код содержится в объявлении пространства имен. Объявление пространства
имен содержит дочернее объявление класса. Объявление класса содержит одно объявление метода.
Синтаксический API создает древовидную структуру, корень которой представляет блок компиляции. Узлы
в дереве представляют директивы using, объявление пространства имен и все остальные элементы
программы. Структура дерева продолжается до самых нижних уровней. Строка "Hello World!" представляет
собой токен строкового литерала, который является потомком аргумента. Синтаксический API
предоставляет доступ к структуре программы. Можно создавать запросы к определенным практикам
написания кода, выполнять обход всего дерева для понимания кода и создавать новые деревья путем
изменения существующего дерева.
Это краткое описание представляет собой обзор видов сведений, к которым можно получить доступ с
помощью синтаксического API. Синтаксический API является не более чем формальным API, который
описывает уже знакомые конструкции кода из C#. Все возможности включают в себя сведения о
форматировании кода, в том числе об использовании разрывов строк, пробелов и отступов. С помощью этих
сведений можно полностью представить код как написанный и прочитанный программистами или
компилятором. Эта структура позволяет взаимодействовать с исходным кодом на глубоко осмысленном
уровне. Это уже не просто строки текста, а данные, представляющие структуру программы на языке C#.
Чтобы приступить к работе, потребуется установить пакет SDK для .NET Compiler Platform:
При перемещении по структуре дерева вы можете найти в файле кода все инструкции, выражения, токены и
пробелы.
С помощью синтаксического API можно найти все, что угодно, в файле кода, но большинство сценариев
предусматривает исследование небольших фрагментов кода либо поиск определенных инструкций или
фрагментов. Ниже приводятся два примера, иллюстрирующие типичные сценарии использования для
просмотра структуры кода или поиска отдельных инструкций.
Обход деревьев
Узлы дерева синтаксиса можно исследовать двумя способами. Можно выполнить обход дерева для
исследования каждого узла или выполнить запрос к определенным элементам или узлам.
Обход вручную
Окончательный код этого примера доступен в репозитории на сайте GitHub.
NOTE
Типы синтаксического дерева используют наследование для описания различных элементов синтаксиса, которые
являются допустимыми в разных местах в программе. Применение этих API часто означает приведение свойств или
элементов коллекции к конкретным производным типам. В следующих примерах назначения и приведения являются
отдельными инструкциями, использующими явно типизированные переменные. Прочитайте код, чтобы увидеть
типы возвращаемых значений API и тип среды выполнения возвращаемых объектов. На практике более
распространено использование неявно типизированных переменных и имен API для описания типа рассматриваемых
объектов.
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
Затем добавьте в константу programText следующий код для создания дерева синтаксиса для текста кода.
Добавьте следующую строку в метод Main :
Эти две строки создают дерево и извлекают корневой узел этого дерева. Теперь можно исследовать узлы в
дереве. Добавьте следующие строки в метод Main для отображения некоторых свойств корневого узла
дерева:
WriteLine($"The tree is a {root.Kind()} node.");
WriteLine($"The tree has {root.Members.Count} elements in it.");
WriteLine($"The tree has {root.Usings.Count} using statements. They are:");
foreach (UsingDirectiveSyntax element in root.Usings)
WriteLine($"\t{element.Name}");
Запустите приложение для просмотра сведений, которые код собрал о корневом узле этого дерева.
Как правило, для получения сведений о коде выполняется обход дерева. В этом примере анализируется
известный вам код для изучения API. Добавьте следующий код для исследования первого элемента узла
root :
Узел объявления метода содержит полные синтаксические сведения о методе. Давайте посмотрим тип
возвращаемого значения для метода Main , количество и типы аргументов, а также основной текст метода.
Добавьте следующий код:
Методы запросов
Помимо обхода деревьев можно также исследовать деревья синтаксиса с помощью методов запросов,
определенных в Microsoft.CodeAnalysis.SyntaxNode. Эти методы известны всем, кто знаком с XPath. Эти
методы можно использовать совместно с LINQ для быстрого поиска в дереве. SyntaxNode содержит такие
методы запросов, как DescendantNodes, AncestorsAndSelf и ChildNodes.
Эти методы запроса можно использовать для поиска аргументов к методу Main в качестве альтернативы
навигации по дереву. Добавьте следующий код в конец метода Main :
WriteLine(argsParameter == argsParameter2);
В первой инструкции используется выражение LINQ и метод DescendantNodes для обнаружения того же
параметра, что и в предыдущем примере.
Запустите программу, и выражение LINQ обнаружит тот же параметр, что и при навигации по дереву
вручную.
В этом образце используются инструкции WriteLine для отображения сведений о деревьях синтаксиса по
мере их обхода. Гораздо больше сведений можно получить, запустив готовую программу в отладчике.
Можно просмотреть другие свойства и методы в составе дерева синтаксиса, созданного для программы
"hello world".
namespace TopLevel
{
using Microsoft;
using System.ComponentModel;
namespace Child1
{
using Microsoft.Win32;
using System.Runtime.InteropServices;
class Foo { }
}
namespace Child2
{
using System.CodeDom;
using Microsoft.CSharp;
class Bar { }
}
}";
Этот исходный текст содержит директивы using , расположенные в четырех разных местах: на уровне
файлов, в пространстве имен верхнего уровня и в двух вложенных пространствах имен. В этом примере
представлен основной сценарий использования класса CSharpSyntaxWalker для выполнения запросов к
коду. Было бы сложно посетить каждый узел в корневом дереве синтаксиса для поиска объявлений using.
Вместо этого создается производный класс и переопределяется метод, который вызывается только в том
случае, когда текущий узел в дереве является директивой using. Обходчик не работает с узлами других
типов. Этот единичный метод проверяет каждую из инструкций using и создает коллекцию пространств
имен, которые не входят в пространство имен System . Создается CSharpSyntaxWalker для проверки всех
инструкций using , но только инструкций using .
Теперь, когда вы определили текст программы, необходимо создать SyntaxTree и получить корень этого
дерева:
Теперь создайте новый класс. В Visual Studio выберите Проект > Добавить новый элемент. В диалоге
Добавление нового элемента введите имя файла UsingCollector.cs.
Вы реализуете функцию обходчика using в классе UsingCollector . Для начала сделайте класс
UsingCollector производным от CSharpSyntaxWalker.
Потребуется хранилище для хранения узлов пространства имен, которые вы будете собирать. Объявите
свойство только для чтения в классе UsingCollector . Эта переменная будет использоваться для хранения
найденных узлов UsingDirectiveSyntax:
Базовый класс, CSharpSyntaxWalker, реализует логику для посещения каждого узла в дереве синтаксиса.
Производный класс переопределяет методы, вызываемые для определенных интересующих вас узлов. В
этом примере вас интересует любая директива using . Это означает, что требуется переопределить метод
VisitUsingDirective(UsingDirectiveSyntax). Единственный аргумент этого метода — объект
Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax. Это важное преимущество обходчиков: они
вызывают переопределенные методы с использованием аргументов, уже приведенных к типу конкретного
узла. Класс Microsoft.CodeAnalysis.CSharp.Syntax.UsingDirectiveSyntax имеет свойство Name, которое
содержит имя импортируемого пространства имен. Это Microsoft.CodeAnalysis.CSharp.Syntax.NameSyntax.
Добавьте следующий код в переопределение VisitUsingDirective(UsingDirectiveSyntax):
Как и в прошлом примере, были добавлены разнообразные инструкции WriteLine для облегчения
понимания этого метода. Вы можете посмотреть, когда он вызывается и какие при этом передаются
аргументы.
Наконец, необходимо добавить две строки кода для создания UsingCollector и направить его в корневой
узел для сбора всех инструкций using . Затем добавьте цикл foreach для отображения всех инструкций
using , найденных сборщиком:
В учебнике предполагается, что вы знакомы с синтаксическим API. Вводные сведения можно найти в статье
о начале работы с синтаксическим анализом.
В этом учебнике вы изучите символы и API привязки. Эти API предоставляют сведения о семантическом
значении программы. Они позволяют задавать вопросы, касающиеся типов, представленных любыми
символами в программе, и получать на них ответы.
Вам нужно установить пакет SDK для .NET Compiler Platform:
NOTE
Типы синтаксического дерева используют наследование для описания различных элементов синтаксиса, которые
являются допустимыми в разных местах в программе. Применение этих API часто означает приведение свойств или
элементов коллекции к конкретным производным типам. В следующих примерах назначения и приведения являются
отдельными инструкциями, использующими явно типизированные переменные. Прочитайте код, чтобы увидеть типы
возвращаемых значений API и тип среды выполнения возвращаемых объектов. На практике более распространено
использование неявно типизированных переменных и имен API для описания типа рассматриваемых объектов.
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
Затем добавьте в константу programText следующий код для создания дерева синтаксиса для текста кода.
Добавьте следующую строку в метод Main :
Далее постройте CSharpCompilation из уже созданного дерева. В примере "Hello World!" используются типы
String и Console. Необходимо сослаться на сборку, объявляющую эти два типа при компиляции. Добавьте
следующую строку в метод Main для создания компиляции дерева синтаксиса, включая ссылку на
соответствующую сборку:
Привязка имени
Compilation создает SemanticModel из SyntaxTree. После создания модели можно выполнить запрос на поиск
первой директивы using и получение сведений о символах для пространства имен System . Добавьте
следующие две строки в метод Main для создания семантической модели и получения символа для первого
оператора using:
// Use the syntax tree to find "using System;"
UsingDirectiveSyntax usingSystem = root.Usings[0];
NameSyntax systemName = usingSystem.Name;
В предыдущем коде показано, как привязать имя в первой директиве using , чтобы получить
Microsoft.CodeAnalysis.SymbolInfo для пространства имен System . В предыдущем коде также
демонстрируется, что для поиска структуры кода используется синтаксическая модель, а для понимания
ее значения — семантическая модель. Синтаксическая модель находит строку System в операторе
using. Семантическая модель располагает всеми сведениями о типах, определенных в пространстве имен
System .
System.Collections
System.Configuration
System.Deployment
System.Diagnostics
System.Globalization
System.IO
System.Numerics
System.Reflection
System.Resources
System.Runtime
System.Security
System.StubHelpers
System.Text
System.Threading
Press any key to continue . . .
NOTE
Выходные данные не содержат каждое пространство имен, имеющее дочернее пространство имен от пространства
имен System . В них отображается каждое пространство имен, которое присутствует в этой компиляции и ссылается
только на сборку, где объявлено System.String . Этой компиляции неизвестно о пространствах имен, объявленных в
других сборках.
Привязка выражения
В предыдущем коде показано, как найти символ путем привязки к имени. В программе C# существуют и
другие не являющиеся именами выражения, которые можно привязать. Чтобы продемонстрировать эту
возможность, выполним привязку к простому строковому литералу.
Программа "Hello World" содержит класс Microsoft.CodeAnalysis.CSharp.Syntax.LiteralExpressionSyntax,
строку "Hello World", отображаемую в консоли.
Строку "Hello World" можно найти путем поиска одного строкового литерала в программе. Затем после
обнаружения узла синтаксиса получите сведения о типе для этого узла из семантической модели. Добавьте
приведенный ниже код в метод Main :
Чтобы завершить действия в этом учебнике, сформируем запрос LINQ, который создает последовательность
всех открытых методов, объявленных в типе string и возвращающих string . Поскольку запрос сложный,
мы будем создавать его построчно, а затем перестроим в один запрос. Источником для этого запроса
является последовательность всех элементов, объявленных в типе string :
Эта исходная последовательность содержит все элементы, включая свойства и поля, поэтому ее нужно
отфильтровать с помощью метода ImmutableArray<T>.OfType, чтобы найти элементы, которые являются
объектами Microsoft.CodeAnalysis.IMethodSymbol:
Затем добавьте другой фильтр, чтобы вернуть только открытые методы, возвращающие string :
Выберите только свойство имени и только уникальные имена, удаляя все перегрузки:
Можно также создать полный запрос с помощью синтаксиса запросов LINQ и затем отобразить имена всех
методов в консоли:
foreach (string name in (from method in stringTypeSymbol
.GetMembers().OfType<IMethodSymbol>()
where method.ReturnType.Equals(stringTypeSymbol) &&
method.DeclaredAccessibility == Accessibility.Public
select method.Name).Distinct())
{
Console.WriteLine(name);
}
Join
Substring
Trim
TrimStart
TrimEnd
Normalize
PadLeft
PadRight
ToLower
ToLowerInvariant
ToUpper
ToUpperInvariant
ToString
Insert
Replace
Remove
Format
Copy
Concat
Intern
IsInterned
Press any key to continue . . .
Вы использовали семантический API для поиска и отображения сведений о символах, которые являются
частью этой программы.
Начало работы с синтаксическими
преобразованиями
23.10.2019 • 21 minutes to read • Edit Online
В основе этого руководства лежат концепции и методы, описанные в кратких руководствах Начало работы с
функциями синтаксического анализа и Начало работы с семантическим анализом. Если вы еще не изучили
их, сделайте это до начала работы с этим руководством.
В этом кратком руководстве вы изучите методы создания и преобразования деревьев синтаксиса.
Объединив их с методами, рассмотренными в предыдущих кратких руководствах, вы сможете создать
рефакторинг в командной строке!
Добавьте следующую директиву using в начало файла Program.cs , чтобы импортировать фабричные методы
класса SyntaxFactory и методы Console. Это позволит применять их без полного описания:
В представленном выше коде создается объект IdentifierNameSyntax и присваивается его переменной name .
Многие API-интерфейсы Roslyn возвращают базовые классы, что упрощает работу со связанными типами.
Переменную name класса NameSyntax можно использовать многократно при создании QualifiedNameSyntax.
Не используйте определение типа при создании образца. Этот шаг для проекта вы сделаете автоматическим.
Итак, вы создали имя. Теперь нужно добавить в дерево больше узлов, создав QualifiedNameSyntax. Новое
дерево использует name в качестве левой части имени, а новый объект IdentifierNameSyntax в качестве
пространства имен Collections , то есть в правой части QualifiedNameSyntax. Добавьте следующий код в
файл program.cs :
Снова выполните этот код и проверьте результаты. Вы создаете дерево узлов, представляющее код. Точно
так же вы создадите QualifiedNameSyntax для пространства имен System.Collections.Generic . Добавьте
следующий код в файл Program.cs :
Запустите программу еще раз и убедитесь, что вы создаете дерево для добавления кода.
Создание измененного дерева
На текущий момент вы создали небольшое синтаксическое дерево, которое содержит одну инструкцию. API-
интерфейсы для создания узлов отлично подходят для создания блоков небольшого размера, например для
одного оператора. Но для более крупных блоков кода следует использовать методы, которые заменяют или
добавляют узлы в существующее дерево. Не забывайте, что синтаксические деревья являются
неизменяемыми. API синтаксиса не предоставляет механизмов для изменения существующего
синтаксического дерева после его создания. Но зато в нем есть методы, которые позволяют создавать новые
деревья на основе существующих с определенными изменениями. Методы With* определяются в
конкретных классах, производных от SyntaxNode, или в методах расширения, объявленных в классе
SyntaxNodeExtensions. Эти методы позволяют создать узел, применяя изменения к дочерним свойствам
существующего узла. Кроме того, можно использовать метод расширения ReplaceNode для замены узлов-
потомков в поддереве. Этот метод также позволяет обновить родительский узел, чтобы он указывал на
новый дочерний элемент, и повторяет этот процесс вверх по всему дереву. Этот процесс называется
повторным прохождением.
На следующем шаге вы создадите дерево, которое представляет целую (небольшую) программу и измените
его. Добавьте следующий код в начало класса Program :
private const string sampleCode =
@"using System;
using System.Collections;
using System.Linq;
using System.Text;
namespace HelloWorld
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine(""Hello, World!"");
}
}
}";
NOTE
В примере кода используется пространство имен System.Collections , а не System.Collections.Generic .
Добавьте следующий код в нижнюю часть метода Main , чтобы выполнить синтаксический анализ текста и
создать дерево:
В этом примере используется метод WithName(NameSyntax), чтобы заменить имя в узле UsingDirectiveSyntax
тем именем, созданном в предыдущем фрагменте кода.
Создайте узел UsingDirectiveSyntax с помощью метода WithName(NameSyntax), чтобы заменить имя
System.Collections тем именем, которое создано в предыдущем фрагменте кода. Добавьте следующий код в
конец метода Main :
Запустите программу и внимательно изучите выходные данные. newusing не помещается в корневой узел
дерева. Исходное дерево не изменяется.
Добавьте следующий код, используя метод расширения ReplaceNode, чтобы создать новое дерево. Новое
дерево формируется путем замены существующего импорта обновленным узлом newUsing . Назначьте это
новое дерево существующей переменной root :
Запустите программу еще раз. На этот раз дерево правильно импортирует пространство имен
System.Collections.Generic .
Методы With* и ReplaceNode удобны для преобразования отдельных ветвей в синтаксическом дереве. Класс
Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter выполняет множественные преобразования
синтаксического дерева. Класс Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter является подклассом
Microsoft.CodeAnalysis.CSharp.CSharpSyntaxVisitor<TResult>. CSharpSyntaxRewriter применяет
преобразование к конкретному типу объекта SyntaxNode. Преобразования можно применить к нескольким
типам объектов SyntaxNode везде, где они встречаются в синтаксическом дереве. В втором проекте в этом
кратком руководстве выполняется рефакторинг в командной строке, при котором удаляются явные типы из
объявлений локальных переменных везде, где используются определения типа.
Создайте новый проект C# для автономного средства анализа кода. В Visual Studio щелкните правой
кнопкой мыши узел решения SyntaxTransformationQuickStart . Выберите Добавить > Новый проект, чтобы
открыть диалоговое окно Новый проект. В разделе Visual C# > Расширяемость выберите Автономное
средство анализа кода. Присвойте проекту имя TransformationCS и нажмите кнопку "ОК".
На первом шаге создается класс, производный от CSharpSyntaxRewriter, чтобы выполнять преобразования.
Добавьте в проект новый файл класса. В Visual Studio выберите Проект > Добавить класс... . В диалоговом
окне Добавление нового элемента введите имя файла TypeInferenceRewriter.cs .
В файл TypeInferenceRewriter.cs добавьте следующие директивы using:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
Добавьте следующий код, чтобы объявить частное поле только для чтения, в котором будет храниться
объект SemanticModel, и инициализируйте его в конструкторе. Это поле потребуется вам позже, чтобы
указать, где можно использовать определение типа:
NOTE
Многие API-интерфейсы Roslyn объявляют типы возвращаемых значений, которые являются базовыми классами для
фактически возвращаемых типов среды выполнения. Во многих сценариях один тип узла может быть заменен другим
типом или даже удален. В этом примере метод VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax)
возвращает SyntaxNode, а не тип, производный от LocalDeclarationStatementSyntax. Этот модуль записи возвращает
новый узел LocalDeclarationStatementSyntax, основанный на существующем узле.
Добавьте следующий код в текст метода VisitLocalDeclarationStatement , чтобы пропускать перезапись этих
форм объявления:
if (node.Declaration.Variables.Count > 1)
{
return node;
}
if (node.Declaration.Variables[0].Initializer == null)
{
return node;
}
Этот метод указывает, что перезапись не происходит, возвращая неизмененное значение параметра node .
Если ни одно из выражений if не принимает значение true, этот узел представляет возможное объявление с
инициализацией. Добавьте следующие инструкции, чтобы извлечь имя типа, указанное в объявлении, и
связать его с помощью поля SemanticModel для получения символа типа:
Наконец, добавьте следующую инструкцию if , чтобы заменять существующее имя типа ключевым словом
var , если тип выражения инициализатора соответствует указанному типу:
if (variableType == initializerInfo.Type)
{
TypeSyntax varTypeName = IdentifierName("var")
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());
Подождите пару секунд, и в этой строке появится волнистая линия, которая обозначает ошибку: метод
CreateTestCompilation не существует. Нажмите клавиши CTRL+точка, чтобы открыть меню лампочки. Затем
нажмите клавишу "ВВОД", чтобы вызвать команду Сгенерировать заглушку метода. Эта команда создаст
заглушку для метода CreateTestCompilation в классе Program . Этот метод вы заполните позднее:
Создайте следующий код, который последовательно перебирает все SyntaxTree в тесте Compilation. Для
каждого из них инициализируйте новый TypeInferenceRewriter с классом SemanticModel для этого дерева:
if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
}
В созданной инструкции foreach добавьте приведенный ниже код, чтобы выполнить преобразование для
каждого исходного дерева. Этот код проверяет, были ли внесены правки, и если были — записывает
преобразованное дерево. Модуль записи должен изменять дерево только в том случае, если он встречает
одно или несколько объявлений локальных переменных, которые можно упростить с помощью определения
типа:
if (newSource != sourceTree.GetRoot())
{
File.WriteAllText(sourceTree.FilePath, newSource.ToFullString());
}
Вы увидите волнистые линии под фрагментом кода File.WriteAllText . Выберите значок лампочки и добавьте
необходимую инструкцию using System.IO; .
Вы почти закончили! Осталось только последнее действие: создать тест Compilation. Так как вы еще не
использовали определение типа в этом кратком руководстве, это будет отличным тестовым случаем. К
сожалению, создание компиляции из файла проекта C# не входит в это пошаговое руководство. Но вы
можете применить обходной путь, если внимательно выполняли все инструкции. Замените содержимое
метода CreateTestCompilation следующим кодом. Этот код позволяет создать компиляцию теста, которая
очень удачно совпадает с проектом, описанным в этом кратком руководстве:
MetadataReference mscorlib =
MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
MetadataReference codeAnalysis =
MetadataReference.CreateFromFile(typeof(SyntaxTree).Assembly.Location);
MetadataReference csharpCodeAnalysis =
MetadataReference.CreateFromFile(typeof(CSharpSyntaxTree).Assembly.Location);
return CSharpCompilation.Create("TransformationCS",
sourceTrees,
references,
new CSharpCompilationOptions(OutputKind.ConsoleApplication));
Скрестите пальцы и запустите проект. В Visual Studio выберите Отладка > Начать отладку. В Visual Studio
появится сообщение о том, что файлы в проекте были изменены. Щелкните Да для всех, чтобы повторно
загрузить все измененные файлы. Изучите их и поразитесь, что вам удалось сделать. Обратите особое
внимание, насколько чище выглядит код без ненужных явных описателей типов.
Поздравляем! С помощью API -интерфейсов компилятора вы создали собственное средство для
рефакторинга, которое ищет определенные синтаксические выражения во всех файлах проекта C#,
анализирует и преобразует семантику исходного кода по указанным шаблонам. Теперь вы официально
считаетесь разработчиком средства рефакторинга!
Учебник. Создание средства для анализа и
исправления кода
23.10.2019 • 41 minutes to read • Edit Online
Пакет SDK для .NET Compiler Platform предоставляет инструменты для создания пользовательских
предупреждений для кода C# или Visual Basic. Анализатор содержит код, который распознает нарушения
правила. Исправление кода содержит код, который исправляет эти нарушения. Правилами, которые вы
реализуете, может быть что угодно — от структуры кода до его стиля или соглашений об именовании и
многое другое. .NET Compiler Platform предоставляет платформу для выполнения анализа во время
написания кода, а также все функции пользовательского интерфейса Visual Studio для отладки, включая
отображение волнистых линий в редакторе, вывод списка ошибок в Visual Studio и отображение значка
лампочки, указывающего на наличие предложений и предлагаемых исправлений.
В этом руководстве описано, как создать анализатор и соответствующее исправление кода с помощью
API Roslyn. Анализатор выполняет анализ исходного кода и сообщает о проблеме пользователю. При
необходимости анализатор может предложить соответствующее исправление для исходного кода
пользователя. В этом руководстве описано, как создать анализатор, ищущий локальные переменные, которые
можно объявить с помощью модификатора const , но которые не объявлены. Сопутствующее исправление
кода добавляет модификатор const в эти объявления.
Предварительные требования
Visual Studio 2017
Visual Studio 2019
Необходимо установить пакет SDK для .NET Compiler Platform через установщик Visual Studio:
int x = 0;
Console.WriteLine(x);
В приведенном выше коде x присваивается значение константы, которое никогда не меняется. Ее можно
объявить с помощью модификатора const :
const int x = 0;
Console.WriteLine(x);
Во втором экземпляре Visual Studio, который вы только что создали, создайте проект C# консольного
приложения (это может быть как проект .NET Core, так и .NET Framework, так как анализаторы работают на
уровне источника). Наведите указатель мыши на токен с волнистым подчеркиванием. Появится текст
предупреждения от анализатора.
Шаблон создает анализатор, который выдает предупреждение на каждое объявление типа, где имя типа
состоит из букв нижнего регистра, как показано ниже:
Также шаблон содержит исправление кода, которое меняет любые буквы нижнего регистра в имени типа на
буквы верхнего регистра. Предлагаемые исправления можно просмотреть, щелкнув значок лампочки рядом
с предупреждением. После принятия изменений имя типа и все ссылки на этот тип будут обновлены. Теперь,
когда вы увидели работу начального анализатора, закройте второй экземпляр Visual Studio и вернитесь к
проекту анализатора.
Для тестирования изменений в анализаторе не требуется каждый раз запускать вторую копию Visual Studio,
так как шаблон создает проект модульного теста. В этом проекте содержатся два теста. TestMethod1
показывает обычный формат теста, при котором анализ кода происходит без активации диагностики.
TestMethod2 — формат, при котором сначала активируется диагностика, а затем применяется исправление
кода. Во время сборки анализатора и исправления кода вы напишете тесты для проверки разных структур.
Модульные тесты проводятся гораздо быстрее, чем интерактивное тестирование анализаторов в Visual
Studio.
TIP
Модульные тесты анализатора — отличный инструмент, если вы знаете, какие конструкции кода должны и не должны
запускать анализатор. В свою очередь запуск анализатора в другой копии Visual Studio позволяет определить и найти
конструкции, о которых вы еще не задумывались.
Регистрация анализатора
В файле MakeConstAnalyzer.cs с помощью шаблона создается начальный класс DiagnosticAnalyzer . В этом
начальном анализаторе отображены два важных свойства каждого анализатора.
В каждом диагностическом анализаторе должен быть указан атрибут [DiagnosticAnalyzer] , который
описывает язык, на котором он работает.
Каждый диагностический анализатор должен наследоваться от класса DiagnosticAnalyzer.
Также в шаблоне отображены базовые функции любого анализатора:
1. Регистрация действий. Действия представляют изменения кода, которые запускают анализатор для
проверки нарушений. Когда Visual Studio обнаруживает изменения в коде, которые соответствуют
зарегистрированному действию, происходит вызов зарегистрированного метода.
2. Создание диагностики. Обнаружив нарушения, анализатор создает объект диагностики, с помощью
которого Visual Studio уведомляет пользователя об этом нарушении.
Действия регистрируются в переопределении метода DiagnosticAnalyzer.Initialize(AnalysisContext). При
работе с этим руководстве вы просмотрите синтаксические узлы локальных объявлений и узнаете, какие
из них имеют значения констант. Если объявление может быть константой, анализатор создаст диагностику и
сформирует отчет.
Сначала обновите константы регистрации и метод Initialize , так как константы определяют ваш
анализатор MakeConst. Большинство строковых констант определены в файле строковых ресурсов.
Используйте их, чтобы упростить локализацию. Откройте файл Resources.resx для проекта анализатора
MakeConst. Откроется редактор ресурсов. Измените строковые ресурсы, как показано ниже:
Компонент AnalyzerTitle измените на Variable can be made constant (Переменная может быть
константой).
Компонент AnalyzerMessageFormat измените на Can be made constant (Может быть константой).
Компонент AnalyzerDescription измените на Make Constant (Сделать константой).
Также измените раскрывающийся список модификатора доступа на public . Это упростит использование
этих констант в модульных тестах. После настройки редактор ресурсов будет иметь следующий вид:
Остальные изменения будут в файле анализатора. Откройте файл MakeConstAnalyzer.cs в Visual Studio.
Измените зарегистрированное действие на символы на действие на синтаксис. В методе
MakeConstAnalyzerAnalyzer.Initialize найдите строку с действием на символы:
context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.None);
context.EnableConcurrentExecution();
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.LocalDeclarationStatement);
int x = 0;
Console.WriteLine(x);
Сначала найдите локальные объявления. Добавьте приведенный ниже код в AnalyzeNode в файле
MakeConstAnalyzer.cs:
Это приведение всегда завершается успешно, так как ваш анализатор зарегистрирован для отслеживания
изменений только локальных объявлений. Другие типы узлов не вызывают метод AnalyzeNode . Затем
проверьте объявление на наличие модификаторов const . Если они есть, сразу же выполните возврат.
Приведенный ниже код ищет модификаторы const в локальном объявлении:
В конце проверьте, может ли переменная быть const . Это означает, что она не может быть назначена после
инициализации.
Выполните семантический анализ с помощью SyntaxNodeAnalysisContext. Используйте аргумент context ,
чтобы определить, можно ли объявление локальной переменной сделать const .
Microsoft.CodeAnalysis.SemanticModel представляет все семантические сведения в одном исходном файле.
См. дополнительные сведения о семантических моделях. С помощью Microsoft.CodeAnalysis.SemanticModel
выполните анализ потока данных на операторе локального объявления. Затем, используя результаты этого
анализа потока данных, убедитесь, что в локальную переменную не записывается новое значение где-либо
еще. Вызовите метод расширения GetDeclaredSymbol, чтобы извлечь ILocalSymbol переменной и убедиться,
что она не содержится в коллекции DataFlowAnalysis.WrittenOutside из анализа потока данных. Добавьте в
конце метода AnalyzeNode приведенный ниже код:
// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
var variable = localDeclaration.Declaration.Variables.Single();
var variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
return;
}
Этот код гарантирует, что переменная не изменена и может быть const . Теперь мы активируем диагностику.
Добавьте приведенный ниже код в последнюю строку метода AnalyzeNode :
context.ReportDiagnostic(Diagnostic.Create(Rule, context.Node.GetLocation()));
Чтобы проверить состояние, запустите анализатор, нажав клавишу F5. Загрузите консольное приложение,
созданное ранее, и добавьте приведенный ниже код теста:
int x = 0;
Console.WriteLine(x);
Появится лампочка и анализатор выдаст отчет с диагностическими сведениями. При этом поведение
лампочки по-прежнему основано на исправлении кода, сгенерированном шаблоном, указывая на то, что
можно использовать буквы верхнего регистра. В следующем разделе показано, как написать исправление
кода.
const int x = 0;
Console.WriteLine(x);
Пользователь выбирает исправление в интерфейсе лампочки в редакторе, а Visual Studio изменяет код.
Откройте файл MakeConstCodeFixProvider.cs, добавленный шаблоном. Это исправление уже привязано к
идентификатору диагностики вашего анализатора, но оно пока не реализует преобразование кода. Сначала
удалите часть кода шаблона. Измените строку заголовка на Make constant (Сделать константой):
var declaration =
root.FindToken(diagnosticSpan.Start).Parent.AncestorsAndSelf().OfType<LocalDeclarationStatementSyntax>
().First();
Затем, чтобы зарегистрировать исправление кода, измените последнюю строку. Исправление создаст
документ, полученный после добавления модификатора const к существующему объявлению:
// Register a code action that will invoke the fix.
context.RegisterCodeFix(
CodeAction.Create(
title: title,
createChangedDocument: c => MakeConstAsync(context.Document, declaration, c),
equivalenceKey: title),
diagnostic);
Под символом MakeConstAsync появятся красные волнистые линии. Добавьте объявление в MakeConstAsync ,
например как указано ниже:
Новый метод MakeConstAsync преобразует Document исходного файла пользователя в новый экземпляр
Document, содержащий объявление const .
Создайте новый токен ключевого слова const и вставьте его перед оператором объявления. Не забудьте
сначала удалить все элементы trivia из первого оператора объявления и подключить к токену const .
Добавьте следующий код в метод MakeConstAsync :
// Insert the const token into the modifiers list, creating a new modifiers list.
var newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);
// Produce the new local declaration.
var newLocal = trimmedLocal
.WithModifiers(newModifiers)
.WithDeclaration(localDeclaration.Declaration);
Для выполнения этого кода требуется новое пространство имен. Добавьте следующий оператор using в
начало файла:
using Microsoft.CodeAnalysis.Formatting;
В конце нужно внести правки. Для этого выполните эти три шага:
1. Получите дескриптор существующего документа.
2. Создайте документ, заменив существующее объявление на новое.
3. Верните новый документ.
Добавьте в конце метода MakeConstAsync приведенный ниже код:
// Replace the old local declaration with the new local declaration.
var oldRoot = await document.GetSyntaxRootAsync(cancellationToken);
var newRoot = oldRoot.ReplaceNode(localDeclaration, formattedLocal);
Ваше исправление кода готово. Нажмите клавишу F5, чтобы запустить проект анализатора во втором
экземпляре Visual Studio. Во втором экземпляре Visual Studio создайте проект C# консольного приложения и
добавьте несколько объявлений локальной переменной, инициализированных со значениями константы в
методе main. Они будут отмечены как предупреждения. См. пример ниже.
Мы уже много сделали. Объявления, которые могут быть const , подчеркнуты волнистой линией. Но нам еще
нужно с ними поработать. Здесь следует добавить const в объявления i , затем j и k . Но если вы
добавите модификатор const в обратном порядке, начиная с k , анализатор выдаст ошибки: k не может
быть объявлен как const , пока i и j не являются const . Нужно выполнить дополнительный анализ,
чтобы убедиться, что переменные можно объявить и инициализировать различными путями.
Вы можете создать новую строку данных для теста. Для этого определите фрагмент кода, который не должен
вызывать предупреждение диагностики. Эта перегрузка VerifyCSharpDiagnostic передается, если для
фрагмента исходного кода диагностика не вызывалась.
Затем замените TestMethod2 этим тестом, который гарантирует вызов диагностики, а также то, что
исправление применяется к этому фрагменту исходного кода:
[DataTestMethod]
[DataRow(LocalIntCouldBeConstant, LocalIntCouldBeConstantFixed, 10, 13)]
public void WhenDiagnosticIsRaisedFixUpdatesCode(
string test,
string fixTest,
int line,
int column)
{
var expected = new DiagnosticResult
{
Id = MakeConstAnalyzer.DiagnosticId,
Message = new LocalizableResourceString(nameof(MakeConst.Resources.AnalyzerMessageFormat),
MakeConst.Resources.ResourceManager, typeof(MakeConst.Resources)).ToString(),
Severity = DiagnosticSeverity.Warning,
Locations =
new[] {
new DiagnosticResultLocation("Test0.cs", line, column)
}
};
VerifyCSharpDiagnostic(test, expected);
VerifyCSharpFix(test, fixTest);
}
Приведенный выше код также вносит изменения в код, который компилирует ожидаемый результат
диагностики. Для этого используются открытые константы, зарегистрированные в анализаторе MakeConst .
Также используются две строковые константы для введенного и исправленного источника. Добавьте
приведенные ниже строковые константы в класс UnitTest :
private const string LocalIntCouldBeConstant = @"
using System;
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int i = 0;
Console.WriteLine(i);
}
}
}";
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
const int i = 0;
Console.WriteLine(i);
}
}
}";
Чтобы убедиться в том, что они переданы, запустите оба теста. Откройте обозреватель тестов в Visual
Studio, последовательно выбрав Тест > Windows > Обозреватель тестов. Щелкните ссылку Выполнить
все.
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int i = 0;
Console.WriteLine(i++);
}
}
}";
Этот тест также проходит. Далее добавьте константы для условий, которые еще не обработаны:
Объявления, которые представляют const , так как они уже являются константами:
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
const int i = 0;
Console.WriteLine(i);
}
}
}";
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int i;
i = 0;
Console.WriteLine(i);
}
}
}";
Объявления, у которых инициализатор не является константой, так как они не могут быть константами
времени компиляции:
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int i = DateTime.Now.DayOfYear;
Console.WriteLine(i);
}
}
}";
Трудность заключается еще в том, что в C# допускается несколько объявлений, работающих как один
оператор. Рассмотрите приведенную ниже строковую константу тестового случая:
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int i = 0, j = DateTime.Now.DayOfYear;
Console.WriteLine(i, j);
}
}
}";
Переменная i может быть константой, а переменная j — нет. Поэтому этот оператор не может быть
объявлением константы. Добавьте объявления DataRow для всех тестов:
[DataTestMethod]
[DataRow(""),
DataRow(VariableAssigned),
DataRow(AlreadyConst),
DataRow(NoInitializer),
DataRow(InitializerNotConstant),
DataRow(MultipleInitializers)]
public void WhenTestCodeIsValidNoDiagnosticIsTriggered(string testCode)
// Retrieve the local symbol for each variable in the local declaration
// and ensure that it is not written outside of the data flow analysis region.
var variable = localDeclaration.Declaration.Variables.Single();
var variableSymbol = context.SemanticModel.GetDeclaredSymbol(variable);
if (dataFlowAnalysis.WrittenOutside.Contains(variableSymbol))
{
return;
}
Первый цикл foreach проверяет каждое объявление переменной с помощью синтаксического анализа. В
первой проверке подтверждается наличие инициализатора. Во второй — что этот инициализатор является
константой. Во втором цикле запускается исходный семантический анализ. Семантические проверки
проходят в отдельных циклах, так как они серьезно влияют на производительность. Снова запустите тест. Все
проверки должны быть пройдены.
Финальные штрихи
Вы почти у цели. Анализатор должен обработать еще несколько условий. Пока пользователь пишет код,
Visual Studio вызывает анализаторы. Часто бывает так, что анализатор вызван для кода, который не
компилируется. Метод AnalyzeNode диагностического анализатора не проверяет, можно ли преобразовать
значение константы в тип переменной. Поэтому в текущей реализации все неверные объявления, такие как
int i = "abc", преобразуются в локальные константы. Добавьте исходную строковую константу в это условие:
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
int x = ""abc"";
}
}
}";
Кроме того, ссылочные типы обрабатываются неправильно. Единственное допустимое значение константы
для ссылочного типа — null , кроме случаев с String, в которых работают строковые литералы. Другими
словами, const string s = "abc" является допустимым, а const object s = "abc" — нет. Этот фрагмент кода
проверяет это условие:
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
object s = ""abc"";
}
}
}";
Чтобы убедиться в том, что можно создать объявление константы для строки, добавьте еще один тест. В
приведенным ниже фрагменте кода определен как код, вызывающий диагностику, так и код после
исправления:
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
string s = ""abc"";
}
}
}";
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
const string s = ""abc"";
}
}
}";
Если переменная объявлена с ключевым словом var , исправление выдает объявление const var , которое не
поддерживается в C#. Чтобы исправить эту ошибку, исправление должно заменить ключевое слово var
именем выведенного типа:
private const string DeclarationUsesVar = @"
using System;
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
var item = 4;
}
}
}";
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
const int item = 4;
}
}
}";
private const string StringDeclarationUsesVar = @"
using System;
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
var item = ""abc"";
}
}
}";
private const string StringDeclarationUsesVarFixedHasType = @"
using System;
namespace MakeConstTest
{
class Program
{
static void Main(string[] args)
{
const string item = ""abc"";
}
}
}";
Эти изменения обновят объявления строк данных для обоих тестов. В приведенном ниже коде показаны
тесты со всеми атрибутами строк данных:
//No diagnostics expected to show up
[DataTestMethod]
[DataRow(""),
DataRow(VariableAssigned),
DataRow(AlreadyConst),
DataRow(NoInitializer),
DataRow(InitializerNotConstant),
DataRow(MultipleInitializers),
DataRow(DeclarationIsInvalid),
DataRow(ReferenceTypeIsntString)]
public void WhenTestCodeIsValidNoDiagnosticIsTriggered(string testCode)
{
VerifyCSharpDiagnostic(testCode);
}
[DataTestMethod]
[DataRow(LocalIntCouldBeConstant, LocalIntCouldBeConstantFixed, 10, 13),
DataRow(ConstantIsString, ConstantIsStringFixed, 10, 13),
DataRow(DeclarationUsesVar, DeclarationUsesVarFixedHasType, 10, 13),
DataRow(StringDeclarationUsesVar, StringDeclarationUsesVarFixedHasType, 10, 13)]
public void WhenDiagosticIsRaisedFixUpdatesCode(
string test,
string fixTest,
int line,
int column)
{
var expected = new DiagnosticResult
{
Id = MakeConstAnalyzer.DiagnosticId,
Message = new LocalizableResourceString(nameof(MakeConst.Resources.AnalyzerMessageFormat),
MakeConst.Resources.ResourceManager, typeof(MakeConst.Resources)).ToString(),
Severity = DiagnosticSeverity.Warning,
Locations =
new[] {
new DiagnosticResultLocation("Test0.cs", line, column)
}
};
VerifyCSharpDiagnostic(test, expected);
VerifyCSharpFix(test, fixTest);
}
К счастью, все вышеперечисленные ошибки можно устранить с помощью методов, которые вы только что
изучили.
Чтобы исправить первую ошибку, сначала откройте файл DiagnosticAnalyzer.cs и найдите цикл foreach, в
котором проверяются инициализаторы локальных объявлений. Им должны быть назначены значения
констант. Сразу же, до выполнения цикла foreach, вызовите context.SemanticModel.GetTypeInfo() , чтобы
извлечь подробные сведения об объявленном типе из локального объявления:
Затем проверьте каждый инициализатор внутри цикла foreach , чтобы убедиться в том, что его можно
преобразовать в тип переменной. Убедившись в том, что инициализатор является константой, добавьте
приведенную ниже проверку:
// Ensure that the initializer value can be converted to the type of the
// local declaration without a user-defined conversion.
var conversion = context.SemanticModel.ClassifyConversion(initializer.Value, variableType);
if (!conversion.Exists || conversion.IsUserDefined)
{
return;
}
Следующее изменение основано на последнем. Перед закрывающей фигурной скобкой первого цикла
foreach добавьте приведенный ниже код. Он проверит тип локального объявления, когда константа является
строкой или имеет значение null.
// Special cases:
// * If the constant value is a string, the type of the local declaration
// must be System.String.
// * If the constant value is null, the type of the local declaration must
// be a reference type.
if (constantValue.Value is string)
{
if (variableType.SpecialType != SpecialType.System_String)
{
return;
}
}
else if (variableType.IsReferenceType && constantValue.Value != null)
{
return;
}
Необходимо заменить ключевое слово var правильным именем типа в вашем поставщике исправлений кода.
Вернитесь к файлу CodeFixProvider.cs. Код, который вы добавите, выполняет следующие шаги:
Проверяет, является ли объявление var , если да:
Создает тип для выводимого типа.
Проверяет, что объявление типа не является псевдонимом. Если это так, можно объявить const var .
Проверяет, что var не является именем типа в программе (если это так, const var является допустимым).
Упрощает полное имя типа.
Кажется, что здесь довольно много кода. Но это не так. Замените строку, которая объявляет и
инициализирует newLocal приведенным ниже кодом. Он выполняется сразу после инициализации
newModifiers :
// If the type of the declaration is 'var', create a new type name
// for the inferred type.
var variableDeclaration = localDeclaration.Declaration;
var variableTypeName = variableDeclaration.Type;
if (variableTypeName.IsVar)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
// Special case: Ensure that 'var' isn't actually an alias to another type
// (e.g. using var = System.String).
var aliasInfo = semanticModel.GetAliasInfo(variableTypeName);
if (aliasInfo == null)
{
// Retrieve the type inferred for var.
var type = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;
// Special case: Ensure that 'var' isn't actually a type named 'var'.
if (type.Name != "var")
{
// Create a new TypeSyntax for the inferred type. Be careful
// to keep any leading and trailing trivia from the var keyword.
var typeName = SyntaxFactory.ParseTypeName(type.ToDisplayString())
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());
using Microsoft.CodeAnalysis.Simplification;
Запустите тесты. Они все должны быть пройдены. Можете поздравить себя, запустив готовый анализатор.
Чтобы запустить проект анализатора во втором экземпляре Visual Studio с загруженным расширением
предварительной версии Roslyn, нажмите клавиши Ctrl+F5.
Во втором экземпляре Visual Studio создайте новый проект C# консольного приложения и добавьте
int x = "abc"; в метод main. Так как первая ошибка была исправлена, для объявления локальной
переменной не должно выдаваться предупреждение (хотя есть ошибка компилятора, как и
предполагалось).
Затем добавьте object s = "abc"; в метод main. Так как вторая ошибка была исправлена, не должно быть
никаких предупреждений.
Наконец, добавьте другую локальную переменную, использующую ключевое слово var . Внизу слева
появится предупреждение и предложение исправлений.
Наведите курсор редактора на волнистую линию и нажмите клавиши Ctrl+. Отобразятся предложенные
исправления. Обратите внимание, что после выбора исправления ключевое слово var теперь
обрабатывается правильно.
В конце добавьте приведенный ниже код:
int i = 2;
int j = 32;
int k = i + j;
После этого только две переменные будут подчеркнуты красными волнистыми линиями. Добавьте const в
i и j . Появится предупреждение, указывающее на то, что k может быть const .
Поздравляем! Вы создали свое первое расширение .NET Compiler Platform, которое выполняет анализ кода в
режиме реального времени, находит проблемы и быстро их исправляет. Кроме того, вы изучили множество
API, входящих в пакет SDK .NET Compiler Platform (API Roslyn). Вы можете сравнить результат своей работы
с готовым примером в нашем репозитории в GitHub. Или загрузить ZIP -файл готового проекта.
Другие источники
Начало работы с функциями синтаксического анализа
Начало работы с семантическим анализом
Руководство по
программированию на C#
25.11.2019 • 2 minutes to read • Edit Online
Разделы о программе
Структура программы C#
Main() и аргументы командной строки
Разделы о языке
Инструкции, выражения и операторы
Типы
Классы и структуры
Интерфейсы
Типы перечисления
Делегаты
Массивы
Строки
Свойства
Индексаторы
События
Универсальные шаблоны
Итераторы
Выражения запросов LINQ
Лямбда-выражения
Пространства имен
Небезопасный код и указатели
Комментарии XML -документации
Разделы о платформе
Домены приложений
Сборки в .NET
Атрибуты
Коллекции
Исключения и обработка исключений
Файловая система и реестр (руководство по программированию на C#)
Взаимодействие
Отражение
См. также
Справочник по C#
Структура программы C#
23.10.2019 • 2 minutes to read • Edit Online
Содержание раздела
Hello World — создаем первую программу
Общая структура программы C#
Имена идентификаторов
Соглашения о написании кода на C#
Связанные разделы
Начало работы с C#
Руководство по программированию на C#
Справочник по C#
Примеры и руководства
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Hello World — создаем первую программу
25.11.2019 • 7 minutes to read • Edit Online
В этой статье вы будете использовать Visual Studio для создания традиционной программы "Hello World!" в
C#. Visual Studio — это профессиональная интегрированная среда разработки (IDE ) с множеством функций,
предназначенных для разработки в среде .NET. Для создания этой программы вы будете использовать лишь
некоторые функции Visual Studio. См. дополнительные сведения о начале работы с Visual C#.
NOTE
Отображаемые на компьютере имена или расположения некоторых элементов пользовательского интерфейса Visual
Studio могут отличаться от указанных в следующих инструкциях. Это зависит от имеющегося выпуска Visual Studio и
используемых параметров. Дополнительные сведения см. в разделе Персонализация среды IDE.
Создание приложения
Windows
macOS
Запустите Visual Studio. В Windows вы увидите следующее изображение:
Выберите Создать проект в правом нижнем углу изображения. В Visual Studio отображается диалоговое
окно Новый проект:
NOTE
Если вы запустили Visual Studio впервые, список Последние шаблоны проектов будет пустым.
В диалоговом окне "Новый проект" выберите "Консольное приложение (.NET Core)" и нажмите кнопку
Далее. Присвойте проекту имя, например "HelloWorld", а затем нажмите кнопку Создать.
Проект открывается в Visual Studio. Он уже является базовым примером Hello, World! Нажмите клавиши
Ctrl + F5 для запуска проекта. Visual Studio выполняет сборку проекта, преобразуя исходный код в
исполняемый файл. Затем запускается командное окно, запускающее новое приложение. В окне должен
отображаться следующий текст:
Hello World!
Элементы программы C#
Давайте рассмотрим важные части этой программы. Первая строка содержит комментарий. Символы //
преобразуют остальную часть строки в комментарий.
Вы можете также закомментировать блок текста, заключив его между символами /* и */ . Эти действия
показаны в следующем примере.
/* A "Hello World!" program in C#.
This program displays the string "Hello World!" on the screen. */
Он также может возвращать целое число. Данное целое число — это код выхода для приложения.
-или-
Параметр args метода Main является массивом значений типа string , который содержит аргументы
командной строки, используемые для вызова программы.
Дополнительные сведения об использовании аргументов командной строки см. в примерах в разделе Main()
и аргументы командной строки.
Ввод и вывод
Программы на C#, как правило, используют службы ввода-вывода, предоставляемые библиотекой времени
выполнения в .NET Framework. Инструкция System.Console.WriteLine("Hello World!"); использует метод
WriteLine. Это один из методов вывода класса Console в библиотеке времени выполнения. Он отображает
свой строковый параметр в стандартном потоке вывода, за которым следует новая строка. Существуют и
другие методы Console для разных операций ввода и вывода. Если вы добавите в начало программы
директиву using System; , классы и методы System можно использовать напрямую, не указывая их полные
имена. Например, можно вызвать Console.WriteLine вместо System.Console.WriteLine :
using System;
Console.WriteLine("Hello World!");
См. также
Руководство по программированию на C#
Примеры и руководства
Main() и аргументы командной строки
Начало работы с Visual C#
Общая структура программы на C# (Руководство
по программированию на C#)
04.11.2019 • 2 minutes to read • Edit Online
Программа на языке C# может состоять из одного или нескольких файлов. Каждый файл может содержать
нуль или несколько пространств имен. Пространство имен может содержать типы, такие как классы,
структуры, интерфейсы, перечисления и делегаты, а также другие пространства имен. Ниже приведена
структура программы на C#, содержащей все эти элементы.
// A skeleton of a C# program
using System;
namespace YourNamespace
{
class YourClass
{
}
struct YourStruct
{
}
interface IYourInterface
{
}
enum YourEnum
{
}
namespace YourNestedNamespace
{
struct YourStruct
{
}
}
class YourMainClass
{
static void Main(string[] args)
{
//Your program starts here...
}
}
}
Связанные разделы
Дополнительные сведения:
Классы
Структуры
Пространства имен
Интерфейсы
Делегаты
Спецификация языка C#
Дополнительные сведения см. в разделе Основные понятия в Спецификации языка C#. Спецификация языка
является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Структура программы C#
Справочник по C#
Имена идентификаторов
23.10.2019 • 2 minutes to read • Edit Online
Идентификатор — это имя, которое вы присваиваете типу (классу, интерфейсу, структуре, делегату или
перечислению), члену, переменной или пространству имен. Допустимые идентификаторы должны
соответствовать следующим правилам.
Идентификатор должен начинаться с буквы или _ .
Идентификаторы могут содержать буквенные символы Юникода, десятичные числа, символы
соединения Юникода, несамостоятельные знаки Юникода или символы форматирования Юникода.
Дополнительные сведения о категориях Юникода см. в разделе База данных категорий Юникода. Вы
можете объявить идентификаторы, соответствующие ключевым словам C#, с помощью префикса
идентификатора @ . @ не является частью имени идентификатора. Например, @if объявляет
идентификатор с именем if . Эти буквальные идентификаторы предназначены главным образом для
взаимодействия с идентификаторами, объявленными в других языках.
Полное определение допустимых идентификаторов см. в разделе об идентификаторах в спецификации
языка C#.
Соглашения об именах
В дополнение к правилам для идентификаторов существует ряд соглашений об именовании,
используемых в API-интерфейсах .NET. По соглашению программы C# используют PascalCase для имен
типов, пространства имен и всех открытых членов. Кроме того, часто используются следующие
соглашения.
Имена интерфейсов начинаются с заглавной буквы I .
Типы атрибутов заканчиваются словом Attribute .
Типы перечисления используют единственное число для объектов, не являющихся флагами, и
множественное число для флагов.
Идентификаторы не должны содержать два последовательных символа _ . Эти имена
зарезервированы для идентификаторов, созданных компилятором.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Структура программы C#
Справочник по C#
Классы
Структуры
Пространства имен
Интерфейсы
Делегаты
Соглашения о написании кода на C# (Руководство
по программированию на C#)
04.11.2019 • 12 minutes to read • Edit Online
Соглашения об именах
В коротких примерах, не содержащих директив using, рекомендуется использовать полные указания
для пространства имен. Если известно, что пространство имен импортировано в проект по умолчанию,
не требуется указывать полные имена из этого пространства имен. Полные имена, если они слишком
длинные для одной строки, можно разбить после точки (.), как показано в следующем примере.
Нет необходимости изменять имена объектов, созданных с помощью инструментов разработки Visual
Studio, чтобы привести их в соответствие с другими соглашениями.
Соглашения о расположении
Чтобы выделить структуру кода и облегчить чтение кода, в хорошем макете используется форматирование.
Примеры и образцы корпорации Майкрософт соответствуют следующим соглашениям.
Использование параметров редактора кода по умолчанию (логичные отступы, отступы по четыре
символа, использование пробелов для табуляции). Дополнительные сведения см. в разделе
"Параметры", "Текстовый редактор", C#, "Форматирование".
Запись только одного оператора в строке.
Запись только одного объявления в строке.
Если отступ для дополнительных строк не ставится автоматически, необходимо сделать для них отступ
на одну позицию табуляции (четыре пробела).
Добавление по крайней мере одной пустой строки между определениями методов и свойств.
Использование скобок для ясности предложений в выражениях, как показано в следующем коде.
if ((val1 > val2) && (val1 > val3))
{
// Take appropriate action.
}
Соглашения о комментариях
Комментарий размещается на отдельной строке, а не в конце строки кода.
Текст комментария начинается с заглавной буквы.
Текст комментария завершается точкой.
Между разделителем комментария (/ /) и текстом комментария вставляется один пробел, как показано
в следующем примере.
Для добавления строк в циклы, особенно при работе с текстами больших размеров, рекомендуется
использовать объект StringBuilder.
// When the type of a variable is clear from the context, use var
// in the declaration.
var var1 = "This is clearly a string.";
var var2 = 27;
var var3 = Convert.ToInt32(Console.ReadLine());
Если тип из правой части назначения не является очевидным, не рекомендуется использовать var.
// When the type of a variable is not clear from the context, use an
// explicit type.
int var4 = ExampleClass.ResultSoFar();
При указании типа переменной не следует полагаться на имя переменной. Имя может быть неверным.
Массивы
При инициализации массивов в строке объявления рекомендуется использовать сокращенный
синтаксис.
// Preferred syntax. Note that you cannot use var here instead of string[].
string[] vowels1 = { "a", "e", "i", "o", "u" };
// If you specify an array size, you must initialize the elements one at a time.
var vowels3 = new string[5];
vowels3[0] = "a";
vowels3[1] = "e";
// And so on.
Делегаты
Для создания экземпляров типа делегата рекомендуется использовать сокращенный синтаксис.
// First, in class Program, define the delegate type and a method that
// has a matching signature.
Использование оператора C# using упрощает код. При наличии оператора try-finally, код которого в
блоке finally содержит только вызов метода Dispose, вместо него рекомендуется использовать
оператор using .
// This try-finally statement only calls Dispose in the finally block.
Font font1 = new Font("Arial", 10.0f);
try
{
byte charset = font1.GdiCharSet;
}
finally
{
if (font1 != null)
{
((IDisposable)font1).Dispose();
}
}
Операторы && и ||
Чтобы избежать возникновения исключений и увеличить производительность за счет пропуска
необязательных сравнений, рекомендуется использовать && вместо & и || вместо | при выполнении
сравнений, как показано в следующем примере.
Оператор New
Рекомендуется использовать сокращенную форму создания экземпляра для объекта с неявным
типизированием, как показано в следующем объявлении.
Обработка событий
При определении обработчика событий, которого не требуется удалять позднее, рекомендуется
использовать лямбда-выражение.
public Form2()
{
// You can use a lambda expression to define an event handler.
this.Click += (s, e) =>
{
MessageBox.Show(
((MouseEventArgs)e).Location.ToString());
};
}
Статический члены
Для вызова статических членов следует использовать имя класса: ClassName.StaticMember. В этом случае
код становится более удобочитаемым за счет четкого доступа. Не присваивайте статическому члену,
определенному в базовом классе, имя производного класса. Во время компиляции кода его читаемость
нарушается, и если добавить статический член с тем же именем в производный классе, код может быть
поврежден.
Запросы LINQ
Используйте значимые имена для переменных запроса. В следующем примере используется
seattleCustomers для клиентов, находящихся в Сиэтле.
Рекомендуется использовать псевдонимы для уверенности в том, что в именах свойств анонимных
типов верно используются прописные буквы при помощи правил использования прописных и
строчных букв языка Pascal.
var localDistributors =
from customer in customers
join distributor in distributors on customer.City equals distributor.City
select new { Customer = customer, Distributor = distributor };
Переименуйте свойства, если имена свойств в результате могут быть неоднозначными. Например, если
запрос возвращает имя клиента и идентификатор распространителя, не оставляйте имена в виде Name
и ID , а переименуйте их, чтобы было ясно, что Name — имя клиента и ID — идентификатор
распространителя.
var localDistributors2 =
from customer in customers
join distributor in distributors on customer.City equals distributor.City
select new { CustomerName = customer.Name, DistributorID = distributor.ID };
Выравнивайте предложения запроса под предложением from, как показано в предыдущих примерах.
Чтобы гарантировать, что более поздние предложения запроса работают с ограниченным,
отфильтрованным набором данных, используйте предложение where перед другими предложениями
запроса.
// Use a compound from to access the inner sequence within each element.
var scoreQuery = from student in students
from score in student.Scores
where score > 90
select new { Last = student.LastName, score };
Безопасность
Следуйте указаниям, изложенным в правилах написания безопасного кода.
См. также
Соглашения о написании кода в Visual Basic
Правила написания безопасного кода
Main() и аргументы командной строки
(Руководство по программированию на C#)
25.11.2019 • 3 minutes to read • Edit Online
Метод Main — это точка входа приложения C#. (Библиотекам и службам точка входа в виде метода
Main не требуется.) Когда приложение запускается, первым вызывается именно метод Main .
В программе на C# может существовать только одна точка входа. Если у вас есть несколько классов с
методом Main , программу нужно компилировать с параметром /main, чтобы указать, какой из методов
Main будет использоваться в качестве точки входа. Дополнительные сведения см. в разделе -main
(параметры компилятора C#).
class TestClass
{
static void Main(string[] args)
{
// Display the number of command line arguments.
Console.WriteLine(args.Length);
}
}
Обзор
Метод Main — это точка входа для выполняемой программы. Это начальный и завершающий этапы
управления программой.
Main объявляется внутри класса или структуры. Метод Main должен быть статическим. Он может не
быть открытым. (В предыдущем примере он по умолчанию получает уровень доступа private
(закрытый).) Класс или структура, в которой он объявлен, не обязаны быть статическими.
Метод Main может иметь значение void , int или (начиная с C# 7.1) Task , а также тип
возвращаемого значения Task<int> .
Если только Main возвращает Task или Task<int> , объявление Main может включать модификатор
async . Обратите внимание, что это, в частности, исключает метод async void Main .
Метод Main может быть объявлен с параметром string[] , который содержит аргументы командной
строки, или без него. Когда вы используете Visual Studio для создания Windows-приложений, вы
можете добавить параметр вручную либо воспользоваться методом GetCommandLineArgs(), чтобы
получить аргументы командной строки. Параметры считываются как аргументы командной строки,
индексы которых начинаются с нуля. В отличие от C и C++, имя программы не рассматривается как
первый аргумент командной строки в массиве args , но является первым элементом метода
GetCommandLineArgs().
Ниже приведен список допустимых подписей Main :
public static void Main() { }
public static int Main() { }
public static void Main(string[] args) { }
public static int Main(string[] args) { }
public static async Task Main() { }
public static async Task<int> Main() { }
public static async Task Main(string[] args) { }
public static async Task<int> Main(string[] args) { }
Добавление значений async и Task , а также типов возвращаемого значения Task<int> упрощает код
программы, когда консольным приложениям требуется запустить и ожидать ( await ) асинхронные
операции в Main .
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Сборка из командной строки с помощью csc.exe
Руководство по программированию на C#
Методы
Структура программы C#
Аргументы командной строки (Руководство по
программированию на C#)
25.11.2019 • 4 minutes to read • Edit Online
Вы можете передавать аргументы в метод Main , определив метод одним из следующих способов:
NOTE
Чтобы включить аргументы командной строки в методе Main в приложении Windows Forms, необходимо вручную
изменить сигнатуру Main в файле program.cs. Код, созданный с помощью конструктора Windows Forms, создает
Main без входного параметра. Также можно использовать Environment.CommandLine или
Environment.GetCommandLineArgs для доступа к аргументам командной строки из любой точки в консоли или
приложении Windows.
Параметр метода Main — это массив String, представляющий аргументы командной строки. Как правило,
определить, существуют ли аргументы, можно, проверив свойство Length , например:
if (args.Length == 0)
{
System.Console.WriteLine("Please enter a numeric argument.");
return 1;
}
Строковые аргументы также можно преобразовать в числовые типы с помощью класса Convert или метода
Parse . Например, следующая инструкция преобразует string в число long с помощью метода Parse:
Пример
В следующем примере показано использование аргументов командной строки в консольном приложении.
Приложение принимает один аргумент времени выполнения, преобразует аргумент в целое число и
вычисляет факториал числа. Если не указано никаких аргументов, приложение выдает сообщение,
поясняющее правильное использование программы.
Чтобы скомпилировать и запустить приложение из командной строки, выполните следующие действия.
1. Вставьте следующий код в любой текстовый редактор и сохраните файл как текстовый файл с именем
Factorial.cs.
// Add a using directive for System if the directive isn't already present.
class MainClass
{
static int Main(string[] args)
{
// Test if input arguments were supplied.
if (args.Length == 0)
{
Console.WriteLine("Please enter a numeric argument.");
Console.WriteLine("Usage: Factorial <num>");
return 1;
}
// Calculate factorial.
long result = Functions.Factorial(num);
// Print result.
if (result == -1)
Console.WriteLine("Input must be >= 0 and <= 20.");
else
Console.WriteLine($"The Factorial of {num} is {result}.");
return 0;
}
}
// If 3 is entered on command line, the
// output reads: The factorial of 3 is 6.
2. На начальном экране или в меню Пуск откройте окно командной строки разработчика Visual
Studio и перейдите к папке, содержащей файл, который вы только что создали.
3. Введите следующую команду для компиляции приложения.
csc Factorial.cs
Если для приложения не выдаются ошибки компиляции, создается исполняемый файл с именем
Factorial.exe.
4. Введите приведенную ниже команду для вычисления факториала числа 3:
Factorial 3
NOTE
При выполнении приложения в Visual Studio аргументы командной строки можно указать на странице "Отладка" в
конструкторе проектов.
См. также
System.Environment
Руководство по программированию на C#
Main() и аргументы командной строки
Практическое руководство. Отображение аргументов командной строки
Значения, возвращаемые методом main()
Классы
Практическое руководство. Отображение
аргументов командной строки (Руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
executable.exe a b c "a"
"b"
"c"
"two"
"три"
NOTE
При выполнении приложения в Visual Studio аргументы командной строки можно указать на странице "Отладка" в
конструкторе проектов.
Пример
Этот пример отображает аргументы командной строки, передаваемые в приложение командной строки.
Показанные выходные данные — первая запись в таблице выше.
class CommandLine
{
static void Main(string[] args)
{
// The Length property provides the number of array elements.
Console.WriteLine($"parameter count = {args.Length}");
См. также
Руководство по программированию на C#
Сборка из командной строки с помощью csc.exe
Main() и аргументы командной строки
Значения, возвращаемые методом main()
Значения, возвращаемые методом Main()
(Руководство по программированию на C#)
23.10.2019 • 5 minutes to read • Edit Online
Если значение, возвращаемое методом Main , не используется, то указание в качестве возвращаемого типа
void несколько упрощает код. Однако возврат целого значения позволяет программе передавать
информацию о своем состоянии другим программам и скриптам, которые вызывают исполняемый файл.
Возвращаемое значение Main рассматривается как код выхода процесса. Если void возвращается из Main ,
код завершения будет неявно задан как 0 . В приведенном ниже примере показано, как получить доступ к
значению, возвращаемому методом Main .
Пример
В этом примере используются средства командной строки .NET Core. Если вы не знакомы со средствами
командной строки .NET Core, можете обратиться к этому руководству по началу работы.
Измените метод Main в файле program.cs следующим образом:
При запуске программы в Windows значение, возвращаемое функцией Main , сохраняется в переменной
среды. Эту переменную можно получить из пакетного файла с помощью команды ERRORLEVEL или в
PowerShell с помощью команды $LastExitCode .
Для сборки приложения можно выполнить команду dotnet build интерфейса командной строки .NET.
Затем создайте сценарий PowerShell для запуска приложения и отображения результата. Вставьте
следующий код в текстовый файл и сохраните его под именем test.ps1 в папке проекта. Запустите
сценарий PowerShell, набрав команду test.ps1 в командной строке PowerShell.
Так как код возвращает нулевое значение, пакетный файл сообщает об успехе. Но если изменить файл
MainReturnValTest.cs, чтобы он возвращал ненулевое значение, и затем повторно скомпилировать
программу, то при последующем выполнении сценария PowerShell будет выдано сообщение об ошибке.
dotnet run
if ($LastExitCode -eq 0) {
Write-Host "Execution succeeded"
} else
{
Write-Host "Execution Failed"
}
Write-Host "Return value = " $LastExitCode
Преимущество нового синтаксиса состоит в том, что компилятор всегда формирует правильный код.
NOTE
Если бы в примерах использовался модификатор async метода Main , компилятор сформировал бы точно такой же
код.
См. также
Руководство по программированию на C#
Справочник по C#
Main() и аргументы командной строки
Практическое руководство. Отображение аргументов командной строки
Основные понятия программирования (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом разделе
ЗАГОЛОВОК ОПИСАНИЕ
Caller Information (C#) (Сведения о вызывающем объекте Описывает, как получить сведения о вызывающем объекте
(C#)) метода. Можно получить такую информацию, как путь к
файлу, номер строки в исходном коде и имя вызывающего
объекта.
Covariance and Contravariance (C#) (Ковариация и Демонстрирует, как активировать неявное преобразование
контрвариантность (C#)) параметров универсального типа в интерфейсах и
делегатах.
Expression Trees (C#) (Деревья выражений (C#)) Объясняет, как использовать деревья выражений для
динамического изменения исполняемого кода.
Связанные разделы
Console.WriteLine("Breakfast is ready!");
}
Компьютеры не рассматривают эти инструкции так же, как люди. Компьютер будет задерживаться над
каждой инструкцией до момента, когда работа будет завершена, прежде чем перейдет к следующему
оператору. Вряд ли такой завтрак вас устроит. Более поздние задачи не будут начаты до завершения
предыдущих. Потребуется гораздо больше времени для приготовления завтрака, к тому же часть уже
остынет еще до подачи.
Если требуется, чтобы компьютер асинхронно выполнил инструкции выше, необходимо писать
асинхронный код.
Эти проблемы важны для программ, которые вы пишете уже сегодня. При написании клиентских
программ требуется, чтобы пользовательский интерфейс реагировал на ввод данных пользователем.
Приложения не должны блокировать телефон при скачивании данных из Интернета. При написании
серверных программ не стоит блокировать потоки. Эти потоки могут обслуживать другие запросы.
Использование синхронного кода в ситуации, когда существуют асинхронные альтернативы, мешает
масштабированию с минимальными затратами. Вы платите за эти заблокированные потоки.
Успешные современные приложения требуют использования асинхронного кода. Без поддержки
языком при написании асинхронного кода требуются обратные вызовы, события завершения или другие
способы, заслоняющие исходное назначение кода. Синхронный код удобен тем, что он довольно прост.
Пошаговые действия упрощают чтение и понимание. Традиционные асинхронные модели заставляют
сосредоточиваться на асинхронности кода, а не на фундаментальных действиях в нем.
Console.WriteLine("Breakfast is ready!");
}
Этот код не блокируется при приготовлении яиц или бекона. Этот код, однако, не запускает других
задач. По-прежнему придется поместить тост в тостер и смотреть на него, пока он не выскочит. Но по
крайней мере можно отвечать всем, кто хочет вашего внимания. В ресторане, где будет размещаться
несколько заказов, повар сможет начать готовить другой завтрак, пока первый готовится.
Теперь поток завтрака не блокируется в ожидании любой запущенной задачи, которая еще не
завершена. Для некоторых приложений это изменение — все, что требуется. Приложение с
графическим интерфейсом будет отвечать пользователю после этого изменения. Тем не менее в этом
сценарии нам нужно больше. Нам не требуется последовательное выполнение каждой из задач
компонента. Лучше запускать каждую из задач компонента, не ожидая завершения предыдущей
задачи.
Console.WriteLine("Breakfast is ready!");
Затем вы можете переместить инструкции await для бекона и яиц в конец метода, сразу перед
подачей завтрака:
Console.WriteLine("Breakfast is ready!");
Предыдущий код работает лучше. Запуск всех асинхронных задач выполняется за один раз. Вы
ожидаете каждую задачу только в том случае, когда требуются результаты. Приведенный выше код
может быть похож на код в веб-приложении, который отправляет запросы для разных микрослужб, а
затем объединяет результаты в одну страницу. Вы отправляете все запросы сразу, а затем вызываете
await , чтобы соединить все задачи и создать веб-страницу.
Сочетаемость задач
У вас все готово для завтрака в одно и то же время, за исключением тостов. Приготовление тоста —
композиция асинхронной операции (поджарить хлеб) и синхронной операции (добавить масло и джем).
Обновление этого кода иллюстрирует важную концепцию:
IMPORTANT
композиция асинхронной операции, за которой следует синхронная задача, является асинхронной операцией.
Говоря иначе, если какая-либо часть операции является асинхронной, то и вся операция является асинхронной.
Приведенный выше код показал, что можно использовать объекты Task или Task<TResult> для хранения
выполняемых задач. Вы вызываете await для каждой задачи, прежде чем использовать ее результат.
Следующим шагом является создание методов, которые представляют сочетание другой работы. Перед
подачей завтрака требуется дождаться задачи, представляющей поджарку хлеба перед добавлением
масла и джема. Вы можете представить эту работу следующим кодом:
Предыдущий метод имеет async модификатор в сигнатуре. Он сообщает компилятору, что этот метод
содержит инструкцию await ; она содержит асинхронные операции. Этот метод представляет задачу, в
рамках которой поджаривается хлеб, а затем добавляется масло и джем. Этот метод возвращает
Task<TResult>, представляющий сочетание этих трех операций. Теперь вид основного блока кода будет
таким:
Console.WriteLine("Breakfast is ready!");
Предыдущее изменение показывает важную методику для работы с асинхронным кодом. Составные
задачи можно создавать, разделяя операции в новом методе, который возвращает задачу. Вы можете
выбрать, когда следует ожидать выполнения созданной задачи. Одновременно можно запускать другие
задачи.
Этот итоговый код выполняется асинхронно. Он более точно отражает, как пользователь будет готовить
завтрак. Сравните предыдущий код с первым примером кода в этой статье. Основные действия по-
прежнему очевидны при прочтении. Этот код можно прочитать так же, как указания по приготовлению
завтрака в начале этой статьи. Возможности языка для async и await делают возможными
преобразования, которые любой человек производит, выполняя эти инструкции: запуск задач без
блокировки в ожидании их завершения.
Асинхронная модель программирования
25.11.2019 • 29 minutes to read • Edit Online
// GetStringAsync returns a Task<string>. That means that when you await the
// task you'll get a string (urlContents).
Task<string> getStringTask = client.GetStringAsync("https://docs.microsoft.com/dotnet");
// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();
Из предыдущего примера вы можете узнать несколько полезных методик. Начните с сигнатуры метода. Она
включает модификатор async . Типом возвращаемого значения является Task<int> (дополнительные
параметры см. в разделе "Типы возвращаемых значений"). Имя метода заканчивается на Async . В теле
метода GetStringAsync возвращает Task<string> . Это означает, что когда вы ожидаете ( await ) задачу, то
получаете string ( urlContents ). Прежде чем ожидать задачу, можно сделать работу, которая не зависит от
string из GetStringAsync .
Оператор return указывает целочисленный результат. Все методы, ожидающие AccessTheWebAsync , получают
значение длины.
Если метод AccessTheWebAsync не выполняет никакие операции между вызовом метода GetStringAsync и его
завершением, можно упростить код, описав вызов и ожидание с помощью следующего простого оператора.
string urlContents = await client.GetStringAsync("https://docs.microsoft.com/dotnet");
Async-методы API-интерфейсов
Где же найти методы для асинхронного программирования (такие как GetStringAsync )? Платформа .NET
Framework 4.5 и более поздние версии, а также .NET Core содержат большое количество элементов,
совместимых с async и await . Они содержат суффикс Async в имени элемента и возвращают тип Task или
Task<TResult>. Например, класс System.IO.Stream имеет такие методы, как CopyToAsync, ReadAsync и
WriteAsync, наряду с синхронными методами CopyTo, Read и Write.
Среда выполнения Windows также содержит множество методов, которые можно использовать в сочетании
с async и await в приложениях Windows. См. дополнительные сведения о потоковом и асинхронном
программировании для разработки UWP, асинхронном программировании (приложения Магазина
Windows), а также вызове асинхронных API в C# или Visual Basic, если вы используете предыдущие версии
среды выполнения Windows.
Потоки
Асинхронные методы используются для неблокирующих операций. Выражение await в асинхронном
методе не блокирует текущий поток на время выполнения ожидаемой задачи. Вместо этого выражение
регистрирует остальную часть метода как продолжение и возвращает управление вызывающему объекту
асинхронного метода.
Ключевые слова async и await не вызывают создания дополнительных потоков. Асинхронные методы не
требуют многопоточности, поскольку асинхронный метод не выполняется в собственном потоке. Метод
выполняется в текущем контексте синхронизации и использует время в потоке, только когда метод активен.
Метод Task.Run можно применять для перемещения операций, использующих ресурсы ЦП, в фоновый
поток, однако фоновый поток не имеет смысла применять для процесса, который просто ждет результата.
Асинхронный подход к асинхронному программированию практически по всем параметрам имеет
преимущество перед другими подходами. В частности, такой подход эффективнее, чем использование класса
BackgroundWorker для привязанных к вводу-выводу операций, так как используется более простой код и вам
не нужно специально предотвращать состояние гонки. В сочетании с методом Task.Run асинхронное
программирование лучше BackgroundWorker для операций, использующих ресурсы процессора, так как оно
отделяет координацию выполнения кода от действий, которые Task.Run перемещает в пул потоков.
Async и Await
Если с помощью модификатора async указать, что метод является асинхронным, у вас появятся следующие
две возможности.
Асинхронный метод сможет использовать await для обозначения точек приостановки. Оператор
await сообщает компилятору, что асинхронный метод не может выполняться после этой точки до
завершения ожидаемого асинхронного процесса. На это время управление возвращается
вызывающему объекту асинхронного метода.
Приостановка асинхронного метода на выражении await не считается выходом из метода, и блоки
finally не выполняются.
// Calls to GetTaskOfTResultAsync
Task<int> returnedTaskTResult = GetTaskOfTResultAsync();
int intResult = await returnedTaskTResult;
// or, in a single statement
int intResult = await GetTaskOfTResultAsync();
// Calls to GetTaskAsync
Task returnedTask = GetTaskAsync();
await returnedTask;
// or, in a single statement
await GetTaskAsync();
Соглашение об именовании
По соглашению имена методов, возвращающих обычно поддерживающие ожидание типы (например, Task ,
Task<T> , ValueTask и ValueTask<T> ), должны заканчиваться на Async. Имена методов, которые запускают
асинхронную операцию, но не возвращают поддерживающий ожидание тип, не должны заканчиваться на
Async, но могут начинаться с Begin, Start или другой команды, предполагающей, что метод не возвращает и
не выдает результат операции.
Соглашение можно игнорировать в тех случаях, когда событие, базовый класс или контракт интерфейса
предлагает другое имя. Например, не следует переименовывать общие обработчики событий, такие как
Button1_Click .
Control Flow in Async Programs (C#) Выполняет подробную трассировку Пример использования Async. Поток
(Поток управления в асинхронных потока управления через управления в асинхронных
программах C#) последовательность выражений программах
ожидания в асинхронной программе.
Fine-Tuning Your Async Application Иллюстрирует добавление следующих Пример использования Async.
(C#) (Тонкая настройка асинхронного функциональных возможностей в Настройка приложения
приложения в C#) асинхронное решение:
WhenAny. Связывание .NET Иллюстрирует, как создать мост Пример использования Async.
Framework и среды выполнения между типами задач на платформе Связывание .NET и среды выполнения
Windows в C# и Visual Basic .NET Framework и IAsyncOperations в Windows с помощью AsTask и
среде выполнения Windows, чтобы WhenAny
можно было использовать WhenAny с
методом среды выполнения Windows.
Отмена Async. Связывание .NET Иллюстрирует, как создать мост Пример использования Async.
Framework и среды выполнения между типами задач на платформе Связывание .NET и среды выполнения
Windows .NET Framework и IAsyncOperations в Windows с помощью AsTask и
среде выполнения Windows, чтобы Cancellation
можно было использовать
CancellationTokenSource с методом
среды выполнения Windows.
Полный пример
Ниже представлен код файла MainWindow.xaml.cs из приложения WPF, которое обсуждается в этой статье.
Вы можете скачать пример использования Async из руководства по использованию ключевых слов Async и
Await.
using System;
using System.Threading.Tasks;
using System.Windows;
namespace AsyncFirstExample
{
public partial class MainWindow : Window
{
// Mark the event handler with async so you can use await in it.
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
// Call and await separately.
//Task<int> getLengthTask = AccessTheWebAsync();
//// You can do independent work here.
//int contentLength = await getLengthTask;
resultsTextBox.Text +=
$"\r\nLength of the downloaded string: {contentLength}.\r\n";
}
// GetStringAsync returns a Task<string>. That means that when you await the
// task you'll get a string (urlContents).
Task<string> getStringTask = client.GetStringAsync("https://docs.microsoft.com/dotnet");
// You can do work here that doesn't rely on the string from GetStringAsync.
DoIndependentWork();
void DoIndependentWork()
{
resultsTextBox.Text += "\r\nWorking . . . . . . .\r\n";
}
}
}
// Sample Output:
// Working . . . . . . .
См. также
async
await
Асинхронное программирование
Общие сведения об асинхронной модели
Пошаговое руководство. Доступ к Интернету с
помощью модификатора Async и оператора
Await (C#)
25.11.2019 • 25 minutes to read • Edit Online
Возможности Async и Await упрощают создание асинхронных программ. Можно написать асинхронный
код, который выглядит как синхронный, и позволить компилятору обрабатывать трудные функции
обратного вызова и продолжения, которые обычно включает асинхронный код.
Дополнительные сведения о возможности Async см. в разделе Асинхронное программирование с
использованием ключевых слов Async и Await (C#).
Это пошаговое руководство начинается с создания синхронного приложения Windows Presentation
Foundation (WPF ), которое суммирует число байтов в списке веб-сайтов. Затем в рамках руководства
приложение преобразуется в асинхронное решение с помощью новых возможностей.
Если вы не хотите создавать приложение самостоятельно, см. пошаговое руководство по доступу к
Интернету и использованию Async (C# и Visual Basic).
NOTE
Для выполнения примеров необходимо, чтобы на компьютере были установлены Visual Studio 2012 или более
поздняя версия и .NET Framework 4.5 или более поздняя версия.
Добавление ссылки
1. В обозревателе решений выделите имя проекта.
2. В строке меню выберите Проект > Добавить ссылку.
Откроется диалоговое окно Диспетчер ссылок.
3. Вверху диалогового окна убедитесь в том, что проект предназначен для платформы .NET
Framework 4.5 или более поздней версии.
4. В категории Сборки выберите Платформа, если этот вариант еще не выбран.
5. В списке имен установите флажок System.Net.Http.
6. Нажмите кнопку ОК, чтобы закрыть диалоговое окно.
using System.Net.Http;
using System.Net;
using System.IO;
resultsTextBox.Clear();
SumPageSizes();
resultsTextBox.Text += "\r\nControl returned to startButton_Click.";
var total = 0;
foreach (var url in urlList)
{
// GetURLContents returns the contents of url as a byte array.
byte[] urlContents = GetURLContents(url);
DisplayResults(url, urlContents);
msdn.microsoft.com/library/windows/apps/br211380.aspx 383832
msdn.microsoft.com 33964
msdn.microsoft.com/library/hh290136.aspx 225793
msdn.microsoft.com/library/ee256749.aspx 143577
msdn.microsoft.com/library/hh290138.aspx 237372
msdn.microsoft.com/library/hh290140.aspx 128279
msdn.microsoft.com/library/dd470362.aspx 157649
msdn.microsoft.com/library/aa578028.aspx 204457
msdn.microsoft.com/library/ms404677.aspx 176405
msdn.microsoft.com/library/ff730837.aspx 143474
Обратите внимание, что вывод результатов на экран занимает несколько секунд. В течение этого времени
поток пользовательского интерфейса заблокирован, пока он ожидает загрузку запрошенных ресурсов.
Соответственно, после нажатия кнопки Start окно нельзя перемещать, разворачивать, сворачивать или
даже закрывать. Эти действия будут завершаться сбоем, пока не появятся результаты подсчета. Если веб-
сайт не отвечает, вы не можете определить, на каком сайте произошел сбой. Трудно даже остановить
ожидание и закрыть программу.
NOTE
По мере выполнения шагов в этом пошаговом руководстве возникают различные ошибки компилятора. Их
можно игнорировать и продолжить процедуры пошагового руководства.
await responseStream.CopyToAsync(content);
4. Все, что остается сделать в GetURLContents , — это изменить подпись метода. Оператор await
можно использовать только в методах, помеченных модификатором async. Добавьте модификатор,
чтобы пометить метод как асинхронный, как показано в приведенном ниже примере кода.
5. Типом возвращаемого значения асинхронного метода может быть только Task, Task<TResult> или
void в C#. Как правило, тип возвращаемого значения void используется только в асинхронном
обработчике событий, где void является обязательным. В других случаях используется Task(T) ,
если завершенный метод имеет оператор return, возвращающий значение типа T, или Task , если
завершенный метод не возвращает осмысленное значение. Можно представить тип
возвращаемого значения Task как Task(void).
Дополнительные сведения см. в разделе Асинхронные типы возвращаемых значений (C#).
Метод GetURLContents имеет оператор return, который возвращает массив байтов. Таким образом,
тип возвращаемого значения асинхронной версии — Task(T), где T — массив байтов. Внесите
следующие изменения в подпись метода.
Измените тип возвращаемого значения на Task<byte[]> .
По соглашению об именовании асинхронные методы имеют имена, заканчивающиеся на
Async, поэтому переименуйте метод в GetURLContentsAsync .
В следующем примере кода показаны эти изменения.
Как и в предыдущих процедурах, вызов можно преобразовать, используя один или два оператора.
В следующем примере кода показаны эти изменения.
// Reenable the button in case you want to run the operation again.
startButton.IsEnabled = true;
пример кода
Следующий код содержит полный пример преобразования решения из синхронного в асинхронное с
помощью написанного вами асинхронного метода GetURLContentsAsync . Обратите внимание, что он очень
напоминает исходное синхронное решение.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// Add the following using directives, and add a reference for System.Net.Http.
using System.Net.Http;
using System.IO;
using System.Net;
namespace AsyncExampleWPF
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
resultsTextBox.Clear();
// Reenable the button in case you want to run the operation again.
// Reenable the button in case you want to run the operation again.
startButton.IsEnabled = true;
}
var total = 0;
DisplayResults(url, urlContents);
Следующий код содержит полный пример решения, использующего метод HttpClient , GetByteArrayAsync .
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// Add the following using directives, and add a reference for System.Net.Http.
using System.Net.Http;
using System.IO;
using System.Net;
namespace AsyncExampleWPF
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
// Reenable the button in case you want to run the operation again.
startButton.IsEnabled = true;
}
var total = 0;
// The following two lines can replace the previous assignment statement.
//Task<byte[]> getContentsTask = client.GetByteArrayAsync(url);
//byte[] urlContents = await getContentsTask;
DisplayResults(url, urlContents);
См. также
Пример использования Async. Пошаговое руководство. Обращение к веб-сайтам (C# и Visual Basic)
async
await
Асинхронное программирование с использованием ключевых слов async и await (C#)
Async Return Types (C#) (Типы возвращаемых значений асинхронных операций в C#)
Асинхронное программирование на основе задач (TAP )
Практическое руководство. Расширение пошагового руководства по асинхронным процедурам с
использованием метода Task.WhenAll (C#)
Практическое руководство. Параллельное выполнение нескольких веб-запросов с использованием
Async и Await (C#)
Практическое руководство. Расширение
пошагового руководства по асинхронным
процедурам с использованием метода
Task.WhenAll (C#)
25.11.2019 • 14 minutes to read • Edit Online
IMPORTANT
Следующие процедуры описывают расширения для асинхронных приложений, разработка которых описывается в
статье Пошаговое руководство. Доступ к Интернету с помощью модификатора Async и оператора Await (C#). Вы
можете разработать приложения, выполнив пошаговое руководство или скачав код на странице Примеры кода от
разработчиков.
Для выполнения этого примера на компьютере должна быть установлена среда Visual Studio 2012 или более
поздней версии.
2. Закомментируйте или удалите цикл foreach в SumPageSizesAsync , как показано в следующем коде.
//var total = 0;
//foreach (var url in urlList)
//{
// byte[] urlContents = await GetURLContentsAsync(url);
// DisplayResults(url, urlContents);
3. Создайте коллекцию задач. Следующий код определяет запрос, который при выполнении метода
ToArray создает коллекцию задач, скачивающих содержимое каждого веб-сайта. Задачи
запускаются при вычислении запроса.
Добавьте следующий код в метод SumPageSizesAsync после объявления urlList .
// Create a query.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURLAsync(url);
// Use ToArray to execute the query and start the download tasks.
Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
5. И, наконец, используйте метод Sum для вычисления суммы длин всех веб-сайтов. Добавьте
следующую строку в SumPageSizesAsync .
int total = lengths.Sum();
2. Закомментируйте или удалите цикл For Each или foreach в SumPageSizesAsync , как показано в
следующем коде.
//var total = 0;
//foreach (var url in urlList)
//{
// // GetByteArrayAsync returns a Task<T>. At completion, the task
// // produces a byte array.
// byte[] urlContent = await client.GetByteArrayAsync(url);
// DisplayResults(url, urlContent);
3. Определите запрос, который при выполнении метода ToArray создает коллекцию задач,
скачивающих содержимое каждого веб-сайта. Задачи запускаются при вычислении запроса.
Добавьте следующий код в метод SumPageSizesAsync после объявления client и urlList .
// Create a query.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURLAsync(url, client);
// Use ToArray to execute the query and start the download tasks.
Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
5. И, наконец, используйте метод Sum для получения суммы длин всех веб-сайтов. Добавьте
следующую строку в SumPageSizesAsync .
Пример
В следующем коде показано расширение для проекта, использующее метод GetURLContentsAsync для
скачивания содержимого из Интернета.
// Add the following using directives, and add a reference for System.Net.Http.
using System.Net.Http;
using System.IO;
using System.Net;
namespace AsyncExampleWPF_WhenAll
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
// Create a query.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURLAsync(url);
// Use ToArray to execute the query and start the download tasks.
Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
//var total = 0;
//foreach (var url in urlList)
//{
// byte[] urlContents = await GetURLContentsAsync(url);
// DisplayResults(url, urlContents);
// The actions from the foreach loop are moved to this async method.
private async Task<int> ProcessURLAsync(string url)
{
var byteArray = await GetURLContentsAsync(url);
DisplayResults(url, byteArray);
return byteArray.Length;
}
Пример
В следующем коде показано расширение для проекта, использующее метод HttpClient.GetByteArrayAsync
для скачивания содержимого из Интернета.
// Add the following using directives, and add a reference for System.Net.Http.
using System.Net.Http;
using System.IO;
using System.Net;
namespace AsyncExampleWPF_HttpClient_WhenAll
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private async void startButton_Click(object sender, RoutedEventArgs e)
{
resultsTextBox.Clear();
// Create a query.
IEnumerable<Task<int>> downloadTasksQuery =
from url in urlList select ProcessURLAsync(url, client);
// Use ToArray to execute the query and start the download tasks.
Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
//var total = 0;
//foreach (var url in urlList)
//{
// // GetByteArrayAsync returns a Task<T>. At completion, the task
// // produces a byte array.
// byte[] urlContent = await client.GetByteArrayAsync(url);
// DisplayResults(url, urlContent);
// The actions from the foreach loop are moved to this async method.
async Task<int> ProcessURLAsync(string url, HttpClient client)
{
byte[] byteArray = await client.GetByteArrayAsync(url);
DisplayResults(url, byteArray);
return byteArray.Length;
}
См. также
Task.WhenAll
Пошаговое руководство: Доступ к Интернету с помощью модификатора Async и оператора Await в C#
Практическое руководство. Параллельное
выполнение нескольких веб-запросов с
использованием Async и Await (C#)
25.11.2019 • 8 minutes to read • Edit Online
В асинхронном методе задачи запускаются в момент создания. Оператор Await применяется к задаче в той
точке в методе, где обработка не может продолжаться до завершения задачи. Часто задачи ожидаются в
момент создания, как показано в следующем примере.
Тем не менее можно отделить создание задачи от ее ожидания, если программа содержит другую работу,
выполнение которой не зависит от завершения задачи.
// While the task is running, you can do other work that doesn't depend
// on the results of the task.
// . . . . .
// The application of await suspends the rest of this method until the task is complete.
var result = await myTask;
Между моментом запуска задачи и ее ожиданием можно запустить другие задачи. Дополнительные задачи
неявно выполняются параллельно, однако дополнительные потоки не создаются.
Следующая программа запускает три асинхронные веб-загрузки, а затем ожидает их в том порядке, в
котором они вызываются. Обратите внимание, что при запуске программы задачи не всегда завершаются в
том порядке, в котором они созданы и ожидаются. Они начинают выполняться в момент создания, и одна
или несколько задач могут завершиться прежде, чем метод достигает выражения await.
NOTE
Для выполнения этого проекта необходимо, чтобы на компьютере были установлены Visual Studio 2012 или более
поздняя версия и .NET Framework 4.5 или более поздняя версия.
Другой пример, в котором одновременно запускается несколько задач, см. в разделе Практическое
руководство. Расширение пошагового руководства по асинхронным процедурам с использованием метода
Task.WhenAll (C#).
Код для этого примера можно скачать на странице Примеры кода от разработчиков.
Настройка проекта
1. Чтобы настроить приложение WPF, выполните следующие действия. Подробные инструкции для
выполнения этих действий можно найти в разделе Пошаговое руководство. Доступ к Интернету с
помощью модификатора Async и оператора Await (C#).
Создайте приложение WPF, которое содержит текстовое поле и кнопку. Назовите кнопку
startButton , а текстовое поле — resultsTextBox .
Добавьте ссылку для System.Net.Http.
Добавьте в файл MainWindow.xaml.cs директиву using для System.Net.Http .
Добавление кода
1. В окне конструктора для MainWindow.xaml дважды нажмите кнопку, чтобы создать обработчик
события startButton_Click в файле MainWindow.xaml.cs.
2. Скопируйте следующий код в тело startButton_Click в файле MainWindow.xaml.cs.
resultsTextBox.Clear();
await CreateMultipleTasksAsync();
resultsTextBox.Text += "\r\n\r\nControl returned to startButton_Click.\r\n";
5. Нажмите клавишу F5, чтобы запустить программу, а затем нажмите кнопку Start .
Запустите программу несколько раз, чтобы убедиться, что три задачи не всегда завершаются в том
же порядке, а порядок их завершения не обязательно совпадает с порядком, в котором они
создаются и ожидаются.
Пример
Следующий код содержит полный пример.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// Add the following using directive, and add a reference for System.Net.Http.
// Add the following using directive, and add a reference for System.Net.Http.
using System.Net.Http;
namespace AsyncExample_MultipleTasks
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
См. также
Пошаговое руководство: Доступ к Интернету с помощью модификатора Async и оператора Await в C#
Асинхронное программирование с использованием ключевых слов async и await (C#)
Практическое руководство. Расширение пошагового руководства по асинхронным процедурам с
использованием метода Task.WhenAll (C#)
Асинхронные типы возвращаемых значений (C#)
23.10.2019 • 11 minutes to read • Edit Online
return leisureHours;
}
}
// The example displays output like the following:
// Today is Wednesday, May 24, 2017
// Today's hours of leisure: 5
// </Snippet >
При вызове GetLeisureHours из выражения await в методе ShowTodaysInfo это выражение await извлекает
целочисленное значение (значение leisureHours ), хранящееся в задаче, которая возвращается методом
GetLeisureHours . Дополнительные сведения о выражениях await см. в разделе await.
Чтобы лучше понять, как это происходит, отделите вызов метода GetLeisureHours от применения await ,
как показано в следующем коде. Вызов метода GetLeisureHours , который не ожидается немедленно,
возвращает Task<int> , как и следовало ожидать из объявления метода. В данном примере эта задача
назначается переменной integerTask . Поскольку integerTask является Task<TResult>, она содержит
свойство Result типа TResult . В этом примере TResult представляет собой целочисленный тип. Если
выражение await применяется к integerTask , выражение await вычисляется как содержимое свойства
Result объекта integerTask . Это значение присваивается переменной ret .
IMPORTANT
Свойство Result является блокирующим свойством. При попытке доступа к нему до завершения его задачи поток,
который в текущий момент активен, блокируется до того момента, пока задача не будет завершена, а ее значение
не станет доступным. В большинстве случаев следует получать доступ к этому значению с помощью await
вместо прямого обращения к свойству.
В предыдущем примере извлекалось значение свойства Result для блокировки основного потока. Это позволяет
закончить выполнение метода ShowTodaysInfo до того, как завершится работа приложения.
var integerTask = GetLeisureHours();
// You can do other work that does not rely on integerTask before awaiting.
using System;
using System.Threading.Tasks;
using System;
using System.Threading.Tasks;
await secondHandlerFinished;
}
// Expected output:
// About to click a button...
// Somebody has clicked a button. Let's raise the event...
// Handler 1 is starting...
// Handler 1 is done.
// Handler 2 is starting...
// Handler 2 is about to go async...
// Handler 3 is starting...
// Handler 3 is done.
// All listeners are notified.
// Button's Click method returned.
// Handler 2 is done.
using System;
using System.Threading.Tasks;
class Program
{
static Random rnd;
await Task.Delay(500);
int diceRoll = rnd.Next(1, 7);
return diceRoll;
}
}
// The example displays output like the following:
// ...Shaking the dice...
// You rolled 8
См. также
FromResult
Пошаговое руководство: Доступ к Интернету с помощью модификатора Async и оператора Await в C#
Control Flow in Async Programs (C#) (Поток управления в асинхронных программах C#)
async
await
Поток управления в асинхронных программах
(C#)
23.10.2019 • 15 minutes to read • Edit Online
Можно намного проще создавать и обслуживать асинхронные программы с помощью ключевых слов
async и await . Однако при непонимании механизма работы асинхронной программы результаты могут
удивить. В этом разделе выполняется трассировка потока управления с помощью простой асинхронной
программы, чтобы продемонстрировать переход потока управления от одного метода к другому, включая
данные, передаваемые в каждом случае.
Как правило, вы помечаете методы, содержащие асинхронный код, с помощью модификатора async (C#).
В методе, помеченном с помощью модификатора async, можно использовать оператор await (C#), чтобы
указать место приостановки метода для ожидания завершения вызванного асинхронного процесса.
Дополнительные сведения см. в разделе Асинхронное программирование с использованием ключевых
слов Async и Await (C#).
В следующем примере асинхронные методы используются для загрузки содержимого указанного веб-
сайта в виде строки и отображения длины строки. Пример содержит следующие два метода:
startButton_Click , который вызывает метод AccessTheWebAsync и выводит результат;
AccessTheWebAsync, который загружает содержимое веб-сайта в виде строки и возвращает длину
строки. AccessTheWebAsync использует для загрузки содержимого асинхронный метод HttpClient
GetStringAsync(String).
Выводимые строки помечены номерами на стратегических этапах программы, чтобы помочь вам понять,
как работает программа и что происходит на каждом отмеченном этапе. Выводимые строки обозначены
номерами от ONE (один) до SIX (шесть). Метки представляют порядок, в котором программа достигает
эти строки кода.
В следующем примере кода показана структура программы.
public partial class MainWindow : Window
{
// . . .
private async void startButton_Click(object sender, RoutedEventArgs e)
{
// ONE
Task<int> getLengthTask = AccessTheWebAsync();
// FOUR
int contentLength = await getLengthTask;
// SIX
resultsTextBox.Text +=
$"\r\nLength of the downloaded string: {contentLength}.\r\n";
}
// THREE
string urlContents = await getStringTask;
// FIVE
return urlContents.Length;
}
}
Настройка программы
Можно загрузить используемый в этом разделе код из MSDN, или же можно создать его самостоятельно.
NOTE
Для выполнения этого примера на компьютере должны быть установлены Visual Studio 2012 или более поздней
версии и .NET Framework 4.5 или более поздней версии.
Скачивание программы
Вы можете скачать приложение для этого раздела на странице примеров Пример Async: поток
управления в асинхронных программах. Следующие шаги описывают процесс открытия и запуска
программы.
1. Распакуйте загруженный файл, а затем запустите Visual Studio.
2. В строке меню выберите Файл > Открыть > Решение или проект.
3. Перейдите к папке, содержащей распакованный пример кода, откройте файл решения (SLN ), а
затем нажмите клавишу F5 для сборки и выполнения проекта.
Самостоятельное создание программы
Следующий проект Windows Presentation Foundation (WPF ) содержит примеры кода для этого раздела.
Чтобы запустить проект, выполните следующие действия.
1. Запустите Visual Studio.
2. В строке меню выберите Файл > Создать > Проект.
Откроется диалоговое окно Новый проект .
3. Выберите категории Установленные > Visual C# > Windows Desktop, а затем выберите
Приложение WPF в списке шаблонов проектов.
4. Введите AsyncTracer в качестве имени проекта и нажмите кнопку ОК.
В обозревателе решений появится новый проект.
5. В редакторе кода Visual Studio перейдите на вкладку MainWindow.xaml .
Если вкладка не отображается, откройте контекстное меню для MainWindow.xaml в обозревателе
решений и выберите пункт Просмотреть код.
6. Замените код в представлении XAML файла MainWindow.xaml на следующий.
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
x:Class="AsyncTracer.MainWindow"
Title="Control Flow Trace" Height="350" Width="592">
<Grid>
<Button x:Name="startButton" Content="Start
" HorizontalAlignment="Left" Margin="250,10,0,0" VerticalAlignment="Top" Width="75" Height="24"
Click="startButton_Click" d:LayoutOverrides="GridBox"/>
<TextBox x:Name="resultsTextBox" HorizontalAlignment="Left" TextWrapping="Wrap"
VerticalAlignment="Bottom" Width="576" Height="265" FontFamily="Lucida Console" FontSize="10"
VerticalScrollBarVisibility="Visible" Grid.ColumnSpan="3"/>
</Grid>
</Window>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace AsyncTracer
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
resultsTextBox.Text +=
$"\r\nLength of the downloaded string: {contentLength}.\r\n";
}
resultsTextBox.Text +=
"\r\n About to await getStringTask and return a Task<int> to
startButton_Click.\r\n";
return urlContents.Length;
}
}
}
10. Нажмите клавишу F5, чтобы запустить программу, а затем нажмите кнопку Запуск.
Появится следующий результат:
Трассировка программы
Шаги ОДИН и ДВА
В первых двух строках прослеживается путь по мере того, как метод startButton_Click вызывает
AccessTheWebAsync , а AccessTheWebAsync вызывает асинхронный метод HttpClient GetStringAsync( String).
Ниже показаны вызовы из метода в метод.
Типом возвращаемого значения и для AccessTheWebAsync , и для client.GetStringAsync является
Task<TResult>. Для AccessTheWebAsync значение TResult является целым числом. Для GetStringAsync
значение TResult является строкой. Дополнительные сведения о возвращаемых типах асинхронных
методов см. в разделе Асинхронные типы возвращаемых значений (C#).
Асинхронный метод, возвращающий задачи, возвращает экземпляр задачи, когда контроль управления
возвращается к вызывающему объекту. Управление передается от асинхронного метода его
вызывающему методу, когда в вызванном методе обнаруживается оператор await или когда вызванный
метод завершается. Отображаемые строки, которые помечены от трёх до шести, отслеживают эту часть
процесса.
Шаг ТРИ
В AccessTheWebAsync для загрузки содержимого целевой веб-страницы вызывается асинхронный метод
GetStringAsync(String). Управление передается от client.GetStringAsync методу AccessTheWebAsync при
возвращении результатов методом client.GetStringAsync .
Метод client.GetStringAsyncвозвращает задачу строки, назначенной переменной getStringTask в
AccessTheWebAsync . Следующая строка в примере программы демонстрирует вызов client.GetStringAsync
и назначение.
Можно представить себе задачу как обещание client.GetStringAsync создать в конечном итоге
фактическую строку. В то же время, если у AccessTheWebAsync есть работа, не зависящая от обещанной
строки, от client.GetStringAsync , эта работа будет продолжена во время ожидания client.GetStringAsync .
В этом примере следующие строки вывода, которые обозначены как "THREE", представляют возможность
сделать независимую работу.
NOTE
Как правило, ожидание вызова асинхронного метода выполняется немедленно. Например, следующее присвоение
может заменить предыдущий код, который создает, а затем ожидает getStringTask :
string urlContents = await client.GetStringAsync("https://msdn.microsoft.com");
В этом разделе оператор await применяется позже для размещения строк, которые отмечают поток управления в
программе.
Шаг ЧЕТЫРЕ
Объявленный тип возвращаемого значения AccessTheWebAsync — Task<int> . Таким образом, когда
выполнение AccessTheWebAsync приостанавливается, он возвращает задачу целого числа в
startButton_Click . Следует понимать, что возвращаемая задача — не getStringTask . Возвращаемая
задача — это новая задача целого числа, представляющая оставшуюся работу в приостановленном
методе AccessTheWebAsync . Задача является обещанием AccessTheWebAsync создать фактическое целое
число после завершения задачи.
Следующий оператор назначает эту задачу переменной getLengthTask .
Шаг ПЯТЬ
Когда client.GetStringAsync уведомляет о завершении, обработка в AccessTheWebAsync возобновляется и
может продолжаться после оператора await. Приведенные ниже строки выходных данных представляют
возобновление обработки.
Выражение await получает из getLengthTask целое значение, представляющее операнд оператора return в
AccessTheWebAsync . Следующий оператор назначает это значение переменной contentLength .
Методы и свойства, доступные при использовании типа Task, позволяют сделать приложение более
точным и гибким. В подразделах этого раздела приводятся примеры, в которых используются
CancellationToken и важные методы Task , такие как Task.WhenAll и Task.WhenAny.
С помощью WhenAny и WhenAll можно легко запускать несколько задач и ожидать их завершения путем
наблюдения за одной из них.
WhenAny возвращает задачу, которая завершается после завершения любой задачи в коллекции.
Примеры использования WhenAny см. в разделах Отмена оставшихся асинхронных задач после
завершения одной из них (C#) и Запуск нескольких асинхронных задач и их обработка по мере
завершения (C#).
WhenAll возвращает задачу, которая завершается после завершения всех задач в коллекции.
Дополнительные сведения и пример, использующий метод WhenAll , см. в разделе Практическое
руководство. Расширение пошагового руководства по асинхронным процедурам с использованием
метода Task.WhenAll (C#).
Этот раздел содержит следующие примеры.
Отмена асинхронной задачи или списка задач (C#)
Отмена асинхронных задач после определенного периода времени (C#)
Отмена оставшихся асинхронных задач после завершения одной из них (C#)
Запуск нескольких асинхронных задач и их обработка по мере завершения (C#)
NOTE
Для выполнения примеров необходимо, чтобы на компьютере были установлены Visual Studio 2012 или более
поздняя версия и .NET Framework 4.5 или более поздняя версия.
Проекты создают пользовательский интерфейс, содержащий кнопку, которая запускает процесс, и кнопку,
которая его отменяет, как показано на следующем рисунке. Кнопки называются startButton и
cancelButton .
Скачать полный проект Windows Presentation Foundation (WPF ) можно со страницы Пример
асинхронности. Тонкая настройка приложения.
См. также
Асинхронное программирование с использованием ключевых слов async и await (C#)
Отмена асинхронной задачи или списка задач в
C#
23.10.2019 • 13 minutes to read • Edit Online
Вы можете настроить кнопку, которая позволит отменить асинхронное приложение в случае, если вы не
захотите дожидаться его завершения. Выполнив код в приведенных ниже примерах, вы сможете добавить в
приложение кнопку отмены, загружающую содержимое одного веб-сайта или список веб-сайтов.
В этих примерах используется пользовательский интерфейс, описанный в разделе Настройка асинхронного
приложения (C#).
NOTE
Для выполнения примеров необходимо, чтобы на компьютере были установлены Visual Studio 2012 или более
поздняя версия и .NET Framework 4.5 или более поздняя версия.
Отмена задачи
Код в первом примере связывает кнопку Отмена с отдельной задачей загрузки. Если нажать эту кнопку,
когда приложение загружает содержимое, загрузка будет отменена.
Скачивание примера
Скачать полный проект Windows Presentation Foundation (WPF ) можно со страницы Пример
асинхронности. Тонкая настройка приложения. Затем выполните следующие шаги.
1. Распакуйте загруженный файл, а затем запустите Visual Studio.
2. В строке меню выберите Файл > Открыть > Решение или проект.
3. В диалоговом окне Открытие проекта откройте папку с примером кода, который вы распаковали, а
затем откройте файл решения (SLN ) для AsyncFineTuningCS.
4. В обозревателе решений откройте контекстное меню проекта CancelATask и выберите команду
Назначить запускаемым проектом.
5. Нажмите клавишу F5, чтобы запустить проект (или нажмите клавиши Ctrl+F5, чтобы запустить
проект без отладки).
TIP
Если не хотите скачивать проект, можете просмотреть файл MainWindow.xaml.cs в конце этого раздела.
Сборка примера
Следующие изменения добавляют кнопку Отмена в приложение, загружающее веб-сайт. Если вы не
хотите загружать или собирать пример, просмотрите конечный результат в разделе "Завершенные
примеры" в конце раздела. Звездочками отмечены изменения в коде.
Для самостоятельной сборки примера шаг за шагом выполните инструкции в разделе "Загрузка примера",
но в качестве запускаемого проекта выберите проект StarterCode, а не CancelATask.
Внесите в файл MainWindow.xaml.cs проекта указанные ниже изменения.
1. Объявите переменную CancellationTokenSource , cts , которая находится в области действия всех
методов, имеющих к ней доступ.
2. Добавьте следующий обработчик событий для кнопки Отмена. Этот обработчик событий
использует метод CancellationTokenSource.Cancel для отправки уведомления в cts при запросе
отмены пользователем.
3. Внесите в обработчик событий указанные ниже изменения для кнопки Пуск, startButton_Click .
Создайте экземпляр CancellationTokenSource , cts .
try
{
// ***Send a token to carry the message if cancellation is requested.
int contentLength = await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += $"\r\nLength of the downloaded string: {contentLength}.\r\n";
}
// *** If cancellation is requested, an OperationCanceledException results.
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownload canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownload failed.\r\n";
}
Ready to download.
Length of the downloaded string: 158125.
Если вы нажмете кнопку Отмена, прежде чем программа завершит загрузку содержимого, будет
выдан представленный ниже результат.
Ready to download.
Download canceled.
resultsTextBox.Text +=
$"\r\nLength of the downloaded string: {urlContents.Length}.\r\n";
}
await AccessTheWebAsync(cts.Token);
Downloads complete.
При нажатии кнопки Отмена до завершения загрузки выходные данные будут включать объем
данных, загруженных до отмены.
Downloads canceled.
Полные примеры
Следующие разделы содержат код каждого из приведенных выше примеров. Обратите внимание на то,
что необходимо добавить ссылку для System.Net.Http.
Вы можете скачать проект из статьи c примером использования async. Тонкая настройка приложения.
Пример отмены задачи
Приведенный ниже код представляет собой полный файл MainWindow.xaml.cs, демонстрирующий отмену
отдельной задачи.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Threading;
namespace CancelATask
{
public partial class MainWindow : Window
public partial class MainWindow : Window
{
// ***Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
public MainWindow()
{
InitializeComponent();
}
resultsTextBox.Clear();
try
{
// ***Send a token to carry the message if cancellation is requested.
int contentLength = await AccessTheWebAsync(cts.Token);
resultsTextBox.Text +=
$"\r\nLength of the downloaded string: {contentLength}.\r\n";
}
// *** If cancellation is requested, an OperationCanceledException results.
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownload canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownload failed.\r\n";
}
// Ready to download.
// Ready to download.
// Download canceled.
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace CancelAListOfTasks
{
public partial class MainWindow : Window
{
// Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
public MainWindow()
{
InitializeComponent();
}
resultsTextBox.Clear();
try
{
await AccessTheWebAsync(cts.Token);
// ***Small change in the display lines.
resultsTextBox.Text += "\r\nDownloads complete.";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownloads canceled.";
resultsTextBox.Text += "\r\nDownloads canceled.";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownloads failed.";
}
resultsTextBox.Text +=
$"\r\nLength of the downloaded string: {urlContents.Length}.\r\n";
}
}
//Downloads complete.
//Downloads canceled.
}
См. также
CancellationTokenSource
CancellationToken
Асинхронное программирование с использованием ключевых слов async и await (C#)
Fine-Tuning Your Async Application (C#) (Тонкая настройка асинхронного приложения в C#)
Пример использования Async. Настройка приложения
Отмена асинхронных задач после определенного
периода времени (C#)
23.10.2019 • 6 minutes to read • Edit Online
Если не нужно дожидаться, пока завершится выполнение асинхронной операции, ее можно отменить по
истечении определенного периода времени с помощью метода CancellationTokenSource.CancelAfter. Этот
метод планирует отмену всех связанных задач, не завершенных в течение времени, установленного
выражением CancelAfter .
В этом примере добавляется код, составленный в разделе Отмена асинхронной задачи или списка задач
(C#), для загрузки списка веб-сайтов и отображения длины содержимого каждого из них.
NOTE
Для выполнения примеров необходимо, чтобы на компьютере были установлены Visual Studio 2012 или более
поздняя версия и .NET Framework 4.5 или более поздняя версия.
Скачивание примера
Скачать полный проект Windows Presentation Foundation (WPF ) можно со страницы Пример
асинхронности. Тонкая настройка приложения. Затем выполните следующие шаги.
1. Распакуйте загруженный файл, а затем запустите Visual Studio.
2. В строке меню выберите Файл > Открыть > Решение или проект.
3. В диалоговом окне Открытие проекта откройте папку с примером кода, который вы распаковали, а
затем откройте файл решения (SLN ) для AsyncFineTuningCS.
4. В обозревателе решений откройте контекстное меню проекта CancelAfterTime и выберите
команду Назначить запускаемым проектом.
5. Нажмите клавишу F5, чтобы запустить проект. (Или нажмите CTRL+F5, чтобы запустить проект без
отладки).
6. Выполните программу несколько раз, чтобы убедиться, что ее выходные данные могут содержать
выходные данные по всем веб-сайтам, по некоторым из них или не содержать никакие данные по веб-
сайтам.
Если вы не хотите скачивать проект, можете просмотреть файл MainWindow.xaml.cs в конце этого раздела.
Сборка примера
Пример в этом разделе добавляется в проект для отмены списка задач, разработка которого описывается в
разделе Отмена асинхронной задачи или списка задач (C#). В примере используется тот же
пользовательский интерфейс, хотя кнопка Отмена не используется явно.
Для самостоятельной сборки примера шаг за шагом следуйте инструкциям в разделе "Загрузка примера", но
выберите в качестве запускаемого проекта проект CancelAfterOneTask. Добавьте изменения,
приведенные в данном разделе, в этот проект.
Чтобы задать максимальный период времени, по истечении которого задачи будут отмечены как
отмененные, добавьте вызов CancelAfter в startButton_Click , как показано в приведенном ниже примере.
Добавления помечены звездочками.
resultsTextBox.Clear();
try
{
// ***Set up the CancellationTokenSource to cancel after 2.5 seconds. (You
// can adjust the time.)
cts.CancelAfter(2500);
await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += "\r\nDownloads succeeded.\r\n";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
cts = null;
}
Выполните программу несколько раз, чтобы убедиться, что ее выходные данные могут содержать выходные
данные по всем веб-сайтам, по некоторым из них или не содержать никакие данные по веб-сайтам.
Следующие результаты приводятся для примера.
Downloads canceled.
Полный пример
Приведенный ниже код — полный текст файла MainWindow.xaml.cs для примера. Звездочками помечаются
элементы, добавленные для этого примера.
Обратите внимание на то, что необходимо добавить ссылку для System.Net.Http.
Вы можете скачать проект из статьи Пример асинхронности. Тонкая настройка приложения.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace CancelAfterTime
{
public partial class MainWindow : Window
{
// Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
public MainWindow()
{
InitializeComponent();
}
resultsTextBox.Clear();
try
{
// ***Set up the CancellationTokenSource to cancel after 2.5 seconds. (You
// can adjust the time.)
cts.CancelAfter(2500);
await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += "\r\nDownloads succeeded.\r\n";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
cts = null;
}
resultsTextBox.Text +=
$"\r\nLength of the downloaded string: {urlContents.Length}.\r\n";
}
}
// Sample Output:
// Downloads canceled.
}
См. также
Асинхронное программирование с использованием ключевых слов async и await (C#)
Пошаговое руководство: Доступ к Интернету с помощью модификатора Async и оператора Await в C#
Отмена асинхронной задачи или списка задач в C#
Fine-Tuning Your Async Application (C#) (Тонкая настройка асинхронного приложения в C#)
Пример использования Async. Настройка приложения
Отмена оставшихся асинхронных задач после
завершения одной из них (C#)
23.10.2019 • 9 minutes to read • Edit Online
Используя метод Task.WhenAny вместе с CancellationToken, можно отменить все оставшиеся задачи после
выполнения отдельной задачи. Метод WhenAny принимает аргумент, который представляет собой
коллекцию задач. Метод запускает все задачи и возвращает одну задачу. Одна задача считается
завершенной, когда завершена любая задача в коллекции.
В этом примере показано, как использовать маркер отмены в сочетании с WhenAny для приостановки
завершения первой задачи из коллекции задач и отмены оставшихся задач. Каждая задача загружает
содержимое веб-сайта. Пример отображает длину содержимого первой завершаемой загрузки и отменяет
другие загрузки.
NOTE
Для выполнения примеров необходимо, чтобы на компьютере были установлены Visual Studio 2012 или более
поздняя версия и .NET Framework 4.5 или более поздняя версия.
Загрузка примера
Скачать полный проект Windows Presentation Foundation (WPF ) можно со страницы Пример
асинхронности. Тонкая настройка приложения. Затем выполните следующие шаги.
1. Распакуйте загруженный файл, а затем запустите Visual Studio.
2. В строке меню выберите Файл, Открыть, Проект/Решение.
3. В диалоговом окне Открытие проекта откройте папку с примером кода, который вы распаковали, а
затем откройте файл решения (SLN ) для AsyncFineTuningCS.
4. В обозревателе решений откройте контекстное меню проекта CancelAfterOneTask и выберите
команду Назначить запускаемым проектом.
5. Нажмите клавишу F5, чтобы запустить проект.
Нажмите сочетание клавиш CTRL+F5, чтобы запустить проект без отладки.
6. Запустите программу несколько раз, чтобы убедиться, что разные задачи завершаются первыми.
Если вы не хотите скачивать проект, можете просмотреть файл MainWindow.xaml.cs в конце этого раздела.
Построение примера
Пример в этом разделе добавляется в проект для отмены списка задач, разработка которого описывается в
разделе Отмена асинхронной задачи или списка задач (C#). В примере используется тот же
пользовательский интерфейс, хотя кнопка Отмена не используется явно.
Для самостоятельной сборки примера шаг за шагом следуйте инструкциям в разделе "Загрузка примера",
но выберите в качестве запускаемого проекта проект CancelAfterOneTask. Добавьте изменения,
приведенные в данном разделе, в этот проект.
В файле MainWindow.xaml.cs проекта CancelAListOfTasks запустите переход, переместив шаги обработки
для каждого веб-сайта из цикла в AccessTheWebAsync в следующий асинхронный метод.
// ***Bundle the processing steps for a website into one async method.
async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)
{
// GetAsync returns a Task<HttpResponseMessage>.
HttpResponseMessage response = await client.GetAsync(url, ct);
return urlContents.Length;
}
В AccessTheWebAsync в этом примере используются запрос, метод ToArray и метод WhenAny для создания и
запуска массива задач. Применение WhenAny к массиву возвращает одну задачу, которая, если ожидается,
вычисляется как первая завершившаяся задача в массиве задач.
Внесите следующие изменения в AccessTheWebAsync . Звездочками отмечены изменения в файле кода.
1. Закомментируйте или удалите цикл.
2. Создайте запрос, который во время выполнения создает коллекцию общих заданий. Каждый вызов
ProcessURLAsync возвращает Task<TResult>, где TResult — это целое число.
3. Вызовите ToArray для выполнения запроса и запуска задач. Применение метода WhenAny в
следующем шаге будет выполнять запрос и запускать задачи без использования ToArray , однако
этот режим может быть недоступен для других методов. Наиболее безопасным способом является
явное принудительное выполнения запроса.
// ***Use ToArray to execute the query and start the download tasks.
Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
4. Вызовите WhenAny для коллекции задач. WhenAny возвращает Task(Of Task(Of Integer)) или
Task<Task<int>> . То есть WhenAny возвращает задачу, которая вычисляется как одна задача
Task(Of Integer) или Task<int> , если она ожидается. Одна задача — это первая завершившаяся
задача в коллекции. Задача, которая завершается первой, назначается firstFinishedTask .
firstFinishedTask имеет тип Task<TResult>, где TResult является целым числом, поскольку это
возвращаемый тип ProcessURLAsync .
// ***Call WhenAny and then await the result. The task that finishes
// first is assigned to firstFinishedTask.
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
5. В этом примере нас интересует только та задача, которая завершается первой. Таким образом,
используйте CancellationTokenSource.Cancel для отмены оставшихся задач.
// ***Cancel the rest of the downloads. You just want the first one.
cts.Cancel();
6. Наконец, мы ожидаем firstFinishedTask для получения длины скачанного содержимого.
Запустите программу несколько раз, чтобы убедиться, что разные задачи завершаются первыми.
Полный пример
Приведенный ниже код — полный файл MainWindow.xaml.cs для примера. Звездочками помечаются
элементы, добавленные для этого примера.
Обратите внимание на то, что необходимо добавить ссылку для System.Net.Http.
Вы можете скачать проект из статьи Пример асинхронности. Тонкая настройка приложения.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace CancelAfterOneTask
{
public partial class MainWindow : Window
{
// Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
public MainWindow()
{
InitializeComponent();
}
resultsTextBox.Clear();
try
{
await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += "\r\nDownload complete.";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownload canceled.";
}
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownload failed.";
}
// resultsTextBox.Text +=
// $"\r\nLength of the downloaded string: {urlContents.Length}.\r\n";
//}
// ***Use ToArray to execute the query and start the download tasks.
Task<int>[] downloadTasks = downloadTasksQuery.ToArray();
// ***Call WhenAny and then await the result. The task that finishes
// first is assigned to firstFinishedTask.
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
// ***Cancel the rest of the downloads. You just want the first one.
cts.Cancel();
// ***Bundle the processing steps for a website into one async method.
async Task<int> ProcessURLAsync(string url, HttpClient client, CancellationToken ct)
{
// GetAsync returns a Task<HttpResponseMessage>.
HttpResponseMessage response = await client.GetAsync(url, ct);
// Retrieve the website contents from the HttpResponseMessage.
byte[] urlContents = await response.Content.ReadAsByteArrayAsync();
return urlContents.Length;
}
// Download complete.
}
См. также
WhenAny
Fine-Tuning Your Async Application (C#) (Тонкая настройка асинхронного приложения в C#)
Асинхронное программирование с использованием ключевых слов async и await (C#)
Пример использования Async. Настройка приложения
Запуск нескольких асинхронных задач и их
обработка по мере завершения (C#)
23.10.2019 • 7 minutes to read • Edit Online
NOTE
Для выполнения примеров необходимо, чтобы на компьютере были установлены Visual Studio 2012 или более
поздняя версия и .NET Framework 4.5 или более поздняя версия.
TIP
Если вы не хотите скачивать проект, просто изучите файл MainWindow.xaml.cs в конце этого раздела.
6. Запустите проект несколько раз, чтобы проверить, что загруженные размеры не всегда
отображаются в одинаковом порядке.
Добавьте цикл while , который выполняет следующие действия для каждой задачи в коллекции.
1. Ожидает вызов WhenAny для определения первой задачи в коллекции, чтобы завершить ее
загрузку.
downloadTasks.Remove(firstFinishedTask);
Запустите проект несколько раз и убедитесь, что размеры скачанных файлов не всегда отображаются в
одном и том же порядке.
Cau t i on
Можно использовать WhenAny в цикле, как описано в примере, для решения проблем, которые включают
небольшое число задач. Однако когда требуется обработка большого числа задач, другие методы будут
более эффективны. Дополнительные сведения и примеры см. в разделе Обработка задач по мере их
завершения.
Полный пример
Приведенный ниже код — это полный текст файла MainWindow.xaml.cs для примера. Звездочками
помечаются элементы, добавленные для этого примера. Также обратите внимание, что необходимо
добавить ссылку для System.Net.Http.
Вы можете скачать проект из статьи Пример асинхронности. Тонкая настройка приложения.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace ProcessTasksAsTheyFinish
{
public partial class MainWindow : Window
{
// Declare a System.Threading.CancellationTokenSource.
CancellationTokenSource cts;
public MainWindow()
{
InitializeComponent();
}
try
{
await AccessTheWebAsync(cts.Token);
resultsTextBox.Text += "\r\nDownloads complete.";
}
catch (OperationCanceledException)
{
resultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
resultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
cts = null;
}
// ***Add a loop to process the tasks one at a time until none remain.
while (downloadTasks.Count > 0)
{
// Identify the first task that completes.
Task<int> firstFinishedTask = await Task.WhenAny(downloadTasks);
// ***Remove the selected task from the list so that you don't
// process it more than once.
downloadTasks.Remove(firstFinishedTask);
return urlContents.Length;
}
}
}
// Sample Output:
См. также
WhenAny
Fine-Tuning Your Async Application (C#) (Тонкая настройка асинхронного приложения в C#)
Асинхронное программирование с использованием ключевых слов async и await (C#)
Пример использования Async. Настройка приложения
Обработка повторного входа в асинхронных
приложениях (C#)
31.10.2019 • 26 minutes to read • Edit Online
При включении асинхронного кода в приложение следует учесть и по возможности избежать повторного
входа, под которым подразумевается повторный ввод асинхронной операции до ее завершения. Если не
определить и не обработать возможности повторного входа, это может привести к непредвиденным
результатам.
Содержание раздела
Распознавание поддержки повторного входа
Обработка поддержки повторного входа
Отключение кнопки запуска
Отмена и перезапуск операции
Запуск нескольких операций и постановка выходных данных в очередь
Проверка и выполнение примера приложения
NOTE
Для выполнения этого примера на компьютере должны быть установлены Visual Studio 2012 или более поздней
версии и .NET Framework 4.5 или более поздней версии.
NOTE
Версия протокола TLS 1.2 теперь является минимальной версией для использования в разработке приложений. Если
приложение предназначено для более ранней версии .NET Framework, чем 4.7, обратитесь к следующей статье, чтобы
ознакомиться с рекомендациями по протоколу TLS в .NET Framework
Однако если пользователь нажимает кнопку больше одного раза, обработчик событий вызывается
несколько раз и процесс загрузки будет каждый раз выполняться повторно. В результате одновременно
запускается несколько асинхронных операций, получаемые выходные данные чередуются и счетчик общего
числа байтов выдает неоднозначные результаты.
1. msdn.microsoft.com/library/hh191443.aspx 83732
2. msdn.microsoft.com/library/aa578028.aspx 205273
3. msdn.microsoft.com/library/jj155761.aspx 29019
4. msdn.microsoft.com/library/hh290140.aspx 117152
5. msdn.microsoft.com/library/hh524395.aspx 68959
1. msdn.microsoft.com/library/hh191443.aspx 83732
2. msdn.microsoft.com/library/aa578028.aspx 205273
6. msdn.microsoft.com/library/ms404677.aspx 197325
3. msdn.microsoft.com/library/jj155761.aspx 29019
7. msdn.microsoft.com 42972
4. msdn.microsoft.com/library/hh290140.aspx 117152
8. msdn.microsoft.com/library/ff730837.aspx 146159
5. msdn.microsoft.com/library/hh524395.aspx 68959
1. msdn.microsoft.com/library/hh191443.aspx 83732
2. msdn.microsoft.com/library/aa578028.aspx 205273
6. msdn.microsoft.com/library/ms404677.aspx 197325
3. msdn.microsoft.com/library/jj155761.aspx 29019
4. msdn.microsoft.com/library/hh290140.aspx 117152
7. msdn.microsoft.com 42972
5. msdn.microsoft.com/library/hh524395.aspx 68959
8. msdn.microsoft.com/library/ff730837.aspx 146159
6. msdn.microsoft.com/library/ms404677.aspx 197325
7. msdn.microsoft.com 42972
8. msdn.microsoft.com/library/ff730837.aspx 146159
Чтобы просмотреть код, создающий эти выходные данные, перейдите к концу раздела. Можно
поэкспериментировать с кодом, загрузив решение на локальный компьютер и затем запустив проект
WebsiteDownload или воспользовавшись кодом в конце этого раздела для создания собственного проекта.
Дополнительные сведения и инструкции см. в разделе Проверка и выполнение примера приложения.
try
{
await AccessTheWebAsync();
}
catch (Exception)
{
ResultsTextBox.Text += "\r\nDownloads failed.";
}
// ***Enable the Start button in case you want to run the program again.
finally
{
StartButton.IsEnabled = true;
}
}
В результате этих изменений кнопка не будет реагировать в течение загрузки веб-сайтов методом
AccessTheWebAsync , поэтому повторный вход в процесс будет невозможен.
// *** Now set cts to a new value that you can use to cancel the current process
// if the button is chosen again.
CancellationTokenSource newCTS = new CancellationTokenSource();
cts = newCTS;
4. В конце процедуры StartButton_Click текущий процесс будет выполнен, поэтому верните для cts
значение null.
// *** When the process is complete, signal that another process can begin.
if (cts == newCTS)
cts = null;
// *** Now set cts to cancel the current process if the button is chosen again.
CancellationTokenSource newCTS = new CancellationTokenSource();
cts = newCTS;
try
{
// ***Send cts.Token to carry the message if there is a cancellation request.
await AccessTheWebAsync(cts.Token);
}
// *** Catch cancellations separately.
catch (OperationCanceledException)
{
ResultsTextBox.Text += "\r\nDownloads canceled.\r\n";
}
catch (Exception)
{
ResultsTextBox.Text += "\r\nDownloads failed.\r\n";
}
// *** When the process is complete, signal that another process can proceed.
if (cts == newCTS)
cts = null;
}
var total = 0;
var position = 0;
При многократном нажатии кнопки Start во время выполнения этого приложения код должен создать
результаты, похожие на следующие выходные данные.
1. msdn.microsoft.com/library/hh191443.aspx 83732
2. msdn.microsoft.com/library/aa578028.aspx 205273
3. msdn.microsoft.com/library/jj155761.aspx 29019
4. msdn.microsoft.com/library/hh290140.aspx 122505
5. msdn.microsoft.com/library/hh524395.aspx 68959
6. msdn.microsoft.com/library/ms404677.aspx 197325
Download canceled.
1. msdn.microsoft.com/library/hh191443.aspx 83732
2. msdn.microsoft.com/library/aa578028.aspx 205273
3. msdn.microsoft.com/library/jj155761.aspx 29019
Download canceled.
1. msdn.microsoft.com/library/hh191443.aspx 83732
2. msdn.microsoft.com/library/aa578028.aspx 205273
3. msdn.microsoft.com/library/jj155761.aspx 29019
4. msdn.microsoft.com/library/hh290140.aspx 117152
5. msdn.microsoft.com/library/hh524395.aspx 68959
6. msdn.microsoft.com/library/ms404677.aspx 197325
7. msdn.microsoft.com 42972
8. msdn.microsoft.com/library/ff730837.aspx 146159
Чтобы исключить частичные списки, раскомментируйте первую строку кода в StartButton_Click , чтобы
очищать текстовое поле при каждом перезапуске операции.
Запуск нескольких операций и постановка выходных данных в очередь
Третий пример является наиболее сложным, так как приложение запускает другую асинхронную операцию
каждый раз, когда пользователь нажимает кнопку Start, и все операции выполняются до завершения. Все
запрошенные операции загружают веб-сайты из списка асинхронно, однако выходные данные операций
представляются последовательно. Получается, что фактические действия загрузки чередуются, как
показывают выходные данные в разделе Распознавание возникновения повторного входа, однако список
результатов для каждой группы отображается отдельно.
Операции совместно используют глобальную переменную Task, pendingWork , служащую привратником для
процесса отображения.
Чтобы настроить этот сценарий, внесите следующие изменения в основной код, который содержится в
разделе Проверка и выполнение примера приложения. Также можно загрузить готовое приложение в
разделе Примеры асинхронности. Поддержка повторного входа в классических приложениях .NET. Этот
проект называется QueueResults.
Ниже показаны выходные данные, отображаемые в результате нажатия кнопки Start только один раз.
Метка A указывает, что это результат первого нажатия кнопки Start. Нумерация показывает порядок
отображения URL -адресов в списке целевых объектов для загрузки.
#Starting group A.
#Task assigned for group A.
#Group A is complete.
Если пользователь нажимает кнопку Start три раза, приложение выдает результат, похожий на приведенный
ниже. Информационные строки, начинающиеся с символа #, отслеживают ход выполнения приложения.
#Starting group A.
#Task assigned for group A.
#Starting group B.
#Task assigned for group B.
#Starting group C.
#Task assigned for group C.
#Group A is complete.
#Group B is complete.
#Group C is complete.
Группы B и C запускаются до завершения группы A, однако результаты для каждой группы отображаются
отдельно. Все выходные данные для группы A отображаются сначала, затем идут все выходные данные для
группы B и, наконец, для группы C. Приложение всегда отображает группы по порядку и для каждой группы
всегда отображает сведения об отдельных веб-сайтах в порядке появления URL -адресов в списке URL -
адресов.
Тем не менее невозможно предсказать порядок, в котором фактически происходит загрузка. После запуска
нескольких групп все созданные ими задачи загрузки остаются активными. Не следует предполагать, что
данные A-1 будут загружены до данных B -1, а данные A-1 будут загружены до данных A-2.
Глобальные определения
Пример кода содержит объявление двух следующих глобальных переменных, доступных для всех методов.
public partial class MainWindow : Window // Class MainPage in Windows Store app.
{
// ***Declare the following variables where all methods can access them.
private Task pendingWork = null;
private char group = (char)('A' - 1);
try
{
// *** Pass the group value to AccessTheWebAsync.
char finishedGroup = await AccessTheWebAsync(group);
// The following line verifies a successful return from the download and
// display procedures.
ResultsTextBox.Text += $"\r\n\r\n#Group {finishedGroup} is complete.\r\n";
}
catch (Exception)
{
ResultsTextBox.Text += "\r\nDownloads failed.";
}
}
Метод AccessTheWebAsync
В этом примере метод AccessTheWebAsync разбит на два метода. Первый метод, AccessTheWebAsync , запускает
все задачи загрузки для группы и передает задаче pendingWork управление процессом отображения. Метод
использует запрос LINQ и ToArray для запуска всех задач загрузки одновременно.
Затем метод AccessTheWebAsync вызывает метод FinishOneGroupAsync для ожидания завершения каждой
загрузки и отображения ее длины.
Метод FinishOneGroupAsync возвращает задачу, которая назначена pendingWork , в AccessTheWebAsync . Это
значение предотвращает прерывание другой операцией до завершения выполнения задачи.
private async Task<char> AccessTheWebAsync(char grp)
{
HttpClient client = new HttpClient();
// ***Kick off the downloads. The application of ToArray activates all the download tasks.
Task<byte[]>[] getContentTasks = urlList.Select(url => client.GetByteArrayAsync(url)).ToArray();
// ***Call the method that awaits the downloads and displays the results.
// Assign the Task that FinishOneGroupAsync returns to the gatekeeper task, pendingWork.
pendingWork = FinishOneGroupAsync(urlList, getContentTasks, grp);
ResultsTextBox.Text += $"\r\n#Task assigned for group {grp}. Download tasks are active.\r\n";
// ***This task is complete when a group has finished downloading and displaying.
await pendingWork;
Метод FinishOneGroupAsync
Этот метод выполняет циклический переход по задачам загрузки в группе, ожидая каждую из них,
отображая длину загруженного веб-сайта и добавляя длину в общую сумму.
Первый оператор в FinishOneGroupAsync использует pendingWork , чтобы гарантировать, что ввод метода не
помешает выполнению операции, которая уже находится в процессе отображения или в состоянии
ожидания. Если такая операция выполняется, новая операция должна дождаться своей очереди.
int total = 0;
Интересующие точки
Информационные строки, начинающиеся с символа # в выходных данных, поясняют, как работает этот
пример.
Выходные данные демонстрируют следующие схемы.
Группу можно запустить, когда предыдущая группа отображает свои выходные данные, но
отображение выходных данных предыдущей группы не прерывается.
#Starting group A.
#Task assigned for group A. Download tasks are active.
#Starting group B.
#Task assigned for group B. Download tasks are active.
#Group A is complete.
Задача pendingWork имеет значение NULL при запуске метода FinishOneGroupAsync только для группы
A, которая запускается первой. Группа A еще не завершила выражение await, когда она достигает
метода FinishOneGroupAsync . Таким образом, управление не возвращается AccessTheWebAsync , а первое
присваивание задаче pendingWork не возникает.
Следующие две строки всегда отображаются в выходных данных вместе. Код не прерывается нигде
между запуском операции группы в обработчике StartButton_Click и назначением задачи для
группы в pendingWork .
#Starting group B.
#Task assigned for group B. Download tasks are active.
NOTE
Для выполнения этого примера как традиционного приложения Windows Presentation Foundation на компьютере
должны быть установлены Visual Studio 2012 или более поздней версии и .NET Framework 4.5 или более поздней
версии.
Загрузка приложения
1. Скачайте сжатый файл в разделе Примеры асинхронности. Поддержка повторного входа в
классических приложениях .NET.
2. Распакуйте загруженный файл, а затем запустите Visual Studio.
3. В строке меню выберите Файл, Открыть, Проект/Решение.
4. Перейдите к папке, содержащей распакованный пример кода, а затем откройте файл решения (SLN -
файл).
5. В обозревателе решений откройте контекстное меню нужного проекта и выберите пункт
Назначить запускаемым проектом.
6. Нажмите сочетание клавиш CTRL+F5, чтобы выполнить сборку и запуск проекта.
Сборка приложения
В следующих разделах приведен код для построения примера как приложения WPF.
П о с тр о е н и е п р и л о ж е н и я W P F
<Window x:Class="WebsiteDownloadWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:WebsiteDownloadWPF"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
// Add the following using directives, and add a reference for System.Net.Http.
using System.Net.Http;
using System.Threading;
namespace WebsiteDownloadWPF
{
public partial class MainWindow : Window
{
public MainWindow()
{
System.Net.ServicePointManager.SecurityProtocol |= System.Net.SecurityProtocolType.Tls12;
InitializeComponent();
}
try
{
await AccessTheWebAsync();
}
catch (Exception)
{
ResultsTextBox.Text += "\r\nDownloads failed.";
}
}
var total = 0;
var position = 0;
11. Нажмите сочетание клавиш CTRL+F5, чтобы запустить программу, а затем несколько раз нажмите
кнопку Start.
12. Внесите изменения, описанные в разделах Отключение кнопки запуска, Отмена и перезапуск
операции или Запуск нескольких операций и постановка выходных данных в очередь для обработки
повторного входа.
См. также
Пошаговое руководство: Доступ к Интернету с помощью модификатора Async и оператора Await в C#
Асинхронное программирование с использованием ключевых слов async и await (C#)
Использование метода Async для доступа к
файлам (C#)
23.10.2019 • 8 minutes to read • Edit Online
Для доступа к файлам можно использовать функцию Async. При использовании функции Async вы можете
вызывать асинхронные методы без использования обратных вызовов или разделения вашего кода на
множество методов или лямбда-выражений. Для выполнения последовательного кода асинхронно просто
вызовите асинхронный метод вместо синхронного метода и добавьте несколько ключевых слов в код.
Можно рассмотреть следующие причины для добавления асинхронности для вызовов для доступа к файлам.
Асинхронность делает приложения пользовательского интерфейса более отзывчивыми, потому что
поток пользовательского интерфейса, который запускает операцию, может продолжать выполнять и
другую работу. Если поток пользовательского интерфейса должен выполнять код, который занимает
много времени (например, более 50 миллисекунд), пользовательский интерфейс можно
приостановить до тех пор, пока не будет завершен ввод-вывод, и затем пользовательский интерфейс
сможет снова обрабатывать ввод с клавиатуры, мыши и другие события.
Асинхронность улучшает масштабируемость ASP.NET и других серверных приложений за счет
уменьшения необходимости использования потоков. Если приложение использует выделенный поток
на ответ и тысяча запросов приходит одновременно, тысяча потоков не потребуется. Асинхронные
операции часто не нуждаются в пользовании потоком во время ожидания. Они пользуются
существующим потоком завершения ввода-вывода короткое время в конце.
Задержка операции доступа к файлу может быть очень низкой при текущих условиях, но может
значительно увеличиться в будущем. Например, файл может быть перемещен на сервер через
Интернет.
Добавленные издержки при использовании функции Async являются малыми.
Асинхронные задачи могут легко выполняться параллельно.
Выполнение примеров
Чтобы выполнить примеры в этом разделе, вы можете создать приложение WPF или приложение
Windows Forms, а затем добавить кнопку. В событии Click кнопки добавьте вызов к первому методу в
каждом примере.
В следующие примеры добавьте указанные ниже операторы using .
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Text;
using System.Threading.Tasks;
Запись текста
Следующие примеры записывают текст в файл. На каждой точке await происходит немедленный выход из
метода. После завершения файлового ввода-вывода метод возобновляет работу с пункта, следующего за
await. Обратите внимание, что модификатор async в определении методов требует наличия await в теле
метода.
Первый оператор возвращает задачу и вызывает запуск обработки файла. Вторая строка с await немедленно
оставляет метод и возвращается в другую задачу. При окончании обработки файла выполнение
возвращается в точку выполнения, которая следует за await. Дополнительные сведения см. в разделе Поток
управления в асинхронных программах (C#).
Чтение текста
Следующий пример считывает текст из файла. Текст добавляется в буфер обмена, а затем, в данном случае,
помещается в StringBuilder. В отличие от предыдущего примера await выдаёт в результате значение. Метод
ReadAsync возвращает Task<Int32>, поэтому по завершении операции оценка await выдает значение Int32
( numRead ). Дополнительные сведения см. в разделе Асинхронные типы возвращаемых значений (C#).
public async Task ProcessReadAsync()
{
string filePath = @"temp2.txt";
if (File.Exists(filePath) == false)
{
Debug.WriteLine("file not found: " + filePath);
}
else
{
try
{
string text = await ReadTextAsync(filePath);
Debug.WriteLine(text);
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
}
return sb.ToString();
}
}
try
{
for (int index = 1; index <= 10; index++)
{
string text = "In file " + index.ToString() + "\r\n";
tasks.Add(theTask);
}
await Task.WhenAll(tasks);
}
finally
{
foreach (FileStream sourceStream in sourceStreams)
{
sourceStream.Close();
}
}
}
При использовании методов WriteAsync и ReadAsync можно указать CancellationToken, который позволяет
отменить операцию в середине потока. Дополнительные сведения см. в разделах Настройка асинхронного
приложения (C#) и Отмена в управляемых потоках.
См. также
Асинхронное программирование с использованием ключевых слов async и await (C#)
Async Return Types (C#) (Типы возвращаемых значений асинхронных операций в C#)
Control Flow in Async Programs (C#) (Поток управления в асинхронных программах C#)
Атрибуты (C#)
25.11.2019 • 8 minutes to read • Edit Online
Атрибуты предоставляют мощное средство для связывания метаданных или декларативной информации с
кодом (сборки, типы, методы, свойства и т. д.). Связав атрибут связан с сущностью программы, вы можете
проверять этот атрибут во время выполнения, используя технику отражения. Подробнее см. в разделе
Отражение (C#).
Атрибуты имеют следующие свойства.
Атрибуты добавляют в программу метаданные. Метаданные — это сведения о типах, определенных в
программе. Все сборки .NET содержат некоторый набор метаданных, описывающих типы и члены
типов, определенные в этой сборке. Вы можете добавить пользовательские атрибуты, чтобы указать
любую дополнительную информацию. Подробнее см. в разделе Создание настраиваемых
атрибутов (C#).
Вы можете применить один или несколько атрибутов ко всей сборке, к модулю или к более мелким
элементам программы, например к классам и свойствам.
Атрибуты могут принимать аргументы, так же как методы и свойства.
Программа может проверить собственные метаданные или метаданные в других программах,
используя отражение. Дополнительные сведения см. в разделе Обращение к атрибутам с помощью
отражения (C#).
Использование атрибутов
Атрибуты можно использовать почти в любых объявлениях, но для каждого атрибута можно ограничить
типы объявлений, в которых он является допустимым. Чтобы указать атрибут на C#, поместите имя
атрибута в квадратных скобках ([]) над объявлением сущности, к которой он применяется.
В этом примере атрибут SerializableAttribute используется для применения определенной характеристики к
классу:
[Serializable]
public class SampleClass
{
// Objects of this type can be serialized.
}
[System.Runtime.InteropServices.DllImport("user32.dll")]
extern static void SampleMethod();
using System.Runtime.InteropServices;
[Conditional("DEBUG"), Conditional("TEST1")]
void TraceMethod()
{
// ...
}
NOTE
По соглашению все имена атрибутов заканчиваются словом Attribute, чтобы отличать их от других элементов
библиотек .NET. Но при использовании атрибута в коде этот суффикс можно не указывать. Например, обращение
[DllImport] эквивалентно [DllImportAttribute] , хотя в библиотеке классов .NET Framework этот атрибут имеет
имя DllImportAttribute .
Параметры атрибутов
Многие атрибуты имеют параметры, которые могут быть позиционными, неименованными или
именованными. Позиционные параметры должны указываться в определенном порядке и не могут
опускаться. Именованные параметры являются необязательными и могут указываться в любом порядке.
Сначала указываются позиционные параметры. Например, эти три атрибута являются эквивалентными:
[DllImport("user32.dll")]
[DllImport("user32.dll", SetLastError=false, ExactSpelling=false)]
[DllImport("user32.dll", ExactSpelling=false, SetLastError=false)]
Первый параметр (имя библиотеки DLL ) является позиционным и всегда располагается первым, остальные
параметры являются именованными. В этом примере оба именованных параметра имеют значение по
умолчанию (false), и мы можем их опустить. Позиционные параметры соответствуют параметрам
конструктора атрибутов. Именованные (или необязательные) параметры соответствуют свойствам или
полям атрибута. Сведения о значениях параметров по умолчанию указываются в документации по
каждому отдельному атрибуту.
Целевые объекты атрибутов
Целевой объект атрибута — это сущность, к которой применяется атрибут. Например, атрибут можно
применить к классу, отдельному методу или ко всей сборке. По умолчанию атрибут применяется к тому
элементу, который следует за ним. Но у вас есть возможность явным образом указать, например, что
атрибут применяется к методу, параметру или возвращаемому значению.
Чтобы явным образом определить целевой объект атрибута, используйте следующий синтаксис:
[target : attribute-list]
event событие
property Свойство.
Следует указать целевое значение field , чтобы применить атрибут к резервной переменной, созданной
для автоматически реализуемого свойства.
Следующий пример демонстрирует, как применить атрибуты к сборкам и модулям. Дополнительные
сведения см. в разделе Общие атрибуты (C#).
using System;
using System.Reflection;
[assembly: AssemblyTitleAttribute("Production assembly 4")]
[module: CLSCompliant(true)]
// applies to method
[method: ValidatedContract]
int Method2() { return 0; }
NOTE
Вне зависимости от целевых объектов, для которых действует атрибут ValidatedContract , необходимо задать
целевой объект для return , даже если атрибут ValidatedContract назначен только возвращаемым значениям.
Другими словами, компилятор не будет использовать сведения AttributeUsage для разрешения конфликтов
между неоднозначными целевыми объектами атрибута. Подробнее см. в разделе AttributeUsage (C#).
Связанные разделы
Дополнительные сведения можно найти в разделе
Создание настраиваемых атрибутов (C#)
Обращение к атрибутам с помощью отражения (C#)
Практическое руководство. Создание объединения C/C++ с помощью атрибутов (C#)
Общие атрибуты (C#)
Caller Information (C#) (Сведения о вызывающем объекте (C#))
См. также
Руководство по программированию на C#
Reflection (C#) (Отражение (C#))
Атрибуты
Использование атрибутов в C#
Создание настраиваемых атрибутов (C#)
23.10.2019 • 2 minutes to read • Edit Online
Собственные настраиваемые атрибуты можно создать, определив класс атрибута, то есть класс, прямо или
косвенно наследующий от Attribute, который упрощает задание определений атрибутов в метаданных.
Предположим, что требуется пометить тип тегом с именем программиста, который его разработал. Вы
можете определить класс настраиваемых атрибутов Author :
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct)
]
public class Author : System.Attribute
{
private string name;
public double version;
Имя класса — это имя атрибута, Author . Он является производным от System.Attribute , поэтому это класс
настраиваемых атрибутов. Параметры конструктора являются позиционными параметрами настраиваемого
атрибута. В этом примере name является позиционным параметром. Все открытые поля или свойства,
доступные для чтения и записи, являются именованными параметрами. В этом случае version —
единственный именованный параметр. Обратите внимание на использование атрибута AttributeUsage ,
делающего атрибут Author допустимым только для класса и объявлений struct .
Этот атрибут можно использовать следующим образом:
AttributeUsage имеет именованный параметр, AllowMultiple , с помощью которого можно задавать для
настраиваемого атрибута однократное или многократное использование. В следующем примере кода
создается многократно используемый атрибут.
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // multiuse attribute
]
public class Author : System.Attribute
См. также
System.Reflection
Руководство по программированию на C#
Написание настраиваемых атрибутов
Reflection (C#) (Отражение (C#))
Атрибуты (C#)
Обращение к атрибутам с помощью отражения (C#)
AttributeUsage (C#)
AttributeUsage (C#)
23.10.2019 • 4 minutes to read • Edit Online
[System.AttributeUsage(System.AttributeTargets.All,
AllowMultiple = false,
Inherited = true)]
class NewAttribute : System.Attribute { }
В этом примере класс NewAttribute можно применить к любому поддерживаемому элементу программы.
Но он применяется к каждому объекту только один раз. Атрибут наследуется производными классами при
применении к базовому классу.
Аргументы AllowMultiple и Inherited являются необязательными, так что следующий код имеет тот же
результат:
[System.AttributeUsage(System.AttributeTargets.All)]
class NewAttribute : System.Attribute { }
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
class NewPropertyOrFieldAttribute : Attribute { }
Начиная с C# 7.3 атрибуты могут применяться либо к свойству, либо к резервному полю для автоматически
реализуемого свойства. Атрибут применяется к свойству, если не указан описатель field для атрибута. Оба
случая показаны в следующем примере:
class MyClass
{
// Attribute attached to property:
[NewPropertyOrField]
public string Name { get; set; }
Если аргументу AllowMultiple присвоено значение true , то результирующий атрибут можно применить
несколько раз к одной сущности, как показано в следующем примере:
[MultiUse]
[MultiUse]
class Class1 { }
[MultiUse, MultiUse]
class Class2 { }
В этом случае MultiUseAttribute можно применять несколько раз, так как AllowMultiple имеет значение
true . Для применения нескольких атрибутов допускаются оба показанных формата.
Если Inherited — false , атрибут не наследуется классами, производными от класса атрибутов. Например:
[NonInherited]
class BClass { }
Примечания
Атрибут AttributeUsage можно использовать только один раз — его нельзя повторно применять к одному и
тому же классу. AttributeUsage является псевдонимом для AttributeUsageAttribute.
Дополнительные сведения см. в разделе Обращение к атрибутам с помощью отражения (C#).
Пример
В приведенном ниже примере демонстрируется действие аргументов Inherited и AllowMultiple по
отношению к атрибуту AttributeUsageAttribute, а также способ перечисления настраиваемых атрибутов,
примененных к классу.
using System;
[AttributeUsage(AttributeTargets.Class)]
class SecondAttribute : Attribute { }
[Third, Third]
class DerivedClass : BaseClass { }
См. также
Attribute
System.Reflection
Руководство по программированию на C#
Атрибуты
Reflection (C#) (Отражение (C#))
Атрибуты
Создание настраиваемых атрибутов (C#)
Обращение к атрибутам с помощью отражения (C#)
Обращение к атрибутам с помощью отражения
(C#)
23.10.2019 • 2 minutes to read • Edit Online
Однако код не выполняется до тех пор, пока у SampleClass не будут запрошены атрибуты. Вызов
GetCustomAttributes в SampleClass приведет к тому, что объект Author будет создан и инициализирован
так, как показано выше. Если класс имеет другие атрибуты, другие объекты атрибутов создаются
аналогично. GetCustomAttributes затем возвращает объект Author и все другие объекты атрибутов в
массиве. Потом можно пройти по этому массиву, определить в зависимости от типа каждого элемента
массива какие атрибуты были применены и извлечь сведения из объектов атрибутов.
Пример
Ниже приведен полный пример. Определяется настраиваемый атрибут, который применяется к
нескольким сущностям и извлекается через отражение.
// Multiuse attribute.
[System.AttributeUsage(System.AttributeTargets.Class |
System.AttributeTargets.Struct,
AllowMultiple = true) // Multiuse attribute.
]
public class Author : System.Attribute
{
string name;
public double version;
// Default value.
version = 1.0;
}
class TestAuthorAttribute
{
static void Test()
{
PrintAuthorInfo(typeof(FirstClass));
PrintAuthorInfo(typeof(SecondClass));
PrintAuthorInfo(typeof(ThirdClass));
}
// Using reflection.
System.Attribute[] attrs = System.Attribute.GetCustomAttributes(t); // Reflection.
// Displaying output.
foreach (System.Attribute attr in attrs)
{
if (attr is Author)
{
Author a = (Author)attr;
System.Console.WriteLine(" {0}, version {1:f}", a.GetName(), a.version);
}
}
}
}
/* Output:
Author information for FirstClass
P. Ackerman, version 1.00
Author information for SecondClass
Author information for ThirdClass
R. Koch, version 2.00
P. Ackerman, version 1.00
*/
См. также
System.Reflection
Attribute
Руководство по программированию на C#
Извлечение информации, сохраненной в атрибуте
Reflection (C#) (Отражение (C#))
Атрибуты (C#)
Создание настраиваемых атрибутов (C#)
Практическое руководство. Создание
объединения C/C++ с помощью атрибутов (C#)
25.11.2019 • 2 minutes to read • Edit Online
С помощью атрибутов можно настраивать расположение структур в памяти. Например, можно создать так
называемое объединение в C/C++ с помощью атрибутов StructLayout(LayoutKind.Explicit) и FieldOffset .
Пример
В этом сегменте кода все поля объединения TestUnion начинаются с одного адреса в памяти.
[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestUnion
{
[System.Runtime.InteropServices.FieldOffset(0)]
public int i;
[System.Runtime.InteropServices.FieldOffset(0)]
public double d;
[System.Runtime.InteropServices.FieldOffset(0)]
public char c;
[System.Runtime.InteropServices.FieldOffset(0)]
public byte b;
}
Пример
Ниже приведен еще один пример, в котором поля начинаются с разных явно заданных адресов.
// Add a using directive for System.Runtime.InteropServices.
[System.Runtime.InteropServices.StructLayout(LayoutKind.Explicit)]
struct TestExplicit
{
[System.Runtime.InteropServices.FieldOffset(0)]
public long lg;
[System.Runtime.InteropServices.FieldOffset(0)]
public int i1;
[System.Runtime.InteropServices.FieldOffset(4)]
public int i2;
[System.Runtime.InteropServices.FieldOffset(8)]
public double d;
[System.Runtime.InteropServices.FieldOffset(12)]
public char c;
[System.Runtime.InteropServices.FieldOffset(14)]
public byte b;
}
См. также
System.Reflection
Attribute
Руководство по программированию на C#
Атрибуты
Reflection (C#) (Отражение (C#))
Атрибуты (C#)
Создание настраиваемых атрибутов (C#)
Обращение к атрибутам с помощью отражения (C#)
Общие атрибуты (C#)
23.10.2019 • 11 minutes to read • Edit Online
В этом разделе описываются атрибуты, которые чаще всего используются в программах C#.
Глобальные атрибуты
Атрибут Obsolete
Атрибут Conditional
Информационные атрибуты вызывающего объекта
Глобальные атрибуты
Большинство атрибутов применяется к определенным элементам языка, таким как классы или методы. Тем
не менее некоторые атрибуты являются глобальными — они применяются ко всей сборке или модулю.
Например, атрибут AssemblyVersionAttribute можно использовать для встраивания сведений о версии в
сборку, например следующим образом:
[assembly: AssemblyVersion("1.0.0.0")]
Глобальные атрибуты отображаются в исходном коде после любых директив using верхнего уровня и
перед всеми объявлениями типов, модулей или пространств имен. Глобальные атрибуты могут содержаться
в нескольких исходных файлах, однако эти файлы должны быть скомпилированы за один проход
компиляции. В проектах C# глобальные атрибуты помещаются в файл AssemblyInfo.cs.
Атрибуты сборки — это значения, которые предоставляют сведения о сборке. Они делятся на следующие
категории:
Атрибуты удостоверения сборки
Информационные атрибуты
Атрибуты манифеста сборки
Атрибуты удостоверения сборки
Три атрибута (со строгим именем, если оно применимо) определяют удостоверение сборки: имя, версию,
язык и региональные параметры. Эти атрибуты формируют полное имя сборки и являются обязательными
при ссылке на нее в коде. Атрибуты можно использовать для задания версии сборки и языка и
региональных параметров. Однако значение имени задается компилятором, в интегрированной среде
разработки Visual Studio, в диалоговом окне сведений о сборке или в компоновщике сборок (Al.exe) при
создании сборки на основе файла, содержащего манифест сборки. Атрибут AssemblyFlagsAttribute
указывает, могут ли сосуществовать несколько копий сборки.
В следующей таблице приведены атрибуты удостоверения.
АТРИБУТ ЦЕЛЬ
Информационные атрибуты
Информационные атрибуты можно использовать для предоставления дополнительных сведений о
компании или продукте в сборке. В следующей таблице показаны информационные атрибуты,
определенные в пространстве имен System.Reflection.
АТРИБУТ ЦЕЛЬ
АТРИБУТ ЦЕЛЬ
В этом примере атрибут Obsolete применяется к классу A и к методу B.OldMethod . Поскольку для второго
аргумента конструктора атрибутов, примененного к B.OldMethod , задано значение true , этот метод будет
вызывать ошибку компилятора, тогда как при использовании класса A будет просто создаваться
предупреждение. При этом вызов B.NewMethod не создает предупреждений или ошибок.
Строка, предоставленная в качестве первого аргумента конструктору атрибута, будет отображаться как
часть предупреждения или ошибки. Например, при использовании с предыдущими определениями
следующий код создает два предупреждения и одну ошибку:
// Generates 2 warnings:
// A a = new A();
Создается два предупреждения для класса A : одно для объявления ссылки на класс, а второе — для
конструктора класса.
Атрибут Obsolete может использоваться без аргументов, однако рекомендуется включать пояснение о том,
почему соответствующий элемент является устаревшим и что следует использовать вместо него.
Атрибут Obsolete является атрибутом однократного использования и может применяться к любой
сущности, допускающей использование атрибутов. Obsolete является псевдонимом для ObsoleteAttribute.
Атрибут Conditional
Атрибут Conditional определяет зависимость выполнения метода от идентификатора предварительной
обработки. Атрибут Conditional является псевдонимом для ConditionalAttribute и может применяться к
методу или классу атрибута.
В этом примере атрибут Conditional применяется к методу для включения и отключения отображения
диагностической информации для конкретной программы:
#define TRACE_ON
using System;
using System.Diagnostics;
[Conditional("DEBUG")]
static void DebugMethod()
{
}
Когда вызывается метод, помеченный как условный, наличие или отсутствие заданного символа
предварительной обработки определяет, включается ли вызов или пропускается. Если этот символ
определен, вызов включается; в противном случае вызов пропускается. Использование атрибута
Conditional — это более понятная, элегантная и менее подверженная ошибкам альтернатива вложению
методов в блоки #if…#endif , например следующим образом:
#if DEBUG
void ConditionalMethod()
{
}
#endif
Условный метод должен быть методом в объявлении класса или структуры и не должен возвращать
значение.
Использование нескольких идентификаторов
Если метод содержит несколько атрибутов Conditional , вызов метода включается, если определен хотя бы
один из условных символов (другими словами, символы логически связаны с помощью оператора ИЛИ ). В
этом примере наличие A или B приведет к вызову метода:
[Conditional("A"), Conditional("B")]
static void DoIfAorB()
{
// ...
}
Для получения эффекта логического связывания символов с помощью оператора И можно определить
последовательные условные методы. Например, второй метод ниже будет выполняться, только если
определены A и B :
[Conditional("A")]
static void DoIfA()
{
DoIfAandB();
}
[Conditional("B")]
static void DoIfAandB()
{
// Code to execute when both A and B are defined...
}
[Conditional("DEBUG")]
public class Documentation : System.Attribute
{
string text;
class SampleClass
{
// This attribute will only be included if DEBUG is defined.
[Documentation("This method displays an integer.")]
static void DoWork(int i)
{
System.Console.WriteLine(i.ToString());
}
}
См. также
System.Reflection
Attribute
Руководство по программированию на C#
Атрибуты
Reflection (C#) (Отражение (C#))
Обращение к атрибутам с помощью отражения (C#)
Сведения о вызывающем объекте (C#)
23.10.2019 • 4 minutes to read • Edit Online
Пример
В следующем примере показано, как использовать информационные атрибуты вызывающего объекта.
При каждом вызове метода TraceMessage сведения о вызывающем объекте подставляются в качестве
аргументов необязательных параметров.
// Sample Output:
// message: Something happened.
// member name: DoProcessing
// source file path: c:\Visual Studio Projects\CallerInfoCS\CallerInfoCS\Form1.cs
// source line number: 31
Примечания
Для каждого необязательного параметра необходимо указать явное значение по умолчанию. Нельзя
применять информационные атрибуты вызывающего объекта к параметрам, которые не были указаны как
необязательные.
Информационные атрибуты вызывающего объекта не делают параметр необязательным. Вместо этого
они влияют на значение по умолчанию, которое передается, если аргумент был опущен.
Информационные значения вызывающего объекта передаются как литералы в IL во время компиляции. В
отличие от результатов свойства StackTrace для исключений, результаты не затрагиваются запутыванием
кода.
Можно явно передать необязательные аргументы, чтобы управлять сведениями о вызывающем объекте
или скрывать сведения о вызывающем объекте.
Имена членов
Можно использовать атрибут CallerMemberName , чтобы не указывать имя члена в виде аргумента String
вызываемому методу. Этот прием позволяет избежать проблемы, заключающейся в том, что операция
рефакторинга и переименования не изменяет значений String . Это особенно полезно при
выполнении следующих задач:
Использование процедур трассировки и диагностики.
Реализация интерфейса INotifyPropertyChanged при привязке данных. Этот интерфейс позволяет
свойству объекта уведомлять связанный элемент управления об изменении свойства, чтобы элемент
управления мог отображать обновленные сведения. Если атрибут CallerMemberName не используется,
необходимо указать имя свойства как литерал.
В следующей таблице представлены имена членов, возвращаемых при использовании атрибута
CallerMemberName .
Метод, свойство или событие Имя метода, свойства или события, из которого
происходил вызов.
Нет содержащего члена (например, уровень сборки или Значение необязательного параметра по умолчанию.
атрибуты, примененные к типам)
См. также
Атрибуты (C#)
Общие атрибуты (C#)
Именованные и необязательные аргументы
Основные понятия программирования (C#)
Коллекции (C#)
04.11.2019 • 19 minutes to read • Edit Online
Во многих приложениях требуется создавать группы связанных объектов и управлять ими. Существует два
способа группировки объектов: создать массив объектов и создать коллекцию.
Массивы удобнее всего использовать для создания и работы с фиксированным числом строго
типизированных объектов. Информацию о массивах см. в разделе Массивы.
Коллекции предоставляют более гибкий способ работы с группами объектов. В отличие от массивов,
коллекция, с которой вы работаете, может расти или уменьшаться динамически при необходимости.
Некоторые коллекции допускают назначение ключа любому объекту, который добавляется в коллекцию,
чтобы в дальнейшем можно было быстро извлечь связанный с ключом объект из коллекции.
Коллекция является классом, поэтому необходимо объявить экземпляр класса перед добавлением в
коллекцию элементов.
Если коллекция содержит элементы только одного типа данных, можно использовать один из классов в
пространстве имен System.Collections.Generic. Универсальная коллекция обеспечивает строгую типизацию,
так что в нее нельзя добавить другие типы данных. При извлечении элемента из универсальной коллекции
не нужно определять или преобразовывать его тип данных.
NOTE
Для примеров в этом разделе включите директивы using для пространств имен System.Collections.Generic и
System.Linq .
Содержание раздела
Использование простой коллекции
Виды коллекций
Классы System.Collections.Generic
Классы System.Collections.Concurrent
Классы System.Collections
Реализация коллекции пар "ключ-значение"
Использование LINQ для доступа к коллекции
Сортировка коллекции
Определение настраиваемой коллекции
Итераторы
Если содержимое коллекции известно заранее, для ее инициализации можно использовать инициализатор
коллекции. Дополнительные сведения см. в разделе Инициализаторы объектов и коллекций.
Следующий пример аналогичен предыдущему за исключением того, что для добавления элементов в
коллекцию используется инициализатор коллекции.
Для перебора коллекции можно использовать оператор for вместо оператора foreach . Для этого доступ к
элементам коллекции осуществляется по позиции индекса. Индекс элементов начинается с 0 и
заканчивается числом, равным количеству элементов минус 1.
В приведенном ниже примере выполняется перебор элементов коллекции с помощью оператора for
вместо foreach .
В приведенном ниже примере элемент удаляется из коллекции путем указания удаляемого объекта.
// Create a list of strings by using a
// collection initializer.
var salmons = new List<string> { "chinook", "coho", "pink", "sockeye" };
В приведенном ниже примере удаляются элементы из универсального списка. Вместо оператора foreach
используется оператор for, выполняющий перебор в порядке убывания. Связано это с тем, что в результате
работы метода RemoveAt элементы, следующие за удаленным элементом, получают меньшее значение
индекса.
Для типа элементов в List<T> можно также определить собственный класс. В приведенном ниже примере
класс Galaxy , который используется объектом List<T>, определен в коде.
private static void IterateThroughList()
{
var theGalaxies = new List<Galaxy>
{
new Galaxy() { Name="Tadpole", MegaLightYears=400},
new Galaxy() { Name="Pinwheel", MegaLightYears=25},
new Galaxy() { Name="Milky Way", MegaLightYears=0},
new Galaxy() { Name="Andromeda", MegaLightYears=3}
};
// Output:
// Tadpole 400
// Pinwheel 25
// Milky Way 0
// Andromeda 3
}
Виды коллекций
Многие типовые коллекции предоставляются платформой .NET Framework. Каждый тип коллекции
предназначен для определенной цели.
В этом разделе описываются следующие часто используемые классы коллекций:
Классы System.Collections.Generic
Классы System.Collections.Concurrent
Классы System.Collections
Классы System.Collections.Generic
Универсальную коллекцию можно создать, используя один из классов в пространстве имен
System.Collections.Generic. Универсальная коллекция применяется в том случае, если все элементы в
коллекции имеют одинаковый тип данных. Универсальная коллекция обеспечивает строгую типизацию,
позволяя добавлять данные только необходимого типа.
В таблице ниже перечислены некоторые из часто используемых классов пространства имен
System.Collections.Generic.
К ЛАСС ОПИСАНИЕ
Дополнительные сведения см. в разделе Часто используемые типы коллекций, Выбор класса коллекции и
System.Collections.Generic.
Классы System.Collections.Concurrent
В .NET Framework 4 или более поздней версии коллекции пространства имен System.Collections.Concurrent
предоставляют эффективные потокобезопасные операции для доступа к элементам коллекции из
нескольких потоков.
Классы пространства имен System.Collections.Concurrent следует использовать вместо соответствующих
типов пространств имен System.Collections.Generic и System.Collections, если несколько потоков параллельно
обращаются к такой коллекции. Дополнительные сведения см. в статьях Потокобезопасные коллекции и
System.Collections.Concurrent.
Некоторые из классов, входящих в пространство имен System.Collections.Concurrent, — это
BlockingCollection<T>, ConcurrentDictionary<TKey,TValue>, ConcurrentQueue<T> и ConcurrentStack<T>.
Классы System.Collections
Классы в пространстве имен System.Collections хранят элементы не в виде конкретно типизированных
объектов, а как объекты типа Object .
Везде, где это возможно, следует использовать универсальные коллекции пространства имен
System.Collections.Generic или пространства имен System.Collections.Concurrent вместо устаревших типов
пространства имен System.Collections .
В следующей таблице перечислены некоторые из часто используемых классов пространства имен
System.Collections :
К ЛАСС ОПИСАНИЕ
return elements;
}
theElement.Symbol = symbol;
theElement.Name = name;
theElement.AtomicNumber = atomicNumber;
Чтобы вместо этого использовать инициализатор коллекции для создания коллекции Dictionary , можно
заменить методы BuildDictionary и AddToDictionary приведенным ниже методом.
private static Dictionary<string, Element> BuildDictionary2()
{
return new Dictionary<string, Element>
{
{"K",
new Element() { Symbol="K", Name="Potassium", AtomicNumber=19}},
{"Ca",
new Element() { Symbol="Ca", Name="Calcium", AtomicNumber=20}},
{"Sc",
new Element() { Symbol="Sc", Name="Scandium", AtomicNumber=21}},
{"Ti",
new Element() { Symbol="Ti", Name="Titanium", AtomicNumber=22}}
};
}
В приведенном ниже примере используется метод ContainsKey и свойство Item[TKey] Dictionary для
быстрого поиска элемента по ключу. Свойство Item позволяет получить доступ к элементу в коллекции
elements с помощью кода elements[symbol] в C#.
if (elements.ContainsKey(symbol) == false)
{
Console.WriteLine(symbol + " not found");
}
else
{
Element theElement = elements[symbol];
Console.WriteLine("found: " + theElement.Name);
}
}
В приведенном ниже примере вместо этого используется метод TryGetValue для быстрого поиска элемента
по ключу.
// LINQ Query.
var subset = from theElement in elements
where theElement.AtomicNumber < 22
orderby theElement.Name
select theElement;
// Output:
// Calcium 20
// Potassium 19
// Scandium 21
}
Сортировка коллекции
Приведенный ниже пример демонстрирует процедуру сортировки коллекции. В примере сортируются
экземпляры класса Car , которые хранятся в List<T>. Класс Car реализует интерфейс IComparable<T>,
который требует реализации метода CompareTo.
Каждый вызов метода CompareTo выполняет одно сравнение, используемое для сортировки. Написанный
пользователем код в методе CompareTo возвращает значение для каждого сравнения текущего объекта с
другим объектом. Возвращаемое значение меньше нуля, если текущий объект меньше другого объекта,
больше нуля, если текущий объект больше другого объекта, и равняется нулю, если объекты равны. Это
позволяет определить в коде условия для отношения «больше», «меньше» и «равно».
В методе ListCars оператор cars.Sort() сортирует список. Этот вызов метода Sort List<T> приводит к
тому, что метод CompareTo вызывается автоматически для объектов Car в List .
// Output:
// blue 50 car4
// blue 30 car5
// blue 20 car1
// green 50 car7
// green 10 car3
// red 60 car6
// red 50 car2
}
return compare;
}
}
// Collection class.
public class AllColors : System.Collections.IEnumerable
{
Color[] _colors =
{
new Color() { Name = "red" },
new Color() { Name = "blue" },
new Color() { Name = "green" }
};
// Custom enumerator.
private class ColorEnumerator : System.Collections.IEnumerator
{
private Color[] _colors;
private int _position = -1;
object System.Collections.IEnumerator.Current
{
get
{
return _colors[_position];
}
}
bool System.Collections.IEnumerator.MoveNext()
{
_position++;
return (_position < _colors.Length);
}
void System.Collections.IEnumerator.Reset()
{
_position = -1;
}
}
}
// Element class.
public class Color
{
public string Name { get; set; }
}
Iterators
Итератор используется для выполнения настраиваемого перебора коллекции. Итератор может быть
методом или методом доступа get . Итератор использует оператор yield return для возврата всех элементов
коллекции по одному за раз.
Итератор вызывается с помощью оператора foreach. Каждая итерация цикла foreach вызывает итератор.
При достижении оператора yield return в итераторе возвращается выражение, и текущее расположение в
коде сохраняется. При следующем вызове итератора выполнение возобновляется с этого места.
Дополнительные сведения см. в разделе Итераторы (C#).
В приведенном ниже примере используется метод-итератор. Метод итератора содержит оператор
yield return , находящийся внутри цикла for. В методе ListEvenNumbers каждая итерация тела оператора
foreach создает вызов метода-итератора, который переходит к следующему оператору yield return .
См. также
Инициализаторы объектов и коллекций
Основные понятия программирования (C#)
Оператор Option Strict
LINQ to Objects (C#)
Parallel LINQ (PLINQ )
Коллекции и структуры данных
Выбор класса коллекции
Сравнение и сортировка в коллекциях
Когда следует использовать универсальные коллекции
Ковариация и контравариантность (C#)
23.10.2019 • 4 minutes to read • Edit Online
// Assignment compatibility.
string str = "test";
// An object of a more derived type is assigned to an object of a less derived type.
object obj = str;
// Covariance.
IEnumerable<string> strings = new List<string>();
// An object that is instantiated with a more derived type argument
// is assigned to an object instantiated with a less derived type argument.
// Assignment compatibility is preserved.
IEnumerable<object> objects = strings;
// Contravariance.
// Assume that the following method is in the class:
// static void SetObject(object o) { }
Action<object> actObject = SetObject;
// An object that is instantiated with a less derived type argument
// is assigned to an object instantiated with a more derived type argument.
// Assignment compatibility is reversed.
Action<string> actString = actObject;
Ковариация для массивов позволяет неявно преобразовать массив более производного типа в массив
менее производного типа. Но эта операция не является типобезопасной, как показано в следующем
примере кода.
Поддержка ковариации и контрвариантности для групп методов позволяет сопоставить сигнатуры методов
с типами делегатов. За счет этого вы можете назначать делегатам не только методы с совпадающими
сигнатурами, но и методы, которые возвращают более производные типы (ковариация) или принимают
параметры с менее производными типами (контрвариантность), чем задает тип делегата. Дополнительные
сведения см. в разделах Вариативность в делегатах (C#) и Использование вариативности в делегатах (C#).
В следующем примере кода демонстрируется поддержка ковариации и контрвариантности для групп
методов.
static object GetObject() { return null; }
static void SetObject(object obj) { }
В .NET Framework 4 и более поздних версиях язык C# поддерживает ковариацию и контрвариантность для
универсальных интерфейсов и делегатов, а также позволяет выполнять неявное преобразование
параметров универсального типа. Дополнительные сведения см. в разделах Вариативность в универсальных
интерфейсах (C#) и Вариативность в делегатах (C#).
В следующем примере кода показано неявное преобразование ссылок для универсальных интерфейсов.
Универсальный интерфейс или делегат называется вариантным, если его универсальные параметры
объявлены ковариантными или контрвариантными. C# позволяет вам создавать собственные вариантные
интерфейсы и делегаты. Дополнительные сведения см. в разделах Создание вариантных универсальных
интерфейсов (C#) и Вариативность в делегатах (C#).
См. также
ЗАГОЛОВОК ОПИСАНИЕ
Создание вариантных универсальных интерфейсов (C#) Узнайте, как создавать ваши собственные вариантные
интерфейсы.
В более ранних версиях .NET Framework этот код приводил к ошибке компиляции в C#, если в Visual Basic
было включено Option Strict . Но теперь можно использовать strings вместо objects , как показано в
предыдущем примере, поскольку интерфейс IEnumerable<T> является ковариантным.
Контравариантность позволяет методу иметь типы аргументов, степень наследования которых меньше, чем
указано в параметре универсального типа интерфейса. Чтобы продемонстрировать функцию
контравариантности, предположим, что создан класса BaseComparer для сравнения экземпляров класса
BaseClass . Класс BaseComparer реализует интерфейс IEqualityComparer<BaseClass> . Поскольку теперь
интерфейс IEqualityComparer<T> является контравариантным, для сравнения экземпляров классов,
наследующих класс BaseClass , можно использовать класс BaseComparer . Это показано в следующем
примере кода.
// Simple hierarchy of classes.
class BaseClass { }
class DerivedClass : BaseClass { }
// Comparer class.
class BaseComparer : IEqualityComparer<BaseClass>
{
public int GetHashCode(BaseClass baseInstance)
{
return baseInstance.GetHashCode();
}
public bool Equals(BaseClass x, BaseClass y)
{
return x == y;
}
}
class Program
{
static void Test()
{
IEqualityComparer<BaseClass> baseComparer = new BaseComparer();
Кроме того, важно помнить, что классы, которые реализуют вариативные интерфейсы, сами являются
инвариантными. Например, несмотря на то, что List<T> реализует ковариантный интерфейс
IEnumerable<T>, неявно преобразовать List<String> в List<Object> нельзя. Это показано в следующем
примере кода.
См. также
Использование вариативности в интерфейсах для универсальных коллекций (C#)
Создание вариантных универсальных интерфейсов (C#)
Универсальные интерфейсы
Вариативность в делегатах (C#)
Создание вариантных универсальных
интерфейсов (C#)
23.10.2019 • 7 minutes to read • Edit Online
Параметры универсального типа можно объявить в интерфейсах как ковариантные или контравариантные.
Ковариация позволяет методам интерфейса иметь тип возвращаемого значения, степень наследования
которого больше, чем указано в параметрах универсального типа. Контравариантность позволяет
методам интерфейса иметь типы аргументов, степень наследования которых меньше, чем указано в
параметре универсального типа. Универсальный интерфейс, который имеет ковариантные или
контравариантные параметры универсального типа, называется вариантным.
NOTE
В платформе .NET Framework 4 появилась поддержка вариативности для нескольких существующих универсальных
интерфейсов. Список вариантных интерфейсов в .NET Framework см. в разделе Вариативность в универсальных
интерфейсах (C#).
IMPORTANT
Параметры ref , in и out в C# не могут быть вариантными. Типы значений также не поддерживают
вариативность.
Для объявления ковариантного параметра универсального типа можно использовать ключевое слово out .
Ковариантный тип должен удовлетворять следующим условиям:
Тип используется только в качестве типа значения, возвращаемого методами интерфейса, и не
используется в качестве типа аргументов метода. Это показано в следующем примере, в котором тип
R объявлен ковариантным.
Существует одно исключение из данного правила. Если в качестве параметра метода используется
контравариантный универсальный делегат, этот тип можно использовать в качестве параметра
универсального типа для этого делегата. Это продемонстрировано ниже на примере типа R .
Дополнительные сведения см. в разделах Вариативность в делегатах (C#) и Использование
вариативности в универсальных методах-делегатах Func и Action (C#).
interface ICovariant<out R>
{
void DoSomething(Action<R> callback);
}
Для объявления контравариантного параметра универсального типа можно использовать ключевое слово
in . Контравариантный тип можно использовать только в качестве типа аргументов метода, но не в
качестве типа значения, возвращаемого методами интерфейса. Контравариантный тип можно также
использовать для универсальных ограничений. В следующем примере кода показано объявление
контравариантного интерфейса и использование универсального ограничения для одного из его методов.
Кроме того, можно реализовать поддержку ковариации и контравариации в одном интерфейсе, но для
разных параметров типа, как показано в следующем примере кода.
Тем не менее, если параметр универсального типа T объявлен ковариантным в одном интерфейсе, его
нельзя объявить контравариантным в расширенном интерфейсе и наоборот. Это показано в следующем
примере кода.
interface ICovariant<out T> { }
// The following statement generates a compiler error.
// interface ICoContraVariant<in T> : ICovariant<T> { }
Недопущение неоднозначности
При реализации вариантных универсальных интерфейсов вариативность может приводить к
неоднозначности. Этого следует избегать.
Например, если вы явно реализуете один вариантный универсальный интерфейс с разными параметрами
универсального типа в одном классе, это может создавать неоднозначность. Компилятор не сообщает об
ошибке в данном случае, но и не указывает, какая реализация интерфейса будет выбрана во время
выполнения. Это может привести к возникновению неявных ошибок в коде. Рассмотрим следующий
пример кода:
IEnumerator IEnumerable.GetEnumerator()
{
// Some code.
return null;
}
IEnumerator<Dog> IEnumerable<Dog>.GetEnumerator()
{
Console.WriteLine("Dog");
// Some code.
return null;
}
}
class Program
{
public static void Test()
{
IEnumerable<Animal> pets = new Pets();
pets.GetEnumerator();
}
}
В этом примере не указано, каким образом метод pets.GetEnumerator делает выбор между Cat и Dog . Это
может вызвать проблемы в вашем коде.
См. также
Вариативность в универсальных интерфейсах (C#)
Использование вариативности в универсальных методах-делегатах Func и Action (C#)
Использование вариативности в интерфейсах для
универсальных коллекций (C#)
23.10.2019 • 2 minutes to read • Edit Online
Ковариантный интерфейс позволяет его методам возвращать более производные типы, чем указанные в
интерфейсе. Контравариантный интерфейс позволяет его методам принимать параметры менее
производных типов, чем указанные в интерфейсе.
В .NET Framework 4 несколько имеющихся интерфейсов стали ковариантными и контравариантными. В их
числе IEnumerable<T> и IComparable<T>. Это позволяет повторно использовать методы, оперирующие
универсальными коллекциями базовых типов, для коллекций производных типов.
Список вариативных интерфейсов в .NET Framework см. в разделе Вариативность в универсальных
интерфейсах (C#).
class Program
{
// The method has a parameter of the IEnumerable<Person> type.
public static void PrintFullName(IEnumerable<Person> persons)
{
foreach (Person person in persons)
{
Console.WriteLine("Name: {0} {1}",
person.FirstName, person.LastName);
}
}
PrintFullName(employees);
}
}
Сравнение универсальных коллекций
Следующий пример иллюстрирует преимущества поддержки контрвариантности в интерфейсе
IComparer<T>. Класс PersonComparer реализует интерфейс IComparer<Person> . Тем не менее этот класс
можно повторно использовать для сравнения последовательности объектов типа Employee , так как
Employee наследует Person .
class Program
{
IEnumerable<Employee> noduplicates =
employees.Distinct<Employee>(new PersonComparer());
См. также
Вариативность в универсальных интерфейсах (C#)
Вариативность в делегатах (C#)
28.10.2019 • 8 minutes to read • Edit Online
В платформе .NET Framework 3.5 появилась поддержка вариативности при сопоставлении сигнатур
методов с типами делегатов во всех делегатах в C#. Это означает, что делегатам можно назначать не
только методы, которые обладают соответствующими сигнатурами, но и методы, которые возвращают
более производные типы (ковариация), или принимают параметры, которые имеют менее производные
типы (контравариативность), чем указано в типе делегата. Это касается не только универсальных методов-
делегатов, но и методов-делегатов, не являющихся универсальными.
Например, рассмотрим следующий код, который содержит два класса и два делегата: универсальный и
неуниверсальный.
При создании делегатов типов SampleDelegate или SampleGenericDelegate<A, R> им можно назначить
любой из следующих методов.
// Matching signature.
public static First ASecondRFirst(Second second)
{ return new First(); }
В следующем примере кода показано неявное преобразование между сигнатурой метода и типом
делегата.
// Assigning a method with a matching signature
// to a non-generic delegate. No conversion is necessary.
SampleDelegate dNonGeneric = ASecondRFirst;
// Assigning a method with a more derived return type
// and less derived argument type to a non-generic delegate.
// The implicit conversion is used.
SampleDelegate dNonGenericConversion = AFirstRSecond;
Если поддержка вариативности используется только для сопоставления сигнатур методов с типами
делегатов, а ключевые слова in и out не используются, можно создать экземпляры делегатов с
одинаковыми лямбда-выражениями или методами, но нельзя назначить один делегат другому.
В следующем примере кода SampleGenericDelegate<String> нельзя явно преобразовать в
SampleGenericDelegate<Object> , хотя String наследует Object . Эту проблему можно устранить, пометив
универсальный параметр T ключевым словом out .
public delegate T SampleGenericDelegate<T>();
IMPORTANT
Параметры ref , in и out в C# невозможно пометить как вариативные.
В одном делегате можно реализовать поддержку вариативности и ковариации, но для разных параметров
типа. Эти действия показаны в следующем примере.
См. также
Универсальные шаблоны
Использование вариативности в универсальных методах-делегатах Func и Action (C#)
Практическое руководство. Объединение делегатов (многоадресные делегаты)
Использование вариативности в делегатах (C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример 1. Ковариантность
ОПИСАНИЕ
В этом примере демонстрируется использование делегатов с методами, типы возвращаемых значений
которых являются производными от типа возвращаемого значения в сигнатуре делегата. Тип данных,
возвращаемый DogsHandler , является типом Dogs , производным от определенного в делегате типа
Mammals .
Код
class Mammals {}
class Dogs : Mammals {}
class Program
{
// Define the delegate.
public delegate Mammals HandlerMethod();
Пример 2: Контрвариантность
ОПИСАНИЕ
В этом примере демонстрируется использование делегатов с методами, параметры типа которых являются
базовыми типами типа параметра сигнатуры делегата. Контравариантность позволяет использовать один
обработчик событий вместо нескольких. В следующем примере используется два делегата:
Делегат KeyEventHandler, определяющий сигнатуру события Button.KeyDown. Его сигнатура:
public delegate void KeyEventHandler(object sender, KeyEventArgs e)
public Form1()
{
InitializeComponent();
См. также
Вариативность в делегатах (C#)
Использование вариативности в универсальных методах-делегатах Func и Action (C#)
Использование вариативности в универсальных
методах-делегатах Func и Action (C#)
23.10.2019 • 2 minutes to read • Edit Online
Эти примеры показывают, как обеспечить возможность многократного использования методов и сделать
код более гибким, используя ковариацию и контравариацию в универсальных методах-делегатах Func и
Action .
}
}
См. также
Covariance and Contravariance (C#) (Ковариантность и контрвариантность (C#))
Универсальные шаблоны
Деревья выражений (C#)
25.11.2019 • 7 minutes to read • Edit Online
Деревья выражений представляют код в виде древовидной структуры, где каждый узел является
выражением, например, вызовом метода или двоичной операцией, такой как x < y .
Вы можете компилировать и выполнять код, представленный деревьями выражений. Это позволяет
динамически изменять выполняемый код, выполнять запросы LINQ в различных базах данных и
создавать динамические запросы. См. дополнительные сведения об использовании деревьев выражений
в LINQ для создания динамических запросов (C#).
Кроме того, деревья выражений используются в среде выполнения динамического языка (DLR ) для
обеспечения взаимодействия между динамическими языками и платформой .NET Framework, а также и
предоставления разработчикам компиляторов возможности выдавать деревья выражений вместо
промежуточного языка Microsoft (MSIL ). Дополнительные сведения о DLR см. в разделе Общие
сведения о среде DLR.
Вы можете использовать компилятор C# или Visual Basic для создания дерева выражений на основе
анонимного лямбда-выражения или создания деревьев выражений вручную с помощью пространства
имен System.Linq.Expressions.
На платформе .NET Framework 4 или более поздней версии API деревьев выражений также
поддерживает присваивание и выражения потока управления, такие как циклы, условные блоки и блоки
try-catch . С помощью API -интерфейса можно создавать деревья выражений, более сложные, чем
деревья, создаваемые компилятором C# из лямбда-выражений. В следующем примере показан способ
создания дерева выражений, которое вычисляет факториал числа.
Console.WriteLine(factorial);
// Prints 120.
Дополнительные сведения см. в записи блога Generating Dynamic Methods with Expression Trees in Visual
Studio 2010 (Создание динамических методов с использованием деревьев выражений в Visual
Studio 2010), которая также применима к более поздним версиям Visual Studio.
Синтаксический анализ деревьев выражений
В следующем примере кода показано, как дерево выражений, представляющее лямбда-выражение
num => num < 5 , может быть разложено на части.
// Prints True.
В этом разделе показано, как выполнить дерево выражения. В результате выполнения дерева выражения
может возвращаться значение или просто выполняться действие, такое как вызов метода.
Можно выполнять только деревья выражений, представляющие лямбда-выражения. Деревья выражений,
представляющие лямбда-выражения, имеют тип LambdaExpression или Expression<TDelegate>. Для
выполнения таких деревьев выражений вызовите метод Compile, чтобы создать исполняемый делегат, а
затем вызовите делегат.
NOTE
Если тип делегата неизвестен, то есть лямбда-выражение имеет тип LambdaExpression, а не тип
Expression<TDelegate>, необходимо вызвать метод DynamicInvoke для делегата, а не напрямую.
Пример
В приведенном ниже примере кода показано, как путем создания и выполнения лямбда-выражения
выполнить дерево выражения, которое представляет возведение числа в степень. Выводится результат,
представляющий число, возведенное в степень.
Компиляция кода
Включите пространство имен System.Linq.Expressions.
См. также
Expression Trees (C#) (Деревья выражений (C#))
Изменение деревьев выражений (C#)
Практическое руководство. Изменение деревьев
выражений (C#)
25.11.2019 • 3 minutes to read • Edit Online
В этом разделе показано, как изменить дерево выражения. Деревья выражений являются неизменяемыми,
что означает невозможность их изменения напрямую. Чтобы изменить дерево выражения, необходимо
создать копию существующего дерева выражения, а затем внести необходимые изменения. Для прохода по
существующему дереву выражения и копирования каждого пройденного узла можно использовать класс
ExpressionVisitor.
Изменение дерева выражения
1. Создайте новый проект консольного приложения.
2. Добавьте в файл директиву using для пространства имен System.Linq.Expressions .
3. Добавьте в проект класс AndAlsoModifier .
return base.VisitBinary(b);
}
}
Этот класс наследует класс ExpressionVisitor и специально предназначен для изменения выражений,
которые представляют условные операции AND . Он изменяет эти операции с условного AND на
условное OR . Для этого класс переопределяет метод VisitBinary базового типа, потому что условные
выражения AND представлены как двоичные выражения. Если выражение, переданное в метод
VisitBinary , представляет условную операцию AND , код создает новое выражение, которое
содержит условный оператор OR вместо условного оператора AND . Если выражение, передаваемое в
VisitBinary , не представляет условную операцию AND , метод передает выполнение реализации
базового класса. Методы базового класса создают узлы, которые похожи на переданные деревья
выражений, однако поддеревья этих деревьев заменены на деревья выражений, которые были
рекурсивно созданы при обходе.
4. Добавьте в файл директиву using для пространства имен System.Linq.Expressions .
5. Добавьте код в метод Main в файле Program.cs для создания дерева выражения и передачи его в
метод, который изменит его.
Console.WriteLine(modifiedExpr);
В приведенном ниже коде создается выражение, которое содержит условную операцию AND . Затем
в нем создается экземпляр класса AndAlsoModifier , и в метод Modify этого класса передается
выражение. Как исходные, так и измененные деревья выражений выводятся для отображения
изменения.
6. Скомпилируйте и запустите приложение.
См. также
Выполнение деревьев выражений (C#)
Expression Trees (C#) (Деревья выражений (C#))
Практическое руководство. Использование
деревьев выражений для построения
динамических запросов (C#)
25.11.2019 • 4 minutes to read • Edit Online
Пример
В следующем примере показано использование деревьев выражений для создания запроса к источнику
данных IQueryable и его выполнения. В коде создается дерево выражения для представления следующего
запроса:
string[] companies = { "Consolidated Messenger", "Alpine Ski House", "Southridge Video", "City Power & Light",
"Coho Winery", "Wide World Importers", "Graphic Design Institute", "Adventure Works",
"Humongous Insurance", "Woodgrove Bank", "Margie's Travel", "Northwind Traders",
"Blue Yonder Airlines", "Trey Research", "The Phone Company",
"Wingtip Toys", "Lucerne Publishing", "Fourth Coffee" };
// Compose the expression tree that represents the parameter to the predicate.
ParameterExpression pe = Expression.Parameter(typeof(string), "company");
ParameterExpression pe = Expression.Parameter(typeof(string), "company");
// ***** Where(company => (company.ToLower() == "coho winery" || company.Length > 16)) *****
// Create an expression tree that represents the expression 'company.ToLower() == "coho winery"'.
Expression left = Expression.Call(pe, typeof(string).GetMethod("ToLower", System.Type.EmptyTypes));
Expression right = Expression.Constant("coho winery");
Expression e1 = Expression.Equal(left, right);
// Create an expression tree that represents the expression 'company.Length > 16'.
left = Expression.Property(pe, typeof(string).GetProperty("Length"));
right = Expression.Constant(16, typeof(int));
Expression e2 = Expression.GreaterThan(left, right);
// Combine the expression trees to create an expression tree that represents the
// expression '(company.ToLower() == "coho winery" || company.Length > 16)'.
Expression predicateBody = Expression.OrElse(e1, e2);
Этот код использует фиксированное число выражений в предикате, передаваемом в метод Queryable.Where .
Тем не менее можно написать приложение, которое будет сочетать переменное число выражений
предиката, зависящих от вводимых пользователем данных. Также можно изменять стандартные операторы
запросов, которые вызываются в запросе, в зависимости от входных данных от пользователя.
Компиляция кода
Включите пространство имен System.Linq.Expressions.
См. также
Expression Trees (C#) (Деревья выражений (C#))
Выполнение деревьев выражений (C#)
Практическое руководство. Динамическое определение фильтров предикатов во время выполнения
Отладка деревьев выражений в Visual Studio (C#)
04.11.2019 • 2 minutes to read • Edit Online
При отладке приложений можно анализировать структуру и содержимое деревьев выражений. Чтобы
получить краткий обзор структуры дерева выражения, вы можете использовать свойство DebugView , которое
представляет деревья выражений, используя специальный синтаксис. (Обратите внимание, что DebugView
доступен только в режиме отладки.)
Так как DebugView представляет собой строку, можно использовать встроенный визуализатор текста для его
просмотра по нескольким строкам, выбрав визуализатор текста из меню со значком лупы рядом с меткой
DebugView .
См. также
Expression Trees (C#) (Деревья выражений (C#))
Отладка в Visual Studio
Создание настраиваемых визуализаторов
Синтаксис DebugView
Синтаксис DebugView
23.10.2019 • 3 minutes to read • Edit Online
Свойство DebugView (доступно только при отладке) предоставляет строковое представление деревьев
выражений. Большая часть синтаксиса достаточно проста для понимания; особые случаи описаны в
разделах ниже.
Каждый пример сопровождается комментарием с DebugView .
ParameterExpression
В начале имен переменных System.Linq.Expressions.ParameterExpression отображается символ $ .
Если параметр не имеет имени, оно назначается автоматически, например $var1 или $var2 .
Примеры
ConstantExpression
Для объектов System.Linq.Expressions.ConstantExpression, представляющих целочисленные значения, строки
и null , отображается значение константы.
Для числовых типов, имеющих стандартные суффиксы, такие как литералы C#, суффикс добавляется к
значению. В следующей таблице показаны суффиксы для различных числовых типов.
System.UInt32 uint U
System.Int64 long L
System.UInt64 ulong UL
System.Double double D
System.Single float C
System.Decimal decimal M
Примеры
int num = 10;
ConstantExpression expr = Expression.Constant(num);
/*
10
*/
BlockExpression
Если тип объекта System.Linq.Expressions.BlockExpression отличается от типа последнего выражения в блоке,
то тип отображается в угловых скобках ( < и > ). В противном случае тип объекта BlockExpression не
отображается.
Примеры
LambdaExpression
Объекты System.Linq.Expressions.LambdaExpression отображаются вместе со своими типами делегатов.
Если лямбда-выражение не имеет имени, оно назначается автоматически, например #Lambda1 или #Lambda2 .
Примеры
LabelExpression
Если указать значение по умолчанию для объекта System.Linq.Expressions.LabelExpression, оно будет
отображаться перед объектом System.Linq.Expressions.LabelTarget.
Маркер .Label указывает начало метки. Маркер .LabelTarget задает конечную цель перехода для
целевого объекта.
Если метка не имеет имени, оно назначается автоматически, например #Label1 или #Label2 .
Примеры
Проверяемые операторы
Проверяемые операторы отображаются с символом # перед оператором. Например, проверяемый
оператор сложения отображается как #+ .
Примеры
Итератор можно использовать для прохода по коллекции, такой как список или массив.
Метод итератора или метод доступа get выполняет настраиваемую итерацию по коллекции. Метод
итератора использует оператор yield return для поочередного возврата каждого элемента. При
достижении инструкции yield return текущее расположение в коде запоминается. При следующем
вызове функции итератора выполнение возобновляется с этого места.
Итератор используется из клиентского кода с помощью оператора foreach или с помощью запроса LINQ.
В приведенном ниже примере первая итерация цикла foreach приводит к вызову метода итератора
SomeNumbers , пока не будет достигнут первый оператор yield return . Эта итерация возвращает
значение 3. Текущее расположение в методе итератора сохраняется. В следующей итерации цикла
выполнение метода итератора возобновляется с того же места и снова приостанавливается при
достижении оператора yield return . Эта итерация возвращает значение 5. Текущее расположение в
методе итератора снова сохраняется. Цикл завершается при достижении конца метода итератора.
Типом возвращаемого метода итератора или метода доступа get может быть IEnumerable,
IEnumerable<T>, IEnumerator или IEnumerator<T>.
Для завершения итерации можно использовать оператор yield break .
NOTE
Все примеры в этом разделе, кроме примера простого итератора, включают директивы using для пространств имен
System.Collections и System.Collections.Generic .
Простой итератор
В следующем примере имеется один оператор yield return , который находится внутри цикла for. В
методе Main каждая итерация оператора foreach создает вызов функции итератора, которая выполняет
следующий оператор yield return .
static void Main()
{
foreach (int number in EvenSequence(5, 18))
{
Console.Write(number.ToString() + " ");
}
// Output: 6 8 10 12 14 16 18
Console.ReadKey();
}
theZoo.AddMammal("Whale");
theZoo.AddMammal("Rhinoceros");
theZoo.AddBird("Penguin");
theZoo.AddBird("Warbler");
Console.ReadKey();
}
// Public methods.
public void AddMammal(string name)
{
animals.Add(new Animal { Name = name, Type = Animal.TypeEnum.Mammal });
}
// Public members.
public IEnumerable Mammals
{
get { return AnimalsForType(Animal.TypeEnum.Mammal); }
}
// Private methods.
// Private methods.
private IEnumerable AnimalsForType(Animal.TypeEnum type)
{
foreach (Animal theAnimal in animals)
{
if (theAnimal.Type == type)
{
yield return theAnimal.Name;
}
}
}
// Private class.
private class Animal
{
public enum TypeEnum { Bird, Mammal }
Console.ReadKey();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
Сведения о синтаксисе
Итератор может являться методом или методом доступа get . Итератор не может использоваться в
событии, конструкторе экземпляра, статическом конструкторе или статическом методе завершения.
Должно существовать неявное преобразование выражения типа в операторе yield return в аргумент
типа для IEnumerable<T>, возвращаемого итератором.
В C# метод итератора не может иметь параметры in , ref или out .
В C# yield не является зарезервированным словом и имеет специальное значение, только если
используется перед ключевым словом return или break .
Техническая реализация
Хотя итератор создается как метод, компилятор переводит его во вложенный класс, который фактически
является конечным автоматом. Этот класс отслеживает положение итератора, пока в клиентском коде
выполняется цикл foreach .
Чтобы просмотреть операции компилятора, воспользуйтесь средством Ildasm.exe, чтобы просмотреть код
промежуточного языка Майкрософт, создаваемый для метода итератора.
При создании итератора для класса или структуры реализация всего интерфейса IEnumerator не требуется.
Когда компилятор обнаруживает итератор, он автоматически создает методы Current , MoveNext и
Dispose интерфейса IEnumerator или IEnumerator<T>.
Использование итераторов
Итераторы позволяют поддерживать простоту цикла foreach , когда необходимо использовать сложный
код для заполнения последовательности списков. Это может оказаться полезным в следующих случаях:
Изменение последовательности списков после первой итерации цикла foreach .
Если необходимо избежать полной загрузки большого списка перед первой итерацией цикла
foreach . Пример: при постраничной загрузке пакета строк таблицы. Другой пример — метод
EnumerateFiles, реализующий итераторы в .NET Framework.
Инкапсулирование построения списка в итераторе. В методе итератора можно построить список, а
затем выдавать каждый результат в цикле.
См. также
System.Collections.Generic
IEnumerable<T>
foreach, in
yield
Использование оператора foreach с массивами
Универсальные шаблоны
Синтаксис LINQ
04.11.2019 • 7 minutes to read • Edit Online
class LINQQueryExpressions
{
static void Main()
{
На приведенном ниже рисунке показан частично выполненный запрос LINQ к базе данных SQL Server
в C# и Visual Basic с полной проверкой типов и поддержкой IntelliSense:
Следующие шаги
Чтобы получить дополнительные сведения о LINQ, сначала ознакомьтесь с некоторыми основным
понятиями в статье Query expression basics (Базовая информация о выражении запроса), а затем
переходите к документации по интересующей вас технологии LINQ.
XML -документы: LINQ to XML
Платформа ADO.NET Entity Framework: LINQ to Entities
Коллекции, файлы, строки и другие сущности .NET: LINQ to Objects (C#)
Чтобы глубже разобраться в базовой концепции LINQ изучите статью о LINQ в C#.
Чтобы быстрее приступить к работе с LINQ в C#, переходите к руководству Working with LINQ (Работа
с LINQ ).
Введение в запросы LINQ (C#)
04.11.2019 • 10 minutes to read • Edit Online
Запрос представляет собой выражение, извлекающее данные из источника данных. Запросы обычно
выражаются на специализированном языке запросов. Со временем для различных типов источников
данных, например SQL для реляционных баз данных и XQuery для XML, были разработаны разные языки.
Поэтому разработчикам приходится учить новый язык запросов для каждого типа источника данных или
формата данных, для которых они должны обеспечить поддержку. LINQ упрощает ситуацию, реализуя
согласованную модель работы с данными для различных типов источников данных и форматов данных. В
запросе LINQ вы всегда работаете с объектами. Вы используете одинаковые базовые шаблоны кода для
запроса и преобразования данных в XML -документы, базы данных SQL, наборы данных ADO.NET,
коллекции .NET и любые другие форматы, для которых доступен поставщик LINQ.
class IntroToLINQ
{
static void Main()
{
// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 };
// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
from num in numbers
where (num % 2) == 0
select num;
// 3. Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
}
}
На следующем рисунке показана полная операция запроса. В LINQ выполнение запроса отличается от
самого запроса; другими словами, вы не получаете данные, просто создав переменную запроса.
Источник данных
Так как источник данных в предыдущем примере является массивом, он неявно поддерживает
универсальный интерфейс IEnumerable<T>. Это значит, что его можно запросить с помощью LINQ.
Запрос выполняется в операторе foreach , и для foreach требуется IEnumerable или IEnumerable<T>.
Типы, которые поддерживают IEnumerable<T> или производный интерфейс, например универсальный
интерфейс IQueryable<T>, называются запрашиваемыми типами.
Запрашиваемый тип не требует внесения изменений или специальной обработки, чтобы служить
источником данных LINQ. Если источник данных не находится в памяти в качестве запрашиваемого типа,
поставщик LINQ должен представить его как таковой. Например, LINQ to XML загружает XML -документ
в запрашиваемый тип XElement:
Запрос
Запрос указывает, какую информацию нужно извлечь из источника или источников данных.
Дополнительно в запросе можно указать, как следует сортировать, группировать и формировать
возвращаемую информацию. Запрос хранится в переменной запроса и инициализируется выражением
запроса. Чтобы упростить написание запросов, в C# был представлен новый синтаксис запроса.
В предыдущем примере запрос возвращает все четные числа из массива целых чисел. Выражение запроса
содержит три предложения: from , where и select . (Если вы знакомы с SQL, то должны были заметить,
что порядок предложений противоположен порядку в SQL.) Предложение from указывает источник
данных, предложение where применяет фильтр, а предложение select задает тип возвращаемых
элементов. Эти и другие предложения запросов подробно описываются в разделе Выражения запросов
LINQ. А сейчас важно то, что в LINQ сама переменная запроса не выполняет никаких действий и не
возвращает никаких данных. Она просто хранит сведения, необходимые для предоставления результатов
при выполнении запроса на более позднем этапе. Дополнительные сведения о принципах
конструирования запросов см. в разделе Общие сведения о стандартных операторах запросов (C#).
NOTE
Запросы могут также выражаться с помощью синтаксиса методов. Дополнительные сведения см. в разделе
Синтаксис запросов и синтаксис методов в LINQ.
Выполнение запроса
Отложенное выполнение
Как уже говорилось ранее, сама переменная запроса хранит команды запроса. Фактическое выполнение
запроса откладывается до тех пор, пока переменная запроса не будет обработана в операторе foreach .
Эта концепция называется отложенным выполнением и демонстрируется в следующем примере:
// Query execution.
foreach (int num in numQuery)
{
Console.Write("{0,1} ", num);
}
Оператор foreach также является частью кода, в которой извлекаются результаты запроса. Например, в
предыдущем запросе переменная итерации num содержит каждое значение (по одному за раз) в
возвращенной последовательности.
Поскольку сама переменная запроса никогда не содержит результаты запроса, вы можете выполнять ее
так часто, как это необходимо. Например, может иметься база данных, постоянно обновляемая отдельным
приложением. В приложении можно создать один запрос, получающий самые последние данные, и
регулярно выполнять его с некоторым интервалом, каждый раз получая разные результаты.
Принудительное немедленное выполнение
Запросы, выполняющие статистические функции для диапазона исходных элементов, сначала должны
выполнить итерацию по этим элементам. Примерами таких запросов являются Count , Max , Average и
First . Они выполняются без явного оператора foreach , поскольку сам запрос должен использовать
foreach для возвращения результата. Обратите внимание, что такие типы запросов возвращают одно
значение, а не коллекцию IEnumerable . Следующий запрос возвращает количество четных чисел в
исходном массиве:
var evenNumQuery =
from num in numbers
where (num % 2) == 0
select num;
Чтобы принудительно вызвать немедленное выполнение любого запроса и кэшировать его результаты, вы
можете вызвать методы ToList или ToArray.
List<int> numQuery2 =
(from num in numbers
where (num % 2) == 0
select num).ToList();
// or like this:
// numQuery3 is still an int[]
var numQuery3 =
(from num in numbers
where (num % 2) == 0
select num).ToArray();
Принудительно вызвать выполнение также можно, поместив цикл foreach сразу после выражения
запроса. При этом путем вызова ToList или ToArray можно также кэшировать все данные в одном
объекте коллекции.
См. также
Приступая к работе с LINQ в C#
Пошаговое руководство: Написание запросов на C#
Выражения запросов LINQ
foreach, in
Ключевые слова запроса (LINQ )
LINQ и универсальные типы (C#)
23.10.2019 • 3 minutes to read • Edit Online
Запросы LINQ основаны на универсальных типах, которые впервые появились в версии 2.0 платформы .NET
Framework. Для того чтобы приступить к написанию запросов, не требуется глубокое знание универсальных
типов. Тем не менее необходимо понимание двух основных принципов.
1. При создании экземпляра универсального класса коллекции, такого как List<T>, замените "T" типом
объектов, которые будут храниться в списке. Например, список строк выражается как List<string> , а
список объектов Customer — как List<Customer> . Универсальный список является строго
типизированным и предоставляет ряд преимуществ по сравнению с коллекциями, которые хранят
свои элементы как Object. При попытке добавить Customer в List<string> будет выдана ошибка во
время компиляции. Универсальные коллекции просты в использовании, поскольку нет необходимости
выполнять приведение типов во время выполнения.
2. IEnumerable<T> — это интерфейс, поддерживающий перечисление универсальных классов коллекций
с помощью оператора foreach . Универсальные классы коллекций поддерживают IEnumerable<T> так
же, как неуниверсальные классы коллекций (например ArrayList) поддерживают IEnumerable.
Дополнительные сведения об универсальных классах см. в разделе Универсальные шаблоны.
IEnumerable<Customer> customerQuery =
from cust in customers
where cust.City == "London"
select cust;
Ключевое слово var удобно, если тип переменной является очевидным, или если не требуется явно
указывать вложенные универсальные типы, например создаваемые групповыми запросами. Как правило,
рекомендуется помнить о том, что использование var делает код более сложным для чтения.
Дополнительные сведения см. в статье Implicitly Typed Local Variables (Неявно типизированные локальные
переменные).
См. также
Универсальные шаблоны
Основные операции запросов LINQ (C#)
04.11.2019 • 8 minutes to read • Edit Online
В этом разделе содержится краткое описание выражений запроса LINQ и некоторые типичные операции,
выполняемые в запросе. Более подробные сведения приводятся в следующих разделах:
Выражения запросов LINQ
Общие сведения о стандартных операторах запроса (C#)
Пошаговое руководство: Написание запросов на C#
NOTE
Если вы уже знакомы с языком запросов, таким как SQL или XQuery, можно пропустить большую часть этого раздела.
Чтобы изучить порядок предложений в выражениях запроса LINQ, прочитайте о предложении from в следующем
разделе.
//queryAllCustomers is an IEnumerable<Customer>
var queryAllCustomers = from cust in customers
select cust;
Переменная диапазона схожа с переменной итерации в цикле foreach за исключением того, что в
выражении запроса не происходит фактической итерации. При выполнении запроса переменная диапазона
будет использоваться как ссылка на каждый последующий элемент в customers . Так как компилятор может
определить тип cust , нет необходимости указывать его в явном виде. Дополнительные переменные
диапазона могут быть введены предложением let . Дополнительные сведения см. в разделе Предложение
let.
NOTE
Для неуниверсальных источников данных, таких как ArrayList, переменная диапазона должна быть явно типизирована.
Дополнительные сведения см. в разделе Практическое руководство. Выполнение запроса к ArrayList с помощью LINQ
(C#) и Предложение from.
Фильтрация
По-видимому, одной из наиболее распространенных операций с запросом является применение фильтра в
форме логического выражения. Фильтр приводит к возвращению запросом только тех элементов, для
которых выражение является истинным. Результат вырабатывается с использованием предложения where .
Фильтр фактически указывает элементы для исключения из исходной последовательности. В приведенном
ниже примере возвращаются только те элементы customers , которые находятся в Лондоне.
var queryLondonCustomers = from cust in customers
where cust.City == "London"
select cust;
Для применения нужного числа выражений фильтра в предложении where можно использовать знакомые
логические операторы C# AND и OR . Например, для получения только заказчиков из Лондона AND с
именем Devon следует написать следующий код:
Для получения заказчиков из Лондона или Парижа следует написать следующий код:
Упорядочение
Часто целесообразно отсортировать возвращенные данные. Предложение orderby сортирует элементы
возвращаемой последовательности в зависимости от компаратора по умолчанию для сортируемого типа.
Например, приведенный ниже запрос можно расширить для сортировки результатов на основе свойства
Name . Так как Name является строкой, сравнение по умолчанию выполняется в алфавитном порядке от А до
Я.
var queryLondonCustomers3 =
from cust in customers
where cust.City == "London"
orderby cust.Name ascending
select cust;
Группирование
Предложение group позволяет группировать результаты на основе указанного ключа. Например, можно
указать, что результаты должны быть сгруппированы по City так, чтобы все заказчики из Лондона или
Парижа оказались в отдельных группах. В этом случае ключом является cust.City .
Соединение
Операции соединения создают связи между последовательностями, неявно смоделированными в
источниках данных. Например, можно выполнить соединение для поиска всех клиентов и дистрибьюторов,
которые находятся в одном месте. В LINQ предложение join всегда работает с коллекциями объектов, а не
непосредственно с таблицами базы данных.
var innerJoinQuery =
from cust in customers
join dist in distributors on cust.City equals dist.City
select new { CustomerName = cust.Name, DistributorName = dist.Name };
В LINQ нет необходимости использовать join так часто, как в SQL, так как внешние ключи в LINQ
представлены в объектной модели свойствами, содержащими коллекцию элементов. Например, объект
Customer содержит коллекцию объектов Order . Вместо выполнения соединения доступ к заказам можно
получить с помощью точечной нотации.
Выбор (проецирование)
Предложение select создает результаты запроса и задает форму или тип каждого возвращаемого элемента.
Например, можно указать, будут ли результаты состоять из полных объектов Customer , только из одного
члена, подмножества членов или некоторых совершенно других типов, на основе вычислений или создания
новых объектов. Когда предложение select создает что-либо отличное от копии исходного элемента,
операция называется проекцией. Использование проекций для преобразования данных является
эффективной возможностью выражений запросов LINQ. Дополнительные сведения см. в разделах
Преобразования данных с помощью LINQ (C#) и Предложение select.
См. также
Выражения запросов LINQ
Пошаговое руководство: Написание запросов на C#
Ключевые слова запроса (LINQ )
Анонимные типы
Преобразования данных с помощью LINQ (C#)
04.11.2019 • 8 minutes to read • Edit Online
LINQ предназначен не только для получения данных. Это эффективный инструмент для их преобразования.
С помощью запроса LINQ можно использовать исходную последовательность в качестве входных данных и
изменять ее различными способами для создания новой выходной последовательности. Можно изменить
саму последовательность, не изменяя элементы, с помощью сортировки и группировки. Однако самой
интересной функцией запросов LINQ можно назвать возможность создания новых типов. Это выполняется
в предложении select. Например, можно выполнить следующие задачи.
Объединение нескольких входных последовательностей в одну выходную последовательность,
имеющую новый тип.
Создание выходных последовательностей, элементы которых состоят из одного или нескольких
свойств каждого элемента в исходной последовательности.
Создание выходных последовательностей, элементы которых состоят из результатов операций,
выполняемых с источником данных.
Создание выходных последовательностей в другом формате. Например, можно преобразовать
данные из строк SQL или текстовых файлов в XML.
Это лишь несколько примеров. Очевидно, что эти преобразования могут сочетаться различными
способами в одном запросе. Более того, выходную последовательность одного запроса можно
использовать как входную последовательность для нового запроса.
class Student
{
public string First { get; set; }
public string Last {get; set;}
public int ID { get; set; }
public string Street { get; set; }
public string City { get; set; }
public List<int> Scores;
}
class Teacher
{
public string First { get; set; }
public string Last { get; set; }
public int ID { get; set; }
public string City { get; set; }
}
2. Для создания элементов, содержащих более одного свойства исходного элемента, можно
использовать инициализатор объектов с именованным объектом или анонимным типом. В
следующем примере показано использование анонимного типа для инкапсуляции двух свойств из
каждого элемента Customer :
class XMLTransform
{
static void Main()
{
// Create the data source by using a collection initializer.
// The Student class was defined previously in this topic.
List<Student> students = new List<Student>()
{
new Student {First="Svetlana", Last="Omelchenko", ID=111, Scores = new List<int>{97, 92, 81,
60}},
new Student {First="Claire", Last="O’Donnell", ID=112, Scores = new List<int>{75, 84, 91, 39}},
new Student {First="Sven", Last="Mortensen", ID=113, Scores = new List<int>{88, 94, 65, 91}},
};
<Root>
<student>
<First>Svetlana</First>
<Last>Omelchenko</Last>
<Scores>97,92,81,60</Scores>
</student>
<student>
<First>Claire</First>
<Last>O'Donnell</Last>
<Scores>75,84,91,39</Scores>
</student>
<student>
<First>Sven</First>
<Last>Mortensen</Last>
<Scores>88,94,65,91</Scores>
</student>
</Root>
NOTE
Вызов методов в выражениях запросов не поддерживается, если запрос будет преобразован в некоторую другую
область. Например, невозможно вызвать обычный метод C# в LINQ to SQL, так как в SQL Server для него отсутствует
контекст. Тем не менее можно сопоставить хранимые процедуры методов и вызвать их. Дополнительные сведения
см. в разделе Хранимые процедуры.
class FormatQuery
{
static void Main()
{
// Data source.
double[] radii = { 1, 2, 3 };
// Query.
IEnumerable<string> query =
from rad in radii
select $"Area = {rad * rad * Math.PI:F2}";
// Query execution.
foreach (string s in query)
Console.WriteLine(s);
См. также
LINQ (C#)
LINQ to SQL
LINQ to DataSet
LINQ to XML (C#)
Выражения запросов LINQ
предложение select
Отношения между типами в операциях запросов
LINQ (C#)
23.10.2019 • 4 minutes to read • Edit Online
Для эффективного написания запросов следует понимать, как типы переменных связаны друг с другом в
полной операции запроса. Понимание этих связей облегчит усвоение примеров LINQ и примеров кода в
документации. Более того, можно будет представить, что происходит в фоновом режиме при неявном
типизировании переменных с помощью var .
Операции запросов LINQ строго типизированы в источнике данных, в самом запросе и при выполнении
запроса. Тип переменных в запросе должен быть совместим с типом элементов в источнике данных и с
типом переменной итерации в операторе foreach . Строгая типизация гарантирует перехват ошибок во
время компиляции, когда их можно будет исправить прежде, чем с ними столкнутся пользователи.
Для демонстрации связи типов большая часть примеров использует явную типизацию для всех
переменных. Последний пример показывает применение тех же принципов даже при использовании
неявной типизации с помощью var.
1. Аргумент типа источника данных всегда является типом переменной диапазона в запросе.
2. Так как оператор select создает анонимный тип, переменная запроса должна неявно
типизирована с помощью var .
3. Поскольку тип переменной запроса неявный, переменная итерации в цикле foreach также должна
быть неявной.
//Query syntax:
IEnumerable<int> numQuery1 =
from num in numbers
where num % 2 == 0
orderby num
select num;
//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);
Оба примера дают одинаковый результат. Видно, что тип переменной запроса в обеих формах
одинаковый: IEnumerable<T>.
Чтобы разобраться в запросе, основанном на методе, изучим его более подробно. Обратите внимание на
то, что в правой части выражения предложение where теперь выражается как метод экземпляра в
объекта numbers , который, как вы, наверное, помните, имеет тип IEnumerable<int> . Если вы знакомы с
универсальным интерфейсом IEnumerable<T>, то знаете, что в нем отсутствует метод Where . При этом,
вызвав список завершения IntelliSense в Visual Studio IDE, вы увидите не только метод Where , но и
многие другие методы, такие как Select , SelectMany , Join и Orderby . Все это — стандартные операторы
запросов.
Может показаться, что класс IEnumerable<T> был переопределен и включает дополнительные методы,
однако это не так. Стандартные операторы запросов реализуются как новый тип метода, который
называется методы расширения. Эти методы "расширяют" существующий тип и могут вызываться так,
как если бы они являлись методами экземпляра для этого типа. Стандартные операторы запроса
расширяют IEnumerable<T>, и поэтому вы можете написать numbers.Where(...) .
Чтобы приступить к использованию LINQ, о методах расширения достаточно знать только то, как ввести
их в область действия в приложении, используя директивы using . С точки зрения приложения метод
расширения и обычные методы экземпляров одинаковы.
Дополнительные сведения о методах расширения см. в разделе Методы расширения. Дополнительные
сведения о стандартных операторах запросов см. в разделе Общие сведения о стандартных операторах
запроса (C#). Некоторые поставщики LINQ, такие как LINQ to SQL и LINQ to XML, помимо
IEnumerable<T> реализуют собственные стандартные операторы запросов и дополнительные методы
расширения для других типов.
Лямбда-выражения
В предыдущем примере обратите внимание на то, что условное выражение ( num % 2 == 0 ) передается в
метод Where как встроенный аргумент: Where(num => num % 2 == 0). . Это встроенное выражение
называется лямбда-выражением. Это удобный способ написания кода, который иначе пришлось бы
записывать более громоздко: как анонимный метод, универсальный метод-делегат или дерево
выражения. В C# => представляет собой лямбда-оператор, который читается как "переходит в". num
слева от оператора — входная переменная, которая соответствует переменной num в выражении
запроса. Компилятор может вывести тип num , поскольку известно, что numbers является универсальным
типом IEnumerable<T>. Тело лямбда-выражения — точно такое же, как выражение в синтаксисе запроса
или в любом другом выражении или операторе C#, и может включать вызовы метода и другие сложные
логические выражения. "Возвращаемое значение" — результат выражения.
Чтобы приступить к использованию LINQ, активно использовать лямбда-выражения необязательно. При
этом одни запросы могут быть выражены с помощью синтаксиса запроса, в то время как другие требуют
лямбда-выражений. После знакомства с лямбда-выражениями станет понятно, что они являются
мощными и гибкими элементами в арсенале элементов LINQ. Дополнительные сведения см. в разделе
Лямбда-выражения.
Совместимость запросов
Обратите внимание на то, что в представленном выше примере кода метод OrderBy вызывается с
помощью оператора точки при вызове Where . Where создает отфильтрованную последовательность, а
затем Orderby ее сортирует. Поскольку запросы возвращают IEnumerable , объедините их в синтаксис
метода, собрав вызовы методов в цепочку. Компилятор выполняет это действие в фоновом режиме, когда
вы пишете запросы, используя синтаксис запросов. А поскольку в переменной запроса результаты
запроса не сохраняются, его можно в любое время изменить или использовать как базу для нового
запроса даже после выполнения.
Возможности C#, поддерживающие LINQ
04.11.2019 • 6 minutes to read • Edit Online
В следующем разделе приведены новые конструкции языка, представленные в C# 3.0. Несмотря на то, что
эти новые возможности в некоторой степени используются с запросами LINQ, они не ограничиваются LINQ
и могут использоваться в любом контексте, где они будут целесообразны.
Выражения запросов
Выражения запросов используют декларативный синтаксис, аналогичный SQL или XQuery, для выполнения
запросов к коллекциям IEnumerable. Во время компиляции синтаксис запроса преобразуется в вызовы
методов к реализации поставщика LINQ методов расширения стандартных операторов запросов.
Приложения управляют стандартными операторами запросов в области, указывая соответствующее
пространство имен с помощью директивы using . Следующее выражение запроса принимает массив строк,
группирует их в соответствии с первым символом в строке и упорядочивает группы.
var number = 5;
var name = "Virginia";
var query = from str in stringArray
where str[0] == 'm'
select str;
Переменные, объявленные как var , настолько же строго типизированы, как и переменные, тип которых вы
задаете явно. Использование var делает возможным создание анонимных типов, однако его можно
использовать только для локальных переменных. Массивы также могут быть объявлены путем неявной
типизации.
Дополнительные сведения см. в статье Implicitly Typed Local Variables (Неявно типизированные локальные
переменные).
Продолжим рассматривать класс Customer . Предположим, что есть источник данных с именем
IncomingOrders и что для каждого заказа с большим значением OrderSize нужно создавать класс Customer
на основе этого заказа. Можно выполнить запрос LINQ для этого источника данных и использовать
инициализацию объекта, чтобы заполнить коллекцию:
У источника данных может быть больше скрытых свойств, чем у класса Customer , такого как OrderSize . Но
инициализация объекта позволяет преобразовать данные, возвращаемые по запросу, в требуемый тип. Мы
выберем данные, соответствующие нашему классу. В результате мы заполнили IEnumerable новыми
классами Customer , как и требовалось. Приведенный выше код также можно представить в синтаксисе
метода LINQ:
var newLargeOrderCustomers = IncomingOrders.Where(x => x.OrderSize > 5).Select(y => new Customer { Name =
y.Name, Phone = y.Phone });
Анонимные типы
Анонимный тип создается компилятором, и имя типа доступно только компилятору. Анонимные типы
обеспечивают удобный способ временной группировки набора свойств в результатах запроса без
необходимости определения отдельного именованного типа. Анонимные типы инициализируются с
помощью нового выражения и инициализатора объектов, как показано ниже:
Методы расширения
Метод расширения представляет собой статический метод, который может быть связан с типом, чтобы его
можно было вызывать, как если бы он являлся методом экземпляра типа. Эта возможность позволяет, по
сути, "добавлять" новые методы в существующие типы, фактически не изменяя их. Стандартные операторы
запросов представляют собой набор методов расширения, предоставляющих функции запросов LINQ для
любого типа, реализующего интерфейс IEnumerable<T>.
Дополнительные сведения см. в разделе Методы расширения.
Лямбда-выражения
Лямбда-выражение — это встроенная функция, которая использует оператор => для отделения входных
параметров от тела функции и может быть преобразована во время компиляции в делегат или дерево
выражения. В программировании LINQ вы сталкиваетесь с лямбда-выражениями при выполнении прямых
вызовов к стандартным операторам запросов.
Дополнительные сведения можно найти в разделе
Анонимные функции
Лямбда-выражения
Expression Trees (C#) (Деревья выражений (C#))
См. также
LINQ (C#)
Пошаговое руководство. Написание запросов на
C# (LINQ)
04.11.2019 • 15 minutes to read • Edit Online
В этом пошаговом руководстве описываются возможности C#, предназначенные для написания выражений
запросов LINQ.
Создание проекта C#
NOTE
Приведенные ниже инструкции относятся к Visual Studio. Если вы пользуетесь другой средой разработки, создайте
консольный проект со ссылкой на библиотеку System.Core.dll и директиву using для пространства имен System.Linq.
Создание запроса
Создание простого запроса
В методе Main приложения создайте простой запрос, при выполнении которого будет возвращаться
список учащихся, набравших в результате тестирования больше 90 баллов. Поскольку выбран весь
объект Student , запрос имеет тип IEnumerable<Student> . Несмотря на то, что код можно также
набрать напрямую, используя ключевое слово var, для демонстрации результатов применяется
неявная типизация. (Дополнительные сведения о var см. в разделе Неявно типизированные
локальные переменные.)
Кроме того переменная диапазона в запросе, student , служит ссылкой на каждый объект Student в
источнике и предоставляет доступ к каждому объекту.
Выполнение запроса
Порядок выполнения запроса
1. Теперь напишите цикл foreach , вызывающий выполнение запроса. Обратите внимание на
следующие моменты:
Доступ к каждому элементу в возвращаемой последовательности осуществляется с помощью
переменной итерации в цикле foreach .
Эта переменная имеет тип Student , а переменная запроса — совместимый тип
IEnumerable<Student> .
2. Добавив код, соберите и запустите приложение, чтобы отобразить результаты в окне Консоль.
// Output:
// Omelchenko, Svetlana
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael
Изменение запроса
Упорядочение результатов
1. С результатами проще работать, если они упорядочены. Возвращаемую последовательность можно
упорядочить по любому доступному полю в исходных элементах. Например, следующее
предложение orderby упорядочивает результаты в алфавитном порядке от А до Z, используя
фамилии учащихся. Добавьте к запросу следующее предложение orderby , указав его после
оператора where и перед оператором select :
2. Теперь измените предложение orderby таким образом, чтобы результаты были упорядочены по
баллам за первый тест в обратном порядке, от наибольшего к наименьшему.
2. Обратите внимание на то, что тип запроса изменился. Теперь он создает последовательность групп с
типом char в качестве ключа и последовательность объектов Student . Поскольку тип запроса
изменился, следующий код изменяет также цикл выполнения foreach :
// Output:
// O
// Omelchenko, Svetlana
// O'Donnell, Claire
// M
// Mortensen, Sven
// G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
// F
// Fakhouri, Fadi
// Feng, Hanying
// T
// Tucker, Lance
// Tucker, Michael
// A
// Adams, Terry
// Z
// Zabokritski, Eugene
// Output:
// O
// Omelchenko, Svetlana
// O'Donnell, Claire
// M
// Mortensen, Sven
// G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
// F
// Fakhouri, Fadi
// Feng, Hanying
// T
// Tucker, Lance
// Tucker, Michael
// A
// Adams, Terry
// Z
// Zabokritski, Eugene
// Output:
//A
// Adams, Terry
//F
// Fakhouri, Fadi
// Feng, Hanying
//G
// Garcia, Cesar
// Garcia, Debra
// Garcia, Hugo
//M
// Mortensen, Sven
//O
// Omelchenko, Svetlana
// O'Donnell, Claire
//T
// Tucker, Lance
// Tucker, Michael
//Z
// Zabokritski, Eugene
// Output:
// Omelchenko Svetlana
// O'Donnell Claire
// Mortensen Sven
// Garcia Cesar
// Fakhouri Fadi
// Feng Hanying
// Garcia Hugo
// Adams Terry
// Zabokritski Eugene
// Tucker Michael
var studentQuery6 =
from student in students
let totalScore = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
select totalScore;
// Output:
// Class average score = 334.166666666667
// Output:
// The Garcias in the class are:
// Cesar
// Debra
// Hugo
2. Код, представленный выше в этом пошаговом руководстве, показывает, что среднее количество
баллов по классу составляет около 334. Для создания последовательности Students , сумма баллов в
которой будет выше, чем средний показатель по классу, с указанием Student ID можно вставить в
оператор select анонимный тип:
var studentQuery8 =
from student in students
let x = student.Scores[0] + student.Scores[1] +
student.Scores[2] + student.Scores[3]
where x > averageScore
select new { id = student.ID, score = x };
// Output:
// Student ID: 113, Score: 338
// Student ID: 114, Score: 353
// Student ID: 116, Score: 369
// Student ID: 117, Score: 352
// Student ID: 118, Score: 343
// Student ID: 120, Score: 341
// Student ID: 122, Score: 368
Следующие шаги
Ознакомившись с основными аспектами работы с запросами в C#, вы будете готовы прочитать
документацию и примеры по интересующему вас типу поставщика LINQ:
LINQ to SQL
LINQ to DataSet
LINQ to XML (C#)
LINQ to Objects (C#)
См. также
LINQ (C#)
Выражения запросов LINQ
Общие сведения о стандартных операторах
запроса (C#)
23.10.2019 • 5 minutes to read • Edit Online
Стандартные операторы запросов — это методы, формирующие шаблон LINQ. Большинство этих
методов работают с последовательностями. В данном контексте последовательность — это объект, тип
которого реализует интерфейс IEnumerable<T> или IQueryable<T>. Функциональные возможности
стандартных операторов запросов включают фильтрацию, проекцию, статистическую обработку,
сортировку и многие другие.
Существует два набора стандартных операторов запросов LINQ, один из них работает с объектами
типа IEnumerable<T>, а другой — с объектами типа IQueryable<T>. Методы, входящие в каждый из
наборов, являются статическими членами классов Enumerable и Queryable соответственно. Они
определяются как методы расширения типа, к которому применяются. Это означает, что их можно
вызывать, используя либо синтаксис статического метода, либо синтаксис метода экземпляра.
Кроме того, несколько стандартных методов операторов запроса работают с типами, отличными от
основанных на IEnumerable<T> или IQueryable<T>. Тип Enumerable определяет два таких метода,
которые работают с объектами типа IEnumerable. Эти методы (Cast<TResult>(IEnumerable) и
OfType<TResult>(IEnumerable)) позволяют выполнять запрос к непараметризованным или не
являющимся универсальными коллекциям с использованием шаблона LINQ. Для этого создается
строго типизированная коллекция объектов. Класс Queryable определяет два схожих метода
(Cast<TResult>(IQueryable) и OfType<TResult>(IQueryable)), которые работают с объектами типа
Queryable.
Стандартные операторы запросов имеют различное время выполнения, которое зависит от того,
возвращают они одноэлементное значение или последовательность значений. Методы, возвращающие
одноэлементное значение (например, Average и Sum), выполняются одновременно. Методы, которые
возвращают последовательность, откладывают выполнение запроса и возвращают перечисляемый
объект.
При использовании методов, которые применяются к коллекциям, т. е. методов, которые расширяют
IEnumerable<T>, возвращаемый перечисляемый объект захватывает переданные в этот метод
аргументы. При перечислении этого объекта задействуется логика оператора запросов и возвращаются
результаты запросов.
При этом методы, расширяющие IQueryable<T>, не реализуют поведение запросов, но формируют
дерево выражения, представляющее выполняемый запрос. Запрос обрабатывается объектом
IQueryable<T> источника.
Вызовы методов запросов можно объединять в один запрос, в результате чего запросы могут
становиться довольно сложными.
В следующем примере кода показано, как использовать стандартные операторы запросов для
получения сведений о последовательности.
string sentence = "the quick brown fox jumps over the lazy dog";
// Split the string into individual words to create a collection.
string[] words = sentence.Split(' ');
Связанные разделы
Следующие ссылки адресуют к разделам, содержащим дополнительные сведения о различных
стандартных операторах запросов в зависимости от их функциональности.
Сортировка данных (C#)
Операции над множествами (C#)
Фильтрация данных (C#)
Операции, использующие квантификаторы (C#)
Операции проекции (C#)
Секционирование данных (C#)
Операции соединения (C#)
Группирование данных (C#)
Операции создания (C#)
Операции сравнения (C#)
Операции с элементами (C#)
Преобразование типов данных (C#)
Операции объединения (C#)
Операции агрегирования (C#)
См. также
Enumerable
Queryable
Введение в запросы LINQ (C#)
Синтаксис выражений запроса для стандартных операторов запроса (C#)
Классификация стандартных операторов запросов по способу выполнения (C#)
Методы расширения
Синтаксис выражений запроса для стандартных
операторов запроса (C#)
23.10.2019 • 2 minutes to read • Edit Online
GroupBy group … by
-или-
group … by … into …
OrderBy<TSource,TKey>(IEnumerable<TSource>, orderby
Func<TSource,TKey>)
(Дополнительные сведения см. в разделе Предложение
orderby.)
МЕТОД СИНТАКСИС ВЫРАЖЕНИЯ ЗАПРОСА C#
Select select
ThenBy<TSource,TKey>(IOrderedEnumerable<TSource>, orderby …, …
Func<TSource,TKey>)
(Дополнительные сведения см. в разделе Предложение
orderby.)
Where where
См. также
Enumerable
Queryable
Общие сведения о стандартных операторах запроса (C#)
Классификация стандартных операторов запросов по способу выполнения (C#)
Классификация стандартных операторов запросов
по способу выполнения (C#)
23.10.2019 • 4 minutes to read • Edit Online
В реализации LINQ to Objects выполнение методов стандартных операторов запросов бывает немедленным
и отложенным. Операторы запросов, использующие отложенное выполнение, можно дополнительно
разделить на две категории: потоковые и непотоковые. Если вы знаете, каким образом выполняются разные
операторы запросов, это может помочь понять результаты, полученные из данного запроса. Это особенно
справедливо при изменении источника данных или создании одного запроса поверх другого. В этом разделе
представлена классификация стандартных операторов запросов по способу выполнения.
Способ выполнения
Интерпретация
Немедленное выполнение означает, что источник данных считывается и операция выполняется в той точке
кода, где объявлен запрос. Все стандартные операторы запросов, возвращающие один, неперечислимый
результат, выполняются немедленно.
Отложено
Отложенное выполнение означает, что операция не выполняется в той точке кода, где объявлен запрос. Она
выполняется только после перечисления переменной запроса, например с помощью оператора foreach .
Это означает, что результаты выполнения запроса зависят от содержимого источника данных при
выполнении запроса, а не при его определении. Если переменная запроса перечисляется несколько раз,
результаты могут каждый раз отличаться. Практически все стандартные операторы запроса, которые
возвращают значения типа IEnumerable<T> или IOrderedEnumerable<TElement>, выполняются отложенным
способом.
Операторы запросов, использующие отложенное выполнение, можно дополнительно разделить на
потоковые и непотоковые.
Потоковые операторы
Потоковые операторы не считывают все исходные данные до создания элементов. Во время выполнения
потоковый оператор выполняет свою операцию с каждым исходным элементом по мере считывания и при
необходимости создает элемент. Потоковый оператор продолжает считывание исходных элементов до того
момента, когда можно будет создать итоговый элемент. Это означает, что для получения одного итогового
элемента может быть считано несколько исходных элементов.
Непотоковые
Непотоковые операторы должны считать все исходные данные до создания итогового элемента. В эту
категорию попадают операции сортировки и группировки. Во время выполнения непотоковые операторы
запросов считывают все исходные данные, помещают их в структуру данных, выполняют операцию и
создают итоговые элементы.
Таблица классификации
В следующей таблице приведена классификация всех методов стандартных операторов запросов по способу
выполнения.
NOTE
Если оператор помечен в двух столбцах, в операции участвуют две входные последовательности, и каждая
последовательность вычисляется по-разному. В таких случаях первая последовательность в списке параметров всегда
вычисляется отложенным потоковым способом.
ОТЛОЖЕННОЕ ОТЛОЖЕННОЕ
СТАНДАРТНЫЙ НЕМЕДЛЕННОЕ ПОТОКОВОЕ НЕПОТОКОВОЕ
ОПЕРАТОР ЗАПРОСА ВОЗВРАЩАЕМЫЙ ТИП ВЫПОЛНЕНИЕ ВЫПОЛНЕНИЕ ВЫПОЛНЕНИЕ
Aggregate TSource X
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
ОТЛОЖЕННОЕ ОТЛОЖЕННОЕ
СТАНДАРТНЫЙ НЕМЕДЛЕННОЕ ПОТОКОВОЕ НЕПОТОКОВОЕ
ОПЕРАТОР ЗАПРОСА ВОЗВРАЩАЕМЫЙ ТИП ВЫПОЛНЕНИЕ ВЫПОЛНЕНИЕ ВЫПОЛНЕНИЕ
Last TSource X
LastOrDefault TSource X
LongCount Int64 X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable X
<TElement>
OrderByDescending IOrderedEnumerable X
<TElement>
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ОТЛОЖЕННОЕ ОТЛОЖЕННОЕ
СТАНДАРТНЫЙ НЕМЕДЛЕННОЕ ПОТОКОВОЕ НЕПОТОКОВОЕ
ОПЕРАТОР ЗАПРОСА ВОЗВРАЩАЕМЫЙ ТИП ВЫПОЛНЕНИЕ ВЫПОЛНЕНИЕ ВЫПОЛНЕНИЕ
ThenBy IOrderedEnumerable X
<TElement>
ThenByDescending IOrderedEnumerable X
<TElement>
ToDictionary Dictionary<TKey,TVal X
ue>
ToList IList<T> X
ToLookup ILookup<TKey,TEleme X
nt>
Union IEnumerable<T> X
Where IEnumerable<T> X
См. также
Enumerable
Общие сведения о стандартных операторах запроса (C#)
Синтаксис выражений запроса для стандартных операторов запроса (C#)
LINQ to Objects (C#)
Сортировка данных (C#)
04.11.2019 • 3 minutes to read • Edit Online
Далее перечислены методы стандартных операторов запроса, которые выполняют сортировку данных.
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
Queryable.OrderByDescendi
ng
Queryable.ThenByDescendin
g
the
fox
quick
brown
jumps
*/
the
quick
jumps
fox
brown
*/
fox
the
brown
jumps
quick
*/
the
fox
quick
jumps
brown
*/
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
предложение orderby
Практическое руководство. Упорядочение результатов предложения соединения
Практическое руководство. Сортировка или фильтрация текстовых данных по любому слову или полю
(LINQ ) (C#)
Операции над множествами (C#)
25.11.2019 • 2 minutes to read • Edit Online
Операции над множествами в LINQ — это операции запросов, результирующие наборы которых
основываются на наличии или отсутствии эквивалентных элементов в одной или другой коллекции (или
наборе).
Методы стандартных операторов запросов, которые выполняют операции над множествами, перечислены в
следующем разделе.
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
Исключения
На следующем рисунке показано поведение Enumerable.Except. Возвращаемая последовательность
содержит только те элементы из первой входной последовательности, которых нет во второй.
Пересечение
На следующем рисунке показано поведение Enumerable.Intersect. Возвращаемая последовательность
содержит элементы, общие для обеих входных последовательностей.
Объединение
На следующем рисунке показана операция объединения двух последовательностей символов.
Возвращаемая последовательность содержит уникальные элементы из обеих входных последовательностей.
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
Практическое руководство. Объединение и сравнение коллекций строк (LINQ ) (C#)
Практическое руководство. Нахождение разности множеств между двумя списками (LINQ ) (C#)
Фильтрация данных (C#)
04.11.2019 • 2 minutes to read • Edit Online
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
the
fox
*/
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
предложение where
Практическое руководство. Динамическое определение фильтров предикатов во время выполнения
Практическое руководство. Выполнение запроса к метаданным сборки при помощи отражения (LINQ )
(C#)
Практическое руководство. Запрос файлов с указанным атрибутом или именем (C#)
Практическое руководство. Сортировка или фильтрация текстовых данных по любому слову или полю
(LINQ ) (C#)
Операции, использующие квантификаторы (C#)
04.11.2019 • 2 minutes to read • Edit Online
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
Практическое руководство. Динамическое определение фильтров предикатов во время выполнения
Практическое руководство. Запрос к предложениям, содержащим указанный набор слов (LINQ ) (C#)
Операции проекции (C#)
23.10.2019 • 5 minutes to read • Edit Online
Проекцией называют операцию преобразования объекта в новую форму, которая часто состоит только из
тех его свойств, которые будут использоваться впоследствии. С помощью проекции можно создать новый
тип, построенный из каждого объекта. Вы можете проецировать свойство и выполнять над ним
математические функции. Также можно проецировать исходный объект, не изменяя его.
Методы стандартных операторов запросов, которые выполняют проецирование, перечислены в следующем
разделе.
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
a
a
a
d
*/
SelectMany
В приведенном ниже примере несколько предложений from используются для проецирования каждого
слова из каждой строки в списке строк.
List<string> phrases = new List<string>() { "an apple a day", "the quick brown fox" };
an
apple
a
day
the
quick
brown
fox
*/
class Bouquet
{
public List<string> Flowers { get; set; }
}
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
предложение select
Практическое руководство. Заполнение коллекций объектов из нескольких источников (LINQ ) (C#)
Практическое руководство. Разделение файла на несколько файлов с помощью групп (LINQ ) (C#)
Секционирование данных (C#)
23.10.2019 • 2 minutes to read • Edit Online
Секционированием в LINQ называют операцию разделения входной последовательности на два раздела без
изменения порядка элементов, а затем возвращения одного из разделов.
На следующем рисунке показаны результаты трех различных операций секционирования в
последовательности символов. Первая операция возвращает первые три элемента в последовательности.
Вторая операция пропускает первые три элемента и возвращает остальные элементы. Третья операция
пропускает первые два элемента в последовательности и возвращает три следующих элемента.
Операторы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ ОПЕРАТОРА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
Операции соединения (C#)
04.11.2019 • 3 minutes to read • Edit Online
Соединение двух источников данных — это связь объектов в одном источнике данных с объектами, которые
имеют общий атрибут в другом источнике данных.
Соединение является важной операцией в запросах, направленных на источники данных, отношения
которых друг к другу нельзя отследить напрямую. В объектно-ориентированном программировании оно
может означать корреляцию между немоделируемыми объектами, например такими, как обратное
направление одностороннего отношения. Примером одностороннего отношения является класс Customer,
имеющий свойство типа City (город), в то время как класс City не имеет свойства, которое является
коллекцией объектов Customer (клиент). В случае наличия списка объектов City для поиска всех клиентов в
каждом городе можно использовать операцию соединения.
На платформе LINQ представлены методы объединения Join и GroupJoin. Они выполняют эквисоединения,
или соединения, которые сопоставляют два источника данных на основе равенства их ключей. (Для
сравнения, Transact-SQL поддерживает операции соединения, отличные от оператора "равно", например
оператор "меньше, чем".) В терминах реляционных баз данных Join реализует внутреннее соединение — тип
соединения, в котором возвращаются только те объекты, у которых есть совпадения в другом наборе
данных. Метод GroupJoin не имеет прямого эквивалента в терминах реляционных баз данных, но реализует
надмножество внутренних соединений и левых внешних соединений. Левое внешнее соединение — это
соединение, которое возвращает каждый элемент первого (левого) источника данных, даже если в другом
источнике данных не имеется соответствующих элементов.
На следующем рисунке показано концептуальное представление из двух наборов и элементов, входящих в
эти наборы, которые включены либо во внутреннее соединение, либо в левое внешнее соединение.
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
Анонимные типы
Формулировка запросов-объединений и запросов векторного произведения
предложение join
Практическое руководство. Соединение с помощью составных ключей
Практическое руководство. Объединение содержимого из файлов разных форматов (LINQ ) (C#)
Практическое руководство. Упорядочение результатов предложения соединения
Практическое руководство. Выполнение пользовательских операций соединения
Практическое руководство. Выполнение групповых соединений
Практическое руководство. Выполнение внутренних соединений
Практическое руководство. Выполнение левых внешних соединений
Практическое руководство. Заполнение коллекций объектов из нескольких источников (LINQ ) (C#)
Группирование данных (C#)
04.11.2019 • 2 minutes to read • Edit Online
Группированием называют операцию объединения данных в группы таким образом, чтобы у элементов в
каждой группе был общий атрибут.
На следующем рисунке показаны результаты операции группирования последовательности символов. Ключ
для каждой группы — это символ.
Далее перечислены методы стандартных операторов запроса, которые группируют элементы данных.
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
Odd numbers:
35
3987
199
329
Even numbers:
44
200
84
4
446
208
*/
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
предложение group
Практическое руководство. Создание вложенной группы
Практическое руководство. Группировка файлов по расширению (LINQ ) (C#)
Практическое руководство. Группировка результатов запросов
Практическое руководство. Вложенный запрос в операции группирования
Практическое руководство. Разделение файла на несколько файлов с помощью групп (LINQ ) (C#)
Операции создания (C#)
23.10.2019 • 2 minutes to read • Edit Online
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
Операции равенства (C#)
25.11.2019 • 2 minutes to read • Edit Online
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
Практическое руководство. Сравнение содержимого двух папок (LINQ ) (C#)
Операции с элементами (C#)
23.10.2019 • 2 minutes to read • Edit Online
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
Практическое руководство. Запрос самого большого файла или файлов в дереве папок (LINQ ) (C#)
Преобразование типов данных (C#)
04.11.2019 • 3 minutes to read • Edit Online
Методы
В следующей таблице перечислены методы стандартных операторов запросов, выполняющие
преобразование типов данных.
Методы преобразования в этой таблице, имена которых начинаются с "As", изменяют статический тип
исходной коллекции, но не выполняют перечисление. Методы, имена которых начинаются с "To",
перечисляют исходную коллекцию и помещают элементы в соответствующий тип коллекции.
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
предложение from
Выражения запросов LINQ
Практическое руководство. Выполнение запроса к ArrayList с помощью LINQ (C#)
Операции объединения (C#)
25.11.2019 • 2 minutes to read • Edit Online
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
Практическое руководство. Объединение и сравнение коллекций строк (LINQ ) (C#)
Операции агрегирования (C#)
25.11.2019 • 2 minutes to read • Edit Online
Методы
СИНТАКСИС ВЫРАЖЕНИЯ ДОПОЛНИТЕЛЬНЫЕ
ИМЯ МЕТОДА ОПИСАНИЕ ЗАПРОСА C# СВЕДЕНИЯ
См. также
System.Linq
Общие сведения о стандартных операторах запроса (C#)
Практическое руководство. Вычисление значений столбцов в текстовом CSV -файле (LINQ ) (C#)
Практическое руководство. Запрос самого большого файла или файлов в дереве папок (LINQ ) (C#)
Практическое руководство. Запрос общего числа байтов в наборе папок (LINQ ) (C#)
LINQ to Objects (C#)
25.11.2019 • 3 minutes to read • Edit Online
Термин "LINQ to Objects" означает использование запросов LINQ с любой коллекцией IEnumerable или
IEnumerable<T> напрямую, без привлечения промежуточного поставщика LINQ, API LINQ to SQL или
LINQ to XML. Вы можете выполнить запрос LINQ к любой перечислимой коллекции, такой как List<T>,
Array или Dictionary<TKey,TValue>. Коллекция может быть определена пользователем или возвращена
API .NET Framework.
В общем смысле LINQ to Objects представляет собой новый подход к коллекциям. Раньше нужно было
написать сложные циклы foreach , определяющие порядок извлечения данных из коллекции. При
использовании LINQ пишется декларативный код, описывающий, какие данные необходимо извлечь.
Кроме того, запросы LINQ предлагают три основных преимущества по сравнению с традиционными
циклами foreach :
1. Они более краткие и удобочитаемые, особенно при фильтрации нескольких условий.
2. Они предоставляют широкие возможности фильтрации, упорядочивания и группировки с
минимумом кода приложения.
3. Они могут переноситься в другие источники данных практически без изменений.
В общем, чем сложнее операция, которую нужно выполнить с данными, тем больше преимуществ вы
получаете при использовании LINQ вместо традиционных способов итерации.
Целью этого раздела является демонстрация подхода LINQ с помощью нескольких примеров. Он не
претендует на исчерпывающий характер.
В этом разделе
LINQ и строки (C#)
Использование LINQ для запроса и преобразования строк и коллекций строк. Ссылки на разделы,
демонстрирующие эти принципы.
LINQ и отражение (C#)
Ссылки на пример, демонстрирующий, как LINQ использует отражение.
LINQ и каталоги файлов (C#)
Использование LINQ для взаимодействия с файловыми системами. Ссылки на разделы,
демонстрирующие эти понятия.
Практическое руководство. Выполнение запроса к ArrayList с помощью LINQ (C#)
Выполнение запроса ArrayList в C#.
Практическое руководство. Добавление настраиваемых методов для запросов LINQ (C#)
Расширение набора методов, которые можно использовать для запросов LINQ путем добавления
методов расширения в интерфейс IEnumerable<T>.
LINQ (C#)
Ссылки на разделы, рассказывающие LINQ и содержащие примеры кода выполнения запросов.
LINQ и строки (C#)
25.11.2019 • 5 minutes to read • Edit Online
LINQ можно использовать для запроса и преобразования строк и коллекций строк. При этом лучше всего
его потенциал раскрывается при работе с частично структурированными данными в текстовых файлах.
Запросы LINQ можно комбинировать с традиционными строковыми функциями и регулярными
выражениями. Например, используя метод String.Split или Regex.Split, можно создать массив строк,
который затем можно запрашивать или изменять с помощью LINQ. Метод Regex.IsMatch можно
использовать в предложении where запроса LINQ. Также LINQ можно использовать для запроса или
изменения результатов MatchCollection, возвращаемых регулярным выражением.
Методы, описанные в этом разделе, позволяют преобразовать частично структурированные текстовые
данные в XML. Дополнительные сведения см. в разделе Практическое руководство. Создание XML из
CSV -файлов (C#).
Примеры в этом разделе делятся на две категории:
См. также
LINQ (C#)
Практическое руководство. Создание XML из CSV -файлов
Практическое руководство. Подсчет вхождений
слова в строке (LINQ) (C#)
25.11.2019 • 2 minutes to read • Edit Online
В этом примере показано, как с помощью запроса LINQ определить, сколько раз то или иное слово
встречается в строке. Обратите внимание на то, что для этого сначала вызывается метод Split, который
создает массив слов. Использование метода Split связано с определенным снижением производительности.
Если для строки выполняется только подсчет слов, рекомендуется вместо него использовать метод Matches
или IndexOf. Если же производительность не критична или вы уже разбили предложение, чтобы выполнить с
ним другие типы запросов, имеет смысл подсчитать слова или фразы с помощью LINQ.
Пример
class CountWords
{
static void Main()
{
string text = @"Historically, the world of data and the world of objects" +
@" have not been well integrated. Programmers work in C# or Visual Basic" +
@" and also in SQL or XQuery. On the one side are concepts such as classes," +
@" objects, fields, inheritance, and .NET Framework APIs. On the other side" +
@" are tables, columns, rows, nodes, and separate languages for dealing with" +
@" them. Data types often require translation between the two worlds; there are" +
@" different standard functions. Because the object world has no notion of query, a" +
@" query can only be represented as a string without compile-time type checking or" +
@" IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to" +
@" objects in memory is often tedious and error-prone.";
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и строки (C#)
Практическое руководство. Запрос к
предложениям, содержащим указанный набор
слов (LINQ) (C#)
23.10.2019 • 3 minutes to read • Edit Online
В этом примере показан поиск предложений, содержащих совпадения для каждого из указанного набора
слов в текстовом файле. Хотя массив терминов для поиска в этом примере жестко закодирован, его можно
заполнять динамически во время выполнения. В этом примере запрос возвращает предложения, которые
содержат слова "Historically", "data" и "integrated".
Пример
class FindSentences
{
static void Main()
{
string text = @"Historically, the world of data and the world of objects " +
@"have not been well integrated. Programmers work in C# or Visual Basic " +
@"and also in SQL or XQuery. On the one side are concepts such as classes, " +
@"objects, fields, inheritance, and .NET Framework APIs. On the other side " +
@"are tables, columns, rows, nodes, and separate languages for dealing with " +
@"them. Data types often require translation between the two worlds; there are " +
@"different standard functions. Because the object world has no notion of query, a " +
@"query can only be represented as a string without compile-time type checking or " +
@"IntelliSense support in the IDE. Transferring data from SQL tables or XML trees to " +
@"objects in memory is often tedious and error-prone.";
// Define the search terms. This list could also be dynamically populated at runtime.
string[] wordsToMatch = { "Historically", "data", "integrated" };
// Find sentences that contain all the terms in the wordsToMatch array.
// Note that the number of terms to match is not specified at compile time.
var sentenceQuery = from sentence in sentences
let w = sentence.Split(new char[] { '.', '?', '!', ' ', ';', ':', ',' },
StringSplitOptions.RemoveEmptyEntries)
where w.Distinct().Intersect(wordsToMatch).Count() == wordsToMatch.Count()
select sentence;
Запрос сначала разделяет текст на предложения, а затем разделяет предложения на массив строк,
содержащих слова. Для каждого из этих массивов метод Distinct удаляет все повторяющиеся слова, после
чего выполняет операцию Intersect в отношении массива слов и массива wordsToMatch . Если число
пересечений совпадает с размером массива wordsToMatch , все слова были найдены и возвращается исходное
предложение.
В вызове Split знаки пунктуации используются как разделители для того, чтобы удалить их из строки. Если
этого не сделать, то, например, можно получить строку "Historically," которая не будет совпадать с
"Historically" в массиве wordsToMatch . В зависимости от знаков препинания в исходном тексте может
потребоваться использовать дополнительные разделители.
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и строки (C#)
Практическое руководство. Запрос символов в
строке (LINQ) (C#)
25.11.2019 • 2 minutes to read • Edit Online
Поскольку класс String реализует универсальный интерфейс IEnumerable<T>, любая строка может
запрашиваться как последовательность символов. Однако это не слишком распространенный пример
использования LINQ. Для сложных операций сопоставления шаблонов используйте класс Regex.
Пример
В следующем примере запрашивается строка, чтобы определить количество содержащихся в ней цифр.
Обратите внимание, что запрос "используется повторно" после первоначального выполнения. Это
становится возможным, поскольку сам запрос не хранит фактические результаты.
class QueryAString
{
static void Main()
{
string aString = "ABCDE99F-J74-12-89A";
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и строки (C#)
Практическое руководство. Объединение запросов LINQ с помощью регулярных выражений (C#)
Практическое руководство. Объединение
запросов LINQ с помощью регулярных выражений
(C#)
25.11.2019 • 2 minutes to read • Edit Online
В этом примере показано, как использовать класс Regex при создании регулярного выражения для более
сложных сопоставлений в текстовых строках. Запрос LINQ упрощает фильтрацию именно тех файлов,
которые требуется найти с помощью регулярного выражения, и формирование результатов.
Пример
class QueryWithRegEx
{
public static void Main()
{
// Modify this path as necessary so that it accesses your version of Visual Studio.
string startFolder = @"C:\Program Files (x86)\Microsoft Visual Studio 14.0\";
// One of the following paths may be more appropriate on your computer.
//string startFolder = @"C:\Program Files (x86)\Microsoft Visual Studio\2017\";
Обратите внимание, что можно также запросить объект MatchCollection, возвращаемый поиском RegEx . В
этом примере в результатах создается только значение каждого совпадения. Тем не менее вы можете
использовать LINQ для выполнения всех видов фильтрации, сортировки и группировки в этой коллекции.
Так как MatchCollection является неуниверсальной коллекцией IEnumerable, необходимо явно указать тип
переменной диапазона в запросе.
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и строки (C#)
LINQ и каталоги файлов (C#)
Практическое руководство. Нахождение разности
множеств между двумя списками (LINQ) (C#)
25.11.2019 • 2 minutes to read • Edit Online
В этом примере показано, как использовать LINQ для сравнения двух списков строк и вывода тех строк,
которые содержатся в файле names1.txt, но не в файле names2.txt.
Создание файлов данных
1. Скопируйте файлы names1.txt и names2.txt в папку решения, как показано в разделе Практическое
руководство. Объединение и сравнение коллекций строк (LINQ ) (C#).
Пример
class CompareLists
{
static void Main()
{
// Create the IEnumerable data sources.
string[] names1 = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] names2 = System.IO.File.ReadAllLines(@"../../../names2.txt");
// Create the query. Note that method syntax must be used here.
IEnumerable<string> differenceQuery =
names1.Except(names2);
Некоторые типы операторов запроса в C#, например Except, Distinct, Union и Concat, могут выражаться
только с использованием синтаксиса на основе методов.
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и строки (C#)
Практическое руководство. Сортировка или
фильтрация текстовых данных по любому слову
или полю (LINQ) (C#)
23.10.2019 • 2 minutes to read • Edit Online
В следующем примере демонстрируется сортировка строк структурированного текста, таких как значения,
разделенные запятыми, по любому полю в строке. Поле можно указывать в среде выполнения динамически.
Допустим, поля в файле scores.csv содержат идентификационные номера учащихся и баллы, которые они
набрали в результате четырех тестов.
Создание файла с данными
1. Скопируйте данные из файла scores.csv (см. раздел Практическое руководство. Объединение
содержимого из файлов разных форматов (LINQ ) (C#)) в папку решения.
Пример
public class SortLines
{
static void Main()
{
// Create an IEnumerable data source
string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv");
return scoreQuery;
}
}
/* Output (if sortField == 1):
Sorted highest to lowest by field [1]:
116, 99, 86, 90, 94
120, 99, 82, 81, 79
111, 97, 92, 81, 60
114, 97, 89, 85, 82
121, 96, 85, 91, 60
122, 94, 92, 91, 91
117, 93, 92, 80, 87
118, 92, 90, 83, 78
113, 88, 94, 65, 91
112, 75, 84, 91, 39
119, 68, 79, 88, 92
115, 35, 72, 91, 70
*/
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и строки (C#)
Практическое руководство. Изменение порядка
полей файла с разделителями (LINQ) (C#)
23.10.2019 • 2 minutes to read • Edit Online
CSV -файл — это текстовый файл, который часто используется для хранения данных электронных таблиц или
других табличных данных, представленных строками и столбцами. Использование метода Split для
разделения полей упрощает создание запросов к CSV -файлам и управление ими с помощью LINQ.
Фактически та же технология может использоваться для изменения порядка частей любой
структурированной строки текста, а не только CSV -файлов.
В следующем примере предполагается, что три столбца представляют "фамилию", "имя" и "идентификатор"
учащихся. Поля группируются в алфавитном порядке по фамилии учащихся. Запрос создает новую
последовательность, в которой столбец идентификатора отображается первым, за ним следует второй
столбец, который объединяет имя и фамилию учащегося. Порядок строк изменен в соответствии с полем
идентификатора. Результаты сохраняются в новый файл, и исходные данные не изменяются.
Создание файла данных
1. Скопируйте следующие строки в обычный текстовый файл с именем spreadsheet1.csv. Сохраните файл
в папке проекта.
Adams,Terry,120
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Cesar,114
Garcia,Debra,115
Garcia,Hugo,118
Mortensen,Sven,113
O'Donnell,Claire,112
Omelchenko,Svetlana,111
Tucker,Lance,119
Tucker,Michael,122
Zabokritski,Eugene,121
Пример
class CSVFiles
{
static void Main(string[] args)
{
// Create the IEnumerable data source
string[] lines = System.IO.File.ReadAllLines(@"../../../spreadsheet1.csv");
// Execute the query and write out the new file. Note that WriteAllLines
// takes a string[], so ToArray is called on the query.
System.IO.File.WriteAllLines(@"../../../spreadsheet2.csv", query.ToArray());
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и строки (C#)
LINQ и каталоги файлов (C#)
Практическое руководство. Создание XML из CSV -файлов (C#)
Практическое руководство. Объединение и
сравнение коллекций строк (LINQ) (C#)
25.11.2019 • 3 minutes to read • Edit Online
В этом примере показано объединение файлов, содержащих строки текста, и последующая сортировка
результатов. В частности, показано выполнение простого сцепления, объединения и пересечения в двух
наборах строк текста.
Настройка проекта и текстовых файлов
1. Скопируйте эти имена в текстовый файл с именем names1.txt и сохраните его в папке проекта:
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
2. Скопируйте эти имена в текстовый файл с именем names2.txt и сохраните его в папке проекта.
Обратите внимание, что в этих двух файлах имеются общие имена.
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
Пример
class MergeStrings
{
static void Main(string[] args)
{
//Put text files in your solution folder
string[] fileA = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] fileB = System.IO.File.ReadAllLines(@"../../../names2.txt");
IEnumerable<String> tempQuery1 =
from name in fileA
let n = name.Split(',')
where n[0] == nameMatch
select name;
IEnumerable<string> tempQuery2 =
from name2 in fileB
let n2 = name2.Split(',')
where n2[0] == nameMatch
select name2;
IEnumerable<string> nameMatchQuery =
tempQuery1.Concat(tempQuery2).OrderBy(s => s);
OutputQueryResults(nameMatchQuery, $"Concat based on partial name match \"{nameMatch}\":");
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и строки (C#)
LINQ и каталоги файлов (C#)
Практическое руководство. Заполнение коллекций
объектов из нескольких источников (LINQ) (C#)
23.10.2019 • 5 minutes to read • Edit Online
В этом примере показано, как объединить данные из разных источников в последовательность новых типов.
NOTE
Не пытайтесь объединить данные в памяти или данные в файловой системе с данными, которые остаются в базе
данных. Такие междоменные соединения могут повлечь за собой непредвиденные результаты из-за разных
способов, с помощью которых операции соединения могут определяться для запросов к базам данных и другим
типам источников. Кроме того, существует риск, что такая операция может вызвать исключение нехватки памяти,
если объем данных в базе данных достаточно большой. Чтобы объединить данные из базы данных с данными в
памяти, сначала вызовите ToList или ToArray для запроса к базе данных, а затем выполните присоединение для
возвращенной коллекции.
Пример
Следующий пример демонстрирует использование именованного типа Student для хранения
объединенных данных из двух коллекций строк в памяти, которые имитируют данные электронной таблицы
в формате CSV. Первая коллекция строк представляет имена и идентификаторы учащихся, а вторая
коллекция — идентификатор учащегося (в первом столбце) и результаты четырех экзаменов.
Идентификатор используется в качестве внешнего ключа.
using System;
using System.Collections.Generic;
using System.Linq;
class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
public List<int> ExamScores { get; set; }
}
class PopulateCollection
{
static void Main()
{
// These data files are defined in How to: Join Content from
// Dissimilar Files (LINQ).
В предложении select инициализатор объектов используется для создания каждого нового объекта Student
на основе данных из двух источников.
Если не требуется хранить результаты запроса, анонимные типы могут быть более удобными, чем
именованные типы. Именованные типы необходимы, если результаты запроса передаются за пределы
метода, в котором выполняется запрос. Следующий пример кода выполняет ту же задачу, что и в
предыдущем примере, но использует анонимные типы вместо именованных типов:
// Merge the data sources by using an anonymous type.
// Note the dynamic creation of a list of ints for the
// ExamScores member. We skip 1 because the first string
// in the array is the student ID, not an exam score.
var queryNamesScores2 =
from nameLine in names
let splitName = nameLine.Split(',')
from scoreLine in scores
let splitScoreLine = scoreLine.Split(',')
where Convert.ToInt32(splitName[2]) == Convert.ToInt32(splitScoreLine[0])
select new
{
First = splitName[0],
Last = splitName[1],
ExamScores = (from scoreAsText in splitScoreLine.Skip(1)
select Convert.ToInt32(scoreAsText))
.ToList()
};
См. также
LINQ и строки (C#)
Инициализаторы объектов и коллекций
Анонимные типы
Практическое руководство. Разделение файла на
несколько файлов с помощью групп (LINQ) (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом примере показан один из способов объединения содержимого двух файлов и последующего
создания набора новых файлов, данные в котором будут организованы иначе.
Создание файлов данных
1. Скопируйте эти имена в текстовый файл с именем names1.txt и сохраните его в папке проекта:
Bankov, Peter
Holm, Michael
Garcia, Hugo
Potra, Cristina
Noriega, Fabricio
Aw, Kam Foo
Beebe, Ann
Toyoshima, Tim
Guy, Wey Yuan
Garcia, Debra
2. Скопируйте эти имена в текстовый файл с именем names2.txt и сохраните его в папке проекта:
Обратите внимание, что в этих двух файлах имеются общие имена.
Liu, Jinghao
Bankov, Peter
Holm, Michael
Garcia, Hugo
Beebe, Ann
Gilchrist, Beth
Myrcha, Jacek
Giakoumakis, Leo
McLin, Nkenge
El Yassir, Mehdi
Пример
class SplitWithGroups
{
static void Main()
{
string[] fileA = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] fileB = System.IO.File.ReadAllLines(@"../../../names2.txt");
// Output to display.
Console.WriteLine(g.Key);
// Write file.
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fileName))
{
foreach (var item in g)
{
sw.WriteLine(item);
// Output to console for example purposes.
Console.WriteLine(" {0}", item);
}
}
}
// Keep console window open in debug mode.
Console.WriteLine("Files have been written. Press any key to exit");
Console.ReadKey();
}
}
/* Output:
A
Aw, Kam Foo
B
Bankov, Peter
Beebe, Ann
E
El Yassir, Mehdi
G
Garcia, Hugo
Guy, Wey Yuan
Garcia, Debra
Gilchrist, Beth
Giakoumakis, Leo
H
Holm, Michael
L
Liu, Jinghao
M
Myrcha, Jacek
McLin, Nkenge
N
Noriega, Fabricio
P
Potra, Cristina
T
Toyoshima, Tim
*/
Программа записывает отдельный файл для каждой группы в ту же папку, где находятся файлы данных.
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и строки (C#)
LINQ и каталоги файлов (C#)
Практическое руководство. Объединение
содержимого из файлов разных форматов (LINQ)
(C#)
23.10.2019 • 3 minutes to read • Edit Online
В этом примере показано, как объединить данные из двух файлов с разделителями-запятыми, которые
имеют общее значение, используемое в качестве совпадающего ключа. Этот способ может оказаться
полезным, если необходимо объединить данные из двух электронных таблиц или из электронной таблицы и
файла, имеющего другой формат, в новый файл. Можно изменить пример для обработки любого типа
структурированного текста.
2. Скопируйте следующие строки в файл с именем names.csv и сохраните его в папке проекта. Этот
файл представляет электронную таблицу, содержащую фамилию, имя и идентификатор учащегося.
Omelchenko,Svetlana,111
O'Donnell,Claire,112
Mortensen,Sven,113
Garcia,Cesar,114
Garcia,Debra,115
Fakhouri,Fadi,116
Feng,Hanying,117
Garcia,Hugo,118
Tucker,Lance,119
Adams,Terry,120
Zabokritski,Eugene,121
Tucker,Michael,122
Пример
using System;
using System.Collections.Generic;
using System.Linq;
class JoinStrings
{
static void Main()
{
// Join content from dissimilar files that contain
// related information. File names.csv contains the student
// name plus an ID number. File scores.csv contains the ID
// and a set of four test scores. The following query joins
// the scores to the student names by using ID as a
// matching key.
В этом примере демонстрируется выполнение статистических вычислений, таких как сумма, среднее,
минимальное и максимальное для столбцов в CSV -файле. Приведенные в примере принципы могут
применяться к другим типам структурированного текста.
Пример
class SumColumns
{
static void Main(string[] args)
{
string[] lines = System.IO.File.ReadAllLines(@"../../../scores.csv");
// Spreadsheet format:
// Student ID Exam#1 Exam#2 Exam#3 Exam#4
// 111, 97, 92, 81, 60
Запрос работает с использованием метода Split для преобразования каждой строки текста в массив. Каждый
элемент массива представляет столбец. И наконец, текст в каждом столбце преобразуется в свое числовое
представление. Если файл является файлом с разделением знаками табуляции, просто измените аргумент в
методе Split на \t .
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и строки (C#)
LINQ и каталоги файлов (C#)
2 minutes to read
Практическое руководство. запрос к метаданным
сборки при помощи отражения (LINQ) (C#)
23.10.2019 • 2 minutes to read • Edit Online
API отражения библиотеки классов .NET Framework можно использовать для просмотра метаданных в
сборке .NET и создания коллекций типов, членов типов, параметров и т. д., присутствующих в этой сборке.
Поскольку эти коллекции поддерживают универсальный интерфейс IEnumerable<T>, их можно
запрашивать с помощью LINQ.
В следующем примере показано использование LINQ с отражением для извлечения определенных
метаданных о методах, соответствующих условиям поиска. В этом примере запрос будет искать имена всех
методов в сборке, которые возвращают перечислимые типы, такие как массивы.
Пример
using System;
using System.Linq;
using System.Reflection;
class ReflectionHowTO
{
static void Main()
{
Assembly assembly = Assembly.Load("System.Core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=
b77a5c561934e089");
var pubTypesQuery = from type in assembly.GetTypes()
where type.IsPublic
from method in type.GetMethods()
where method.ReturnType.IsArray == true
|| ( method.ReturnType.GetInterface(
typeof(System.Collections.Generic.IEnumerable<>).FullName ) != null
&& method.ReturnType.FullName != "System.String" )
group method.ToString() by type.ToString();
В примере используется метод Assembly.GetTypes для возвращения массива типов в указанной сборке.
Чтобы вернуть только открытые типы, применяется фильтр where. Для каждого открытого типа создается
вложенный запрос с использованием массива MethodInfo, который возвращается из вызова
Type.GetMethods. Эти результаты фильтруются для возвращения только тех методов, возвращаемый тип
которых является массивом или типом, который реализует IEnumerable<T>. Наконец, эти результаты
группируются с помощью имени типа в качестве ключа.
См. также
LINQ to Objects (C#)
LINQ и каталоги файлов (C#)
25.11.2019 • 4 minutes to read • Edit Online
Комментарии
Существуют некоторые сложности, связанные с созданием источника данных, который точно
представляет содержимое файловой системы и корректно обрабатывает исключения. В примерах этого
раздела создается моментальный снимок коллекции объектов FileInfo, представляющих все файлы в
указанной корневой папке и всех ее подпапках. Фактическое состояние каждого объекта FileInfo может
измениться в период между началом и окончанием выполнения запроса. Например, можно создать
список объектов FileInfo для использования в качестве источника данных. При попытке доступа к свойству
Length в запросе объект FileInfo попытается получить доступ к файловой системе для обновления
значения Length . Если файл больше не существует, вы получите исключение FileNotFoundException в
запросе, даже если не запрашиваете файловую систему напрямую. Некоторые запросы в этом разделе
используют отдельный метод, который использует эти конкретные исключения в определенных случаях.
Другой возможностью является поддержка источника данных, обновляемого динамически с помощью
FileSystemWatcher.
См. также
LINQ to Objects (C#)
Практическое руководство. Запрос файлов с
указанным атрибутом или именем (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом примере показано, как обнаружить все файлы с указанным расширением (например, TXT) в заданном
дереве каталогов. Кроме того, он показывает, как обнаружить самый новый или самый старый файл в дереве,
используя время создания.
Пример
class FindFileByExtension
{
// This query will produce the full path for all .txt files
// under the specified folder including subfolders.
// It orders the list according to the file name.
static void Main()
{
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";
См. также
LINQ to Objects (C#)
LINQ и каталоги файлов (C#)
Практическое руководство. Группировка файлов
по расширению (LINQ) (C#)
23.10.2019 • 3 minutes to read • Edit Online
В этом примере показано, как можно использовать LINQ для выполнения расширенного группирования и
сортировки списков файлов или папок. Кроме того, здесь показывается, как разбить на страницы выходные
данные в окне консоли с помощью методов Skip и Take.
Пример
Следующий запрос демонстрирует группирование содержимого указанного дерева каталогов по
расширению файла.
class GroupByExtension
{
// This query will sort all the files under the specified folder
// and subfolder into groups keyed by the file extension.
private static void Main()
{
// Take a snapshot of the file system.
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\Common7";
// This method specifically handles group queries of FileInfo objects with string keys.
// It can be modified to work for any long listings of data. Note that explicit typing
// must be used in method signatures. The groupbyExtList parameter is a query that produces
// groups of FileInfo objects with string keys.
private static void PageOutput(int rootLength,
IEnumerable<System.Linq.IGrouping<string, System.IO.FileInfo>>
groupByExtList)
{
// Flag to break out of paging loop.
bool goAgain = true;
// "3" = 1 line for extension + 1 for "Press any key" + 1 for input cursor.
int numLines = Console.WindowHeight - 3;
// Iterate through the outer collection of groups.
foreach (var filegroup in groupByExtList)
{
// Start a new extension at the top of a page.
int currentLine = 0;
// Output only as many lines of the current group as will fit in the window.
do
{
Console.Clear();
Console.WriteLine(filegroup.Key == String.Empty ? "[none]" : filegroup.Key);
if (goAgain == false)
break;
}
}
}
Вывод этой программы может быть длинным в зависимости от объема данных локальной файловой системы
и значения startFolder . В этом примере демонстрируется постраничный просмотр, который позволяет
просматривать все результаты. Те же методы могут применяться для приложений Windows и веб-
приложений. Обратите внимание, что поскольку код разбивает элементы в группе на страницы, требуется
вложенный цикл foreach . Также существует некоторая дополнительная логика для вычисления текущей
позиции в списке и предоставления пользователю возможности остановить разбиение по страницам и
выйти из программы. В данном конкретном случае запрос разбиения на страницы выполняется для
кэшированных результатов исходного запроса. В других контекстах, например LINQ to SQL, подобное
кэширование не требуется.
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ to Objects (C#)
LINQ и каталоги файлов (C#)
Практическое руководство. Запрос общего числа
байтов в наборе папок (LINQ) (C#)
23.10.2019 • 3 minutes to read • Edit Online
В этом примере показано, как получить общее число байтов, используемое всеми файлами в указанной
папке и всех ее вложенных папках.
Пример
Метод Sum суммирует значения всех элементов, выбранных в предложении select . Этот запрос можно
легко изменить, чтобы получить самый большой или маленький файл в заданном дереве каталогов, вызвав
метод Min или Max вместо Sum.
class QuerySize
{
public static void Main()
{
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\VC#";
// Return the total number of bytes in all the files under the specified folder.
long totalBytes = fileLengths.Sum();
Если требуется только подсчитать число байтов в указанном дереве каталогов, можно сделать это более
эффективно без создания запроса LINQ, который вносит дополнительные издержки, связанные с созданием
коллекции списков в качестве источника данных. Практичность подхода LINQ увеличивается по мере
усложнения запроса или при необходимости выполнения нескольких запросов к одному источнику данных.
Для получения длины файла запрос вызывает отдельный метод. Это необходимо для обработки возможных
исключений, которые могут возникнуть из-за удаления файла в другом потоке после создания объекта
FileInfo вызовом GetFiles . Даже если объект FileInfo уже создан, может возникнуть исключение, так как
объект FileInfo будет пытаться обновить свойство Length, используя самую последнюю длину при первом
обращении к свойству. При помещении этой операции в блок try-catch вне запроса будет выполнено
правило исключения использования в запросах операций, которые могут вызвать побочные эффекты. В
целом необходимо соблюдать осторожность при перехвате исключений, чтобы убедиться, что приложение
не остается в неизвестном состоянии.
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ to Objects (C#)
LINQ и каталоги файлов (C#)
Практическое руководство. Сравнение
содержимого двух папок (LINQ) (C#)
25.11.2019 • 3 minutes to read • Edit Online
NOTE
Методы, представленные в этом примере, можно адаптировать для сравнения последовательностей объектов
любого типа.
Пример
namespace QueryCompareTwoDirs
{
class CompareDirs
{
if (areIdentical == true)
{
Console.WriteLine("the two folders are the same");
}
else
{
Console.WriteLine("The two folders are not the same");
}
if (queryCommonFiles.Count() > 0)
{
Console.WriteLine("The following files are in both folders:");
foreach (var v in queryCommonFiles)
{
Console.WriteLine(v.FullName); //shows which items end up in result list
}
}
else
{
Console.WriteLine("There are no common files in the two folders.");
}
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ to Objects (C#)
LINQ и каталоги файлов (C#)
Практическое руководство. Запрос самого
большого файла или файлов в дереве папок
(LINQ) (C#)
23.10.2019 • 5 minutes to read • Edit Online
Пример
Следующий пример содержит пять отдельных запросов, которые показывают, как запрашивать и
группировать файлы на основании их размера в байтах. Эти примеры можно легко изменить, чтобы создать
запрос на основании других свойств объекта FileInfo.
class QueryBySize
{
static void Main(string[] args)
{
QueryFilesBySize();
Console.WriteLine("Press any key to exit");
Console.ReadKey();
}
Console.WriteLine("The largest file under {0} is {1} with a length of {2} bytes",
startFolder, longestFile.FullName, longestFile.Length);
Console.WriteLine("The smallest file under {0} is {1} with a length of {2} bytes",
startFolder, smallestFile.FullName, smallestFile.Length);
Чтобы вернуть один или несколько полных объектов FileInfo, запрос должен сначала проверить каждый из
них в источнике данных, а затем отсортировать их по значению свойства Length. Затем он может вернуть
один объект или последовательность объектов с максимальной длиной. First возвращает первый элемент в
списке. Take возвращает первые n элементов. Укажите порядок сортировки по убыванию для размещения
наименьших элементов в начале списка.
Запрос вызывает отдельный метод, чтобы получить размер файла в байтах для обработки возможных
исключений, которые будут вызваны при удалении файла в другом потоке за период времени с момента
создания объекта FileInfo в вызове GetFiles . Даже если объект FileInfo уже создан, может возникнуть
исключение, так как объект FileInfo будет пытаться обновить свойство Length, используя самый последний
размер в байтах при первом обращении к свойству. Поместив эту операцию в блок try-catch вне запроса,
будет выполнено правило исключения использования операций в запросах, которые могут вызвать
побочные эффекты. В целом, необходимо соблюдать осторожность при перехвате исключений, чтобы
убедиться, что приложение не остается в неизвестном состоянии.
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ to Objects (C#)
LINQ и каталоги файлов (C#)
Практическое руководство. Запрос
повторяющихся файлов в дереве папок (LINQ)
(C#)
23.10.2019 • 4 minutes to read • Edit Online
Иногда файлы с одинаковыми именами могут находиться в нескольких папках. Например, в папке установки
Visual Studio несколько папок содержат файл readme.htm. В этом примере показано, как запросить такие
повторяющиеся имена файлов в указанной корневой папке. Во втором примере показано, как запросить
файлы, размер и время последней записи которых также совпадают.
Пример
class QueryDuplicateFileNames
{
static void Main(string[] args)
{
// Uncomment QueryDuplicates2 to run that query.
QueryDuplicates();
// QueryDuplicates2();
int i = queryDupFiles.Count();
PageOutput<PortableKey, string>(queryDupFiles);
}
// "3" = 1 line for extension + 1 for "Press any key" + 1 for input cursor.
int numLines = Console.WindowHeight - 3;
// Output only as many lines of the current group as will fit in the window.
do
{
Console.Clear();
Console.WriteLine("Filename = {0}", filegroup.Key.ToString() == String.Empty ? "[none]" :
filegroup.Key.ToString());
if (goAgain == false)
break;
}
}
}
В первом запросе используется простой ключ для определения соответствия; при этом выполняется поиск
файлов, которые имеют то же имя, однако их содержимое может быть другим. Второй запрос использует
составной ключ для сравнения трех свойств объекта FileInfo. Этот запрос дает гораздо больше шансов найти
файлы, имеющие одинаковые имена и схожее или идентичное содержимое.
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ to Objects (C#)
LINQ и каталоги файлов (C#)
Практическое руководство. Запрос содержимого
текстовых файлов в папке (LINQ) (C#)
25.11.2019 • 2 minutes to read • Edit Online
В этом примере показано, как запросить все файлы в указанном дереве каталогов, открыть каждый файл и
проверить его содержимое. Этот способ позволяет создать индексы для содержимого дерева каталогов или
обратить их порядок. В этом примере выполняется простой поиск строки. Более сложные типы
сопоставления шаблонов можно выполнять с помощью регулярных выражений. Дополнительные сведения
см. в разделе Практическое руководство. Объединение запросов LINQ с помощью регулярных выражений
(C#).
Пример
class QueryContents
{
public static void Main()
{
// Modify this path as necessary.
string startFolder = @"c:\program files\Microsoft Visual Studio 9.0\";
Компиляция кода
Создайте проект консольного приложения C# с директивами using для пространств имен System.Linq и
System.IO.
См. также
LINQ и каталоги файлов (C#)
LINQ to Objects (C#)
Практическое руководство. Выполнение запроса
к ArrayList с помощью LINQ (C#)
25.11.2019 • 2 minutes to read • Edit Online
При использовании LINQ для запросов к неуниверсальным коллекциям IEnumerable, таким как ArrayList,
необходимо явно объявить тип переменной диапазона, чтобы отразить конкретный тип объектов в
коллекции. Например, если у вас есть список ArrayList объектов Student , предложение from должно иметь
следующий вид:
Пример
В следующем примере показан простой запрос к объекту ArrayList. Обратите внимание на то, что в этом
примере инициализаторы объектов используются, когда код вызывает метод Add, но это не обязательно.
using System;
using System.Collections;
using System.Linq;
namespace NonGenericLINQ
{
public class Student
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int[] Scores { get; set; }
}
class Program
{
static void Main(string[] args)
{
ArrayList arrList = new ArrayList();
arrList.Add(
new Student
{
FirstName = "Svetlana", LastName = "Omelchenko", Scores = new int[] { 98, 92, 81, 60
}
});
arrList.Add(
new Student
{
FirstName = "Claire", LastName = "O’Donnell", Scores = new int[] { 75, 84, 91, 39 }
});
arrList.Add(
new Student
{
FirstName = "Sven", LastName = "Mortensen", Scores = new int[] { 88, 94, 65, 91 }
});
arrList.Add(
new Student
{
FirstName = "Cesar", LastName = "Garcia", Scores = new int[] { 97, 89, 85, 82 }
});
См. также
LINQ to Objects (C#)
Практическое руководство. Добавление
настраиваемых методов для запросов LINQ (C#)
25.11.2019 • 7 minutes to read • Edit Online
Вы можете расширить набор методов, которые можно использовать для запросов LINQ, путем добавления
методов расширения в интерфейс IEnumerable<T>. Например, помимо стандартных операций вычисления
среднего или максимального значения, можно создать настраиваемый метод агрегирования для вычисления
одного значения на основе последовательности значений. Также можно создать метод, который работает
как настраиваемый фильтр или особое преобразование данных для последовательности значений и
возвращает новую последовательность. Примерами таких методов являются Distinct, Skip и Reverse.
При расширении интерфейса IEnumerable<T> настраиваемые методы можно применять к любой
перечислимой коллекции. Дополнительные сведения см. в разделе Методы расширения.
if (sortedList.Count() % 2 == 0)
{
// Even number of items.
return (sortedList.ElementAt(itemIndex) + sortedList.ElementAt(itemIndex - 1)) / 2;
}
else
{
// Odd number of items.
return sortedList.ElementAt(itemIndex);
}
}
}
Этот метод расширения вызывается для любых перечислимых коллекций так же, как другие методы
агрегирования из интерфейса IEnumerable<T>.
В следующем примере кода показано использование метода Median для массива типа double .
double[] numbers1 = { 1.9, 2, 8, 4, 5.7, 6, 7.2, 0 };
/*
This code produces the following output:
//int overload
Теперь можно вызвать перегрузки Median для типов integer и double , как показано в следующем примере
кода:
int[] numbers2 = { 1, 2, 3, 4, 5 };
/*
This code produces the following output:
// Generic overload.
Теперь вы можете вызвать метод Median для последовательности объектов любого типа. Если тип не
содержит собственную перегрузку метода, вам потребуется передать параметр делегата. В C# для этой цели
можно использовать лямбда-выражение. Кроме того, только в Visual Basic, если вы используете
предложение Aggregate или Group By вместо вызова метода, вы можете передать любое значение или
выражение, которое находится в области этого предложения.
В следующем примере кода показано, как вызвать метод Median для массива целых чисел и массива строк.
Для строк вычисляется срединное значение длин строк в массиве. В примере демонстрируется передача
параметра делегата Func<T,TResult> в метод Median для каждого из случаев.
int[] numbers3 = { 1, 2, 3, 4, 5 };
/*
You can use the num=>num lambda expression as a parameter for the Median method
so that the compiler will implicitly convert its value to double.
If there is no implicit conversion, the compiler will display an error message.
*/
// With the generic overload, you can also use numeric properties of objects.
/*
This code produces the following output:
Integer: Median = 3
String: Median = 4
*/
int i = 0;
i++;
}
return list;
}
Этот метод расширения вызывается для любых перечислимых коллекций так же, как другие методы из
интерфейса IEnumerable<T>, как показано в следующем примере кода:
a
c
e
*/
См. также
IEnumerable<T>
Методы расширения
Общие сведения о LINQ to XML (C#)
23.10.2019 • 7 minutes to read • Edit Online
LINQ to XML обеспечивает интерфейс программирования для работы с XML в памяти на основе
платформы .NET LINQ Framework. Интерфейс LINQ to XML использует возможности .NET и может быть
сравним с обновленным, переработанным программным интерфейсом XML модели DOM.
XML широко используется для форматирования данных в целом ряде контекстов. Примеры XML можно
обнаружить в веб-среде, в файлах конфигурации, в файлах Microsoft Office Word и в базах данных.
В LINQ to XML реализован современный пересмотренный подход к программированию средствами
XML. Кроме того, реализованы встроенные в память возможности модификации документов модели
DOM, поддерживаются выражения запросов LINQ. Хотя в синтаксическом отношении эти выражения
запросов отличаются от XPath, они предоставляют аналогичные функциональные возможности.
// Load the XML file from our project directory containing the purchase orders
var filename = "PurchaseOrder.xml";
var currentDirectory = Directory.GetCurrentDirectory();
var purchaseOrderFilepath = Path.Combine(currentDirectory, filename);
Еще один пример. Пусть требуется сформировать отсортированный в соответствии с номерами деталей
список продуктов стоимостью выше 100 долл. Для получения этих сведений можно выполнить
следующий запрос:
// Load the XML file from our project directory containing the purchase orders
var filename = "PurchaseOrder.xml";
var currentDirectory = Directory.GetCurrentDirectory();
var purchaseOrderFilepath = Path.Combine(currentDirectory, filename);
XElement contacts =
new XElement("Contacts",
new XElement("Contact",
new XElement("Name", "Patrick Hines"),
new XElement("Phone", "206-555-0144",
new XAttribute("Type", "Home")),
new XElement("phone", "425-555-0145",
new XAttribute("Type", "Work")),
new XElement("Address",
new XElement("Street1", "123 Main St"),
new XElement("City", "Mercer Island"),
new XElement("State", "WA"),
new XElement("Postal", "68042")
)
)
);
См. также
Ссылка (LINQ to XML )
Сравнение LINQ to XML с моделью DOM (C#)
Сравнение LINQ to XML с другими XML -технологиями
System.Xml.Linq
LINQ to XML или модель DOM (C#)
23.10.2019 • 9 minutes to read • Edit Online
В этом разделе описываются некоторые основные различия между LINQ to XML и текущим
преобладающим программным интерфейсом API XML, а именно моделью DOM консорциума W3C.
Такой стиль программирования не дает визуального представления о деталях структуры XML -дерева. LINQ
to XML поддерживает такой подход к построению XML -дерева, но также предлагает и альтернативный
способ, функциональное построение. При функциональном построении для создания XML -дерева
используются конструкторы XElement и XAttribute.
Вот как можно построить такое же XML -дерево с помощью функционального построения LINQ to XML.
XElement contacts =
new XElement("Contacts",
new XElement("Contact",
new XElement("Name", "Patrick Hines"),
new XElement("Phone", "206-555-0144",
new XAttribute("Type", "Home")),
new XElement("phone", "425-555-0145",
new XAttribute("Type", "Work")),
new XElement("Address",
new XElement("Street1", "123 Main St"),
new XElement("City", "Mercer Island"),
new XElement("State", "WA"),
new XElement("Postal", "68042")
)
)
);
Обратите внимание, что отступы в коде, который строит XML -дерево, показывают структуру базового
документа XML.
Дополнительные сведения см. в разделе Создание деревьев XML (C#).
Если элемент требуется использовать в нескольких документах, нужно будет импортировать в эти документы
узлы. LINQ to XML позволяет избавиться от этих сложностей.
При использовании LINQ to XML класс XDocument применяется только, если требуется добавить
комментарий или инструкцию по обработке на корневом уровне документа.
Поддержка фрагментов
LINQ to XML не предоставляет эквивалент класса XmlDocumentFragment . Однако во многих случаях понятие
XmlDocumentFragment можно обрабатывать с помощью результата запроса, который типизируется как объект
IEnumerable<T> объекта XNode или объект IEnumerable<T> объекта XElement.
Поддержка XPathNavigator
LINQ to XML обеспечивает поддержку XPathNavigator через методы расширения в пространстве имен
System.Xml.XPath. Дополнительные сведения можно найти по адресу: System.Xml.XPath.Extensions.
Поддержка заметок
Элементы LINQ to XML поддерживают расширяемый набор заметок. Это полезно для отслеживания
различной информации об элементе, например сведений о схеме, сведений о привязке элемента к
пользовательскому интерфейсу, а также любых других сведений, относящихся к конкретному приложению.
Дополнительные сведения см. в разделе Заметки LINQ to XML.
Поддержка сведений схемы
LINQ to XML обеспечивает поддержку проверки правильности по схеме XSD через методы расширения в
пространстве имен System.Xml.Schema. Можно проверить XML -дерево на соответствие схеме XSD. XML -
дерево можно заполнить при помощи модуля проверки после обработки схемы (PSVI). Дополнительные
сведения см. в разделе Практическое руководство. Проверка с использованием XSD и Extensions.
См. также
Начало работы (LINQ to XML )
LINQ to XML или Другие XML-технологии
23.10.2019 • 7 minutes to read • Edit Online
В этом разделе LINQ to XML сравнивается со следующими XML -технологиями: XmlReader, XSLT, MSXML и
XmlLite. Данные сведения могут помочь в выборе технологии.
Сравнение LINQ to XML с моделью DOM см. в разделе Сравнение LINQ to XML или DOM (C#).
См. также
Начало работы (LINQ to XML )
Сравнение функционального Процедурное
программирование (LINQ to XML) (C#)
23.10.2019 • 2 minutes to read • Edit Online
См. также
Общие сведения о программировании LINQ to XML (C#)
Общие сведения о классах LINQ to XML (C#)
23.10.2019 • 5 minutes to read • Edit Online
В этом разделе приведен список классов LINQ to XML в пространстве имен System.Xml.Linq с кратким
описанием каждого из них.
См. также
Общие сведения о программировании LINQ to XML (C#)
Общие сведения о классе XElement (C#)
23.10.2019 • 3 minutes to read • Edit Online
Класс XElement - это один из фундаментальных классов в LINQ to XML. Он обозначает элемент XML. Этот
класс можно использовать для создания элементов, изменения содержимого элемента, добавления,
изменения или удаления дочерних элементов, добавления к элементам атрибутов или сериализации
содержимого элемента в текстовой форме. Можно также настроить взаимодействие с другими классами в
System.Xml, например XmlReader, XmlWriter и XslCompiledTransform.
В этом разделе рассматриваются функциональные особенности класса XElement.
Создание XML-деревьев
Можно создавать XML -деревья несколькими способами.
Можно создать XML -дерево при помощи кода. Дополнительные сведения см. в разделе Создание
деревьев XML (C#).
Можно выполнить синтаксический анализ XML из нескольких источников, в том числе из TextReader,
текстовых файлов или веб-адреса (URL -адреса). Дополнительные сведения см. в разделе Анализ XML
(C#).
Для распределения контента по дереву можно использовать XmlReader. Дополнительные сведения
можно найти по адресу: ReadFrom.
Если установлен модуль, позволяющий заносить содержимое в средство XmlWriter, то можно
использовать метод CreateWriter, чтобы создать модуль записи, передать его этому модулю, после
чего использовать контент, записанный в систему XmlWriter, чтобы заполнить XML -дерево.
Однако наиболее распространенным является такой метод создания XML -дерева:
XElement contacts =
new XElement("Contacts",
new XElement("Contact",
new XElement("Name", "Patrick Hines"),
new XElement("Phone", "206-555-0144"),
new XElement("Address",
new XElement("Street1", "123 Main St"),
new XElement("City", "Mercer Island"),
new XElement("State", "WA"),
new XElement("Postal", "68042")
)
)
);
Другим распространенным способом создания дерева XML является использование результатов запроса
LINQ для заполнения дерева XML, как показано в следующем примере:
XElement srcTree = new XElement("Root",
new XElement("Element", 1),
new XElement("Element", 2),
new XElement("Element", 3),
new XElement("Element", 4),
new XElement("Element", 5)
);
XElement xmlTree = new XElement("Root",
new XElement("Child", 1),
new XElement("Child", 2),
from el in srcTree.Elements()
where (int)el > 2
select el
);
Console.WriteLine(xmlTree);
<Root>
<Child>1</Child>
<Child>2</Child>
<Element>3</Element>
<Element>4</Element>
<Element>5</Element>
</Root>
См. также
Общие сведения о программировании LINQ to XML (C#)
Общие сведения о классе XAttribute (C#)
23.10.2019 • 2 minutes to read • Edit Online
Атрибуты - это пары «имя-значение», ассоциированные с элементом. Класс XAttribute представляет XML -
атрибуты.
Обзор
Работа с атрибутами LINQ to XML аналогична работе с элементами. Они имеют аналогичные конструкторы.
Аналогичны и методы, используемые для получения их коллекций. По своему виду выражение запроса LINQ
для коллекции атрибутов весьма напоминает выражение запроса LINQ для коллекции элементов.
Порядок, в котором атрибуты добавлялись к элементу, сохраняется. Иначе говоря, при просмотре атрибутов
они отображаются в том же порядке, в каком были добавлены.
Конструктор XAttribute
Чаще всего используется следующий конструктор класса XAttribute.
КОНСТРУКТОР ОПИСАНИЕ
XAttribute(XName name, object content) Создает объект XAttribute. Аргумент name указывает имя
атрибута; content указывает содержимое атрибута.
<Phone Type="Home">555-555-5555</Phone>
<Customers>
<Customer>
<Name>John Doe</Name>
<PhoneNumbers>
<Phone type="home">555-555-5555</Phone>
<Phone type="work">666-666-6666</Phone>
</PhoneNumbers>
</Customer>
</Customers>
См. также
Общие сведения о программировании LINQ to XML (C#)
Общие сведения о классе XDocument (C#)
23.10.2019 • 3 minutes to read • Edit Online
Компоненты XDocument
Документ XDocument может включать в себя следующие элементы:
Один объект XDeclaration. XDeclaration позволяет указать соответствующие части XML -объявления:
версию XML, кодировку документа, а также то, является ли этот XML -документ изолированным.
Один объект XElement. Это корневой узел XML -документа.
Любое количество объектов XProcessingInstruction. Инструкция по обработке передает сведения в
приложение, обрабатывающее XML -код.
Любое количество объектов XComment. Комментарии и корневой элемент находятся на одном
уровне. Объект XComment не может быть первым аргументом в списке, так как XML -документ не
может начинаться с комментария.
Один тип документа XDocumentType для DTD.
При сериализации документа XDocument, даже если значением декларации XDocument.Declaration является
null , выходные данные будут иметь XML -декларацию, если для свойства Writer.Settings.OmitXmlDeclaration
автор указал значение false (применяется по умолчанию).
По умолчанию LINQ to XML указывает для версии значение «1.0», а для кодировки значение «utf-8».
Использование XDocument
При создании объектов XDocument используется функциональное построение, так же как и при создании
объектов XElement.
Следующий фрагмент кода создает объект XDocument и ассоциированные с ним вложенные объекты.
d.Save("test.xml");
См. также
Общие сведения о программировании LINQ to XML (C#)
Практическое руководство. Сборка примеров LINQ
to XML (C#)
25.11.2019 • 2 minutes to read • Edit Online
В разных фрагментах кода и примерах, приведенных в этой документации, используются классы и типы из
различных пространств имен. При компиляции кода C# необходимо указывать соответствующие директивы
using .
Пример
Следующий код содержит директивы using , которые требуются для построения и запуска примеров на C#.
Не все директивы using требуются для каждого примера.
using System;
using System.Diagnostics;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Text;
using System.Linq;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Xml.Xsl;
using System.IO;
using System.Threading;
using System.Reflection;
using System.IO.Packaging;
См. также
Общие сведения о программировании LINQ to XML (C#)
Функциональное построение (LINQ to XML) (C#)
23.10.2019 • 2 minutes to read • Edit Online
LINQ to XML предоставляет эффективный способ создания XML -элементов, который называется
функциональным построением. Функциональное построение — это возможность создать XML -дерево
одной инструкцией.
Существует несколько основных функций интерфейса программирования LINQ to XML, обеспечивающих
функциональное построение.
Конструктор XElement принимает различные типы аргументов для содержимого. Например, можно
передать еще один объект XElement, который станет дочерним элементом. Можно передать объект
XAttribute, который станет атрибутом элемента. Можно также передать любой другой тип объекта,
который будет преобразован в строку и станет текстовым содержимым элемента.
Конструктор XElement принимает массив params типа Object, так что этому конструктору можно
передать любое количество объектов. Это позволяет создавать элементы со сложным содержимым.
Если объект реализует интерфейс IEnumerable<T>, коллекция в этом объекте перечисляется и
добавляются все элементы коллекции. Если коллекция содержит объекты XElement или XAttribute,
каждый ее элемент добавляется отдельно. Это важно, так как позволяет передавать результаты
запроса LINQ конструктору.
Эти возможности позволяют использовать код для создания XML -деревьев. Ниже представлен пример
такого кода.
XElement contacts =
new XElement("Contacts",
new XElement("Contact",
new XElement("Name", "Patrick Hines"),
new XElement("Phone", "206-555-0144"),
new XElement("Address",
new XElement("Street1", "123 Main St"),
new XElement("City", "Mercer Island"),
new XElement("State", "WA"),
new XElement("Postal", "68042")
)
)
);
Эти возможности позволяют также написать код, в котором используются результаты запросов LINQ при
создании дерева XML, как показано ниже.
XElement srcTree = new XElement("Root",
new XElement("Element", 1),
new XElement("Element", 2),
new XElement("Element", 3),
new XElement("Element", 4),
new XElement("Element", 5)
);
XElement xmlTree = new XElement("Root",
new XElement("Child", 1),
new XElement("Child", 2),
from el in srcTree.Elements()
where (int)el > 2
select el
);
Console.WriteLine(xmlTree);
<Root>
<Child>1</Child>
<Child>2</Child>
<Element>3</Element>
<Element>4</Element>
<Element>5</Element>
</Root>
Создание деревьев XML на языке C# (LINQ to
XML)
23.10.2019 • 7 minutes to read • Edit Online
В этом разделе размещены сведения о создании XML -деревьев при помощи языка C#.
Дополнительные сведения об использовании результатов запросов LINQ как содержимого для XElement см.
в разделе Функциональное построение (LINQ to XML ) (C#).
Построение элементов
Сигнатуры конструкторов XElement и XAttribute позволяют передать конструктору содержимое элемента
или атрибута в качестве аргументов. Поскольку один из конструкторов принимает переменное количество
аргументов, можно пропустить любое количество дочерних элементов. Естественно, каждый из этих
дочерних элементов может содержать собственные дочерние элементы. Для любого элемента можно
добавить любое количество атрибутов.
При добавлении объектов XNode (в т. ч. XElement) или XAttribute, если новое содержимое не обладает
родительской структурой, объекты просто прикрепляются к XML -дереву. Если у нового содержимого уже
есть родитель и оно является частью другого XML -дерева, то новое содержимое клонируется и
присоединяется к XML -дереву. Это демонстрирует последний пример из данного раздела.
Чтобы создать contacts XElement, можно использовать следующий код:
XElement contacts =
new XElement("Contacts",
new XElement("Contact",
new XElement("Name", "Patrick Hines"),
new XElement("Phone", "206-555-0144"),
new XElement("Address",
new XElement("Street1", "123 Main St"),
new XElement("City", "Mercer Island"),
new XElement("State", "WA"),
new XElement("Postal", "68042")
)
)
);
Если цель определена верно, то код для создания объектов XElement становится похож на структуру
соответствующего XML.
Конструкторы XElement
В классе XElement используются следующие конструкторы для функционального построения. Обратите
внимание, что существуют другие конструкторы для XElement, однако, поскольку они не используются для
функциональных построений, они здесь не приводятся.
КОНСТРУКТОР ОПИСАНИЕ
XElement(XName name, object content) Создает XElement. Параметр name задает имя элемента;
content задает содержание элемента.
КОНСТРУКТОР ОПИСАНИЕ
XElement(XName name, params object[] content) Создает XElement с инициализацией XName в соответствии
с указанным именем. Атрибуты и/или дочерние элементы
создаются из содержимого списка параметров.
Параметр content чрезвычайно гибок. Он поддерживает любой тип объекта, который является
действительным дочерним объектом XElement. К различным типам объектов, передающимся в этом
параметре, применимы следующие правила.
Строка добавляется как текстовое содержимое.
XElement добавляется к дочернему элементу.
XAttribute добавляется как атрибут.
XProcessingInstruction, XComment или XText добавляется в качестве дочернего содержимого.
IEnumerable перечисляется, и эти правила рекурсивно применяются к результатам.
Для любого другого типа вызывается метод ToString , при этом результат добавляется как текстовое
содержимое.
Создание элемента XElement с содержимым
Можно создать XElement с простым содержимым при помощи одного вызова метода. Чтобы это сделать,
укажите содержимое в качестве второго параметра следующим образом:
<Customer>Adventure Works</Customer>
Содержимому можно передать любой тип объекта. Например, в следующем коде выполняется создание
элемента, в котором в качестве содержимого содержится число с плавающей запятой:
<Cost>324.5</Cost>
Число с плавающей запятой помещается в контейнер и передается конструктору. Это число в контейнере
преобразуется в строку и используется в качестве содержимого элемента.
Создание элемента XElement с дочерним элементом
Если передать экземпляр класса XElement как аргумент содержимого, конструктор создаст элемент с
дочерним элементом:
XElement shippingUnit = new XElement("ShippingUnit",
new XElement("Cost", 324.50)
);
Console.WriteLine(shippingUnit);
<ShippingUnit>
<Cost>324.5</Cost>
</ShippingUnit>
<Address>
<Street1>123 Main St</Street1>
<City>Mercer Island</City>
<State>WA</State>
<Postal>68042</Postal>
</Address>
Расширив предыдущим пример, можно создать все XML -дерево следующим образом:
XElement contacts =
new XElement("Contacts",
new XElement("Contact",
new XElement("Name", "Patrick Hines"),
new XElement("Phone", "206-555-0144"),
new XElement("Address",
new XElement("Street1", "123 Main St"),
new XElement("City", "Mercer Island"),
new XElement("State", "WA"),
new XElement("Postal", "68042")
)
)
);
Console.WriteLine(contacts);
<Phone Type="Home">555-555-5555</Phone>
<Customer />
Присоединение и клонирование
Как упоминалось ранее, при добавлении объектов XNode (в т. ч. XElement) или XAttribute, если новое
содержимое не обладает родительской структурой, объекты просто прикрепляются к XML -дереву. Если у
нового содержимого уже есть родитель и оно является частью другого XML -дерева, то новое содержимое
клонируется и присоединяется к XML -дереву.
В следующем примере кода показано поведение при добавлении к дереву элемента с родителем и при
добавлении к дереву элемента без родителей.
// Create a tree with a child element.
XElement xmlTree1 = new XElement("Root",
new XElement("Child1", 1)
);
См. также
Создание деревьев XML (C#)
Практическое руководство. Анализ строки (C#)
25.11.2019 • 2 minutes to read • Edit Online
В этом разделе демонстрируется анализ строки для создания XML -дерева в C#.
Пример
В следующем коде C# показано, как выполнять синтаксический анализ строки XML.
Корневой узел Contacts содержит два узла Contact . Для доступа к определенным данным в
проанализированном XML используйте метод XElement.Elements(), который в этом случае возвращает
дочерние элементы корневого узла Contacts . В следующем примере в окне консоли выводится первый узел
Contact :
См. также
Практическое руководство. Поиск элементов с определенным атрибутом (C#)
Практическое руководство. Загрузка XML-кода из
файла (C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В следующем примере показана загрузка XML -документа из файла. В следующем примере загружается
файл books.xml и происходит вывод XML -дерева на консоль.
В этом примере используется следующий XML -документ: Пример XML -файла. Книги (LINQ to XML ).
<Catalog>
<Book id="bk101">
<Author>Garghentini, Davide</Author>
<Title>XML Developer's Guide</Title>
<Genre>Computer</Genre>
<Price>44.95</Price>
<PublishDate>2000-10-01</PublishDate>
<Description>An in-depth look at creating applications
with XML.</Description>
</Book>
<Book id="bk102">
<Author>Garcia, Debra</Author>
<Title>Midnight Rain</Title>
<Genre>Fantasy</Genre>
<Price>5.95</Price>
<PublishDate>2000-12-16</PublishDate>
<Description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</Description>
</Book>
</Catalog>
Сохранение пробелов при загрузке и анализе XML
23.10.2019 • 3 minutes to read • Edit Online
В этой статье показан способ управления тем, как обрабатываются пробелы в LINQ to XML.
Типичный сценарий состоит в чтении XML с отступами, создании XML -дерева в памяти без текстовых узлов с
пробелами (то есть без сохранения пробелов), выполнении определенных операций над XML и сохранении
XML с отступами. При сериализации XML с форматированием сохраняются только значимые пробелы XML -
дерева. Это поведение LINQ to XML по умолчанию.
Другой типичный сценарий заключается в чтении и изменении XML с уже существующими
преднамеренными отступами. Эти отступы ни в коем случае изменять нельзя. Для этого в LINQ to XML
можно сохранить пробелы при загрузке или синтаксическом анализе XML и отключить форматирование при
сериализации XML.
В этой статье описывается, как методы, заполняющие XML -деревья, обрабатывают пробелы. Сведения об
управлении пробелами при сериализации деревьев XML см. в разделе Сохранение пробелов при
сериализации.
В этом разделе показано, как обнаружить код XML, имеющий неправильный формат или не прошедший
проверку правильности.
Технология LINQ to XML реализуется с помощью объекта XmlReader. Если средствам LINQ to XML
передается код XML, имеющий неправильный формат или не прошедший проверку правильности, то в
базовом классе XmlReader активизируется исключение. Различные методы, выполняющие синтаксический
анализ XML, например XElement.Parse, не перехватывают это исключение; его можно перехватить позднее в
приложении.
Пример
В следующем коде предпринимается попытка выполнить синтаксический анализ кода XML, не прошедшего
проверку правильности.
try {
XElement contacts = XElement.Parse(
@"<Contacts>
<Contact>
<Name>Jim Wilson</Name>
</Contact>
</Contcts>");
Console.WriteLine(contacts);
}
catch (System.Xml.XmlException e)
{
Console.WriteLine(e.Message);
}
The 'Contacts' start tag on line 1 does not match the end tag of 'Contcts'. Line 5, position 13.
В этом разделе рассказывается, как создать XML -дерево непосредственно из XmlReader. Чтобы создать
XElement на основе XmlReader, необходимо указать для модуля XmlReader узел элемента. Модуль XmlReader
пропускает комментарии и инструкции по обработке, но если для XmlReader будет указан текстовый узел, то
выдается ошибка. Чтобы избежать подобных ошибок, всегда задавайте для XmlReader элемент, прежде чем
приступать к созданию XML -дерева на основе XmlReader.
Пример
В этом примере используется следующий XML -документ: Пример XML -файла. Книги (LINQ to XML ).
Следующий код создает объект T:System.Xml.XmlReader , а затем читает узлы, пока не найдет узел первого
элемента. Затем он загружает объект XElement.
XmlReader r = XmlReader.Create("books.xml");
while (r.NodeType != XmlNodeType.Element)
r.Read();
XElement e = XElement.Load(r);
Console.WriteLine(e);
<Catalog>
<Book id="bk101">
<Author>Garghentini, Davide</Author>
<Title>XML Developer's Guide</Title>
<Genre>Computer</Genre>
<Price>44.95</Price>
<PublishDate>2000-10-01</PublishDate>
<Description>An in-depth look at creating applications
with XML.</Description>
</Book>
<Book id="bk102">
<Author>Garcia, Debra</Author>
<Title>Midnight Rain</Title>
<Genre>Fantasy</Genre>
<Price>5.95</Price>
<PublishDate>2000-12-16</PublishDate>
<Description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</Description>
</Book>
</Catalog>
См. также
Анализ XML (C#)
Практическое руководство. Потоковая передача
фрагментов XML из XmlReader (C#)
23.10.2019 • 3 minutes to read • Edit Online
При необходимости обработать большой XML -файл загрузка в память полного XML -дерева, возможно,
будет неосуществима. В этом разделе показано, как обрабатывать фрагменты в потоке с помощью
XmlReader.
Одним из самых эффективных способов использования XmlReader для чтения объектов XElement является
написание собственного метода оси. Метод оси, как правило, возвращает коллекцию, например
IEnumerable<T> элементов XElement, как показано в примере этого раздела. В пользовательском методе оси
после создания XML -фрагмента с помощью вызова метода ReadFrom возвратите коллекцию, используя
yield return . Тем самым в пользовательском методе оси обеспечивается семантика отложенного
выполнения.
При создании XML -дерева из объекта XmlReader модулю чтения XmlReader должен быть указан
обрабатываемый элемент. Метод ReadFrom не выполняет возврат до тех пор, пока не считает закрывающий
тег элемента.
Если нужно создать частичное дерево, можно создать экземпляр XmlReader, указать для модуля чтения узел,
который должен быть преобразован в дерево XElement, и создать объект XElement.
В разделе Практическое руководство. Потоковая передача фрагментов XML с доступом к сведениям
заголовка (C#) приводятся сведения и пример потоковой передачи более сложного документа.
В разделе Практическое руководство. Выполнение потокового преобразования крупных XML -документов
(C#) содержится пример использования LINQ to XML для преобразования чрезвычайно больших XML -
документов с использованием небольшого объема памяти.
Пример
В следующем примере создается пользовательский метод оси. Его можно запросить с помощью запроса
LINQ. Пользовательский метод оси StreamRootChildDoc специально разработан для чтения документа с
повторяющимся элементом Child .
static IEnumerable<XElement> StreamRootChildDoc(StringReader stringReader)
{
using (XmlReader reader = XmlReader.Create(stringReader))
{
reader.MoveToContent();
// Parse the file and display each of the nodes.
while (reader.Read())
{
switch (reader.NodeType)
{
case XmlNodeType.Element:
if (reader.Name == "Child") {
XElement el = XElement.ReadFrom(reader) as XElement;
if (el != null)
yield return el;
}
break;
}
}
}
}
IEnumerable<string> grandChildData =
from el in StreamRootChildDoc(new StringReader(markup))
where (int)el.Attribute("Key") > 1
select (string)el.Element("GrandChild");
bbb
ccc
В этом примере документ-источник весьма невелик. Тем не менее, даже если бы он содержал миллионы
элементов Child , для этого примера потребовался бы очень небольшой объем памяти.
Практическое руководство. Заполнение дерева
XML с помощью XmlWriter (LINQ to XML) (C#)
23.10.2019 • 2 minutes to read • Edit Online
Одним из вариантов заполнения XML -дерева является использование CreateWriter для создания объекта
XmlWriter, а затем запись в объект XmlWriter. XML -дерево заполняется всеми узлами, которые записаны в
объект XmlWriter.
Обычно этот метод применяется при использовании LINQ to XML с другим классом, который предназначен
для записи в объект XmlWriter, например XslCompiledTransform.
Пример
Модуль CreateWriter может использоваться при вызове преобразования XSLT. В этом примере создается
XML -дерево, создается объект XmlReader на основе XML -дерева, создается новый документ, а затем
создается объект XmlWriter для записи в новый документ. Затем вызывается преобразование XSLT, и ему
передаются объекты XmlReader и XmlWriter. После успешного завершения преобразования новое XML -
дерево заполняется ее результатами.
Console.WriteLine(newTree);
См. также
CreateWriter
XmlWriter
XslCompiledTransform
Создание деревьев XML (C#)
Практическое руководство. Проверка с
использованием XSD (LINQ to XML) (C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В следующем примере создается набор схем XmlSchemaSet, затем с его помощью проводится проверка
правильности двух объектов XDocument. Правильность одного документа подтверждается, а второго - нет.
string xsdMarkup =
@"<xsd:schema xmlns:xsd='http://www.w3.org/2001/XMLSchema'>
<xsd:element name='Root'>
<xsd:complexType>
<xsd:sequence>
<xsd:element name='Child1' minOccurs='1' maxOccurs='1'/>
<xsd:element name='Child2' minOccurs='1' maxOccurs='1'/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>";
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add("", XmlReader.Create(new StringReader(xsdMarkup)));
Console.WriteLine("Validating doc1");
bool errors = false;
doc1.Validate(schemas, (o, e) =>
{
Console.WriteLine("{0}", e.Message);
errors = true;
});
Console.WriteLine("doc1 {0}", errors ? "did not validate" : "validated");
Console.WriteLine();
Console.WriteLine("Validating doc2");
errors = false;
doc2.Validate(schemas, (o, e) =>
{
Console.WriteLine("{0}", e.Message);
errors = true;
});
Console.WriteLine("doc2 {0}", errors ? "did not validate" : "validated");
В этом примере выводятся следующие данные:
Validating doc1
doc1 validated
Validating doc2
The element 'Root' has invalid child element 'Child3'. List of possible elements expected: 'Child2'.
doc2 did not validate
Пример
В следующем примере подтверждается, что XML -документ из статьи Пример XML -файла. Заказчики и
заказы (LINQ to XML ) является правильным согласно схеме из статьи Пример XSD -файла. Заказчики и
заказы. Затем в исходный XML -документ вносятся изменения. Изменяется атрибут CustomerID первого
клиента. После этого изменения заказы ссылаются на несуществующего клиента, поэтому XML -документ
больше не должен пройти проверку правильности.
В этом примере используется следующий XML -документ: Пример XML -файла. Заказчики и заказы (LINQ to
XML ).
В этом примере используется следующая XSD -схема: Пример XSD -файла. Заказчики и заказы.
Console.WriteLine("Attempting to validate");
XDocument custOrdDoc = XDocument.Load("CustomersOrders.xml");
bool errors = false;
custOrdDoc.Validate(schemas, (o, e) =>
{
Console.WriteLine("{0}", e.Message);
errors = true;
});
Console.WriteLine("custOrdDoc {0}", errors ? "did not validate" : "validated");
Console.WriteLine();
// Modify the source document so that it will not validate.
custOrdDoc.Root.Element("Orders").Element("Order").Element("CustomerID").Value = "AAAAA";
Console.WriteLine("Attempting to validate after modification");
errors = false;
custOrdDoc.Validate(schemas, (o, e) =>
{
Console.WriteLine("{0}", e.Message);
errors = true;
});
Console.WriteLine("custOrdDoc {0}", errors ? "did not validate" : "validated");
Attempting to validate
custOrdDoc validated
См. также
Validate
Создание деревьев XML (C#)
Допустимое содержимое объектов XElement и
XDocument
23.10.2019 • 4 minutes to read • Edit Online
В этом разделе описываются допустимые аргументы, которые можно передавать конструкторам, а также
методы, которые можно использовать для добавления содержимого в элементы и документы.
Допустимое содержимое
Возвращаемые запросами данные часто выражаются с помощью коллекций IEnumerable<T> элементов
XElement или с помощью коллекций IEnumerable<T> атрибутов XAttribute. Можно передавать коллекции
объектов XElement или XAttribute в конструктор XElement. Таким образом, передавать результаты запроса
методам и конструкторам, используемым для заполнения XML -деревьев, удобно в виде содержимого.
При добавлении простого содержимого этому методу могут передаваться различные типы. Допустимые
типы:
String
Double
Single
Decimal
Boolean
DateTime
TimeSpan
DateTimeOffset
Любой тип, реализующий Object.ToString .
Любой тип, реализующий IEnumerable<T>.
При добавлении сложного содержимого этому методу могут передаваться различные типы:
XObject
XNode
XAttribute
Любой тип, реализующий IEnumerable<T>
Если объект реализует интерфейс IEnumerable<T>, коллекция в этом объекте перечисляется и добавляются
все элементы коллекции. Если коллекция содержит объекты XNode или XAttribute, каждый ее элемент
добавляется отдельно. Если коллекция содержит текст (или объекты, преобразованные в текст), текст в
коллекции объединяется и добавляется в виде единого текстового узла.
Если содержимое имеет значение null , ничего не добавляется. При передаче коллекции ее элементы могут
иметь значение null . Элементы коллекции со значением null не влияют на дерево.
Добавленный атрибут должен иметь уникальное имя внутри содержащего его элемента.
Если при добавлении объектов XNode или XAttribute новое содержимое не имеет родителя, то эти объекты
просто добавляются к XML -дереву. Если же новое содержимое уже имеет родителя и является частью
другого XML -дерева, тогда это содержимое клонируется, а вновь созданный клон присоединяется к XML -
дереву.
МЕТОД ОПИСАНИЕ
Имена XML
Имена XML часто становятся источником сложности при программировании на XML. Имя XML состоит
из пространства имен XML (которое также называется URI-кодом пространства имен XML ) и
локального имени. Пространство имен XML аналогично пространству имен в программе на основе .NET
Framework. Позволяет уникально квалифицировать имена элементов и атрибутов. Это помогает
избежать конфликтов имен в разных частях XML -документа. При задании пространства имен XML
можно выбрать локальное имя, которое должно быть уникальным только по значению пространства
имен.
Другим аспектом имен XML являются префиксы пространств имен XML. Именно префиксы создают
основную сложность в работе с именами XML. Эти префиксы позволяют создавать ярлык пространства
имен XML, что делает XML -документ более организованным и понятным. Однако, чтобы префиксы
XML несли значение, необходимо, чтобы они были соотнесены с определенным контекстом, а это
вносит дополнительную сложность. Например, префикс XML aw можно ассоциировать с одним
пространством имен XML в одной части XML -дерева и с другим пространством имен XML в другой его
части.
Одним из преимуществ использования LINQ to XML в C# является отсутствие необходимости
использования префиксов XML. Когда LINQ to XML выполняет загрузку или синтаксический анализ
XML -документа, каждый префикс XML соотносится с соответствующим пространством имен XML.
После этого при работе с документом, в котором используются пространства имен, почти всегда можно
оценить пространство имен при помощи URI-кода пространства имен, а не по префиксу. Когда
разработчики работают с именами XML на LINQ to XML, они всегда работают с полностью
выраженными именами XML (т. е. пространство имен XML и локальное имя). Однако при
необходимости LINQ to XML позволяет управлять префиксами пространства имен и обрабатывать их.
В LINQ to XML следующий класс представляет имена XML: XName. Имена XML часто появляются в API
LINQ to XML, и, когда требуется использовать имя XML, обнаруживается параметр XName. Однако
напрямую работать с XName приходится редко. XName содержит неявное преобразование строки.
Дополнительные сведения см. в разделах XNamespace и XName.
Практическое руководство. Создание документа с
пространствами имен (C#) (LINQ to XML)
25.11.2019 • 4 minutes to read • Edit Online
Пример
Чтобы создать элемент или атрибут, находящийся в пространстве имен, необходимо сначала объявить и
инициализировать объект XNamespace. Затем следует использовать перегруженный оператор сложения для
объединения пространства имен с локальным именем, выраженным строкой.
В следующем примере создается документ с одним пространством имен. По умолчанию LINQ to XML
сериализует документ с использованием пространства имен по умолчанию.
<Root xmlns="http://www.adventure-works.com">
<Child>child content</Child>
</Root>
Пример
В следующем примере создается документ с одним пространством имен. Он также создает атрибут, который
объявляет пространство имен с префиксом пространства имен. Создать атрибут, объявляющий пространство
имен с префиксом, можно, указав имя атрибута в качестве префикса пространства имен и поместив его в
пространство имен Xmlns. Значение этого атрибута представляет собой URI пространства имен.
<aw:Root xmlns:aw="http://www.adventure-works.com">
<aw:Child>child content</aw:Child>
</aw:Root>
Пример
Следующий пример иллюстрирует создание документа, содержащего два пространства имен. Одно из них -
пространство имен по умолчанию. Другое - пространство имен с префиксом.
При включении атрибутов пространств имен в корневой элемент пространства имен сериализуются, поэтому
http://www.adventure-works.com становится пространством имен по умолчанию, а www.fourthcoffee.com
сериализуется с префиксом "fc". Чтобы создать атрибут, объявляющий применяемое по умолчанию
пространство имен, необходимо создать атрибут с именем «xmlns» без пространства имен. Значение этого
атрибута является используемым по умолчанию идентификатором URI пространства имен.
Пример
В этом примере создается документ, который содержит два пространства имен с префиксами.
XNamespace aw = "http://www.adventure-works.com";
XNamespace fc = "www.fourthcoffee.com";
XElement root = new XElement(aw + "Root",
new XAttribute(XNamespace.Xmlns + "aw", aw.NamespaceName),
new XAttribute(XNamespace.Xmlns + "fc", fc.NamespaceName),
new XElement(fc + "Child",
new XElement(aw + "DifferentChild", "other content")
),
new XElement(aw + "Child2", "c2 content"),
new XElement(fc + "Child3", "c3 content")
);
Console.WriteLine(root);
Пример
Другой метод получения того же результата состоит в использовании развернутых имен вместо объявления и
создания объекта XNamespace.
Этот подход влияет на производительность. Всякий раз при передаче LINQ to XML строки, содержащей
развернутое имя, система LINQ to XML должна проанализировать это имя, обнаружить атомизированное
пространство имен и атомарное имя. Этот процесс требует затрат процессорного времени. Если
производительность является важным фактором, целесообразнее объявить и использовать объект
XNamespace явным образом.
Если производительность важна, дополнительные сведения см. в разделе Предварительная атомизация
объектов XName (LINQ to XML ) (C#).
<aw:Root xmlns:aw="http://www.adventure-works.com">
<aw:Child>child content</aw:Child>
</aw:Root>
См. также
Обзор пространств имен (LINQ to XML ) (C#)
Практическое руководство. Управление
префиксами пространств имен (C#) (LINQ to XML)
25.11.2019 • 2 minutes to read • Edit Online
В этом разделе описывается управление префиксами пространств имен при сериализации дерева XML.
Во многих ситуациях управлять префиксами пространств имен нет необходимости.
Однако некоторые средства программирования XML требуют, чтобы была возможность управления
префиксами пространства имен. Например, при выполнении манипуляций с таблицами стилей XSLT или с
документами XAML, которые содержат ссылающиеся на те или иные префиксы пространства имен
внедренные выражения XPath, важно, чтобы документ сериализовался с этими конкретными префиксами.
Это основная причина, по которой необходимо управление префиксами пространства имен.
Кроме того, возможность управления префиксами пространства имен требуется в тех случаях, когда
необходимо, чтобы пользователи редактировали XML -документы вручную и могли без труда вводить
префиксы с клавиатуры. Пусть создается документ XSD. В соответствии с соглашениями по схемам при этом в
качестве префиксов пространства имен схемы используются xs или xsd .
Для управления префиксами пространства имен нужно вставлять атрибуты, объявляющие пространства
имен. В случае объявления пространств имен с теми или иными префиксами LINQ to XML попытается
учитывать префиксы пространств имен при сериализации.
Создать атрибут, объявляющий пространство имен с префиксом, можно, указав для имени атрибута
пространство имен Xmlns, а само имя атрибута в качестве префикса пространства имен. Значение атрибута
представляет собой URI пространства имен.
Пример
В этом примере объявляются два пространства имен. В нем указывается, что пространство имен
http://www.adventure-works.com имеет префикс aw , а пространство имен www.fourthcoffee.com — префикс fc .
XNamespace aw = "http://www.adventure-works.com";
XNamespace fc = "www.fourthcoffee.com";
XElement root = new XElement(aw + "Root",
new XAttribute(XNamespace.Xmlns + "aw", "http://www.adventure-works.com"),
new XAttribute(XNamespace.Xmlns + "fc", "www.fourthcoffee.com"),
new XElement(fc + "Child",
new XElement(aw + "DifferentChild", "other content")
),
new XElement(aw + "Child2", "c2 content"),
new XElement(fc + "Child3", "c3 content")
);
Console.WriteLine(root);
См. также
Обзор пространств имен (LINQ to XML ) (C#)
Области пространств имен по умолчанию в C#
23.10.2019 • 2 minutes to read • Edit Online
Применяемые по умолчанию пространства имен, представленные в XML -дереве, находятся вне области
запросов. Если имеется XML, расположенный в используемом по умолчанию пространстве имен, для
получения полного имени, которое может быть применено в запросе, то необходимо объявить переменную
XNamespace и использовать ее в сочетании с локальным именем.
Одной из наиболее типичных проблем при запросах к XML -деревьям является то, что, если XML -дерево
содержит пространство имен по умолчанию, разработчик иногда пишет запрос так, как если бы XML -код не
располагался в пространстве имен.
Первый набор примеров в данном разделе показывает типичный способ загрузки XML в пространстве имен
по умолчанию и неправильного запроса к нему.
Второй набор примеров показывает необходимые исправления для запроса XML в пространстве имен.
Пример
Этот пример показывает создание XML в пространстве имен, а также запрос, возвращающий пустой
результирующий набор.
Код
Комментарии
Этот пример выдает следующий результат:
Пример
Этот пример показывает создание XML в пространстве имен, а также запрос, код которого написан
правильно.
В отличие от вышеприведенного примера с неправильным кодом, правильный подход с использованием C#
заключается в объявлении и инициализации объекта XNamespace и его использовании при указании
объектов XName. В данном случае аргументом для метода Elements является объект XName.
Код
Комментарии
Этот пример выдает следующий результат:
См. также
Обзор пространств имен (LINQ to XML ) (C#)
Практическое руководство. Создание запросов к
XML в пространствах имен (C#)
23.10.2019 • 2 minutes to read • Edit Online
Для записи XML -запросов в пространстве имен необходимо использовать объекты XName с правильно
заданным пространством имен.
Что касается языка C#, то наиболее распространенный подход состоит в инициализации XNamespace с
помощью строки, содержащей URI, а затем использовании перегруженного оператора сложения для
объединения пространства имен с локальным именем.
Первый набор примеров в данном разделе показывает порядок создания XML -дерева в пространстве имен
по умолчанию. Второй набор показывает порядок создания XML -дерева в пространстве имен с префиксом.
Пример
В следующем примере создается XML -дерево, находящееся в пространстве имен по умолчанию. Затем
извлекается коллекция элементов.
XNamespace aw = "http://www.adventure-works.com";
XElement root = XElement.Parse(
@"<Root xmlns='http://www.adventure-works.com'>
<Child>1</Child>
<Child>2</Child>
<Child>3</Child>
<AnotherChild>4</AnotherChild>
<AnotherChild>5</AnotherChild>
<AnotherChild>6</AnotherChild>
</Root>");
IEnumerable<XElement> c1 =
from el in root.Elements(aw + "Child")
select el;
foreach (XElement el in c1)
Console.WriteLine((int)el);
1
2
3
Пример
В C# порядок составления запросов одинаков как при выполнении запросов к XML -дереву, использующему
пространство имен с префиксом, так и при выполнении запросов к XML -дереву, находящемуся в
пространстве имен по умолчанию.
В следующем примере создается XML -дерево, находящееся в пространстве имен с префиксом. Затем
извлекается коллекция элементов.
XNamespace aw = "http://www.adventure-works.com";
XElement root = XElement.Parse(
@"<aw:Root xmlns:aw='http://www.adventure-works.com'>
<aw:Child>1</aw:Child>
<aw:Child>2</aw:Child>
<aw:Child>3</aw:Child>
<aw:AnotherChild>4</aw:AnotherChild>
<aw:AnotherChild>5</aw:AnotherChild>
<aw:AnotherChild>6</aw:AnotherChild>
</aw:Root>");
IEnumerable<XElement> c1 =
from el in root.Elements(aw + "Child")
select el;
foreach (XElement el in c1)
Console.WriteLine((int)el);
1
2
3
См. также
Обзор пространств имен (LINQ to XML ) (C#)
Сохранение пробелов при сериализации
23.10.2019 • 2 minutes to read • Edit Online
В этом разделе описывается, как указывать, должна ли при сериализации формироваться XML -декларация.
Формирование XML-декларации
При сериализации в File или TextWriter с помощью метода XElement.Save или метода XDocument.Save
формируется XML -декларация. При сериализации в XmlWriter параметры модуля записи (заданные в
объекте XmlWriterSettings) определяют, будет ли сформирована XML -декларация.
При сериализации в строку при помощи метода ToString итоговый XML -документ не будет содержать XML -
декларацию.
Сериализация с использованием декларации XML
Следующий пример создает XElement, сохраняет документ в файл, а затем выводит файл на консоль.
<Root><Child>child content</Child></Root>
См. также
Сериализация XML -деревьев (C#)
Сериализация в файлы и объекты TextWriters и
XmlWriters
23.10.2019 • 2 minutes to read • Edit Online
XML -деревья можно сериализовать для File, TextWriter или для XmlWriter.
Любой компонент XML, включая XDocument и XElement, можно сериализовать для строки с помощью
метода ToString .
Если в процессе сериализации в строку необходимо подавить форматирование, эту задачу можно решить с
помощью метода XNode.ToString.
При выполнении сериализации для файла поведение по умолчанию заключается в форматировании
(посредством создания отступов) результирующего XML -документа. При создании отступов не имеющие
значения пробелы в XML -дереве не сохраняются. Для выполнения сериализации с форматированием нужно
использовать одну из перегрузок следующих методов, не принимающих SaveOptions в качестве аргумента:
XDocument.Save
XElement.Save
Если необходимо воздержаться от создания отступов и сохранить не имеющие значения пробелы в XML -
дереве, нужно использовать одну из перегрузок следующих методов, принимающих SaveOptions в качестве
аргумента:
XDocument.Save
XElement.Save
Примеры см. в следующем разделе справки.
См. также
Сериализация XML -деревьев (C#)
Сериализация в XmlReader (вызов XSLT) (C#)
23.10.2019 • 2 minutes to read • Edit Online
При использовании средств взаимодействия System.Xml, реализованных в LINQ to XML, можно применять
CreateReader для создания XmlReader. Модуль, считывающий из этого XmlReader, считывает узлы XML -
дерева и обрабатывает их соответствующим образом.
Console.WriteLine(newTree);
<Root>
<C1>Child1 data</C1>
<C2>Child2 data</C2>
</Root>
См. также
Сериализация XML -деревьев (C#)
Общие сведения об осях LINQ to XML (C#)
04.11.2019 • 5 minutes to read • Edit Online
После создания XML -дерева или загрузки XML -документа в XML -дерево можно опросить его для поиска
элементов и атрибутов и извлечения их значений. Коллекции получают с помощью методов оси,
называемых также осями. Некоторые оси являются методами в классах XElement и XDocument, которые
возвращают коллекции IEnumerable<T>. Некоторые оси являются методами расширений в классе
Extensions. Оси, которые реализованы как методы расширений, работают с коллекциями и возвращают
коллекции.
Как описано в разделе Общие сведения о классе XElement, объект XElement представляет собой узел с
одним элементом. Содержимое элемента может быть сложным (иногда называется структурированным
содержимым), или это может быть простой элемент. Простой элемент может быть пустым или содержать
значение. Если узел содержит структурированное содержимое, можно использовать различные методы
оси, чтобы получить перечисление элементов-потомков. Наиболее часто используемыми методами оси
являются Elements и Descendants.
В дополнение к методам оси, которые возвращают коллекции, есть еще два метода, которые обычно
используются в запросах LINQ to XML. Метод Element возвращает одиночный XElement. Метод Attribute
возвращает одиночный XAttribute.
Во многих случаях запросы LINQ обеспечивают самый эффективный способ проверки и преобразования
дерева и извлечения из него данных. Запросы LINQ работают с объектами, которые реализуют
IEnumerable<T>, а оси LINQ to XML возвращают IEnumerable<T> коллекций XElement и IEnumerable<T>
коллекций XAttribute. Эти коллекции нужны для выполнения запросов.
В дополнение к методам оси, получающим коллекции элементов и атрибутов, существуют методы оси,
которые позволяют проходить по всему дереву максимально подробно. Например, вместо того чтобы
иметь дело с элементами и атрибутами, можно работать с узлами дерева. Узлы - это более глубокий
уровень гранулярности, чем элементы и атрибуты. При работе с узлами можно просматривать
комментарии XML, текстовые узлы XML, инструкции по обработке XML и др. Эти возможности важны,
например, для разработчика текстового процессора, который хочет сохранить документы как XML. Обычно
большинство программистов на XML в первую очередь связаны с элементами, атрибутами и их
значениями.
МЕТОД ОПИСАНИЕ
МЕТОД ОПИСАНИЕ
МЕТОД ОПИСАНИЕ
МЕТОД ОПИСАНИЕ
МЕТОД ОПИСАНИЕ
См. также
Оси LINQ to XML (C#)
Практическое руководство. Извлечение коллекции
элементов (LINQ to XML) (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом разделе показан метод Elements. Этот метод получает коллекцию дочерних элементов того или иного
элемента.
Пример
В этом примере выполняется итерация по дочерним элементам элемента purchaseOrder .
В этом примере используется следующий XML -документ: Пример XML -файла. Типичный заказ на покупку
(LINQ to XML ).
XElement po = XElement.Load("PurchaseOrder.xml");
IEnumerable<XElement> childElements =
from el in po.Elements()
select el;
foreach (XElement el in childElements)
Console.WriteLine("Name: " + el.Name);
Name: Address
Name: Address
Name: DeliveryNotes
Name: Items
См. также
Оси LINQ to XML (C#)
Практическое руководство. Извлечение значений
элемента (LINQ to XML) (C#)
23.10.2019 • 4 minutes to read • Edit Online
В этом разделе показано получение значений элементов. Это можно сделать двумя основными способами.
Первый способ состоит в приведении XElement или XAttribute к желаемому типу. Оператор явного
преобразования, который преобразует содержимое элемента или атрибута в указанный тип и присваивает
полученное значение указанной переменной. Иначе можно использовать свойство XElement.Value или
XAttribute.Value.
Однако при использовании C# приведение, как правило, является лучшим подходом. В частности, становится
проще написание кода, обеспечивающего получение значения элемента или атрибута, который может
существовать или не существовать, после приведения элемента (или атрибута) к типу, допускающему
значение NULL. Это демонстрирует последний пример из данного раздела. Однако нельзя так задать
содержимое элемента через приведение, как через свойство XElement.Value.
Пример
Для получения значения элемента нужно просто привести объект XElement к желаемому типу. Всегда можно
привести элемент к строке следующим образом:
<StringElement>abcde</StringElement>
Value of e:abcde
Пример
Также можно приводить элементы к типам, отличным от строковых. Например, если имеется элемент,
содержащий целое число, его можно привести к типу int , как показано в следующем коде:
<Age>44</Age>
Value of e:44
LINQ to XML предусматривает операторы явного приведения для следующих типов данных: string , bool ,
bool? , int , int? , uint , uint? , long , long? , ulong , ulong? , float , float? , double , double? , decimal ,
decimal? , DateTime , DateTime? , TimeSpan , TimeSpan? , GUID и GUID? .
LINQ to XMLпредоставляет аналогичные операторы приведения для объектов XAttribute.
Пример
Свойство Value может использоваться для получения содержимого элемента.
<StringElement>abcde</StringElement>
Value of e:abcde
Пример
Иногда осуществляются попытки получить значение элемента, в отношении которого неизвестно,
существует ли он. Таким образом, при назначении приведенного элемента типу, допускающему значение
NULL ( string или одному из таких типов в .NET Framework), если элемент не существует, переменной
просто присваивается null . В следующем коде показано, что при отсутствии сведений о том, существует ли
элемент, проще использовать приведение типа, чем свойство Value.
XElement root = new XElement("Root",
new XElement("Child1", "child 1 content"),
new XElement("Child2", "2")
);
string c1 = (string)root.Element("Child1");
Console.WriteLine("c1:{0}", c1 == null ? "element does not exist" : c1);
int? c2 = (int?)root.Element("Child2");
Console.WriteLine("c2:{0}", c2 == null ? "element does not exist" : c2.ToString());
string c3 = (string)root.Element("Child3");
Console.WriteLine("c3:{0}", c3 == null ? "element does not exist" : c3);
int? c4 = (int?)root.Element("Child4");
Console.WriteLine("c4:{0}", c4 == null ? "element does not exist" : c4.ToString());
Console.WriteLine();
XElement e1 = root.Element("Child1");
string v1;
if (e1 == null)
v1 = null;
else
v1 = e1.Value;
Console.WriteLine("v1:{0}", v1 == null ? "element does not exist" : v1);
XElement e2 = root.Element("Child2");
int? v2;
if (e2 == null)
v2 = null;
else
v2 = Int32.Parse(e2.Value);
Console.WriteLine("v2:{0}", v2 == null ? "element does not exist" : v2.ToString());
XElement e3 = root.Element("Child3");
string v3;
if (e3 == null)
v3 = null;
else
v3 = e3.Value;
Console.WriteLine("v3:{0}", v3 == null ? "element does not exist" : v3);
XElement e4 = root.Element("Child4");
int? v4;
if (e4 == null)
v4 = null;
else
v4 = Int32.Parse(e4.Value);
Console.WriteLine("v4:{0}", v4 == null ? "element does not exist" : v4.ToString());
v1:child 1 content
v2:2
v3:element does not exist
v4:element does not exist
Как правило, использование приведения для получения содержимого элементов и атрибутов позволяет
создавать более простой код.
См. также
Оси LINQ to XML (C#)
Практическое руководство. Фильтрация по именам
элементов (LINQ to XML) (C#)
25.11.2019 • 2 minutes to read • Edit Online
При вызове одного из методов, возвращающих коллекцию IEnumerable<T> элементов XElement, можно
осуществить фильтрацию по именам элементов.
Пример
В этом примере показано получение отфильтрованной коллекции потомков, в которой содержатся только
потомки с указанным именем.
В этом примере используется следующий XML -документ: Пример XML -файла. Типичный заказ на покупку
(LINQ to XML ).
XElement po = XElement.Load("PurchaseOrder.xml");
IEnumerable<XElement> items =
from el in po.Descendants("ProductName")
select el;
foreach(XElement prdName in items)
Console.WriteLine(prdName.Name + ":" + (string) prdName);
ProductName:Lawnmower
ProductName:Baby Monitor
В других методах, возвращающих коллекции IEnumerable<T> элементов XElement, заложен тот же принцип.
Их сигнатуры подобны Elements и Descendants. Ниже следует полный список методов, имеющих
аналогичные сигнатурам методов:
Ancestors
Descendants
Elements
ElementsAfterSelf
ElementsBeforeSelf
AncestorsAndSelf
DescendantsAndSelf
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
В этом примере используется следующий XML -документ: Пример XML -файла. Типичный заказ на покупку в
пространстве имен.
XNamespace aw = "http://www.adventure-works.com";
XElement po = XElement.Load("PurchaseOrderInNamespace.xml");
IEnumerable<XElement> items =
from el in po.Descendants(aw + "ProductName")
select el;
foreach (XElement prdName in items)
Console.WriteLine(prdName.Name + ":" + (string)prdName);
{http://www.adventure-works.com}ProductName:Lawnmower
{http://www.adventure-works.com}ProductName:Baby Monitor
См. также
Оси LINQ to XML (C#)
Практическое руководство. Связанные вызовы
метода оси (LINQ to XML) (C#)
25.11.2019 • 3 minutes to read • Edit Online
Обычно при написании кода вы будете придерживаться схемы, по которой вызывается метод оси, после чего
вызывается одна из осей метода расширений.
Существуют две оси с именем Elements , которые возвращают коллекцию элементов: методы
XContainer.Elements и Extensions.Elements. Можно сочетать эти две оси таким образом, чтобы найти все
элементы с указанным именем на заданной глубине дерева.
Пример
В этом примере с помощью методовXContainer.Elements и Extensions.Elements выполняется поиск всех
элементов Name во всех элементах Address во всех элементах PurchaseOrder .
В этом примере используется следующий XML -документ: Пример XML -файла. Несколько заказов на покупку
(LINQ to XML ).
<Name>Ellen Adams</Name>
<Name>Tai Yee</Name>
<Name>Cristian Osorio</Name>
<Name>Cristian Osorio</Name>
<Name>Jessica Arnold</Name>
<Name>Jessica Arnold</Name>
Такой подход оправдан, поскольку одна из реализаций оси Elements представляет собой метод расширений
IEnumerable<T> по отношению к XContainer. XElement вычисляется из XContainer, что позволяет вызвать
метод Extensions.Elements по результатам вызова метода XContainer.Elements.
Пример
Иногда может потребоваться получить все элементы с определенной глубины структуры элементов при
условии, что неизвестно, имеются промежуточные предки или нет. Например, в следующем документе может
потребоваться получение всех элементов ConfigParameter , которые являются дочерними по отношению к
элементу Customer , кроме ConfigParameter , которые являются дочерними по отношению к элементу Root .
<Root>
<ConfigParameter>RootConfigParameter</ConfigParameter>
<Customer>
<Name>Frank</Name>
<Config>
<ConfigParameter>FirstConfigParameter</ConfigParameter>
</Config>
</Customer>
<Customer>
<Name>Bob</Name>
<!--This customer doesn't have a Config element-->
</Customer>
<Customer>
<Name>Bill</Name>
<Config>
<ConfigParameter>SecondConfigParameter</ConfigParameter>
</Config>
</Customer>
</Root>
<ConfigParameter>FirstConfigParameter</ConfigParameter>
<ConfigParameter>SecondConfigParameter</ConfigParameter>
Пример
Следующий пример демонстрирует тот же способ обработки XML, что и в пространстве имен.
Дополнительные сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
В этом примере используется следующий XML -документ: Пример XML -файла. Несколько заказов на покупку
в пространстве имен.
XNamespace aw = "http://www.adventure-works.com";
XElement purchaseOrders = XElement.Load("PurchaseOrdersInNamespace.xml");
IEnumerable<XElement> names =
from el in purchaseOrders
.Elements(aw + "PurchaseOrder")
.Elements(aw + "Address")
.Elements(aw + "Name")
select el;
foreach (XElement e in names)
Console.WriteLine(e);
См. также
Оси LINQ to XML (C#)
Практическое руководство. Извлечение одного
дочернего элемента (LINQ to XML) (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом разделе объясняется, как обеспечить получение отдельных дочерних элементов, когда известно имя
этого дочернего элемента. Если известно имя дочернего элемента, а также то, что есть только один элемент с
таким именем, удобнее получить один элемент, а не целую коллекцию.
Метод Element возвращает первый дочерний элемент XElement с указанным именем XName.
Если требуется получить отдельный дочерний элемент в Visual Basic, обычно используется XML -свойство, а
затем происходит получение первого элемента при помощи обозначения индексатора массива.
Пример
В следующем примере иллюстрируется использование метода Element. В этом примере берется XML -дерево
po и осуществляется поиск первого элемента с именем Comment .
В примере по Visual Basic демонстрируется использование обозначения индексатора массива для получения
отдельного элемента.
В этом примере используется следующий XML -документ: Пример XML -файла. Типичный заказ на покупку
(LINQ to XML ).
XElement po = XElement.Load("PurchaseOrder.xml");
XElement e = po.Element("DeliveryNotes");
Console.WriteLine(e);
Пример
В следующем примере демонстрируется тот же код XML -документа, который находится в пространстве
имен. Дополнительные сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
В этом примере используется следующий XML -документ: Пример XML -файла. Типичный заказ на покупку в
пространстве имен.
XElement po = XElement.Load("PurchaseOrderInNamespace.xml");
XNamespace aw = "http://www.adventure-works.com";
XElement e = po.Element(aw + "DeliveryNotes");
Console.WriteLine(e);
В этом разделе представлен метод Attributes. Этот метод извлекает атрибуты того или иного элемента.
Пример
В следующем примере показано, как просматривать коллекцию атрибутов элемента.
ID="1243"
Type="int"
ConvertableTo="double"
См. также
Оси LINQ to XML (C#)
Практическое руководство. Извлечение одного
атрибута (LINQ to XML) (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом разделе приведены объяснения способа получения одного атрибута элемента при условии, что
название атрибута известно. Это полезно для составления выражений запросов, при которых требуется найти
элемент с определенным атрибутом.
Метод Attribute класса XElement возвращает значение XAttribute с указанным именем.
Пример
В следующем примере используется метод Attribute.
В этом примере выполняется поиск всех потомков в дереве с названием Phone , затем атрибута с названием
type .
home
work
Пример
Если требуется получить значение атрибута, можно его задать точно так же, как при работе с объектами
XElement. В следующем примере это показано.
XElement cust = new XElement("PhoneNumbers",
new XElement("Phone",
new XAttribute("type", "home"),
"555-555-5555"),
new XElement("Phone",
new XAttribute("type", "work"),
"555-555-6666")
);
IEnumerable<XElement> elList =
from el in cust.Descendants("Phone")
select el;
foreach (XElement el in elList)
Console.WriteLine((string)el.Attribute("type"));
home
work
LINQ to XML предоставляет явные операторы приведения класса XAttribute к string , bool , bool? , int ,
int? , uint , uint? , long , long? , ulong , ulong? , float , float? , double , double? , decimal , decimal? ,
DateTime , DateTime? , TimeSpan , TimeSpan? , GUID и GUID? .
Пример
Следующий пример демонстрирует тот же код атрибута, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
XNamespace aw = "http://www.adventure-works.com";
XElement cust = new XElement(aw + "PhoneNumbers",
new XElement(aw + "Phone",
new XAttribute(aw + "type", "home"),
"555-555-5555"),
new XElement(aw + "Phone",
new XAttribute(aw + "type", "work"),
"555-555-6666")
);
IEnumerable<XElement> elList =
from el in cust.Descendants(aw + "Phone")
select el;
foreach (XElement el in elList)
Console.WriteLine((string)el.Attribute(aw + "type"));
home
work
См. также
Оси LINQ to XML (C#)
Практическое руководство. Получение значения
атрибута (LINQ to XML) (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом разделе показано получение значений атрибутов. Существует два основных способа. Можно привести
XAttribute к требуемому типу, после этого оператор явного преобразования преобразует содержимое
элемента или атрибута в указанный тип. Иначе можно использовать свойство Value. Однако приведение, как
правило, является лучшим подходом. В частности, становится проще написание кода, обеспечивающего
получение значения атрибута, который может существовать или не существовать, после приведения атрибута
к типу, допускающему значение NULL. Примеры применения этого способа см. в разделе Практическое
руководство. Извлечение значений элемента (LINQ to XML ) (C#).
Пример
Для получения значения атрибута нужно просто привести объект XAttribute к желаемому типу.
Пример
В следующем примере показано, как получить значение атрибута, если атрибут находится в пространстве
имен. Дополнительные сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
XNamespace aw = "http://www.adventure-works.com";
XElement root = new XElement(aw + "Root",
new XAttribute(aw + "Attr", "abcde")
);
string str = (string)root.Attribute(aw + "Attr");
Console.WriteLine(str);
abcde
См. также
Оси LINQ to XML (C#)
Практическое руководство. Извлечение
поверхностного значения элемента (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом разделе показано, как получить неглубокое значение элемента. Неглубокое значение - это значение
только конкретного элемента, в отличие от глубокого значения, которое содержит значения всех элементов-
потомков, объединенные в одной строке.
При получении значения элемента при помощи приведения или свойства XElement.Value выполняется
извлечение глубокого значения. Чтобы получить неглубокое значение, можно использовать метод
расширения ShallowValue , как показано в следующем примере. Извлечение неглубокого значения полезно
тогда, когда требуется выбрать элементы в зависимости от их содержимого.
В следующем примере объявляется метод расширений, который извлекает неглубокое значение элемента.
После этого метод расширения используется в запросе, чтобы вывести список всех элементов с вычисленным
значением.
Пример
Следующий текстовый файл Report.xml в этом примере будет исходным.
class Program
{
static void Main(string[] args)
{
XElement root = XElement.Load("Report.xml");
См. также
Оси LINQ to XML (C#)
Практическое руководство. Поиск элементов с
определенным атрибутом (C#)
25.11.2019 • 2 minutes to read • Edit Online
Пример
В примере показано, как выполнить поиск элемента Address , имеющего атрибут Type со значением
«Billing».
В этом примере используется следующий XML -документ: Пример XML -файла. Типичный заказ на покупку
(LINQ to XML ).
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
В этом примере используется следующий XML -документ: Пример XML -файла. Типичный заказ на покупку в
пространстве имен.
См. также
Attribute
Elements
Общие сведения о стандартных операторах запроса (C#)
Операции проекции (C#)
Практическое руководство. Поиск элементов с
определенным дочерним элементом (C#)
25.11.2019 • 2 minutes to read • Edit Online
В этом разделе показан определенный элемент, имеющий дочерний элемент с заданным значением.
Пример
В этом примере осуществляется поиск элемента Test , имеющего дочерний элемент CommandLine со
значением «Examp2.EXE ».
В этом примере используется следующий XML -документ: Пример XML -файла. Конфигурация тестирования
(LINQ to XML ).
0002
0006
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
В этом примере используется следующий XML -документ: Пример XML -файла. Конфигурация тестирования в
пространстве имен.
0002
0006
См. также
Attribute
Elements
Общие сведения о стандартных операторах запроса (C#)
Операции проекции (C#)
Сравнение запросов к XML-документам Запросы к
XElement (C#)
23.10.2019 • 2 minutes to read • Edit Online
При загрузке документа через XDocument.Load обратите внимание на то, что запросы придется составлять
не так, как при загрузке через XElement.Load.
Следующий пример такой же, как и пример выше, за исключением того, что XML -дерево загружается в
XDocument, а не в XElement.
// Create a simple document and write it to a file
File.WriteAllText("Test.xml", @"<Root>
<Child1>1</Child1>
<Child2>2</Child2>
<Child3>3</Child3>
</Root>");
Обратите внимание, что при таком же запросе выводится только узел Root , а не три дочерних узла.
Одним из подходов при этом может быть использование свойства Root перед доступом к методам оси:
Теперь на этот запрос выводятся те же результаты, что и при запросе по дереву, корень которого размещен в
XElement. Пример выводит следующие результаты:
Иногда возникает необходимость найти всех потомков с определенным именем. В таких случаях можно
написать код для просмотра всех потомков, но проще использовать ось Descendants.
Пример
В следующем примере показано, как находить потомков на основе имени элемента.
Console.WriteLine(str);
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
XElement root = XElement.Parse(@"<root xmlns='http://www.adatum.com'>
<para>
<r>
<t>Some text </t>
</r>
<n>
<r>
<t>that is broken up into </t>
</r>
</n>
<n>
<r>
<t>multiple segments.</t>
</r>
</n>
</para>
</root>");
XNamespace ad = "http://www.adatum.com";
IEnumerable<string> textSegs =
from seg in root.Descendants(ad + "t")
select (string)seg;
Console.WriteLine(str);
См. также
Descendants
Практическое руководство. Поиск одного потомка
с помощью метода потомков (C#)
25.11.2019 • 2 minutes to read • Edit Online
Метод оси Descendants можно использовать для быстрого написания кода с целью поиска одного уникально
именованного элемента. Этот способ особенно полезен, если нужно найти конкретного потомка с заданным
именем. Можно написать собственный код для перехода к нужному элементу, но часто быстрей и легче
написать такой код с помощью оси Descendants.
Пример
В этом примере используется стандартный оператор запроса First.
GC3 Value
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
XElement root = XElement.Parse(@"<aw:Root xmlns:aw='http://www.adventure-works.com'>
<aw:Child1>
<aw:GrandChild1>GC1 Value</aw:GrandChild1>
</aw:Child1>
<aw:Child2>
<aw:GrandChild2>GC2 Value</aw:GrandChild2>
</aw:Child2>
<aw:Child3>
<aw:GrandChild3>GC3 Value</aw:GrandChild3>
</aw:Child3>
<aw:Child4>
<aw:GrandChild4>GC4 Value</aw:GrandChild4>
</aw:Child4>
</aw:Root>");
XNamespace aw = "http://www.adventure-works.com";
string grandChild3 = (string)
(from el in root.Descendants(aw + "GrandChild3")
select el).First();
Console.WriteLine(grandChild3);
GC3 Value
Практическое руководство. Создание запросов со
сложной фильтрацией (C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В этом примере показано, как найти все элементы PurchaseOrder , имеющие дочерний элемент Address с
атрибутом Type , равным «Доставка», и дочерним элементом State , равным «NY ». В нем используется
вложенный запрос в предложении Where , а оператор Any возвращает значение true , если коллекция
содержит элементы. Сведения об использовании синтаксиса запросов на основе методов см. в разделе
Синтаксис запросов и синтаксис методов в LINQ.
В этом примере используется следующий XML -документ: Пример XML -файла. Несколько заказов на покупку
(LINQ to XML ).
Дополнительные сведения об операторе Any см. в разделе Операции, использующие квантификаторы (C#).
99505
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
В этом примере используется следующий XML -документ: Пример XML -файла. Несколько заказов на покупку
в пространстве имен.
XElement root = XElement.Load("PurchaseOrdersInNamespace.xml");
XNamespace aw = "http://www.adventure-works.com";
IEnumerable<XElement> purchaseOrders =
from el in root.Elements(aw + "PurchaseOrder")
where
(from add in el.Elements(aw + "Address")
where
(string)add.Attribute(aw + "Type") == "Shipping" &&
(string)add.Element(aw + "State") == "NY"
select add)
.Any()
select el;
foreach (XElement el in purchaseOrders)
Console.WriteLine((string)el.Attribute(aw + "PurchaseOrderNumber"));
99505
См. также
Attribute
Elements
Операции проекции (C#)
Операции, использующие квантификаторы (C#)
Практическое руководство. Фильтрация по
необязательным элементам (C#)
25.11.2019 • 2 minutes to read • Edit Online
Иногда необходимо выполнить фильтрацию элемента, даже если неизвестно, существует ли он в документе
XML. Поиск должен быть выполнен, чтобы, если конкретный элемент не имеет дочернего узла, при
фильтрации по этому элементу не возникло исключение null reference. В следующем примере элемент
Child5 не имеет дочернего узла Type , тем не менее запрос выполняется правильно.
Пример
Этот пример использует метод расширений Elements.
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
XElement root = XElement.Parse(@"<Root xmlns='http://www.adatum.com'>
<Child1>
<Text>Child One Text</Text>
<Type Value=""Yes""/>
</Child1>
<Child2>
<Text>Child Two Text</Text>
<Type Value=""Yes""/>
</Child2>
<Child3>
<Text>Child Three Text</Text>
<Type Value=""No""/>
</Child3>
<Child4>
<Text>Child Four Text</Text>
<Type Value=""Yes""/>
</Child4>
<Child5>
<Text>Child Five Text</Text>
</Child5>
</Root>");
XNamespace ad = "http://www.adatum.com";
var cList =
from typeElement in root.Elements().Elements(ad + "Type")
where (string)typeElement.Attribute("Value") == "Yes"
select (string)typeElement.Parent.Element(ad + "Text");
foreach (string str in cList)
Console.WriteLine(str);
См. также
XElement.Attribute
XContainer.Elements
Extensions.Elements
Общие сведения о стандартных операторах запроса (C#)
Операции проекции (C#)
Практическое руководство. Поиск всех узлов в
пространстве имен (C#)
25.11.2019 • 2 minutes to read • Edit Online
Для поиска всех узлов в отдельном пространстве имен можно применить к нему фильтр по каждому
элементу или атрибуту.
Пример
В следующем примере создается XML -дерево с двумя пространствами имен. Затем в нем выполняется
итерация по дереву и печать имен всех элементов и атрибутов в одном из этих пространств имен.
Пример
Для доступа к XML -файлу применяется следующий запрос, содержащий заказы на покупку в двух разных
пространствах имен. В запросе создается новое дерево с элементами одного из пространств имен.
В этом примере используется следующий XML -документ: Пример XML -файла. Консолидированные заказы на
покупку.
<Root>
<aw:PurchaseOrder PONumber="11223" Date="2000-01-15" xmlns:aw="http://www.adventure-works.com">
<aw:ShippingAddress>
<aw:Name>Chris Preston</aw:Name>
<aw:Street>123 Main St.</aw:Street>
<aw:City>Seattle</aw:City>
<aw:State>WA</aw:State>
<aw:Zip>98113</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:ShippingAddress>
<aw:BillingAddress>
<aw:Name>Chris Preston</aw:Name>
<aw:Street>123 Main St.</aw:Street>
<aw:City>Seattle</aw:City>
<aw:State>WA</aw:State>
<aw:Zip>98113</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:BillingAddress>
<aw:DeliveryInstructions>Ship only complete order.</aw:DeliveryInstructions>
<aw:Item PartNum="LIT-01">
<aw:ProductID>Litware Networking Card</aw:ProductID>
<aw:Qty>1</aw:Qty>
<aw:Price>20.99</aw:Price>
</aw:Item>
<aw:Item PartNum="LIT-25">
<aw:ProductID>Litware 17in LCD Monitor</aw:ProductID>
<aw:Qty>1</aw:Qty>
<aw:Price>199.99</aw:Price>
</aw:Item>
</aw:PurchaseOrder>
</Root>
Практическое руководство. Сортировка элементов
(C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В этом примере используется следующий XML -документ: Пример XML -файла. Числовые данные (LINQ to
XML ).
0.99
4.95
6.99
24.50
29.00
66.00
89.99
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
В этом примере используется следующий XML -документ: Пример XML -файла. Числовые данные в
пространстве имен.
См. также
Сортировка данных (C#)
Практическое руководство. Сортировка элементов
по нескольким ключам (C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В этом примере результаты упорядочиваются вначале по почтовому коду доставки, а затем по дате заказа.
В этом примере используется следующий XML -документ: Пример XML -файла. Заказчики и заказы (LINQ to
XML ).
XElement co = XElement.Load("CustomersOrders.xml");
var sortedElements =
from c in co.Element("Orders").Elements("Order")
orderby (string)c.Element("ShipInfo").Element("ShipPostalCode"),
(DateTime)c.Element("OrderDate")
select new {
CustomerID = (string)c.Element("CustomerID"),
EmployeeID = (string)c.Element("EmployeeID"),
ShipPostalCode = (string)c.Element("ShipInfo").Element("ShipPostalCode"),
OrderDate = (DateTime)c.Element("OrderDate")
};
foreach (var r in sortedElements)
Console.WriteLine("CustomerID:{0} EmployeeID:{1} ShipPostalCode:{2} OrderDate:{3:d}",
r.CustomerID, r.EmployeeID, r.ShipPostalCode, r.OrderDate);
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
В этом примере используется следующий XML -документ: Пример XML -файла. Заказчики и заказы в
пространстве имен.
XElement co = XElement.Load("CustomersOrdersInNamespace.xml");
XNamespace aw = "http://www.adventure-works.com";
var sortedElements =
from c in co.Element(aw + "Orders").Elements(aw + "Order")
orderby (string)c.Element(aw + "ShipInfo").Element(aw + "ShipPostalCode"),
(DateTime)c.Element(aw + "OrderDate")
select new
{
CustomerID = (string)c.Element(aw + "CustomerID"),
EmployeeID = (string)c.Element(aw + "EmployeeID"),
ShipPostalCode = (string)c.Element(aw + "ShipInfo").Element(aw + "ShipPostalCode"),
OrderDate = (DateTime)c.Element(aw + "OrderDate")
};
foreach (var r in sortedElements)
Console.WriteLine("CustomerID:{0} EmployeeID:{1} ShipPostalCode:{2} OrderDate:{3:d}",
r.CustomerID, r.EmployeeID, r.ShipPostalCode, r.OrderDate);
В этом примере показано, как вычислять промежуточные значения, которые используются в сортировке,
фильтрации и выборке.
Пример
В следующем примере используется предложение Let .
В этом примере используется следующий XML -документ: Пример XML -файла. Числовые данные (LINQ to
XML ).
55.92
73.50
89.99
198.00
435.00
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
В этом примере используется следующий XML -документ: Пример XML -файла. Числовые данные в
пространстве имен.
Иногда требуется написать запрос, который выбирает элементы, исходя из их контекста. Может
потребоваться использовать фильтрацию с учетом предыдущих или следующих одноуровневых элементов.
Может потребоваться использовать фильтрацию с учетом дочерних или родительских элементов.
Это можно сделать, написав запрос и используя результаты запроса в предложении where . Если требуется
сначала провести проверку на наличие значения null, а затем проверить само значение, более удобным будет
выполнить запрос в предложении let , а затем использовать результаты в предложении where .
Пример
В следующем примере выбираются все элементы p , сразу за которыми следует элемент ul .
IEnumerable<XElement> items =
from e in doc.Descendants("p")
let z = e.ElementsAfterSelf().FirstOrDefault()
where z != null && z.Name.LocalName == "ul"
select e;
id = 1
id = 3
id = 6
Пример
Следующий пример демонстрирует тот же запрос XML, что и в пространстве имен. Дополнительные
сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
XElement doc = XElement.Parse(@"<Root xmlns='http://www.adatum.com'>
<p id=""1""/>
<ul>abc</ul>
<Child>
<p id=""2""/>
<notul/>
<p id=""3""/>
<ul>def</ul>
<p id=""4""/>
</Child>
<Child>
<p id=""5""/>
<notul/>
<p id=""6""/>
<ul>abc</ul>
<p id=""7""/>
</Child>
</Root>");
XNamespace ad = "http://www.adatum.com";
IEnumerable<XElement> items =
from e in doc.Descendants(ad + "p")
let z = e.ElementsAfterSelf().FirstOrDefault()
where z != null && z.Name == ad.GetName("ul")
select e;
id = 1
id = 3
id = 6
См. также
Parse
Descendants
ElementsAfterSelf
FirstOrDefault
Практическое руководство. Отладка пустых
результирующих наборов запроса (C#)
25.11.2019 • 2 minutes to read • Edit Online
Одной из наиболее типичных проблем при запросах к XML -деревьям является то, что, если XML -дерево
содержит пространство имен по умолчанию, разработчик иногда пишет запрос так, как если бы XML -код не
располагался в пространстве имен.
Первый набор примеров в данном разделе показывает типичный способ загрузки XML в пространстве имен
по умолчанию и неправильного запроса к нему.
Второй набор примеров показывает необходимые исправления для запроса XML в пространстве имен.
Дополнительные сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
Пример
Этот пример показывает создание XML в пространстве имен, а также запрос, возвращающий пустой
результирующий набор.
Пример
Этот пример показывает создание XML в пространстве имен, а также запрос, код которого написан
правильно.
Решение заключается в объявлении и инициализации объекта XNamespace и его использовании при
указании объектов XName. В данном случае аргументом для метода Elements является объект XName.
XElement root = XElement.Parse(
@"<Root xmlns='http://www.adventure-works.com'>
<Child>1</Child>
<Child>2</Child>
<Child>3</Child>
<AnotherChild>4</AnotherChild>
<AnotherChild>5</AnotherChild>
<AnotherChild>6</AnotherChild>
</Root>");
XNamespace aw = "http://www.adventure-works.com";
IEnumerable<XElement> c1 =
from el in root.Elements(aw + "Child")
select el;
Console.WriteLine("Result set follows:");
foreach (XElement el in c1)
Console.WriteLine((int)el);
Console.WriteLine("End of result set");
Часто бывает удобно преобразовать структуры данных в XML, а затем преобразовать XML в другие
структуры данных. В этом разделе показана конкретная реализация этого общего подхода на примере
преобразования Dictionary<TKey,TValue> в XML и обратно.
Пример
В этом примере используется форма функционального построения, в которой запрос проецирует новые
объекты XElement, а итоговая коллекция передается в качестве аргумента конструктору корневого объекта
XElement.
<Root>
<Child1>Value1</Child1>
<Child2>Value2</Child2>
<Child3>Value3</Child3>
<Child4>Value4</Child4>
</Root>
Пример
Следующий код создает словарь на основе XML.
Понятие форма применительно к XML -документу обозначает совокупность имен элементов, атрибутов и
характеристик его иерархии.
Иногда может потребоваться изменить форму XML -документа. Например, может потребоваться передать
существующий XML -документ в другую систему, которая использует другие названия элементов и атрибутов.
Можно пройти по документу, удаляя и переименовывая элементы согласно требованиям, однако
использование результатов функциональных построений дает более читаемый код и просто код для
обработки. Дополнительные сведения о функциональном построении см. в разделе Функциональное
построение (LINQ to XML ) (C#).
В первом примере выполнится изменение организации всего XML -документа. Происходит перемещение
сложных элементов из одного места дерева в другое.
Во втором примере, приведенном в этом разделе, создается XML -документ, форма которого отлична от
формы исходного документа. Выполняется изменение тегов имен элементов, переименование некоторых
элементов и опущение некоторых элементов, присутствующих в исходном дереве.
Пример
Следующий код позволяет изменить форму XML -файла при помощи внедренного выражения запроса.
Исходный XML -документ в этом примере содержит Customers элемент Root , в котором содержится список
всех клиентов. Он также содержит элемент Orders под элементом Root , который содержит все заказы. В
этом примере создается новое XML -дерево, в котором заказы каждого клиента содержатся в элементе
Orders , который является дочерним по отношению к элементу Customer . Оригинальный документ также
содержит элемент CustomerID внутри элемента Order . Этот элемент будет удален из преобразованного
документа.
В этом примере используется следующий XML -документ: Пример XML -файла. Заказчики и заказы (LINQ to
XML ).
XElement co = XElement.Load("CustomersOrders.xml");
XElement newCustOrd =
new XElement("Root",
from cust in co.Element("Customers").Elements("Customer")
select new XElement("Customer",
cust.Attributes(),
cust.Elements(),
new XElement("Orders",
from ord in co.Element("Orders").Elements("Order")
where (string)ord.Element("CustomerID") == (string)cust.Attribute("CustomerID")
select new XElement("Order",
ord.Attributes(),
ord.Element("EmployeeID"),
ord.Element("OrderDate"),
ord.Element("RequiredDate"),
ord.Element("ShipInfo")
)
)
)
);
Console.WriteLine(newCustOrd);
<Root>
<Customer CustomerID="GREAL">
<CompanyName>Great Lakes Food Market</CompanyName>
<ContactName>Howard Snyder</ContactName>
<ContactTitle>Marketing Manager</ContactTitle>
<Phone>(503) 555-7555</Phone>
<FullAddress>
<Address>2732 Baker Blvd.</Address>
<City>Eugene</City>
<Region>OR</Region>
<PostalCode>97403</PostalCode>
<Country>USA</Country>
</FullAddress>
<Orders />
</Customer>
<Customer CustomerID="HUNGC">
<CompanyName>Hungry Coyote Import Store</CompanyName>
<ContactName>Yoshi Latimer</ContactName>
<ContactTitle>Sales Representative</ContactTitle>
<Phone>(503) 555-6874</Phone>
<Fax>(503) 555-2376</Fax>
<FullAddress>
<Address>City Center Plaza 516 Main St.</Address>
<City>Elgin</City>
<Region>OR</Region>
<PostalCode>97827</PostalCode>
<Country>USA</Country>
</FullAddress>
<Orders />
</Customer>
. . .
Пример
В этом примере выполняется переименование некоторых элементов и преобразование некоторых атрибутов
в элементы.
Код вызывает ConvertAddress , который возвращает объекты XElement. По отношению к методу аргументом
является запрос, который определяет сложный элемент Address , в котором атрибут Type обладает
значением "Shipping" .
В этом примере используется следующий XML -документ: Пример XML -файла. Типичный заказ на покупку
(LINQ to XML ).
<PO>
<ID>99503</ID>
<DATE>1999-10-20T00:00:00</DATE>
<NAME>Ellen Adams</NAME>
<STREET>123 Maple Street</STREET>
<CITY>Mill Valley</CITY>
<ST>CA</ST>
<POSTALCODE>10999</POSTALCODE>
<COUNTRY>USA</COUNTRY>
</PO>
Практическое руководство. Управление типом
проекции (C#)
25.11.2019 • 2 minutes to read • Edit Online
Проекция - это процесс, включающий выбор одного набора данных, его фильтрацию, изменение его формы
и даже изменение его типа. Почти все выражения запросов выполняют операции проекции. Результатом
вычисления почти всех выражений, представленных в этом разделе, должен быть элемент IEnumerable<T>
XElement, но пользователь может управлять типом проекции для создания коллекций других типов. В
настоящем разделе показано, как это делается.
Пример
В следующем примере определяется новый тип - Customer . Затем выражение запроса создает новые
экземпляры Customer в предложении Select . В результате тип выражения запроса определяется как
IEnumerable<T> Customer .
В этом примере используется следующий XML -документ: Пример XML -файла. Заказчики и заказы (LINQ to
XML ).
public class Customer
{
private string customerID;
public string CustomerID{ get{return customerID;} set{customerID = value;}}
class Program
{
static void Main(string[] args)
{
XElement custOrd = XElement.Load("CustomersOrders.xml");
IEnumerable<Customer> custList =
from el in custOrd.Element("Customers").Elements("Customer")
select new Customer(
(string)el.Attribute("CustomerID"),
(string)el.Element("CompanyName"),
(string)el.Element("ContactName")
);
foreach (Customer cust in custList)
Console.WriteLine(cust);
}
}
См. также
Select
Практическое руководство. Проецирование нового
типа (LINQ to XML) (C#)
23.10.2019 • 2 minutes to read • Edit Online
В других примерах данного раздела показаны запросы, возвращающие результаты в виде значений
IEnumerable<T> типа XElement, значений IEnumerable<T> типа string и значений IEnumerable<T> типа
int . Это наиболее распространенные типы результатов, но подходят не для всех сценариев. Во многих
случаях требуется, чтобы запросы возвращали IEnumerable<T> какого-то другого типа.
Пример
В данном примере показано, как создавать экземпляры объектов в предложении select . Сначала в коде
определяется новый класс с помощью конструктора, а затем модифицируется инструкция select , чтобы это
выражение представляло новый экземпляр нового класса.
В этом примере используется следующий XML -документ: Пример XML -файла. Типичный заказ на покупку
(LINQ to XML ).
class NameQty
{
public string name;
public int qty;
public NameQty(string n, int q)
{
name = n;
qty = q;
}
};
class Program {
public static void Main()
{
XElement po = XElement.Load("PurchaseOrder.xml");
IEnumerable<NameQty> nqList =
from n in po.Descendants("Item")
select new NameQty(
(string)n.Element("ProductName"),
(int)n.Element("Quantity")
);
Lawnmower:1
Baby Monitor:2
Практическое руководство. Проецирование графа
объекта (C#)
23.10.2019 • 3 minutes to read • Edit Online
Данный раздел иллюстрирует способ проецирования, или наполнения, из XML графа объектов.
Пример
В следующем коде происходит заполнение графа объектов классами Address , PurchaseOrder и
PurchaseOrderItem из раздела Пример XML -файла. Типичный заказ на покупку ( LINQ to XML ).
class Address
{
public enum AddressUse
{
Shipping,
Billing,
}
class PurchaseOrderItem
{
private string partNumber;
private string productName;
private int quantity;
private Decimal usPrice;
private string comment;
private DateTime shipDate;
class PurchaseOrder
{
private string purchaseOrderNumber;
private DateTime orderDate;
private string comment;
private List<Address> addresses;
private List<PurchaseOrderItem> items;
class Program {
public static void Main()
{
XElement po = XElement.Load("PurchaseOrder.xml");
PurchaseOrder purchaseOrder = new PurchaseOrder {
PurchaseOrderNumber = (string)po.Attribute("PurchaseOrderNumber"),
OrderDate = (DateTime)po.Attribute("OrderDate"),
Addresses = (
from a in po.Elements("Address")
select new Address {
AddressType = ((string)a.Attribute("Type") == "Shipping") ?
Address.AddressUse.Shipping :
Address.AddressUse.Billing,
Name = (string)a.Element("Name"),
Street = (string)a.Element("Street"),
City = (string)a.Element("City"),
State = (string)a.Element("State"),
Zip = (string)a.Element("Zip"),
Country = (string)a.Element("Country")
}
).ToList(),
Items = (
from i in po.Element("Items").Elements("Item")
select new PurchaseOrderItem {
PartNumber = (string)i.Attribute("PartNumber"),
ProductName = (string)i.Element("ProductName"),
Quantity = (int)i.Element("Quantity"),
USPrice = (Decimal)i.Element("USPrice"),
Comment = (string)i.Element("Comment"),
ShipDate = (i.Element("ShipDate") != null) ?
(DateTime)i.Element("ShipDate") :
DateTime.MinValue
}
).ToList()
};
Console.WriteLine(purchaseOrder);
}
}
В данном примере результат запроса LINQ возвращается в виде IEnumerable<T> класса PurchaseOrderItem .
Элементы в классе PurchaseOrder относятся к типу IEnumerable<T> класса PurchaseOrderItem . В коде
используется метод расширения ToList для создания коллекции List<T> исходя из результатов запроса.
Пример выводит следующие результаты:
PurchaseOrderNumber: 99503
OrderDate: 10/20/1999
Addresses
=====
Type: Shipping
Name: Ellen Adams
Street: 123 Maple Street
City: Mill Valley
State: CA
Zip: 10999
Country: USA
Type: Billing
Name: Tai Yee
Street: 8 Oak Avenue
City: Old Town
State: PA
Zip: 95819
Country: USA
Items
=====
PartNumber: 872-AA
ProductName: Lawnmower
Quantity: 1
USPrice: 148.95
Comment: Confirm this is electric
PartNumber: 926-AA
ProductName: Baby Monitor
Quantity: 2
USPrice: 39.98
ShipDate: 5/21/1999
См. также
Select
ToList
Практическое руководство. Проецирование
анонимного типа (C#)
23.10.2019 • 2 minutes to read • Edit Online
В некоторых случаях может потребоваться проецировать запрос на новый тип, даже если известно, что этот
тип будет использоваться недолго. Создание нового типа для использования в проекции требует много
дополнительной работы. Гораздо более эффективный подход заключается в проецировании на анонимный
тип. Анонимные типы позволяют определять класс и инициализировать его объект, не присваивая имя этому
классу.
Анонимные типы являются реализацией в языке C# математического понятия кортежа. Математический
термин «кортеж» обозначает одноэлементные, двухэлементные, трехэлементные, четырехэлементные,
пятиэлементные и n-элементные последовательности. Он относится к конечной последовательности
объектов, каждый из которых имеет конкретный тип. Иногда такую последовательность называют списком
пар «имя/значение». Например, содержимое адреса в XML -документе Пример XML -файла. Типичный заказ
на покупку (LINQ to XML ) можно представить следующим образом:
Создание экземпляра анонимного типа удобно трактовать как создание n-элементного кортежа. При
написании запроса, создающего кортеж в предложении select , запрос возвращает объект IEnumerable
кортежа.
Пример
В этом примере предложение select проецирует анонимный тип. Затем в этом примере используется тип
var для создания объекта IEnumerable . В цикле foreach переменная итерации становится экземпляром
анонимного типа, созданного в выражении запроса.
В этом примере используется следующий XML -документ: Пример XML -файла. Заказчики и заказы (LINQ to
XML ).
В этом примере показано, как создавать файл с разделителями-запятыми (csv) из XML -файла.
Пример
Приведенная версия этого примера на C# использует синтаксис метода и оператор Aggregate для создания
файла CSV из XML -документа в одном выражении. Дополнительные сведения см. в разделе Синтаксис
запросов и синтаксис методов в LINQ.
В этом примере используется следующий XML -документ: Пример XML -файла. Заказчики и заказы (LINQ to
XML ).
См. также
Проекции и преобразования (LINQ to XML ) (C#)
Практическое руководство. Создание XML из
CSV-файлов (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом примере показано, как LINQ и LINQ to XML используются для создания XML -документа из файла с
разделителями-запятыми (.csv).
Пример
В следующем коде выполняется запрос LINQ по массиву строк.
Запрос использует предложение let , чтобы разбить каждую строку на массив полей.
Элемент или атрибут в XML -документе может иногда относиться к другому элементу или атрибуту.
Например, XML -документ из раздела Пример XML -файла. Заказчики и заказы (LINQ to XML ) содержит
список заказчиков и список заказов. Каждый элемент Customer содержит атрибут CustomerID . Каждый
элемент Order содержит элемент CustomerID . Элемент CustomerID для каждого заказа ссылается на атрибут
CustomerID , присвоенный также и клиенту.
Раздел Пример XSD -файла. Заказчики и заказы содержит XSD -файл, с помощью которого можно проверить
этот документ. Здесь используются функции XSD xs:key и xs:keyref для установления того, что атрибут
CustomerID элемента Customer является ключом, а также для установления связи между элементом
CustomerID каждого из элементов Order и атрибутом CustomerID каждого из элементов Customer .
В LINQ to XML можно воспользоваться преимуществами таких связей элементов и атрибутов при помощи
предложения join .
Обратите внимание, что, поскольку индекс недоступен, такое соединение будет сильно загружать среду.
Более подробные сведения о функции join см. в разделе Операции соединения (C#).
Пример
В следующем примере выполняется соединение элементов Customer с элементами Order и создается новый
XML -документ, который включает элемент CompanyName в заказы.
Перед выполнением запроса в примере выполняется проверка созданного документа по схеме, приведенной
в разделе Пример XSD -файла. Заказчики и заказы. Это позволяет обеспечить бесперебойную работу
предложения соединения.
В этом запросе сначала выполняется получение всех элементов Customer , а затем присоединение их к
элементам Order . Он отбирает только заказы от клиентов со значением CustomerID больше «K». После этого
выполняется проецирование нового элемента Order , который содержит сведения о клиентах внутри
каждого заказа.
В этом примере используется следующий XML -документ: Пример XML -файла. Заказчики и заказы (LINQ to
XML ).
В этом примере используется следующая XSD -схема: Пример XSD -файла. Заказчики и заказы.
Обратите внимание, что такое соединение не будет работать оптимально. Соединение осуществляется путем
линейного поиска. Не используются хэш-таблицы и индексы, которые могли бы помочь оптимизировать
производительность.
XmlSchemaSet schemas = new XmlSchemaSet();
schemas.Add("", "CustomersOrders.xsd");
if (!errors)
{
// Join customers and orders, and create a new XML document with
// a different shape.
Этот пример показывает, как группировать данные и затем создавать код XML на основе группирования.
Пример
В этом примере сначала данные группируются по категориям, а затем создается новый XML -файл, в котором
XML -иерархия отражает группирование.
В этом примере используется следующий XML -документ: Пример XML -файла. Числовые данные (LINQ to
XML ).
В этом разделе описываются методы расширения, обеспечивающие запрос XML -дерева с помощью XPath.
Подробные сведения об использовании данных методов расширения см. в разделе
System.Xml.XPath.Extensions.
Рекомендуется использовать XPath с LINQ to XML, только если есть основательная причина для применения
запросов на основе XPath, такая как широкое использование кода прежних версий. Запросы XPath не
действуют столь эффективно, как запросы LINQ to XML.
Пример
В следующем примере создается небольшое XML -дерево и для получения набора элементов используется
запрос XPathSelectElements.
<Child2>4</Child2>
<Child2>5</Child2>
<Child2>6</Child2>
Практическое руководство. Написание метода оси
LINQ to XML (C#)
23.10.2019 • 4 minutes to read • Edit Online
Можно написать свои собственные методы оси для получения коллекций из XML -дерева. Один из лучших
способов выполнения этого состоит в написании метода расширения, возвращающего коллекцию элементов
или атрибутов. Метод расширения можно написать для возврата указанных поднаборов элементов или
атрибутов с учетом требований приложения.
Пример
В следующем примере используются два метода расширения. Первый метод расширения, GetXPath ,
действует применительно к XObject и возвращает выражение XPath, которое после его вычисления
возвращает узел или атрибут. Второй метод расширения, Find , применяется к XElement. Он возвращает
коллекцию объектов XAttribute и XElement, содержащих некоторый указанный текст.
В этом примере используется следующий XML -документ: Пример XML -файла. Несколько заказов на покупку
(LINQ to XML ).
class Program
{
static void Main(string[] args)
{
XElement purchaseOrders = XElement.Load("PurchaseOrders.xml");
IEnumerable<XObject> subset =
from xobj in purchaseOrders.Find("1999")
select xobj;
/PurchaseOrders/PurchaseOrder[1]/@OrderDate
1999-10-20
/PurchaseOrders/PurchaseOrder[1]/Items/Item[2]/ShipDate
1999-05-21
/PurchaseOrders/PurchaseOrder[2]/@OrderDate
1999-10-22
/PurchaseOrders/PurchaseOrder[3]/@OrderDate
1999-10-22
Практическое руководство. Выполнение потоковых
преобразований текста в XML (C#)
23.10.2019 • 2 minutes to read • Edit Online
Одним из вариантов обработки текстового файла является написание метода расширения, который
обрабатывает текстовый файл построчно при помощи конструкции yield return . Затем можно будет
написать запрос LINQ, обрабатывающий текстовый файл в отложенной манере. При использовании объекта
XStreamingElement для формирования выходного потока можно создать преобразование из текстового
файла в XML, которое будет использовать минимальный объем памяти независимо от размера исходного
текстового файла.
Следует сказать несколько слов о потоковых преобразованиях. Потоковое преобразование лучше всего
применять в ситуациях, когда можно обработать весь файл один раз, а также если можно обработать строки
в том порядке, в котором они расположены в исходном документе. Если файл требуется обрабатывать более
одного раза или необходимо сортировать строки перед обработкой, потоковый метод теряет многие из своих
преимуществ.
Пример
В качестве исходного для этого примера используется текстовый файл People.txt.
#This is a comment
1,Tai,Yee,Writer
2,Nikolay,Grachev,Programmer
3,David,Wright,Inventor
Следующий код содержит метод расширения, который обрабатывает строки текстового файла в отложенной
манере.
public static class StreamReaderSequence
{
public static IEnumerable<string> Lines(this StreamReader source)
{
if (source == null)
throw new ArgumentNullException(nameof(source));
string line;
while ((line = source.ReadLine()) != null)
{
yield return line;
}
}
}
class Program
{
static void Main(string[] args)
{
var sr = new StreamReader("People.txt");
var xmlTree = new XStreamingElement("Root",
from line in sr.Lines()
let items = line.Split(',')
where !line.StartsWith("#")
select new XElement("Person",
new XAttribute("ID", items[0]),
new XElement("First", items[1]),
new XElement("Last", items[2]),
new XElement("Occupation", items[3])
)
);
Console.WriteLine(xmlTree);
sr.Close();
}
}
<Root>
<Person ID="1">
<First>Tai</First>
<Last>Yee</Last>
<Occupation>Writer</Occupation>
</Person>
<Person ID="2">
<First>Nikolay</First>
<Last>Grachev</Last>
<Occupation>Programmer</Occupation>
</Person>
<Person ID="3">
<First>David</First>
<Last>Wright</Last>
<Occupation>Inventor</Occupation>
</Person>
</Root>
См. также
XStreamingElement
Практическое руководство. Перечисление всех
узлов в дереве (C#)
23.10.2019 • 5 minutes to read • Edit Online
Иногда нужно вывести список всех узлов дерева. Это полезно, если требуется узнать, как метод или свойство
повлияли на дерево. Одним из подходов для вывода списка всех узлов в текстовом формате является
создание выражения XPath, которое точно определяет каждый конкретный узел дерева.
Не слишком удобно выполнять выражения XPath с помощью LINQ to XML. Выражения XPath имеют более
низкую производительность, чем запросы LINQ to XML, к тому же запросы LINQ to XML являются более
мощными. Однако для определения узлов в XML -дереве выражения XPath подходят хорошо.
Пример
В этом примере показана функция с именем GetXPath , создающая конкретное выражение XPath для каждого
узла XML -дерева. Она формирует соответствующие выражения XPath, даже если узлы находятся в
пространстве имен. Выражения XPath создаются с помощью префиксов пространства имен.
Затем пример создает небольшое XML -дерево, содержащее несколько типов узлов. После этого он проходит
по узлам-потомкам и выводит выражение XPath для каждого узла.
Обратите внимание, что XML -декларация не является узлом дерева.
Следующий XML -файл содержит несколько типов узлов.
Ниже приводится список узлов в XML -дереве, представленном в виде выражений XPath.
/processing-instruction()
/Root
/Root/@AttName
/Root/@xmlns:aw
/Root/comment()
/Root/Child[1]
/Root/Child[1]/text()
/Root/Child[2]
/Root/Child[2]/text()
/Root/ChildWithMixedContent
/Root/ChildWithMixedContent/text()[1]
/Root/ChildWithMixedContent/b
/Root/ChildWithMixedContent/b/text()
/Root/ChildWithMixedContent/text()[2]
/Root/aw:ElementInNamespace
/Root/aw:ElementInNamespace/aw:ChildInNamespace
class Program
{
static void Main(string[] args)
{
XNamespace aw = "http://www.adventure-works.com";
XDocument doc = new XDocument(
new XDeclaration("1.0", "utf-8", "yes"),
new XProcessingInstruction("target", "data"),
new XElement("Root",
new XAttribute("AttName", "An Attribute"),
new XAttribute(XNamespace.Xmlns + "aw", aw.ToString()),
new XComment("This is a comment"),
new XElement("Child",
new XText("Text")
),
new XElement("Child",
new XText("Other Text")
),
new XElement("ChildWithMixedContent",
new XText("text"),
new XElement("b", "BoldText"),
new XText("otherText")
),
new XElement(aw + "ElementInNamespace",
new XElement(aw + "ChildInNamespace")
)
)
);
doc.Save("Test.xml");
Console.WriteLine(File.ReadAllText("Test.xml"));
Console.WriteLine("------");
foreach (XObject obj in doc.DescendantNodes())
{
Console.WriteLine(obj.GetXPath());
XElement el = obj as XElement;
if (el != null)
foreach (XAttribute at in el.Attributes())
Console.WriteLine(at.GetXPath());
}
}
}
В этом примере выводятся следующие данные:
В этом разделе представлен пример, открывающий документ Office Open XML и получающий коллекцию
всех абзацев документа.
Дополнительные сведения об Office Open XML см. в руководстве по пакету SDK для Open XML и на сайте
www.ericwhite.com.
Пример
В этом примере открывается пакет Office Open XML и используются связи внутри пакета Open XML для
поиска документа и секций стилей. Затем в этом примере выполняется запрос к документу и создается
проекция коллекции анонимного типа, содержащая узел XElement абзаца, имя стиля каждого абзаца и текст
каждого абзаца.
В этом примере используется метод расширения с именем StringConcatenate , который также показан в
примере.
Подробный учебник, объясняющий работу этого примера, см. в разделе Чистые функциональные
преобразования XML (C#).
В этом примере используются классы, находящиеся в сборке WindowsBase. Используются типы из
пространства имен System.IO.Packaging.
class Program
{
public static string ParagraphText(XElement e)
{
XNamespace w = e.Name.Namespace;
return e
.Elements(w + "r")
.Elements(w + "t")
.StringConcatenate(element => (string)element);
}
string defaultStyle =
(string)(
from style in styleDoc.Root.Elements(w + "style")
from style in styleDoc.Root.Elements(w + "style")
where (string)style.Attribute(w + "type") == "paragraph" &&
(string)style.Attribute(w + "default") == "1"
select style
).First().Attribute(w + "styleId");
При запуске с образцом документа Open XML, описанным в разделе Создание исходного документа в
формате Office Open XML (C#), этот пример выводит следующие результаты:
В этом разделе представлен пример, в котором открывается документ Office Open XML, затем он изменяется
и сохраняется.
Дополнительные сведения об Office Open XML см. в руководстве по пакету SDK для Open XML и на сайте
www.ericwhite.com.
Пример
В этом примере осуществляется поиск первого абзаца документа. В примере происходит получение текста из
этого абзаца, а затем все текстовые прогоны в абзаце удаляются. Создается новый текстовый прогон,
состоящий из текста первого абзаца, преобразованного в верхний регистр. Затем измененный XML
сериализуется в пакет Open XML и закрывается.
В этом примере используются классы, находящиеся в сборке WindowsBase. Используются типы из
пространства имен System.IO.Packaging.
class Program
class Program
{
public static string ParagraphText(XElement e)
{
XNamespace w = e.Name.Namespace;
return e
.Elements(w + "r")
.Elements(w + "t")
.StringConcatenate(element => (string)element);
}
if (styleRelation != null)
{
Uri styleUri = PackUriHelper.ResolvePartUri(documentUri, styleRelation.TargetUri);
stylePart = wdPackage.GetPart(styleUri);
paraNode.Add(
new XElement(w + "r",
new XElement(w + "t", paraText.ToUpper())
)
);
Если открыть файл SampleDoc.docx после выполнения этой программы, можно будет увидеть, что программа
преобразовала первый абзац в документе в верхний регистр.
При запуске с образцом документа Open XML, описанным в разделе Создание исходного документа в
формате Office Open XML (C#), этот пример выводит следующие результаты:
Пример
В следующем примере происходит заполнение XML -дерева из локальной файловой системы при помощи
рекурсии. Затем выполняется запрос по дереву и вычисляется общий размер всех файлов в дереве.
class Program
{
static XElement CreateFileSystemXmlTree(string source)
{
DirectoryInfo di = new DirectoryInfo(source);
return new XElement("Dir",
new XAttribute("Name", di.Name),
from d in Directory.GetDirectories(source)
select CreateFileSystemXmlTree(d),
from fi in di.GetFiles()
select new XElement("File",
new XElement("Name", fi.Name),
new XElement("Length", fi.Length)
)
);
}
XPath и LINQ to XML имеют некоторые сходные функциональные возможности. И то и другое можно
использовать, чтобы запрашивать XML -дерево для возвращения таких результатов, как коллекция элементов,
коллекция атрибутов, коллекция узлов или значение элемента или атрибута. Однако между ними также есть
некоторые различия.
Упорядочение результатов
В документе XPath 1.0 Recommendation указано, что коллекция, которая является результатом вычисления
выражения XPath, не упорядочена.
Однако при просмотре коллекции, возвращенной методом оси XPath LINQ to XML, можно отметить, что
узлы в коллекции возвращены в том же порядке, что и в документе. И они располагаются так даже при
доступе к осям XPath, где предикаты выражены в обратном порядке по отношению к документу, например
preceding и preceding-sibling .
В отличие от этого, большинство осей LINQ to XML возвращают коллекции в порядке, соответствующем
документу, однако две из них, Ancestors и AncestorsAndSelf, возвращают коллекции в порядке, обратном по
отношению к документу. В следующей таблице перечисляются эти оси и указывается порядок коллекций для
каждой из них.
Позиционные предикаты
В выражении XPath позиционные предикаты представляются с учетом порядка расположения в документе
для многих осей, однако они располагаются в порядке, обратном по отношению к документу, для обратных
осей, а именно preceding , preceding-sibling , ancestor и ancestor-or-self . Например, выражение XPath
preceding-sibling::*[1] возвращает ближайший предшествующий одноуровневый элемент. Так происходит
даже несмотря на то, что итоговый результирующий набор представляется в порядке документа.
В отличие от этого все позиционные предикаты в LINQ to XML всегда выражаются в порядке оси. Например,
anElement.ElementsBeforeSelf().ElementAt(0) возвращает первый дочерний элемент родителя запрашиваемого
элемента, а не ближайший предшествующий одноуровневый элемент. Другой пример:
anElement.Ancestors().ElementAt(0) возвращает родительский элемент.
Если вам необходимо найти ближайший предшествующий элемент в LINQ to XML, следует использовать
выражение:
ElementsBeforeSelf().Last()
ElementsBeforeSelf().Last()
Различия в производительности
Запросы XPath, которые используют функциональность XPath в LINQ to XML, менее производительны по
сравнению с запросами LINQ to XML.
Сравнение композиций
Состав запроса LINQ to XML немного напоминает состав выражения XPath, хотя по синтаксису они
совершенно различны.
Например, если в переменной с именем customers имеется элемент и требуется найти внучатый элемент с
именем CompanyName во всех дочерних элементах с именем Customer , то нужно написать следующее
выражение XPath:
customers.XPathSelectElements("./Customer/CompanyName")
customers.XPathSelectElements("./Customer/CompanyName")
customers.Elements("Customer").Elements("CompanyName")
customers.Elements("Customer").Elements("CompanyName")
или
XElement.Attributes
или
XContainer.DescendantNodes
descendant-or-self XElement.DescendantsAndSelf
или
XElement.DescendantNodesAndSelf
ОСЬ XPATH ОСЬ LINQ TO XML
following-sibling XNode.ElementsAfterSelf
или
XNode.NodesAfterSelf
preceding-sibling XNode.ElementsBeforeSelf
или
XNode.NodesBeforeSelf
В этом разделе сравнивается ось дочерних элементов XPath с методом LINQ to XMLElement.
Выражение XPath - DeliveryNotes .
Пример
В этом примере производится поиск дочернего элемента DeliveryNotes .
В этом примере используется следующий XML -документ: Пример XML -файла. Несколько заказов на покупку
(LINQ to XML ).
// XPath expression
XElement el2 = po.XPathSelectElement("DeliveryNotes");
// same as "child::DeliveryNotes"
// same as "./DeliveryNotes"
if (el1 == el2)
Console.WriteLine("Results are identical");
else
Console.WriteLine("Results differ");
Console.WriteLine(el1);
В этом разделе проводится сравнение оси дочерних элементов XPath с осью LINQ to XMLElements.
Выражение XPath: ./* .
Пример
В этом примере осуществляется поиск всех дочерних элементов элемента Address .
В этом примере используется следующий XML -документ: Пример XML -файла. Несколько заказов на покупку
(LINQ to XML ).
// XPath expression
IEnumerable<XElement> list2 = po.XPathSelectElements("./*");
В этом разделе показан поиск корневого элемента с помощью XPath и LINQ to XML.
Выражение XPath:
/PurchaseOrders
Пример
В этом примере осуществляется поиск корневого элемента.
В этом примере используется следующий XML -документ: Пример XML -файла. Несколько заказов на покупку
(LINQ to XML ).
XDocument po = XDocument.Load("PurchaseOrders.xml");
// XPath expression
XElement el2 = po.XPathSelectElement("/PurchaseOrders");
if (el1 == el2)
Console.WriteLine("Results are identical");
else
Console.WriteLine("Results differ");
Console.WriteLine(el1.Name);
Пример
В этом примере обнаруживаются все потомки с именем Name .
В этом примере используется следующий XML -документ: Пример XML -файла. Несколько заказов на покупку
(LINQ to XML ).
XDocument po = XDocument.Load("PurchaseOrders.xml");
// XPath expression
IEnumerable<XElement> list2 = po.XPathSelectElements("//Name");
В этом разделе показано, как получать элементы-потомки с указанным именем и атрибут с заданным
значением.
Выражение XPath:
.//Address[@Type='Shipping']
Пример
В этом примере обнаруживаются все элементы-потомки с именем Address и с атрибутом Type , имеющим
значение «Доставка».
В этом примере используется следующий XML -документ: Пример XML -файла. Несколько заказов на покупку
(LINQ to XML ).
XDocument po = XDocument.Load("PurchaseOrders.xml");
// XPath expression
IEnumerable<XElement> list2 = po.XPathSelectElements(".//Address[@Type='Shipping']");
В этом разделе показано, как возвращать элемент, выбирая атрибут, обращение к которому осуществляется с
помощью значения другого элемента.
Выражение XPath:
.//Customer[@CustomerID=/Root/Orders/Order[12]/CustomerID]
Пример
В этом примере обнаруживается 12-й элемент Order , а затем определяется клиент, сделавший этот заказ.
Обратите внимание, что индексирование в списках .NET начинается с нуля. Индексирование в коллекции
узлов в предикате XPath начинается с единицы. Данное различие находит отражение в следующем примере.
В этом примере используется следующий XML -документ: Пример XML -файла. Заказчики и заказы (LINQ to
XML ).
XDocument co = XDocument.Load("CustomersOrders.xml");
// XPath expression
XElement customer3 = co.XPathSelectElement(
".//Customer[@CustomerID=/Root/Orders/Order[12]/CustomerID]");
Выражения XPath позволяют находить узлы в конкретном пространстве имен. Для указания пространств
имен в выражениях XPath используются префиксы пространств имен. Для синтаксического анализа
выражения XPath, содержащего префиксы пространств имен, необходимо передать объект методу XPath,
реализующему IXmlNamespaceResolver. В этом примере используется XmlNamespaceManager.
Выражение XPath:
./aw:*
Пример
В следующем примере считывается XML -дерево, содержащее два пространства имен. Для считывания XML -
документа используется XmlReader. Затем в примере происходит получение XmlNameTable из XmlReader и
XmlNamespaceManager из XmlNameTable. При выделении элементов используется XmlNamespaceManager.
В этом разделе сравнивается ось XPath preceding-sibling с дочерней для LINQ to XML осью
XNode.ElementsBeforeSelf.
Выражение XPath:
preceding-sibling::*
Пример
В следующем примере находится элемент FullAddress , после чего при помощи оси preceding-sibling
получаются предыдущие элементы.
В этом примере используется следующий XML -документ: Пример XML -файла. Заказчики и заказы (LINQ to
XML ).
XElement co = XElement.Load("CustomersOrders.xml");
// XPath expression
IEnumerable<XElement> list2 = add.XPathSelectElements("preceding-sibling::*");
Пример
В этом примере имитируются проблемы извлечения текста из XML -представления документа текстового
редактора. В нем сначала выделяются все элементы Paragraph , а затем в нем выделяются все элементы-
потомки Text каждого элемента Paragraph . В этом примере элементы-потомки Text элемента Comment не
выделяются.
XElement root = XElement.Parse(
@"<Root>
<Paragraph>
<Text>This is the start of</Text>
</Paragraph>
<Comment>
<Text>This comment is not part of the paragraph text.</Text>
</Comment>
<Paragraph>
<Annotation Emphasis='true'>
<Text> a sentence.</Text>
</Annotation>
</Paragraph>
<Paragraph>
<Text> This is a second sentence.</Text>
</Paragraph>
</Root>");
// XPath expression
string str2 =
((IEnumerable)root.XPathEvaluate("./Paragraph//Text/text()"))
.Cast<XText>()
.Select(s => s.Value)
.Aggregate(
new StringBuilder(),
(s, i) => s.Append(i),
s => s.ToString()
);
if (str1 == str2)
Console.WriteLine("Results are identical");
else
Console.WriteLine("Results differ");
Console.WriteLine(str2);
XPath позволяет найти объединение результатов определения двух путей доступа XPath.
Выражение XPath:
//Category|//Price
Пример
В этом примере осуществляется поиск всех элементов Category и всех элементов Price и объединение их в
одну коллекцию. Обратите внимание, что в запросе LINQ to XML вызывается метод InDocumentOrder для
упорядочения результатов. Эти результаты вычисления выражения XPath также представлены в той же
последовательности, что и в документе.
В этом примере используется следующий XML -документ: Пример XML -файла. Числовые данные (LINQ to
XML ).
// XPath expression
IEnumerable<XElement> list2 = data.XPathSelectElements("//Category|//Price");
Допустим, что требуется найти все одноуровневые элементы с указанным именем. Полученная в результате
коллекция может содержать контекстный узел, если этот контекстный узел также имеет указанное имя.
Выражение XPath:
../Book
Пример
В этом примере вначале осуществляется поиск элемента Book , а затем всех одноуровневых элементов с
именем Book . Полученная в результате коллекция содержит контекстный узел.
В этом примере используется следующий XML -документ: Пример XML -файла. Книги (LINQ to XML ).
XElement book =
books
.Root
.Elements("Book")
.Skip(1)
.First();
// XPath expression
IEnumerable<XElement> list2 = book.XPathSelectElements("../Book");
Данный раздел показывает способ перехода к родительскому элементу и нахождения его атрибута.
Выражение XPath:
../@id
Пример
Сначала в данном примере осуществляется поиск элемента Author . Затем в нем осуществляется поиск
атрибута id родительского элемента.
В этом примере используется следующий XML -документ: Пример XML -файла. Книги (LINQ to XML ).
XElement author =
books
.Root
.Element("Book")
.Element("Author");
// XPath expression
XAttribute att2 = ((IEnumerable)author.XPathEvaluate("../@id")).Cast<XAttribute>().First();
if (att1 == att2)
Console.WriteLine("Results are identical");
else
Console.WriteLine("Results differ");
Console.WriteLine(att1);
В этом разделе показано, как найти все атрибуты одноуровневых элементов контекстного узла. В коллекции
возвращаются только атрибуты с заданным именем.
Выражение XPath:
../Book/@id
Пример
В этом примере вначале происходит поиск элемента Book , затем всех одноуровневых элементов с именем
Book , а после этого всех атрибутов с именем id . Результатом становится коллекция атрибутов.
В этом примере используется следующий XML -документ: Пример XML -файла. Книги (LINQ to XML ).
XElement book =
books
.Root
.Element("Book");
// XPath expression
IEnumerable<XAttribute> list2 =
((IEnumerable)book.XPathEvaluate("../Book/@id")).Cast<XAttribute>();
Иногда может понадобиться найти все элементы, имеющие конкретный атрибут. Вас не интересует
содержимое атрибута. Вместо этого вы хотите сделать выбор на основании самого существования атрибута.
Выражение XPath:
./*[@Select]
Пример
Следующий код осуществляет выборку элементов с атрибутом Select .
// XPath expression
IEnumerable<XElement> list2 =
((IEnumerable)doc.XPathEvaluate("./*[@Select]")).Cast<XElement>();
Иногда требуется найти элементы на основании их позиции. Может понадобиться найти второй элемент или
найти третий элемент через пятый.
Выражение XPath:
Test[position() >= 2 and position() <= 4]
Существует два простых подхода к написанию этого запроса LINQ to XML. Можно использовать операторы
Skip и Take или перегруженный оператор Where, который потребляет индекс. При использовании перегрузки
оператора Where необходимо использовать лямбда-выражение, которое имеет два аргумента. Следующий
пример показывает обе возможности выбора на основе позиции.
Пример
В данном примере производится поиск второго элемента через четвертый элемент Test . Результатом
является коллекция элементов.
В этом примере используется следующий XML -документ: Пример XML -файла. Конфигурация тестирования
(LINQ to XML ).
// XPath expression
IEnumerable<XElement> list3 =
testCfg.XPathSelectElements("Test[position() >= 2 and position() <= 4]");
Иногда требуется найти ближайший предшествующий одноуровневый элемент узла. Из-за разности в
семантике позиционных предикатов для осей предшествующих одноуровневых элементов в XPath и в LINQ
to XML это сравнение является одним из наиболее интересных.
Пример
В этом примере в запросе LINQ to XML оператор Last используется для обнаружения последнего узла в
коллекции, возвращенной методом ElementsBeforeSelf. В отличие от этого, в выражении XPath для
обнаружения ближайшего предшествующего элемента используется предикат со значением 1.
// XPath expression
XElement el2 =
((IEnumerable)child4
.XPathEvaluate("preceding-sibling::*[1]"))
.Cast<XElement>()
.First();
if (el1 == el2)
Console.WriteLine("Results are identical");
else
Console.WriteLine("Results differ");
Console.WriteLine(el1);
В этом разделе
РАЗДЕЛ ОПИСАНИЕ
IMPORTANT
До конца данного учебника термин «чистая возможность» используется в основном для указания подхода к
программированию, а не специальной характеристики языка.
Отметим, что чистые функции должны быть реализованы как методы в языке C#.
Также не надо путать чистые функции с чисто виртуальными методами в C++. Второй случай показывает, что
содержащийся класс абстрактный и текст метода не указан.
Функциональное программирование
Термин функциональное программирование обозначает такой подход к программированию, при котором
непосредственно поддерживаются чисто функциональные преобразования.
Исторически языки функционального программирования общего назначения, такие как ML, Scheme, Haskell
и F#, представляли интерес в первую очередь для научного сообщества. Хотя на языке C# всегда можно
было создавать чисто функциональные преобразования, эта возможность не привлекала большинство
программистов из-за своей сложности. В последних версиях C#, однако, новые конструкции языка, такие как
лямбда-выражения и определение типов, сделали функциональное программирование гораздо более
простым и продуктивным.
Дополнительные сведения о функциональном программировании см. в разделе Сравнение
функционального и императивного программирования (C#).
Предметные языки функционального программирования
Не смотря на то что основные языки функционального программирования широко не используются,
специальные предметные языки функционального программирования имеют больший успех. Например,
каскадные таблицы стилей (CSS ) используются для определения вида многих веб-страниц, а преобразования
XSLT широко используются в обработке XML -данных. Дополнительные сведения об XSLT см. в разделе
Преобразования XSLT.
Терминология
Следующие таблицы определяют некоторые термины, связанные с функциональными преобразованиями.
функция высокого порядка (первого класса)
Функция, которая может быть интерпретирована как программный объект. Например, функция высокого
порядка может быть передана и возвращена другими функциями. В C# делегаты и лямбда-выражения
являются компонентами языка, которые поддерживают функции высокого порядка. Для написания функции
высокого порядка необходимо объявить один или несколько аргументов для принятия делегатов, при этом
обычно используются лямбда-выражения. Многие стандартные операторы запроса являются функциями
высокого порядка.
Дополнительные сведения см. в разделе Общие сведения о стандартных операторах запроса (C#).
лямбда-выражение
По сути это встроенная анонимная функция, которая может использоваться, когда тип делегата неизвестен.
Это упрощенное значение лямбда-выражений, однако оно достаточно для целей данного учебника.
Дополнительные сведения см. в разделе Лямбда-выражения.
коллекция
Структурированный набор данных обычно стандартного типа. Для обеспечения совместимости с LINQ
коллекция должна реализовывать интерфейс IEnumerable или интерфейс IQueryable (или один из их
основных прототипов IEnumerator<T> или IQueryable<T>).
кортеж (анонимные типы)
Как математическое понятие, кортеж представляет собой конечную последовательность объектов, каждый
определенного типа. Кортеж называется также упорядоченным списком. Анонимные типы представляют
собой языковую реализацию этой концепции, которая позволяет объявить класс неименованного типа и
одновременно создать экземпляр объекта этого типа.
Дополнительные сведения см. в статье Анонимные типы.
определение типов (скрытое типизирование)
Возможность компилятора определить тип переменной при отсутствии явной декларации типа.
Дополнительные сведения см. в статье Implicitly Typed Local Variables (Неявно типизированные локальные
переменные).
отложенное исполнение и отложенные вычисления
Задержка вычисления выражения до тех пор, пока его результат не станет нужен. В коллекциях
поддерживается отложенное исполнение.
Дополнительные сведения см. в разделах Введение в запросы LINQ (C#) и Отложенное выполнение и
отложенное вычисление в LINQ to XML (C#).
Эти языковые возможности будут использованы в образцах кода на протяжении данного раздела.
См. также
Введение в чистые функциональные преобразования (C#)
Сравнение функционального и императивного программирования (C#)
Сравнение функционального и Императивное
программирование (C#)
23.10.2019 • 7 minutes to read • Edit Online
Управление основным потоком Циклы, условия и вызовы функций Вызовы функций, включая
данных (методов). рекурсивные.
Основная единица обработки Экземпляры структур или классов. Функции как полноценные объекты и
коллекции данных.
См. также
Введение в чистые функциональные преобразования (C#)
Преобразования XSLT
Рефакторинг в чистые функции (C#)
Рефакторинг в чистые функции (C#)
23.10.2019 • 5 minutes to read • Edit Online
NOTE
Общим подходом к функциональному программированию является оптимизация кода программ для получения
чистых функций. В Visual Basic и C++ это равносильно использованию функций. Но в C# функции называются
методами. В данном случае чистая функция в C# реализуется как метод.
Как отмечалось ранее в этом разделе, чистые функции имеют две полезные характеристики.
У них отсутствуют побочные эффекты. Функции не изменяют значения или данные любого типа, не
входящие в область их определения.
Функции действуют единообразно. После получения того же набора входных данных они всегда
возвращают одно и то же выходное значение.
Одним из способов перехода к функциональному программированию является оптимизация
существующего кода с целью устранения ненужных побочных эффектов и внешних зависимостей. Таким
образом, из существующего кода создаются чистые функции.
В этом разделе объясняется, что представляет собой чистая функция и чем она не является. В разделе
Учебник. Управление содержимым в документе WordprocessingML (C#) показано управление документом
WordprocessingML и содержатся два примера рефакторинга с помощью чистых функций.
Обратите внимание, что неважно, какой доступ имеют изменяемые данные, public или private , и
являются ли они членом класса static или элементом экземпляра. Чистая функция не изменяет не
относящиеся к ней данные.
Обычная функция , изменяющая аргумент
Более того, следующая версия той же функции представляет собой обычную функцию, поскольку изменяет
содержимое своего параметра sb .
Эта версия программы дает те же выходные данные, что и первая версия, поскольку функция
HyphenatedConcat изменила значение (состояние) своего первого параметра, вызвав функцию-член Append.
Обратите внимание, что это изменение происходит несмотря на то, что функция HyphenatedConcat
использует передачу параметра по значению.
IMPORTANT
Передача параметров по значению для ссылочных типов приводит к созданию копии ссылки на передаваемый
объект. Эта копия все еще связана с теми же данными экземпляра, что и первоначальная ссылка (пока ссылочная
переменная не будет присвоена новому объекту). Вызов по значению необязательно требуется функции для
изменения параметра.
Чистая функция
Следующая версия этой программы реализует функцию HyphenatedConcat в виде чистой функции.
class Program
{
public static string HyphenatedConcat(string s, string appendStr)
{
return (s + '-' + appendStr);
}
И снова она дает ту же строку вывода: StringOne-StringTwo . Обратите внимание, что для сохранения
соединенного значения оно сохраняется в промежуточной переменной s2 .
Одним из полезных подходов является написание функций, которые локально являются обычными (то есть
в них объявляются и изменяются локальные переменные), но глобально остаются чистыми. Такие функции
имеют многие характеристики, благоприятные с точки зрения компоновки, но позволяют обойтись без
использования некоторых более сложных средств функционального программирования, тех, что диктуют,
например, необходимость использовать рекурсию, невзирая на возможность добиться того же результата с
помощью простого цикла.
См. также
Введение в чистые функциональные преобразования (C#)
Сравнение функционального и императивного программирования (C#)
Применимость функциональных преобразований
(C#)
23.10.2019 • 3 minutes to read • Edit Online
См. также
Введение в чистые функциональные преобразования (C#)
Функциональное преобразование XML (C#)
Рефакторинг в чистые функции (C#)
Функциональное преобразование XML (C#)
23.10.2019 • 3 minutes to read • Edit Online
В этом разделе описан подход с использованием чисто функционального преобразования для изменения
XML -документов и производится сравнение его с процедурным подходом.
Изменение XML-документа
Одной из наиболее распространенных задач программиста на XML состоит в том, чтобы преобразовать
XML из одной формы в другую. Форма XML -документа представляет собой структуру документа, которая
включает:
иерархию, выраженную в документе;
имена элементов и атрибутов;
типы данных элементов и атрибутов.
В целом наиболее эффективный подход к преобразованию XML из одной формы в другую состоит в чисто
функциональном преобразовании. При данном подходе основной задачей программиста будет создать
преобразование, которое применяется ко всему XML -документу (к одному или множеству жестко
определенных узлов). Функциональное преобразование является, возможно, самым простым для кода
(после того как программист приобретет навыки работы с данным подходом), дает наилучший для
сопровождения код и часто более компактно, чем другие подходы.
Технологии функционального преобразования XML
Корпорация Майкрософт предлагает две технологии функционального преобразования, которые можно
использовать в XML -документах: XSLT и LINQ to XML. XSLT поддерживается в управляемом пространстве
имен System.Xml.Xsl, а также в собственной COM реализации MSXML. Не смотря на то что XSLT
представляет собой надежную технологию работы с XML -документами, для ее использования требуется
опыт в специализированных областях, а именно в области языка XSLT и поддерживающих его API-
интерфейсов.
В LINQ to XML предусмотрены инструменты, необходимые для чисто функциональных преобразований,
кодируемых выразительно и эффективно внутри кода на языках C# и Visual Basic. Например, многие
примеры в документации LINQ to XML используют чисто функциональный подход. Также в разделе
Учебник. Управление содержимым в документе WordprocessingML (C#) используется функциональный
подход к LINQ to XML для управления сведениями в документе Microsoft Word.
Для более полного сравнения LINQ to XML с другими технологиями Microsoft XML см. раздел Сравнение
LINQ to XML с другими XML -технологиями.
XSLT - это рекомендованный инструмент для преобразований, ориентированных на работу с документами,
когда исходный документ имеет неправильную структуру. Несмотря на это, LINQ to XML также может
осуществлять преобразования, ориентированные на работу с документами. Дополнительные сведения см. в
разделе Практическое руководство. Использование заметок для преобразования деревьев LINQ to XML в
стиль XSLT (C#).
См. также
Введение в чистые функциональные преобразования (C#)
Учебник. Обработка содержимого документа WordprocessingML (C#)
Сравнение LINQ to XML с другими XML -технологиями
Отложенное выполнение и отложенное
вычисление в LINQ to XML (C#)
23.10.2019 • 3 minutes to read • Edit Online
Отложенное выполнение
Отложенное выполнение означает, что вычисление выражения откладывается до тех пор, пока не возникнет
действительная потребность в его реализованном значении. Отложенное выполнение может значительно
повышать производительность в ситуациях, когда приходится манипулировать большими коллекциями
данных, особенно в программах, содержащих ряд последовательных запросов или манипуляций. В лучшем
случае отложенное выполнение обеспечивает только один проход по исходной коллекции.
Технологии LINQ предусматривают широкое использование отложенного выполнения как в членах основных
классов System.Linq, так и в методах расширений в различных пространствах имен LINQ, таких как
System.Xml.Linq.Extensions.
Отложенное выполнение напрямую поддерживается в языке C# с помощью ключевого слова yield
yield-return (в форме оператора ), когда оно используется внутри блока итератора. Такой итератор должен
возвращать коллекцию типа IEnumerator либо IEnumerator<T> (или одного из производных типов).
Следующие шаги
Следующий раздел настоящего учебника иллюстрирует отложенное выполнение:
Пример отложенного выполнения (C#)
См. также
Учебник. Объединение запросов в цепочки (C#)
Основные принципы и терминология (функциональное преобразование) (C#)
Операции агрегирования (C#)
yield
Пример отложенного выполнения (C#)
23.10.2019 • 2 minutes to read • Edit Online
В данном разделе показано влияние отложенного выполнения и отложенного вычисления на запросы LINQ
to XML.
Пример
В следующем примере демонстрируется порядок выполнения при использовании метода расширения, в
котором применяется отложенное выполнение. В этом примере объявляется массив из трех строк. Затем в
нем производится итерация по коллекции, возвращенной ConvertCollectionToUpperCase .
class Program
{
static void Main(string[] args)
{
string[] stringArray = { "abc", "def", "ghi" };
См. также
Учебник. Объединение запросов в цепочки (C#)
Пример связывания запросов (C#)
23.10.2019 • 2 minutes to read • Edit Online
Этот пример основан на предыдущем примере и показывает, что происходит при соединении в цепочку двух
запросов, использующих отложенное выполнение и отложенное вычисление.
Пример
В этом примере представлен другой метод расширения, AppendString , который добавляет указанную строку
в каждую строку исходной коллекции, а затем выдает новые строки.
class Program
{
static void Main(string[] args)
{
string[] stringArray = { "abc", "def", "ghi" };
IEnumerable<string> q1 =
from s in stringArray.ConvertCollectionToUpperCase()
select s;
IEnumerable<string> q2 =
from s in q1.AppendString("!!!")
select s;
В этом примере видно, что каждый метод расширения работает поочередно с каждым элементом исходной
коллекции.
Этот пример показывает, что даже при соединении в цепочку двух запросов, формирующих коллекции,
промежуточные коллекции не материализуются. Вместо этого каждый элемент передается от одного
отложенного метода к другому. Это приводит к использованию намного меньшего объема памяти, чем при
подходе, который сначала принимает один массив строк, затем создает второй массив строк,
преобразованных в символы верхнего регистра, и наконец создает третий массив строк, где каждая строка
имеет добавленные к ней восклицательные знаки.
Следующий раздел учебника иллюстрирует промежуточную материализацию:
Промежуточная материализация (C#)
См. также
Учебник. Объединение запросов в цепочки (C#)
Промежуточная материализация (C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В этом примере внесены изменения по сравнению с предыдущим примером. В методе AppendString
осуществляется вызов ToList до начала итераций по данным источника. Это становится причиной
материализации.
public static class LocalExtensions
{
public static IEnumerable<string>
ConvertCollectionToUpperCase(this IEnumerable<string> source)
{
foreach (string str in source)
{
Console.WriteLine("ToUpper: source >{0}<", str);
yield return str.ToUpper();
}
}
class Program
{
static void Main(string[] args)
{
string[] stringArray = { "abc", "def", "ghi" };
IEnumerable<string> q1 =
from s in stringArray.ConvertCollectionToUpperCase()
select s;
IEnumerable<string> q2 =
from s in q1.AppendString("!!!")
select s;
В этом примере можно видеть, что применение вызова ToList вынуждает метод AppendString сформировать
перечисление по всему источнику, прежде чем возвратить первый элемент. Если источник представляет
собой большой массив, то в результате характер использования памяти приложением существенно
изменится.
Стандартные операторы запроса можно также соединять в виде цепочки. Это показано в последнем разделе
учебника.
Связывание стандартных операторов запросов (C#)
См. также
Учебник. Объединение запросов в цепочки (C#)
Связывание стандартных операторов запросов
(C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В данном примере метод Where вызывается до вызова метода ConvertCollectionToUpperCase . Метод Where
действует практически так же, как отложенные методы, использовавшиеся в предыдущих примерах,
приведенных в этом учебнике, ConvertCollectionToUpperCase и AppendString .
Отличие заключается в том, что в этом случае метод Where просматривает свою исходную коллекцию,
определяет, что первый элемент не передает предикат, а затем переходит к следующему элементу, который
предикат передает. После этого метод выдает второй элемент.
Но базовый принцип остается тем же: промежуточные коллекции материализуются только при
необходимости.
При использовании выражений запросов они преобразуются в вызовы стандартных операторов запросов, а
затем применяется тот же принцип.
Во всех примерах, приведенных в этом разделе, в которых запрашиваются документы Office Open XML,
используется один и тот же принцип. Отложенное выполнение и отложенное вычисление - это
основополагающие принципы, которые необходимо понимать, чтобы эффективно использовать LINQ (и
LINQ to XML ).
public static class LocalExtensions
{
public static IEnumerable<string>
ConvertCollectionToUpperCase(this IEnumerable<string> source)
{
foreach (string str in source)
{
Console.WriteLine("ToUpper: source >{0}<", str);
yield return str.ToUpper();
}
}
class Program
{
static void Main(string[] args)
{
string[] stringArray = { "abc", "def", "ghi" };
IEnumerable<string> q1 =
from s in stringArray.ConvertCollectionToUpperCase()
where s.CompareTo("D") >= 0
select s;
IEnumerable<string> q2 =
from s in q1.AppendString("!!!")
select s;
Отложенное выполнение
Отложенное выполнение означает, что вычисление выражения откладывается до тех пор, пока не
возникнет действительная потребность в его реализованном значении. Отложенное выполнение может
значительно повышать производительность в ситуациях, когда приходится манипулировать большими
коллекциями данных, особенно в программах, содержащих ряд последовательных запросов или
манипуляций. В лучшем случае отложенное выполнение обеспечивает только один проход по исходной
коллекции.
Технологии LINQ предусматривают широкое использование отложенного выполнения как в членах
основных классов System.Linq, так и в методах расширений в различных пространствах имен LINQ, таких
как System.Xml.Linq.Extensions.
Отложенное выполнение напрямую поддерживается в языке C# с помощью ключевого слова yield
yield-return (в форме оператора ), когда оно используется внутри блока итератора. Такой итератор
должен возвращать коллекцию типа IEnumerator либо IEnumerator<T> (или одного из производных
типов).
Следующие шаги
Следующий раздел настоящего учебника иллюстрирует отложенное выполнение:
Пример отложенного выполнения (C#)
См. также
Учебник. Объединение запросов в цепочки (C#)
Основные принципы и терминология (функциональное преобразование) (C#)
Операции агрегирования (C#)
yield
Пример отложенного выполнения (C#)
23.10.2019 • 2 minutes to read • Edit Online
В данном разделе показано влияние отложенного выполнения и отложенного вычисления на запросы LINQ
to XML.
Пример
В следующем примере демонстрируется порядок выполнения при использовании метода расширения, в
котором применяется отложенное выполнение. В этом примере объявляется массив из трех строк. Затем в
нем производится итерация по коллекции, возвращенной ConvertCollectionToUpperCase .
class Program
{
static void Main(string[] args)
{
string[] stringArray = { "abc", "def", "ghi" };
См. также
Учебник. Объединение запросов в цепочки (C#)
Пример связывания запросов (C#)
23.10.2019 • 2 minutes to read • Edit Online
Этот пример основан на предыдущем примере и показывает, что происходит при соединении в цепочку
двух запросов, использующих отложенное выполнение и отложенное вычисление.
Пример
В этом примере представлен другой метод расширения, AppendString , который добавляет указанную строку
в каждую строку исходной коллекции, а затем выдает новые строки.
class Program
{
static void Main(string[] args)
{
string[] stringArray = { "abc", "def", "ghi" };
IEnumerable<string> q1 =
from s in stringArray.ConvertCollectionToUpperCase()
select s;
IEnumerable<string> q2 =
from s in q1.AppendString("!!!")
select s;
В этом примере видно, что каждый метод расширения работает поочередно с каждым элементом исходной
коллекции.
Этот пример показывает, что даже при соединении в цепочку двух запросов, формирующих коллекции,
промежуточные коллекции не материализуются. Вместо этого каждый элемент передается от одного
отложенного метода к другому. Это приводит к использованию намного меньшего объема памяти, чем при
подходе, который сначала принимает один массив строк, затем создает второй массив строк,
преобразованных в символы верхнего регистра, и наконец создает третий массив строк, где каждая строка
имеет добавленные к ней восклицательные знаки.
Следующий раздел учебника иллюстрирует промежуточную материализацию:
Промежуточная материализация (C#)
См. также
Учебник. Объединение запросов в цепочки (C#)
Промежуточная материализация (C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В этом примере внесены изменения по сравнению с предыдущим примером. В методе AppendString
осуществляется вызов ToList до начала итераций по данным источника. Это становится причиной
материализации.
public static class LocalExtensions
{
public static IEnumerable<string>
ConvertCollectionToUpperCase(this IEnumerable<string> source)
{
foreach (string str in source)
{
Console.WriteLine("ToUpper: source >{0}<", str);
yield return str.ToUpper();
}
}
class Program
{
static void Main(string[] args)
{
string[] stringArray = { "abc", "def", "ghi" };
IEnumerable<string> q1 =
from s in stringArray.ConvertCollectionToUpperCase()
select s;
IEnumerable<string> q2 =
from s in q1.AppendString("!!!")
select s;
В этом примере можно видеть, что применение вызова ToList вынуждает метод AppendString сформировать
перечисление по всему источнику, прежде чем возвратить первый элемент. Если источник представляет
собой большой массив, то в результате характер использования памяти приложением существенно
изменится.
Стандартные операторы запроса можно также соединять в виде цепочки. Это показано в последнем
разделе учебника.
Связывание стандартных операторов запросов (C#)
См. также
Учебник. Объединение запросов в цепочки (C#)
Связывание стандартных операторов запросов
(C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В данном примере метод Where вызывается до вызова метода ConvertCollectionToUpperCase . Метод Where
действует практически так же, как отложенные методы, использовавшиеся в предыдущих примерах,
приведенных в этом учебнике, ConvertCollectionToUpperCase и AppendString .
Отличие заключается в том, что в этом случае метод Where просматривает свою исходную коллекцию,
определяет, что первый элемент не передает предикат, а затем переходит к следующему элементу, который
предикат передает. После этого метод выдает второй элемент.
Но базовый принцип остается тем же: промежуточные коллекции материализуются только при
необходимости.
При использовании выражений запросов они преобразуются в вызовы стандартных операторов запросов, а
затем применяется тот же принцип.
Во всех примерах, приведенных в этом разделе, в которых запрашиваются документы Office Open XML,
используется один и тот же принцип. Отложенное выполнение и отложенное вычисление - это
основополагающие принципы, которые необходимо понимать, чтобы эффективно использовать LINQ (и
LINQ to XML ).
public static class LocalExtensions
{
public static IEnumerable<string>
ConvertCollectionToUpperCase(this IEnumerable<string> source)
{
foreach (string str in source)
{
Console.WriteLine("ToUpper: source >{0}<", str);
yield return str.ToUpper();
}
}
class Program
{
static void Main(string[] args)
{
string[] stringArray = { "abc", "def", "ghi" };
IEnumerable<string> q1 =
from s in stringArray.ConvertCollectionToUpperCase()
where s.CompareTo("D") >= 0
select s;
IEnumerable<string> q2 =
from s in q1.AppendString("!!!")
select s;
В этом документе два абзаца. Оба они содержат по одному текстовому периоду, каждый включает один
текстовый фрагмент.
Самый простой способ просмотра содержимого документа WordprocessingML в форме XML состоит в
создании документа Microsoft Word, сохранении этого документа и запуске следующей программы,
выводящей код XML на консоль.
В этом примере используются классы, находящиеся в сборке WindowsBase. Используются типы из
пространства имен System.IO.Packaging.
Внешние ресурсы
Знакомство с форматами файлов Office (2007) Open XML
Общие сведения о WordprocessingML
Структура файла WordProcessingML
Общие сведения о WordprocessingML
Office 2003: страница скачивания ссылочных XML -схем
См. также
Учебник. Обработка содержимого документа WordprocessingML (C#)
Создание исходного документа в формате Office
Open XML (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом разделе показано создание документа Office Open XML WordprocessingML, который используется
в примерах этого учебника. Если следовать приведенным ниже инструкциям, выходные данные будут
соответствовать выходным данным каждого примера.
Тем не менее примеры в этом учебнике работают с любым допустимым документом WordprocessingML.
Чтобы создать документ, который используется в этом учебнике, необходимо иметь установленный
выпуск 2007 системы Microsoft Office или более поздней версии либо Microsoft Office 2003 с пакетом
обеспечения совместимости Microsoft Office для форматов файлов Word, Excel и PowerPoint 2007.
using System;
class Program {
public static void Main(string[] args) {
Console.WriteLine("Hello World");
}
}
Hello World
NOTE
Если используется Microsoft Word 2003, в раскрывающемся списке Тип файла выберите Документ
Word 2007.
Поиск стиля абзаца по умолчанию (C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
ОПИСАНИЕ
В следующем примере открывается документ Office Open XML WordprocessingML, осуществляется поиск
секций с документом и стилями в пакете, а затем выполняется запрос, который находит имя стиля по
умолчанию. Сведения о пакетах документов Office Open XML и частях, из которых они состоят, см. в разделе
Сведения о документах WordprocessingML в формате Office Open XML (C#).
Этот запрос находит узел с именем w:style , который имеет атрибут w:type со значением «paragraph», а
также имеет атрибут w:default со значением «1». Так как XML -узел с этими атрибутами будет только один,
запрос использует оператор Enumerable.First, чтобы преобразовать коллекцию в один элемент. Затем он
возвращает значение атрибута w:styleId .
В этом примере используются классы из сборки WindowsBase. Используются типы из пространства имен
System.IO.Packaging.
Код
const string fileName = "SampleDoc.docx";
// The following query finds all the paragraphs that have the default style.
string defaultStyle =
(string)(
from style in styleDoc.Root.Elements(w + "style")
where (string)style.Attribute(w + "type") == "paragraph"&&
(string)style.Attribute(w + "default") == "1"
select style
).First().Attribute(w + "styleId");
Комментарии
В этом примере выводятся следующие данные:
Следующие шаги
В следующем примере будет создан похожий запрос, который ищет все абзацы в документе и их стили:
Извлечение абзацев и стилей (C#)
Извлечение абзацев и стилей (C#)
23.10.2019 • 5 minutes to read • Edit Online
В этом примере составляется запрос, при котором получаются узлы абзацев из документа
WordprocessingML. Также идентифицируется стиль каждого абзаца.
Этот запрос основан на запросе из предыдущего примера — Поиск стиля абзаца по умолчанию (C#),
который получает стиль по умолчанию из списка стилей. Эти сведения требуются для того, чтобы запрос мог
идентифицировать стиль абзацев, для которых явно не установлен стиль. Стили абзацев устанавливаются
при помощи элемента w:pPr . Если абзац не содержит этот элемент, выполняется форматирование стилем по
умолчанию.
В этом разделе объясняется значение некоторых фрагментов запроса, после чего запрос показывается в
составе целостного рабочего примера.
Пример
Источник этого запроса по получению всех абзацев документа и их стилей таков:
Это выражение напоминает источник запроса предыдущего примера — Поиск стиля абзаца по умолчанию
(C#). Основная разница в том, что здесь используется ось Descendants вместо оси Elements. В запросе
используется ось Descendants, поскольку в документах, в которых имеются разделы, абзацы не будут
прямыми потомками элемента текста, а будут находиться на два уровня ниже в иерархии. За счет
использования оси Descendants этот код будет работать вне зависимости от того, используются разделы или
нет.
Пример
В этом разделе используется предложение let , чтобы определить элемент, содержащий узел стиля. Если
элемент не найден, то styleNode устанавливается в значение null :
Предложение let сначала использует ось Elements для поиска всех элементов с названием pPr , затем
использует метод расширений Elements для поиска всех дочерних элементов с названием pStyle и затем
использует стандартный оператор запросов FirstOrDefault для объединения коллекции в одно целое. Если
коллекция пуста, styleNode устанавливается в значение null . Это выражение полезно для поиска потомков
узла pStyle . Обратите внимание, что, если дочерний узел pPr не существует, код продолжает работать, а не
выводит исключение. Вместо этого узел styleNode устанавливается в значение null , что именно и
требуется для предложения let .
В этом запросе выполняется проецирование коллекции анонимного типа с двумя членами, StyleName и
ParagraphNode .
Пример
В данном примере обрабатывается документ WordprocessingML, из которого извлекаются узлы абзацев.
Также идентифицируется стиль каждого абзаца. Этот пример основан на предыдущих примерах данного
учебника. Новый запрос выявляется в комментариях в нижеприведенном коде.
Инструкции по созданию исходного документа для данного примера можно найти в разделе Создание
исходного документа в формате Office Open XML (C#).
В этом примере используются классы, находящиеся в сборке WindowsBase. Используются типы из
пространства имен System.IO.Packaging.
string defaultStyle =
(string)(
from style in styleDoc.Root.Elements(w + "style")
where (string)style.Attribute(w + "type") == "paragraph"&&
(string)style.Attribute(w + "default") == "1"
select style
).First().Attribute(w + "styleId");
Этот пример выводит следующие результаты, будучи примененным к документу, описанному в разделе
Создание исходного документа в формате Office Open XML (C#).
StyleName:Heading1
StyleName:Normal
StyleName:Normal
StyleName:Normal
StyleName:Code
StyleName:Code
StyleName:Code
StyleName:Code
StyleName:Code
StyleName:Code
StyleName:Code
StyleName:Normal
StyleName:Normal
StyleName:Normal
StyleName:Code
Следующие шаги
В следующем разделе Извлечение текста абзацев (C#) вы создадите запрос для извлечение текста абзацев.
См. также
Учебник. Обработка содержимого документа WordprocessingML (C#)
Извлечение текста абзацев (C#)
04.11.2019 • 5 minutes to read • Edit Online
Этот пример основан на предыдущем примере Извлечение абзацев и стилей (C#). В этом примере текст
каждого абзаца получается в строку.
Чтобы получить текст, в этом примере добавляется дополнительный запрос, который последовательно
проходит по коллекции анонимных типов и проецирует новую коллекцию анонимного типа с добавлением
нового члена, Text . Он использует стандартный оператор запроса Aggregate, чтобы объединить несколько
строк в одну.
Этот способ (т. е. сначала проецирование в коллекцию анонимного типа, затем использование этой
коллекции для проекции в новую коллекцию анонимного типа) широко распространен и полезен. Этот
запрос не удалось бы создать без создания проекции к первому анонимному типу. Однако из-за применения
неспешных вычислений это не требует много дополнительных вычислительных мощностей. Происходит
создание большего количества кратковременных объектов в куче, однако производительность при этом
снижается незначительно.
Конечно, было бы возможным создать единичный запрос, который содержит функциональные возможности
получать абзацы, стиль и текст каждого абзаца. Однако часто полезно разбить более сложный запрос на
несколько запросов, поскольку при этом результирующий код выглядит более модульным и легким для
поддержки. Более того, если потребуется повторно использовать часть запроса, будет легче выполнить
оптимизацию кода.
Эти связанные в цепочку запросы используют модель обработки, подробно рассматриваемую в разделе
Учебник. Объединение запросов в цепочки (C#).
Пример
В этом примере выполняется обработка документа WordprocessingML, определение узла элемента, имени
стиля и текста каждого абзаца. Этот пример основан на предыдущих примерах данного учебника. Новый
запрос выявляется в комментариях в нижеприведенном коде.
Инструкции по созданию исходного документа для этого примера см. в разделе Создание исходного
документа в формате Office Open XML (C#).
В этом примере используются классы из сборки WindowsBase. Используются типы из пространства имен
System.IO.Packaging.
string defaultStyle =
(string)(
from style in styleDoc.Root.Elements(w + "style")
where (string)style.Attribute(w + "type") == "paragraph"&&
(string)style.Attribute(w + "default") == "1"
select style
).First().Attribute(w + "styleId");
Этот пример выводит следующие результаты, будучи примененным к документу, описанному в разделе
Создание исходного документа в формате Office Open XML (C#).
Следующие шаги
В следующем примере показано, как использовать метод расширений вместо Aggregate, чтобы объединить
несколько строк в одну.
Рефакторинг с использованием метода расширения (C#)
См. также
Учебник. Обработка содержимого документа WordprocessingML (C#)
Отложенное выполнение и отложенное вычисление в LINQ to XML (C#)
Рефакторинг с использованием метода
расширения (C#)
23.10.2019 • 5 minutes to read • Edit Online
Этот пример основан на предыдущем примере, Извлечение текста абзацев (C#), и в нем применяется
оптимизация кода объединения строк с помощью чистой функции, которая реализуется в виде метода
расширения.
В предыдущем примере использовался стандартный оператор запроса Aggregate для объединения
нескольких строк в одну. Однако более удобно записывать для этого метод расширения, так как
результирующий запрос становится меньше и проще.
Пример
В этом примере обрабатывается документ WordprocessingML и происходит получение абзацев, стилей и
текста каждого абзаца. Этот пример основан на предыдущих примерах данного учебника.
В примере представлено несколько перегруженных вариантов метода StringConcatenate .
Инструкции по созданию исходного документа для данного примера можно найти в разделе Создание
исходного документа в формате Office Open XML (C#).
В этом примере используются классы из сборки WindowsBase. Используются типы из пространства имен
System.IO.Packaging.
public static class LocalExtensions
{
public static string StringConcatenate(this IEnumerable<string> source)
{
StringBuilder sb = new StringBuilder();
foreach (string s in source)
sb.Append(s);
return sb.ToString();
}
Пример
Рассматриваются четыре перегруженных варианта метода StringConcatenate . Первый перегруженный метод
принимает коллекцию строк и возвращает одну строку. Следующий перегруженный метод может
принимать коллекцию любого типа и делегировать эти проекты из одного элемента коллекции в строку. Еще
два перегруженных метода позволяют указывать строку разделителя.
Все четыре перегруженных метода используются в следующем коде.
Console.WriteLine("{0}", numbers.StringConcatenate());
Console.WriteLine("{0}", numbers.StringConcatenate(":"));
int[] intNumbers = { 1, 2, 3 };
Console.WriteLine("{0}", intNumbers.StringConcatenate(i => i.ToString()));
Console.WriteLine("{0}", intNumbers.StringConcatenate(i => i.ToString(), ":"));
onetwothree
one:two:three:
123
1:2:3:
Пример
Теперь пример можно изменить для получения преимущества нового метода расширения:
class Program
{
static void Main(string[] args)
{
const string fileName = "SampleDoc.docx";
string defaultStyle =
(string)(
from style in styleDoc.Root.Elements(w + "style")
where (string)style.Attribute(w + "type") == "paragraph" &&
(string)style.Attribute(w + "default") == "1"
select style
).First().Attribute(w + "styleId");
Этот пример выводит следующие результаты, будучи примененным к документу, описанному в разделе
Создание исходного документа в формате Office Open XML (C#).
StyleName:Heading1 >Parsing WordprocessingML with LINQ to XML<
StyleName:Normal ><
StyleName:Normal >The following example prints to the console.<
StyleName:Normal ><
StyleName:Code >using System;<
StyleName:Code ><
StyleName:Code >class Program {<
StyleName:Code > public static void (string[] args) {<
StyleName:Code > Console.WriteLine("Hello World");<
StyleName:Code > }<
StyleName:Code >}<
StyleName:Normal ><
StyleName:Normal >This example produces the following output:<
StyleName:Normal ><
StyleName:Code >Hello World<
Обратите внимание, что данная оптимизация кода является вариантом оптимизации кода в чистую
функцию. В следующем разделе принципы разбиения на чистые функции рассматриваются более подробно.
Следующие шаги
В следующем примере показано, как обеспечить оптимизацию данного кода другим способом, с
использованием чистых функций:
Рефакторинг с использованием чистых функций (Visual Basic)
См. также
Учебник. Обработка содержимого документа WordprocessingML (C#)
Рефакторинг в чистые функции (C#)
Рефакторинг с использованием чистых функций
(C#)
23.10.2019 • 3 minutes to read • Edit Online
Пример
В данном примере обрабатывается документ WordprocessingML, из которого извлекаются узлы абзацев.
Также идентифицируется стиль каждого абзаца. Этот пример основан на предыдущих примерах данного
учебника. Оптимизированный код поясняется в комментариях кода ниже.
Инструкции по созданию исходного документа для этого примера см. в разделе Создание исходного
документа в формате Office Open XML (C#).
В этом примере используются классы из сборки WindowsBase. Используются типы из пространства имен
System.IO.Packaging.
class Program
class Program
{
// This is a new method that assembles the paragraph text.
public static string ParagraphText(XElement e)
{
XNamespace w = e.Name.Namespace;
return e
.Elements(w + "r")
.Elements(w + "t")
.StringConcatenate(element => (string)element);
}
string defaultStyle =
(string)(
from style in styleDoc.Root.Elements(w + "style")
where (string)style.Attribute(w + "type") == "paragraph" &&
(string)style.Attribute(w + "default") == "1"
select style
).First().Attribute(w + "styleId");
Следующие шаги
В следующем примере показано, как выполнить проекцию XML в другую форму:
Проецирование XML в другую форму (C#)
См. также
Учебник. Обработка содержимого документа WordprocessingML (C#)
Рефакторинг с использованием метода расширения (C#)
Рефакторинг в чистые функции (C#)
Проецирование XML в другую форму (C#)
23.10.2019 • 4 minutes to read • Edit Online
В этом разделе показан пример проецированного XML, который находится в форме, отличной от исходного
XML.
Множество типичных преобразований XML состоят из цепочек запросов, как в примере. Принято начинать с
XML в некой форме, проецировать промежуточные результаты как коллекции анонимных типов или
именованных типов, затем опять проецировать результаты в XML, который совсем отличен от исходного
XML.
Пример
В данном примере обрабатывается документ WordprocessingML, из которого извлекаются узлы абзацев.
Также идентифицируется стиль и текст каждого абзаца. Наконец, в примере проецируется XML с другой
формой. Этот пример основан на предыдущих примерах данного учебника. Новая инструкция, которая
выполняет проекцию, выявляется в комментариях в нижеприведенном коде.
Инструкции по созданию исходного документа для этого примера см. в разделе Создание исходного
документа в формате Office Open XML (C#).
В этом примере используются классы из сборки WindowsBase. Используются типы из пространства имен
System.IO.Packaging.
class Program
{
public static string ParagraphText(XElement e)
{
XNamespace w = e.Name.Namespace;
return e
.Elements(w + "r")
.Elements(w + "t")
.StringConcatenate(element => (string)element);
}
string defaultStyle =
(string)(
from style in styleDoc.Root.Elements(w + "style")
where (string)style.Attribute(w + "type") == "paragraph" &&
(string)style.Attribute(w + "default") == "1"
select style
).First().Attribute(w + "styleId");
// The following is the new code that projects XML in a new shape.
XElement root = new XElement("Root",
from p in paraWithText
select new XElement("Paragraph",
new XElement("StyleName", p.StyleName),
new XElement("Text", p.Text)
)
);
Console.WriteLine(root);
}
}
Следующие шаги
В следующем примере составлен запрос на выявление всего текста в документе Word:
Поиск текста в документах Word (C#)
Поиск текста в документах Word (C#)
23.10.2019 • 6 minutes to read • Edit Online
В этом разделе производится расширение предыдущих запросов, которое позволит выполнить некоторые
полезные действия: найти все повторения определенной строки в документе.
Пример
В этом примере выполняется обработка документа WordprocessingML, чтобы найти все вхождения
определенного фрагмента текста в документе. Чтобы сделать это, для примера используем запрос, по
которому найдем строку «Здравствуйте». Этот пример основан на предыдущих примерах данного учебника.
Новый запрос выявляется в комментариях в нижеприведенном коде.
Инструкции по созданию исходного документа для этого примера см. в разделе Создание исходного
документа в формате Office Open XML (C#).
В этом примере используются классы, находящиеся в сборке WindowsBase. Используются типы из
пространства имен System.IO.Packaging.
class Program
{
public static string ParagraphText(XElement e)
{
{
XNamespace w = e.Name.Namespace;
return e
.Elements(w + "r")
.Elements(w + "t")
.StringConcatenate(element => (string)element);
}
string defaultStyle =
(string)(
from style in styleDoc.Root.Elements(w + "style")
where (string)style.Attribute(w + "type") == "paragraph" &&
(string)style.Attribute(w + "default") == "1"
select style
).First().Attribute(w + "styleId");
Можно изменить поисковый запрос таким способом, чтобы был выполнен поиск строк по определенному
стилю. В следующем запросе выполняется поиск всех пустых строк со стилем Code:
class Program
{
public static string ParagraphText(XElement e)
{
XNamespace w = e.Name.Namespace;
return e
.Elements(w + "r")
.Elements(w + "t")
.StringConcatenate(element => (string)element);
}
string defaultStyle =
(string)(
(string)(
from style in styleDoc.Root.Elements(w + "style")
where (string)style.Attribute(w + "type") == "paragraph" &&
(string)style.Attribute(w + "default") == "1"
select style
).First().Attribute(w + "styleId");
// Retrieve all paragraphs that have no text and are styled Code.
var blankCodeParagraphs =
from para in paraWithText
where para.Text == "" && para.StyleName == "Code"
select new
{
ParagraphNode = para.ParagraphNode,
StyleName = para.StyleName,
Text = para.Text
};
StyleName:Code ><
Это пример можно улучшить несколькими способами. Например, чтобы выполнить поиск по тексту, можно
использовать регулярные выражения, можно последовательно пройти по всем файлам Word в определенном
каталоге и т. п.
Обратите внимание, что этот пример работает почти так же хорошо, как если бы составлялся только один
запрос. Поскольку каждый запрос выполняется отложенным неспешным образом, не происходит выдачи
результатов каждого запроса до тех пор, пока не будет закончен поиск по всему заданному пространству.
Дополнительные сведения о выполнении и отложенном вычислении см. в разделе Отложенное выполнение
и отложенное вычисление в LINQ to XML (C#).
Следующие шаги
В следующем разделе приводятся дополнительные сведения о документах WordprocessingML:
Сведения о документах WordprocessingML в формате Office Open XML (C#)
См. также
Учебник. Обработка содержимого документа WordprocessingML (C#)
Рефакторинг с использованием чистых функций (C#)
Отложенное выполнение и отложенное вычисление в LINQ to XML (C#)
Документ WordprocessingML со стилями
23.10.2019 • 4 minutes to read • Edit Online
В этом разделе показан пример секции стилей документа Office Open XML WordprocessingML.
Пример
В следующем примере показан XML -документ, который составляет секцию стилей документа Office Open
XML WordprocessingML.
В стиле абзаца по умолчанию предусмотрен элемент со следующим открывающим тегом:
Эти сведения необходимо знать при написании запроса для поиска идентификатора стиля по умолчанию,
чтобы запрос мог обнаружить стиль абзацев, имеющих стиль по умолчанию.
Отметим, что эти документы очень простые в сравнении с обычными документами, создаваемыми в
Microsoft Word. Во многих случаях Word сохраняет большой объем дополнительных сведений,
форматирования и метаданных. Более того, Word не форматирует строки так, чтобы их было легко читать, как
в этом примере, сохраняет XML -документ без отступов. Однако все документы WordprocessingML имеют
общую базовую XML -форму. В силу этого запросы, показанные в этом учебнике, можно использовать и для
более сложных документов.
В этом разделе разъясняется, как открывать документ Office Open XML и как обращаться к частям этого
документа.
Пример
В следующем примере открывается документ Office Open XML, после чего часть, содержащая сам документ,
и часть, содержащая стиль, выводятся на консоль.
В этом примере используются классы из сборки WindowsBase. Используются типы из пространства имен
System.IO.Packaging.
const string fileName = "SampleDoc.docx";
Console.WriteLine("TargetUri:{0}", docPackageRelationship.TargetUri);
Console.WriteLine("==================================================================");
Console.WriteLine(xdoc.Root);
Console.WriteLine();
Console.WriteLine("TargetUri:{0}", styleRelation.TargetUri);
Console.WriteLine("==================================================================");
Console.WriteLine(styleDoc.Root);
Console.WriteLine();
}
}
}
Сравнение изменения XML-дерева в памяти с
Функциональное построение (LINQ to XML) (C#)
23.10.2019 • 6 minutes to read • Edit Online
Изменение XML -дерева на месте является традиционным подходом к изменению формы XML -документа.
Стандартное приложение загружает документ в источник данных, например DOM или LINQ to XML, при
помощи интерфейса программирования вставляет, удаляет узлы или изменяет их содержимое, а затем
сохраняет XML в файл или передает по сети.
LINQ to XML поддерживает другой подход, который удобно использовать во многих сценариях, —
функциональное построение. При функциональном построении изменение данных рассматривается как
задача преобразования, а не как детализированное управление хранилищем данных. Если можно взять
представление данных и эффективно преобразовать его из одной формы в другую, то результат будет таким
же, как если бы в одном хранилище данных были произведены некоторые изменения, направленные на то,
чтобы оно приняло другую форму. Основным элементом функционального построения является передача
результатов запросов конструкторам XDocument и XElement.
Во многих случаях можно написать код для преобразования, затратив лишь часть того времени, которое
ушло бы на внесение изменений в хранилище данных, причем этот код надежнее и его проще
сопровождать. В таких случаях это более эффективный способ изменения данных, несмотря на то что для
его реализации может потребоваться больше вычислительной мощности. Если разработчик знаком с
функциональным подходом, то во многих случаях итоговый код получается проще для понимания. Проще
найти код, который изменяет каждую часть дерева.
Вариант, когда XML -дерево изменяется на месте, в большей степени известен многим программистам DOM,
а код, написанный при помощи функционального подхода, может показаться программисту, который еще
не разобрался в этом методе, незнакомым. Если требуется внести лишь небольшое изменение в большое
XML -дерево, то при изменении дерева на месте во многих случаях требуется меньше времени ЦП.
В этом разделе приведен пример, реализованный обоими методами.
<Root>
<Child1>Content</Child1>
<Data1>123</Data1>
<Data2>456</Data2>
</Root>
В этом примере формируется такой же итоговый XML -документ, как и в первом примере. Но обратите
внимание, что при функциональном подходе можно видеть итоговую структуру нового XML -документа.
Можно видеть создание элемента Root , код, который получает по запросу элемент Child1 из исходного
дерева, а также код, который преобразует атрибуты из исходного дерева в элементы в новом дереве.
В этом случае функциональный код вряд ли будет короче, чем код из первого примера, и совсем не проще.
Но если в XML -дерево требуется внести много изменений, то подход, отличный от функционального, может
стать довольно сложным и громоздким. В отличие от этого, при использовании функционального подхода
происходит просто формирование требуемого XML -документа, когда соответствующие запросы и
выражения встраиваются, чтобы получать по запросу нужное содержимое. При функциональном подходе
получается код, более простой в сопровождении.
Обратите внимание, что в этом случае функциональный подход, вероятно, обеспечит более низкую
производительность в сравнении с внесением изменений в дерево. Основная проблема заключается в том,
что при функциональном подходе создается больше объектов, существующих в течение
непродолжительного времени. Однако этим недостатком можно пренебречь, если использование
функционального подхода позволяет повысить производительность работы программиста.
Это очень простой пример, однако он хорошо иллюстрирует различие в основах этих двух подходов.
Функциональный подход обеспечивает большую производительность при преобразовании больших XML -
документов.
Добавление элементов, атрибутов и узлов в
дерево XML (C#)
23.10.2019 • 2 minutes to read • Edit Online
Можно добавлять содержимое (элементы, атрибуты, комментарии, инструкции по обработке, текст и CDATA)
к существующему XML -дереву.
МЕТОД ОПИСАНИЕ
Следующие методы позволяют добавлять содержимое как одноуровневые узлы для XNode. Самым
распространенным узлом, к которому можно добавлять содержимое как одноуровневые узлы, является
XElement, хотя можно добавлять содержимое в виде таких узлов и к другим типам узлов, например XText или
XComment.
МЕТОД ОПИСАНИЕ
Пример
Описание
В следующем примере создаются два XML -дерева, затем выполняется изменение одного из них.
Код
XElement srcTree = new XElement("Root",
new XElement("Element1", 1),
new XElement("Element2", 2),
new XElement("Element3", 3),
new XElement("Element4", 4),
new XElement("Element5", 5)
);
XElement xmlTree = new XElement("Root",
new XElement("Child1", 1),
new XElement("Child2", 2),
new XElement("Child3", 3),
new XElement("Child4", 4),
new XElement("Child5", 5)
);
xmlTree.Add(new XElement("NewChild", "new content"));
xmlTree.Add(
from el in srcTree.Elements()
where (int)el > 3
select el
);
// Even though Child9 does not exist in srcTree, the following statement will not
// throw an exception, and nothing will be added to xmlTree.
xmlTree.Add(srcTree.Element("Child9"));
Console.WriteLine(xmlTree);
Комментарии
Этот код выводит следующие результаты:
<Root>
<Child1>1</Child1>
<Child2>2</Child2>
<Child3>3</Child3>
<Child4>4</Child4>
<Child5>5</Child5>
<NewChild>new content</NewChild>
<Element4>4</Element4>
<Element5>5</Element5>
</Root>
Изменение элементов, атрибутов и узлов в дереве
XML
23.10.2019 • 2 minutes to read • Edit Online
В следующей таблице приведена сводка методов и свойств, используемых для изменения элемента, его
дочерних элементов или его атрибутов.
Следующие методы используются для изменения XElement.
МЕТОД ОПИСАНИЕ
МЕТОД ОПИСАНИЕ
Следующие методы предназначены для изменения XNode (включая XElement или XDocument).
МЕТОД ОПИСАНИЕ
МЕТОД ОПИСАНИЕ
Можно вносить изменения в XML -дерево, удаляя элементы, атрибуты и узлы других типов.
Удаление одного элемента или атрибута из XML -документа является простой операцией. Но при удалении
коллекций элементов или атрибутов необходимо вначале материализовать коллекцию в список, а затем
удалить элементы или атрибуты из списка. Наилучшим подходом является использование метода
расширения Remove, позволяющего выполнить эту задачу.
Основной причиной этого является то, что большинство коллекций, получаемых из XML -дерева,
формируется с помощью отложенного выполнения. Если не проводится их предварительная материализация
в список или не используются методы расширения, то становится возможным возникновение определенного
класса ошибок. Дополнительные сведения см. в разделе Ошибки смешанного декларативного и
императивного кода (LINQ to XML ) (C#).
Следующие методы позволяют удалять узлы и атрибуты из XML -дерева.
МЕТОД ОПИСАНИЕ
Пример
ОПИСАНИЕ
В этом примере показано три подхода к удалению элементов. Сначала удаляется одиночный элемент. Затем
он возвращает коллекцию элементов, материализует их с помощью оператора Enumerable.ToList и удаляет
коллекцию. Наконец, он получает коллекцию элементов и удаляет их с помощью метода расширения
Remove.
Дополнительные сведения об операторе ToList см. в разделе Преобразование типов данных (C#).
Код
Комментарии
Этот код выводит следующие результаты:
<Root>
<Child1>
<GrandChild2 />
<GrandChild3 />
</Child1>
<Child2 />
<Child3 />
</Root>
Обратите внимание, что первый внучатый элемент был удален из Child1 . Все внучатые элементы были
удалены из Child2 и Child3 .
Обслуживание пар "имя-значение" (C#)
23.10.2019 • 3 minutes to read • Edit Online
Множеству приложений приходится сохранять данные, которые лучше всего хранить в виде пар «имя-
значение». Эти данные могут представлять сведения о конфигурации или глобальные параметры. LINQ to
XML содержит несколько методов, которые облегчают хранение пар «имя-значение». Можно либо оставить
информацию в виде атрибутов, либо в виде набора дочерних элементов.
Одно из отличий между хранением информации в виде атрибутов и в виде дочерних элементов состоит в
том, что атрибуты имеют ограничение в том, что для элемента может быть только один атрибут с данным
именем. Это ограничение не относится к дочерним элементам.
Пример
Следующий пример показывает, как создать элемент без атрибутов. Затем используется метод
SetAttributeValue для создания и поддержания списка пар «имя-значение».
// Create an element with no content.
XElement root = new XElement("Root");
// Remove DefaultColor.
root.SetAttributeValue("DefaultColor", null);
Console.WriteLine(root);
Пример
Следующий пример показывает, как создать элемент без дочерних элементов. Затем используется метод
SetElementValue для создания и поддержания списка пар «имя-значение».
// Remove DefaultColor.
root.SetElementValue("DefaultColor", null);
Console.WriteLine(root);
См. также
SetAttributeValue
SetElementValue
Изменение деревьев XML (LINQ to XML ) (C#)
Практическое руководство. Изменение
пространства имен для всего дерева XML (C#)
25.11.2019 • 2 minutes to read • Edit Online
Иногда необходимо программно изменить пространство имен для элемента или атрибута. В LINQ to XML это
делается легко. Можно установить свойство XElement.Name. Свойство XAttribute.Name не может быть
установлено, но можно легко скопировать атрибуты в объект System.Collections.Generic.List<T>, заменив
существующие атрибуты, а затем добавить новые атрибуты из нового пространства имен.
Дополнительные сведения см. в статье Обзор пространств имен DFS (LINQ to XML ) (C#).
Пример
Следующий код создает два XML -дерева без пространства имен. Затем он изменяет пространства имен
каждого дерева и объединяет их в одно дерево.
XElement tree1 = new XElement("Data",
new XElement("Child", "content",
new XAttribute("MyAttr", "content")
)
);
XElement tree2 = new XElement("Data",
new XElement("Child", "content",
new XAttribute("MyAttr", "content")
)
);
XNamespace aw = "http://www.adventure-works.com";
XNamespace ad = "http://www.adatum.com";
// change the namespace of every element and attribute in the first tree
foreach (XElement el in tree1.DescendantsAndSelf())
{
el.Name = aw.GetName(el.Name.LocalName);
List<XAttribute> atList = el.Attributes().ToList();
el.Attributes().Remove();
foreach (XAttribute at in atList)
el.Add(new XAttribute(aw.GetName(at.Name.LocalName), at.Value));
}
// change the namespace of every element and attribute in the second tree
foreach (XElement el in tree2.DescendantsAndSelf())
{
el.Name = ad.GetName(el.Name.LocalName);
List<XAttribute> atList = el.Attributes().ToList();
el.Attributes().Remove();
foreach (XAttribute at in atList)
el.Add(new XAttribute(ad.GetName(at.Name.LocalName), at.Value));
}
// add attribute namespaces so that the tree will be serialized with
// the aw and ad namespace prefixes
tree1.Add(
new XAttribute(XNamespace.Xmlns + "aw", "http://www.adventure-works.com")
);
tree2.Add(
new XAttribute(XNamespace.Xmlns + "ad", "http://www.adatum.com")
);
// create a new composite tree
XElement root = new XElement("Root",
tree1,
tree2
);
Console.WriteLine(root);
<Root>
<aw:Data xmlns:aw="http://www.adventure-works.com">
<aw:Child aw:MyAttr="content">content</aw:Child>
</aw:Data>
<ad:Data xmlns:ad="http://www.adatum.com">
<ad:Child ad:MyAttr="content">content</ad:Child>
</ad:Data>
</Root>
Производительность связанных запросов (LINQ to
XML) (C#)
23.10.2019 • 3 minutes to read • Edit Online
Одним из наиболее важных преимуществ LINQ (и LINQ to XML ) является возможность эффективного
выполнения цепочек запросов наряду с одиночными большими и более сложными запросами.
Цепочкой запросов называется запрос, использующий в качестве источника другой запрос. Например, в
следующем простом коде запрос query2 в качестве источника включает в себя запрос query1 .
Объекты XName и XNamespace являются атомарными; иными словами, если они имеют идентичное полное
имя, они ссылаются на один и тот же объект. Это способствует повышению производительности при
использовании запросов. При сравнении двух атомарных имен для проверки их равенства соответствующий
промежуточный язык должен определить, ссылаются ли они на один и тот же объект. Эта операция
используется в промежуточном коде вместо более длительной операции сравнения строк.
Семантика атомизации
Атомизация означает, что два объекта XName имеющие идентичное локальное имя и находящиеся в одном
пространстве имен, совместно используют один и тот же экземпляр. Аналогично, если два объекта
XNamespace имеют идентичный идентификатор URI пространства имен, то они совместно используют один
и тот же экземпляр.
Чтобы разрешить создание атомарных объектов класса, необходимо, чтобы конструктор класса был
закрытым, а не открытым. Это требование основано на том, что если бы конструктор был открытым, можно
было бы создавать неатомарные объекты. Классы XName и XNamespace реализуют неявный оператор
преобразования строки в объект XName или XNamespace. Экземпляры этих объектов могут быть получены
только таким способом. Создание экземпляров с помощью конструктора невозможно, так как конструктор
недоступен.
Классы XName иXNamespace также реализуют операторы для проверки равенства и неравенства,
определяющие, ссылаются ли два сравниваемых объекта на один и тот же экземпляр.
Пример
Следующий код создает объекты XElement и демонстрирует, что идентичные имена совместно используют
один и тот же экземпляр.
if ((object)r1.Name == (object)r2.Name)
Console.WriteLine("r1 and r2 have names that refer to the same instance.");
else
Console.WriteLine("Different");
XName n = "Root";
if ((object)n == (object)r1.Name)
Console.WriteLine("The name of r1 and the name in 'n' refer to the same instance.");
else
Console.WriteLine("Different");
<C1>1</C1>
<C1>1</C1>
Предварительная атомизация объектов XName
(LINQ to XML) (C#)
23.10.2019 • 3 minutes to read • Edit Online
Пример
В следующем примере это показано.
Console.WriteLine(root);
<Root>
<Data ID="1">4,100,000</Data>
<Data ID="2">3,700,000</Data>
<Data ID="3">1,150,000</Data>
</Root>
Следующий пример демонстрирует ту же технику, когда XML -документ находится в пространстве имен.
XNamespace aw = "http://www.adventure-works.com";
XName Root = aw + "Root";
XName Data = aw + "Data";
XName ID = "ID";
Console.WriteLine(root);
<aw:Root xmlns:aw="http://www.adventure-works.com">
<aw:Data ID="1">4,100,000</aw:Data>
<aw:Data ID="2">3,700,000</aw:Data>
<aw:Data ID="3">1,150,000</aw:Data>
</aw:Root>
Следующий пример наиболее приближен к реальной ситуации. В этом примере содержимое элементов
предоставляется запросом.
DateTime t1 = DateTime.Now;
XElement root = new XElement(Root,
from i in System.Linq.Enumerable.Range(1, 100000)
select new XElement(Data,
new XAttribute(ID, i),
i * 5)
);
DateTime t2 = DateTime.Now;
DateTime t1 = DateTime.Now;
XElement root = new XElement("Root",
from i in System.Linq.Enumerable.Range(1, 100000)
select new XElement("Data",
new XAttribute("ID", i),
i * 5)
);
DateTime t2 = DateTime.Now;
Одним из важнейших преимуществ LINQ to XML перед XmlDocument с точки зрения производительности
является то, что в LINQ to XML запросы компилируются статически, тогда как запросы XPath
интерпретируются во время выполнения. Это встроенная функция LINQ to XML, поэтому вам не нужно будет
принимать какие-либо подготовительные меры для ее использования, однако, чтобы сделать обоснованный
выбор той или другой технологии, важно понимать их различие. Данное различие описано в текущем
разделе.
XDocument po = XDocument.Load("PurchaseOrders.xml");
IEnumerable<XElement> list1 =
from el in po.Descendants("Address")
where (string)el.Attribute("Type") == "Shipping"
select el;
XDocument po = XDocument.Load("PurchaseOrders.xml");
IEnumerable<XElement> list1 =
po
.Descendants("Address")
.Where(el => (string)el.Attribute("Type") == "Shipping");
Метод Where является методом расширения. Дополнительные сведения см. в статье Методы расширения.
Поскольку используется метод расширения Where, представленный выше запрос компилируется так, как
показано далее.
XDocument po = XDocument.Load("PurchaseOrders.xml");
IEnumerable<XElement> list1 =
System.Linq.Enumerable.Where(
po.Descendants("Address"),
el => (string)el.Attribute("Type") == "Shipping");
Этот пример приводит к получению в точности таких же результатов, что и два предыдущих примера. Таким
образом, демонстрируется возможность эффективной компиляции запросов в вызовы методов со
статическими ссылками. В сочетании с семантикой отложенного выполнения итераторов это позволяет
повысить производительность. Дополнительные сведения о семантике отложенного выполнения итераторов
см. в разделе Отложенное выполнение и отложенное вычисление в LINQ to XML (C#).
NOTE
В этих примерах показаны образцы кода, который может быть составлен компилятором. Действительная реализация
может несколько отличаться от этих примеров, однако производительность останется такой же или приблизительно
такой же.
Этот запрос возвращает такие же выходные данные, что и в примерах на основе LINQ to XML, за
исключением того, что запрос LINQ to XML расставляет отступы в результирующем файле XML, а запрос
XmlDocument - нет.
Однако производительность подхода на основе XmlDocument в целом ниже, чем при использовании LINQ to
XML, поскольку метод SelectNodes должен при каждом вызове выполнять следующие внутренние операции:
Анализирует строку, содержащую выражение XPath, и разбивает ее на лексемы.
Проверяет лексемы, чтобы подтвердить правильность выражения XPath.
Транслирует выражение во внутреннее дерево выражений.
Проходит по узлам, выбирает соответствующие узлы на основании оценки выражения и добавляет их
в набор результатов.
При этом выполняется значительно больший объем работы, чем при использовании аналогичного запроса
LINQ to XML. Конкретный выигрыш по производительности зависит от типа запроса, однако в общем случае
запросы LINQ to XML выполняют меньше работы и поэтому более эффективны, чем операции по оценке
выражений XPath с помощью XmlDocument.
Примечания LINQ to XML
23.10.2019 • 2 minutes to read • Edit Online
Заметки в LINQ to XML позволяют ассоциировать любой произвольный объект любого произвольного типа
с любым XML -компонентом XML -дерева.
Чтобы добавить заметку к компоненту XML, например XElement или XAttribute, следует вызвать метод
AddAnnotation. Заметки получаются по типу.
Обратите внимание, что заметки не являются частью набора сведений XML, они не могут быть
сериализованы или десериализованы.
Методы
При работе с заметками можно использовать следующие методы.
МЕТОД ОПИСАНИЕ
События LINQ to XML позволяют получать уведомления, когда изменяется дерево XML.
Можно добавить события в экземпляр любого объекта XObject. Обработчик события будет получать
уведомления об изменениях объекта XObject и всех его потомков. Например, можно добавить обработчик
события в корень дерева и обрабатывать все изменения дерева в этом обработчике события.
Примеры таких событий LINQ to XML см. в разделах Changing и Changed.
Типы и события
Используйте следующие типы при работе с событиями.
ТИП ОПИСАНИЕ
СОБЫТИЕ ОПИСАНИЕ
Пример
ОПИСАНИЕ
События полезны, когда нужно поддержать какие-либо статистические данные в дереве XML. Например,
нужно рассчитать сумму элементов строки. Следующий пример использует события для подсчета суммы всех
дочерних элементов сложного элемента Items .
Код
XElement root = new XElement("Root",
new XElement("Total", "0"),
new XElement("Items")
);
XElement total = root.Element("Total");
XElement items = root.Element("Items");
items.Changed += (object sender, XObjectChangeEventArgs cea) =>
{
switch (cea.ObjectChange)
{
case XObjectChange.Add:
if (sender is XElement)
total.Value = ((int)total + (int)(XElement)sender).ToString();
if (sender is XText)
total.Value = ((int)total + (int)((XText)sender).Parent).ToString();
break;
case XObjectChange.Remove:
if (sender is XElement)
total.Value = ((int)total - (int)(XElement)sender).ToString();
if (sender is XText)
total.Value = ((int)total - Int32.Parse(((XText)sender).Value)).ToString();
break;
}
Console.WriteLine("Changed {0} {1}",
sender.GetType().ToString(), cea.ObjectChange.ToString());
};
items.SetElementValue("Item1", 25);
items.SetElementValue("Item2", 50);
items.SetElementValue("Item2", 75);
items.SetElementValue("Item3", 133);
items.SetElementValue("Item1", null);
items.SetElementValue("Item4", 100);
Console.WriteLine("Total:{0}", (int)total);
Console.WriteLine(root);
Комментарии
Этот код выводит следующие результаты:
Разработчикам LINQ to XML, которым требуется написать такие программы, как XML -редактор, система
преобразования или модуль формирования отчетов, часто приходится писать программы, которые работают
на более высоком уровне гранулярности по сравнению с элементами и атрибутами. Им часто приходится
работать на уровне узлов, обрабатывая текстовые узлы, инструкции по обработке и комментарии. В этом
разделе приводятся некоторые сведения о программировании на уровне узлов.
Сведения об узле
Программист, работающий на уровне узлов, должен учитывать целый ряд нюансов программирования.
Родительскому свойству дочерних узлов XDocument задано значение Null
Свойство Parent содержит родительский объект XElement, а не родительский узел. Дочерние узлы объекта
XDocument не имеют родительского объекта XElement. Их родителем является документ, поэтому свойству
Parent этих узлов задается значение NULL.
Следующий пример демонстрирует это:
True
True
Console.WriteLine(xmlTree.Nodes().OfType<XText>().Count());
// the following line does not cause the removal of the text node.
textNode.Value = "";
>><<
<Child1></Child1>
<Child2 />
xmlns="http://www.adventure-works.com" IsNamespaceDeclaration:True
xmlns:fc="www.fourthcoffee.com" IsNamespaceDeclaration:True
AnAttribute="abc" IsNamespaceDeclaration:False
<Root/>
<!--a comment-->
", LoadOptions.PreserveWhitespace);
3
0
// this shows that there is only one child node of the document
Console.WriteLine(doc.Nodes().Count());
LINQ to XML содержит различные методы, которые позволяют прямо модифицировать XML -дерево.
Можно добавить элементы, удалить элементы, изменить содержимое элемента, добавить атрибуты и т. п.
Интерфейс программирования описывается в разделе Изменение деревьев XML (LINQ to XML ) (C#). Если
выполняется переход в пределах одной оси, например Elements, и при этом выполняется изменение XML -
дерева, можно в итоге обнаружить некоторые неожиданные ошибки.
Этот вид ошибки иногда называется Halloween Problem.
Теперь предположим, что необходимо пройти по связанному списку, добавив три новых пункта (a', b' и c').
При этом необходимо, чтобы результирующий список выглядел так:
a -> a' -> b -> b' -> c -> c'
Пишется код, который последовательно обращается к элементам списка и для каждого пункта добавляет
новый пункт после него. При этом код распознает элемент a и вставит элемент a' после него. Теперь код
перейдет к следующему узлу списка, которым является a' ! После чего он добавляет новый элемент списка -
a'' .
Как это следовало бы решить в реальной ситуации? Можно сделать копию оригинального списка, после чего
создать полностью новый список. Или, если вы пишете чисто императивный код, можно найти первый пункт,
добавить новый пункт и перейти на два шага вперед по списку, чтобы перескочить элемент, который был
только что добавлен.
Этот код представляет собой бесконечный цикл. Инструкция foreach последовательно применяется ко всей
оси Elements() , при этом добавляются новые элементы к элементу doc . После этого она переходит на
только что добавленные элементы. И поскольку она выделяет память для новых объектов на каждом шаге,
она захватит всю доступную память.
Эту неполадку можно устранить за счет переноса массива в память, используя стандартный оператор
запросов ToList следующим образом.
Теперь код работает, как и положено. В итоге получается следующее XML -дерево:
<Root>
<A>1</A>
<B>2</B>
<C>3</C>
<A>1</A>
<B>2</B>
<C>3</C>
</Root>
Однако этот код не выполнит поставленной задачи. В такой ситуации после удаления первого элемента A он
удаляется из XML -дерева, которое содержится в корне, при этом коду метода элементов (Elements method),
который выполняет последовательный переход, не удастся найти следующий элемент.
Предыдущий код представит следующий вывод.
<Root>
<B>2</B>
<C>3</C>
</Root>
Решение снова заключается в вызове ToList, чтобы материализовать коллекцию следующим образом.
<Root />
Кроме того, можно совсем исключить итерацию за счет вызова RemoveAll на родительском элементе.
var z =
from e in root.Elements()
where TestSomeCondition(e)
select DoMyProjection(e);
Руководство
Во-первых, не стоит смешивать декларативный и императивный код.
Даже если вам точно известна семантика ваших коллекций и методов, которые применяются для изменения
XML -дерева, если удастся создать некий код, в котором такие категории проблем невозможны, код все равно
потребуется поддерживать другим разработчикам в будущем, а для них это не всегда может быть так же
ясно, как для вас. Если смешать декларативный и императивный стили, то код значительно усложнится.
Если удастся написать код, который материализует коллекцию таким образом, что эти проблемы удастся
избежать, отметьте это в комментариях кода, чтобы другие программисты понимали, как это сделано.
Во-вторых, если требования производительности и другие условия позволяют, используйте только
декларативный код. Не следует изменять существующее XML -дерево. Лучше создать новое.
Иногда приходится считывать достаточно большие XML -файлы и разрабатывать приложения таким
образом, чтобы объем памяти, используемой этим приложением, был прогнозируемым. Если попытаться
заполнить XML -дерево большим XML -файлом, используемый объем памяти будет пропорционален
размеру файла, то есть излишним. Поэтому следует вместо этого использовать потоки.
Одна из возможностей состоит в том, чтобы разработать приложение с использованием метода XmlReader.
При этом для запроса XML -дерева можно использовать метод LINQ. В этом случае можно написать
собственный пользовательский метод оси. Дополнительные сведения см. в разделе Практическое
руководство. Написание метода оси LINQ to XML (C#).
Чтобы написать собственный метод оси, нужно написать небольшой метод, который использует метод
XmlReader для считывания узлов, до тех пор пока не достигнет одного из интересующих вас узлов. Затем
метод вызывает метод ReadFrom, который читает из объекта XmlReader и создает экземпляр фрагмента
XML. Затем он выдает фрагмент с помощью ключевого слова yield return методу, который выполняет
перечисление по пользовательскому методу оси. После этого можно написать запросы в LINQ на основе
пользовательского метода оси.
Использование потоков лучше всего уместно в ситуациях, когда требуется обработать исходный документ
только один раз, и элементы можно обрабатывать в порядке их следования в документе. Некоторые
стандартные операторы запросов, например OrderBy, проходят через источник, собирают все данные,
сортируют их и выдают первый элемент последовательности. Обратите внимание, что, если вы используете
оператор запроса, который материализует источник раньше, чем выбирает первый элемент, вы не
сохраните малый объем используемой памяти.
Пример
Иногда проблема становится немного интереснее. В следующем XML -документе потребитель
пользовательского метода оси должен знать имя клиента, которому принадлежит каждый элемент.
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<Customer>
<Name>A. Datum Corporation</Name>
<Item>
<Key>0001</Key>
</Item>
<Item>
<Key>0002</Key>
</Item>
<Item>
<Key>0003</Key>
</Item>
<Item>
<Key>0004</Key>
</Item>
</Customer>
<Customer>
<Name>Fabrikam, Inc.</Name>
<Item>
<Key>0005</Key>
</Item>
<Item>
<Key>0006</Key>
</Item>
<Item>
<Key>0007</Key>
</Item>
<Item>
<Key>0008</Key>
</Item>
</Customer>
<Customer>
<Name>Southridge Video</Name>
<Item>
<Key>0009</Key>
</Item>
<Item>
<Key>0010</Key>
</Item>
</Customer>
</Root>
Подход, который используется в данном примере, также отслеживает эти данные о заголовках, сохраняет
эти данные о заголовках, а затем строит небольшое XML -дерево, которое содержит и эти данные о
заголовках, и сведения, которые вы перечисляете. При использовании этого подхода метод оси позволяет
получить это новое небольшое XML -дерево. Тогда запрос имеет доступ к данным о заголовках наряду с
вашими сведениями.
При данном подходе используется малый объем памяти. После того как выданы все данные фрагмента XML,
ссылки на предыдущий фрагмент не сохраняются, и он становится пригодным для сборки мусора. Обратите
внимание, что этот метод создает в куче много объектов с небольшим периодом жизни.
В следующем примере показано, как реализовать и использовать пользовательский метод оси, который
осуществляет потоковую передачу XML -фрагментов из файла, указанного в URI. Эта пользовательская ось
специально написана таким образом, что ожидает документа с элементами Customer , Name и Item ,
упорядоченными, как в указанном документе Source.xml . Это упрощенная реализация. Будет подготовлена
более надежная реализация анализа неверных документов.
static IEnumerable<XElement> StreamCustomerItem(string uri)
{
using (XmlReader reader = XmlReader.Create(uri))
{
XElement name = null;
XElement item = null;
reader.MoveToContent();
// Parse the file, save header information when encountered, and yield the
// Item XElement objects as they are created.
Иногда необходимо преобразовывать большие XML -файлы, при этом приложение должно быть написано
так, чтобы используемый им объем памяти был прогнозируемым. Если вставить в XML -дерево очень
большой XML -файл, то объем используемой памяти будет пропорциональным размеру файла (то есть
чрезмерным). Поэтому следует вместо этого использовать потоки.
Использование потоков лучше всего уместно в ситуациях, когда требуется обработать исходный документ
только один раз, и элементы можно обрабатывать в порядке их следования в документе. Некоторые
стандартные операторы запросов, например OrderBy, проходят через источник, собирают все данные,
сортируют их и выдают первый элемент последовательности. Отметим, что при использовании оператора
запроса, который материализует свой источник перед тем, как выдать первый элемент, приложение снова
будет использовать большой объем памяти.
Даже при использовании приема, описанного в разделе Практическое руководство. Потоковая передача
фрагментов XML с доступом к сведениям заголовка (C#) при попытке собрать XML -дерево, содержащее
трансформированный документ, объем используемой памяти будет слишком большим.
Существует два основных подхода. Один подход заключается в использовании возможностей отложенной
обработки объекта XStreamingElement. Другой подход состоит в создании XmlWriter и использовании
возможностей LINQ to XML для записи элементов в модуле XmlWriter. В этом разделе рассказывается об
обоих подходах.
Пример
Следующий пример является продолжением примера, приведенного в разделе Практический пример.
Потоковая передача фрагментов XML с доступом к сведениям заголовка (C#).
В этом примере возможности отложенной обработки объекта XStreamingElement используются для
создания выходного потока. Данный пример может преобразовать очень большой документ при
незначительном использовании памяти.
Заметьте, что пользовательская ось ( StreamCustomerItem ) специально написана таким образом, что ожидает
документа с элементами Customer , Name и Item , упорядоченными, как в следующем документе Source.xml.
Однако более надежная реализации должна предусматривать такую ситуацию, что при проведении
синтаксического анализа встретится документ, не прошедший проверку правильности.
Далее показан исходный документ, Source.xml:
<?xml version="1.0" encoding="utf-8" ?>
<Root>
<Customer>
<Name>A. Datum Corporation</Name>
<Item>
<Key>0001</Key>
</Item>
<Item>
<Key>0002</Key>
</Item>
<Item>
<Key>0003</Key>
</Item>
<Item>
<Key>0004</Key>
</Item>
</Customer>
<Customer>
<Name>Fabrikam, Inc.</Name>
<Item>
<Key>0005</Key>
</Item>
<Item>
<Key>0006</Key>
</Item>
<Item>
<Key>0007</Key>
</Item>
<Item>
<Key>0008</Key>
</Item>
</Customer>
<Customer>
<Name>Southridge Video</Name>
<Item>
<Key>0009</Key>
</Item>
<Item>
<Key>0010</Key>
</Item>
</Customer>
</Root>
static IEnumerable<XElement> StreamCustomerItem(string uri)
{
using (XmlReader reader = XmlReader.Create(uri))
{
XElement name = null;
XElement item = null;
reader.MoveToContent();
// Parse the file, save header information when encountered, and yield the
// Item XElement objects as they are created.
Пример
Следующий пример также является продолжением примера, приведенного в разделе Практический пример.
Потоковая передача фрагментов XML с доступом к сведениям заголовка (C#).
В этом примере используются возможности LINQ to XML по записи элементов в XmlWriter. Данный пример
может преобразовать очень большой документ при незначительном использовании памяти.
Заметьте, что пользовательская ось ( StreamCustomerItem ) специально написана таким образом, что ожидает
документа с элементами Customer , Name и Item , упорядоченными, как в следующем документе Source.xml.
Однако при более надежной реализации должна быть либо выполнена проверка правильности исходного
документа с помощью XSD, либо проведена подготовка на тот случай, что при проведении синтаксического
анализа встретится документ, не прошедший проверку правильности.
В этом примере используется тот же исходный документ, Source.xml, как и в предыдущем примере этого
раздела. Он также выдает точно такие же выходные данные.
Использование XStreamingElement для создания выходного потока итогового XML -документа
предпочтительнее, чем запись в XmlWriter.
reader.MoveToContent();
// Parse the file, save header information when encountered, and yield the
// Item XElement objects as they are created.
<Root>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0001</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0002</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0003</Key>
</Item>
<Item>
<Customer>A. Datum Corporation</Customer>
<Key>0004</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0005</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0006</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0007</Key>
</Item>
<Item>
<Customer>Fabrikam, Inc.</Customer>
<Key>0008</Key>
</Item>
<Item>
<Customer>Southridge Video</Customer>
<Key>0009</Key>
</Item>
<Item>
<Customer>Southridge Video</Customer>
<Key>0010</Key>
</Item>
</Root>
Практическое руководство. Чтение и запись
закодированного документа (C#)
23.10.2019 • 2 minutes to read • Edit Online
Чтобы создать закодированный XML -документ, следует добавить объект XDeclaration в XML -дерево, задав
требуемое имя кодовой страницы для кодировки.
Любое значение, возвращенное методом WebName, является допустимым.
При чтении закодированного документа свойству Encoding нужно будет задать имя этой кодовой страницы.
Если свойству Encoding задать допустимое имя кодовой страницы, LINQ to XML выполняет сериализацию с
указанной кодировкой.
Пример
В следующем примере создаются два документа: один с кодировкой utf-8, а другой с кодировкой utf-16.
Документы затем загружаются, а кодировка указывается в консоли.
Encoded document:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Root>Content</Root>
Encoded document:
<?xml version="1.0" encoding="utf-16" standalone="yes"?>
<Root>Content</Root>
См. также
XDeclaration.Encoding
Использование XSLT для преобразования дерева
XML (C#)
25.11.2019 • 2 minutes to read • Edit Online
Можно создавать XML -дерево, XmlReader из XML -дерева, новый документ и XmlWriter, который будет
заносить информацию в новый документ. После этого можно вызвать преобразование XSLT, передавая
XmlReader и XmlWriter этой трансформации. После успешного завершения преобразования новое XML -
дерево заполняется ее результатами.
Пример
string xslt = @"<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl='http://www.w3.org/1999/XSL/Transform' version='1.0'>
<xsl:template match='/Parent'>
<Root>
<C1>
<xsl:value-of select='Child1'/>
</C1>
<C2>
<xsl:value-of select='Child2'/>
</C2>
</Root>
</xsl:template>
</xsl:stylesheet>";
См. также
XContainer.CreateWriter
XNode.CreateReader
Практическое руководство. Использование
заметок для преобразования деревьев
LINQ to XML в стиль XSLT (C#)
23.10.2019 • 10 minutes to read • Edit Online
У любого заданного текстового узла может быть любое количество дочерних элементов <b> и <i> . Этот
подход можно применять в ряде других ситуаций, например для страниц, которые содержат разнообразные
дочерние элементы, такие как обычные абзацы, маркированные абзацы и растровые изображения. Ячейки
таблицы могут содержать текст, раскрывающиеся списки или битовые карты. Одной из основных
отличительных особенностей кода XML, предназначенного для представления документов, является то, что
неизвестно, какие дочерние элементы будет иметь тот или иной конкретный элемент.
Если требуется преобразовать элементы в дереве, о дочерних элементах которого знать не обязательно, то
данный подход, использующий заметки, становится эффективным.
Сводка этого подхода состоит в следующем.
Во-первых, обозначить элементы в дереве заметками в виде элемента замены.
Во-вторых, провести итерацию по всему дереву, создавая новое дерево, в котором каждый элемент
заменяется его заметкой. В этом примере реализуются итерация и создание нового дерева в функции
с именем XForm .
В более подробном изложении этот подход состоит в следующем.
Выполняются один или несколько запросов LINQ to XML, возвращающих набор элементов, которые
требуется преобразовать из одной формы в другую. К каждому элементу запроса в качестве заметки
добавляется новый объект XElement. Новый элемент заменяет элемент с заметкой в новом,
преобразованном дереве. Как видно из примера, этот код прост для написания.
Новый элемент, добавляемый в виде заметки, может содержать новые дочерние узлы. Он может
формировать поддерево любой необходимой формы.
Предусмотрено специальное правило. Если дочерний узел нового элемента находится в другом
пространстве имен, специально созданном для этой цели (в этом примере таким пространством имен
является http://www.microsoft.com/LinqToXmlTransform/2007 ), то дочерний элемент не копируется в новое
дерево. Вместо этого, если пространство имен представляет собой упомянутое выше специальное
пространство имен, а локальным именем элемента является ApplyTransforms , последовательно
производится итерация по дочерним узлам элемента в исходном дереве и копирование их в новое
дерево (за исключением того, что обозначенные заметками дочерние элементы преобразуются сами
согласно этим правилам).
Это в какой-то мере аналогично спецификации преобразований в XSL. Запрос, выбирающий набор
узлов, аналогичен выражению XPath для шаблона. Код создания нового элемента XElement,
сохраняемого в виде заметки, аналогичен конструктору последовательности в XSL, а элемент
ApplyTransforms по своему назначению аналогичен элементу xsl:apply-templates в XSL.
Одним из преимуществ данного подхода является то, что запросы при формировании всегда
создаются применительно к неизмененному исходному дереву. Можно не беспокоиться о том, как
изменения в дереве повлияют на формируемые запросы.
Преобразование дерева
В этом первом примере переименовываются все узлы Paragraph в para .
XNamespace xf = "http://www.microsoft.com/LinqToXmlTransform/2007";
XName at = xf + "ApplyTransforms";
// The XForm method, shown later in this topic, accomplishes the transform
XElement newRoot = XForm(root);
Console.WriteLine(newRoot);
<Root>
<para>This is a sentence with <b>bold</b> and <i>italic</i> text.</para>
<para>More text.</para>
</Root>
// while adding annotations, you can query the source tree all you want,
// as the tree is not mutated while annotating.
var avg = data.Elements("Data").Select(z => (Decimal)z).Average();
data.AddAnnotation(
new XElement("Root",
new XElement(xf + "ApplyTransforms"),
new XElement("Average", $"{avg:F4}"),
new XElement("Sum",
data
.Elements("Data")
.Select(z => (int)z)
.Sum()
)
)
);
Console.WriteLine("Before Transform");
Console.WriteLine("----------------");
Console.WriteLine(data);
Console.WriteLine();
Console.WriteLine();
// The XForm method, shown later in this topic, accomplishes the transform
XElement newData = XForm(data);
Console.WriteLine("After Transform");
Console.WriteLine("----------------");
Console.WriteLine(newData);
Before Transform
----------------
<Root>
<Data>20</Data>
<Data>10</Data>
<Data>3</Data>
</Root>
After Transform
----------------
<Root>
<Data>20</Data>
<Data>10</Data>
<Data>3</Data>
<Average>11.0000</Average>
<Sum>33</Sum>
</Root>
Влияние на преобразование
Небольшая функция XForm создает новое преобразованное дерево из исходного дерева, обозначенного
заметками.
Псевдокод для функции достаточно прост:
if (source.Annotation<XElement>() != null)
{
XElement anno = source.Annotation<XElement>();
return new XElement(anno.Name,
anno.Attributes(),
anno
.Nodes()
.Select(
(XNode n) =>
{
XElement annoEl = n as XElement;
if (annoEl != null)
{
if (annoEl.Name == at)
return (object)(
source.Nodes()
.Select(
(XNode n2) =>
{
XElement e2 = n2 as XElement;
if (e2 == null)
return n2;
else
return XForm(e2);
}
)
);
else
return n;
}
else
return n;
}
)
);
}
else
{
return new XElement(source.Name,
source.Attributes(),
source
.Nodes()
.Select(n =>
{
XElement el = n as XElement;
if (el == null)
return n;
else
return XForm(el);
}
)
);
}
}
Полный пример
Следующий код является полным примером, включающим функцию XForm . Он включает несколько
типичных вариантов использования преобразования данного типа:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
class Program
{
static XNamespace xf = "http://www.microsoft.com/LinqToXmlTransform/2007";
static XName at = xf + "ApplyTransforms";
// replace Other with NewOther, add new child elements around original content
foreach (var el in root.Elements("Other"))
el.AddAnnotation(
new XElement("NewOther",
new XElement("MyNewChild", 1),
// same idea as xsl:apply-templates
new XElement(xf + "ApplyTransforms"),
new XElement("ChildThatComesAfter")
)
);
Console.WriteLine("After Transform");
Console.WriteLine("----------------");
Console.WriteLine(newRoot);
}
}
Before Transform
----------------
<Root Att1="123">
<!--A comment-->
<Child>1</Child>
<Child>2</Child>
<Other>
<GC>3</GC>
<GC>4</GC>
</Other>
<SomeMixedContent>This is <i>an</i> element that <b>has</b> some mixed content</SomeMixedContent>
<AnUnchangedElement>42</AnUnchangedElement>
</Root>
After Transform
----------------
<Root Att1="123">
<!--A comment-->
<NewChild>1</NewChild>
<NewChild>2</NewChild>
<NewOther>
<MyNewChild>1</MyNewChild>
<GrandChild ANewAttribute="999">3</GrandChild>
<GC>4</GC>
<ChildThatComesAfter />
</NewOther>
<MixedContent>This is <Italic>an</Italic> element that <Bold>has</Bold> some mixed content</MixedContent>
<AnUnchangedElement>42</AnUnchangedElement>
</Root>
Практическое руководство. Сериализация с
использованием XmlSerializer (C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В следующем примере создается некоторое количество объектов, содержащих объекты XElement. Затем
объекты сериализуются в поток памяти и далее десериализуются из потока памяти.
using System;
using System.IO;
using System.Linq;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Linq;
public XElementContainer()
{
member = XLinqTest.CreateXElement();
}
public XElementNullContainer()
{
}
}
class XLinqTest
{
static void Main(string[] args)
{
Test<XElementNullContainer>(new XElementNullContainer());
Test<XElement>(CreateXElement());
Test<XElementContainer>(new XElementContainer());
}
Пример
В следующем примере создается некоторое количество объектов, содержащих объекты XElement. Затем они
сериализуются в текстовые файлы и десериализуются из текстовых файлов.
using System;
using System.Xml;
using System.Xml.Linq;
using System.IO;
using System.Runtime.Serialization;
[DataContract]
public class XElementContainer
{
[DataMember]
public XElement member;
public XElementContainer()
{
member = XLinqTest.CreateXElement();
}
}
[DataContract]
public class XElementNullContainer
{
[DataMember]
public XElement member;
public XElementNullContainer()
{
member = null;
}
}
В этом разделе обсуждаются проблемы безопасности, связанные с LINQ to XML. Кроме того, в нем
приводятся рекомендации по снижению рисков нарушения безопасности.
См. также
Руководство по программированию (LINQ to XML ) (C#)
Пример XML-файла: типичный заказ на покупку
(LINQ to XML)
23.10.2019 • 2 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Этот файл
представляет собой типичный заказ на покупку.
PurchaseOrder.xml
<?xml version="1.0"?>
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes>
<Items>
<Item PartNumber="872-AA">
<ProductName>Lawnmower</ProductName>
<Quantity>1</Quantity>
<USPrice>148.95</USPrice>
<Comment>Confirm this is electric</Comment>
</Item>
<Item PartNumber="926-AA">
<ProductName>Baby Monitor</ProductName>
<Quantity>2</Quantity>
<USPrice>39.98</USPrice>
<ShipDate>1999-05-21</ShipDate>
</Item>
</Items>
</PurchaseOrder>
Пример XML-файла: типичный заказ на покупку в
пространстве имен
23.10.2019 • 2 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Этот файл
представляет собой типичный заказ на покупку. XML располагается в пространстве имен.
PurchaseOrderInNamespace.xml
<?xml version="1.0"?>
<aw:PurchaseOrder
aw:PurchaseOrderNumber="99503"
aw:OrderDate="1999-10-20"
xmlns:aw="http://www.adventure-works.com">
<aw:Address aw:Type="Shipping">
<aw:Name>Ellen Adams</aw:Name>
<aw:Street>123 Maple Street</aw:Street>
<aw:City>Mill Valley</aw:City>
<aw:State>CA</aw:State>
<aw:Zip>10999</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:Address>
<aw:Address aw:Type="Billing">
<aw:Name>Tai Yee</aw:Name>
<aw:Street>8 Oak Avenue</aw:Street>
<aw:City>Old Town</aw:City>
<aw:State>PA</aw:State>
<aw:Zip>95819</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:Address>
<aw:DeliveryNotes>Please leave packages in shed by driveway.</aw:DeliveryNotes>
<aw:Items>
<aw:Item aw:PartNumber="872-AA">
<aw:ProductName>Lawnmower</aw:ProductName>
<aw:Quantity>1</aw:Quantity>
<aw:USPrice>148.95</aw:USPrice>
<aw:Comment>Confirm this is electric</aw:Comment>
</aw:Item>
<aw:Item aw:PartNumber="926-AA">
<aw:ProductName>Baby Monitor</aw:ProductName>
<aw:Quantity>2</aw:Quantity>
<aw:USPrice>39.98</aw:USPrice>
<aw:ShipDate>1999-05-21</aw:ShipDate>
</aw:Item>
</aw:Items>
</aw:PurchaseOrder>
Пример XML-файла: несколько заказов на
покупку (LINQ to XML)
23.10.2019 • 2 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Этот файл
содержит несколько заказов на покупку.
PurchaseOrders.xml
<?xml version="1.0"?>
<PurchaseOrders>
<PurchaseOrder PurchaseOrderNumber="99503" OrderDate="1999-10-20">
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes>
<Items>
<Item PartNumber="872-AA">
<ProductName>Lawnmower</ProductName>
<Quantity>1</Quantity>
<USPrice>148.95</USPrice>
<Comment>Confirm this is electric</Comment>
</Item>
<Item PartNumber="926-AA">
<ProductName>Baby Monitor</ProductName>
<Quantity>2</Quantity>
<USPrice>39.98</USPrice>
<ShipDate>1999-05-21</ShipDate>
</Item>
</Items>
</PurchaseOrder>
<PurchaseOrder PurchaseOrderNumber="99505" OrderDate="1999-10-22">
<Address Type="Shipping">
<Name>Cristian Osorio</Name>
<Street>456 Main Street</Street>
<City>Buffalo</City>
<State>NY</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Cristian Osorio</Name>
<Street>456 Main Street</Street>
<City>Buffalo</City>
<State>NY</State>
<Zip>98112</Zip>
<Country>USA</Country>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please notify me before shipping.</DeliveryNotes>
<Items>
<Item PartNumber="456-NM">
<ProductName>Power Supply</ProductName>
<Quantity>1</Quantity>
<USPrice>45.99</USPrice>
</Item>
</Items>
</PurchaseOrder>
<PurchaseOrder PurchaseOrderNumber="99504" OrderDate="1999-10-22">
<Address Type="Shipping">
<Name>Jessica Arnold</Name>
<Street>4055 Madison Ave</Street>
<City>Seattle</City>
<State>WA</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Jessica Arnold</Name>
<Street>4055 Madison Ave</Street>
<City>Buffalo</City>
<State>NY</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<Items>
<Item PartNumber="898-AZ">
<ProductName>Computer Keyboard</ProductName>
<Quantity>1</Quantity>
<USPrice>29.99</USPrice>
</Item>
<Item PartNumber="898-AM">
<ProductName>Wireless Mouse</ProductName>
<Quantity>1</Quantity>
<USPrice>14.99</USPrice>
</Item>
</Items>
</PurchaseOrder>
</PurchaseOrders>
Пример XML-файла: несколько заказов на покупку
в пространстве имен
23.10.2019 • 2 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Этот файл
содержит несколько заказов на покупку. XML располагается в пространстве имен.
PurchaseOrdersInNamespace.xml
<?xml version="1.0" encoding="utf-8"?>
<aw:PurchaseOrders xmlns:aw="http://www.adventure-works.com">
<aw:PurchaseOrder aw:PurchaseOrderNumber="99503" aw:OrderDate="1999-10-20">
<aw:Address aw:Type="Shipping">
<aw:Name>Ellen Adams</aw:Name>
<aw:Street>123 Maple Street</aw:Street>
<aw:City>Mill Valley</aw:City>
<aw:State>CA</aw:State>
<aw:Zip>10999</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:Address>
<aw:Address aw:Type="Billing">
<aw:Name>Tai Yee</aw:Name>
<aw:Street>8 Oak Avenue</aw:Street>
<aw:City>Old Town</aw:City>
<aw:State>PA</aw:State>
<aw:Zip>95819</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:Address>
<aw:DeliveryNotes>Please leave packages in shed by driveway.</aw:DeliveryNotes>
<aw:Items>
<aw:Item aw:PartNumber="872-AA">
<aw:ProductName>Lawnmower</aw:ProductName>
<aw:Quantity>1</aw:Quantity>
<aw:USPrice>148.95</aw:USPrice>
<aw:Comment>Confirm this is electric</aw:Comment>
</aw:Item>
<aw:Item aw:PartNumber="926-AA">
<aw:ProductName>Baby Monitor</aw:ProductName>
<aw:Quantity>2</aw:Quantity>
<aw:USPrice>39.98</aw:USPrice>
<aw:ShipDate>1999-05-21</aw:ShipDate>
</aw:Item>
</aw:Items>
</aw:PurchaseOrder>
<aw:PurchaseOrder aw:PurchaseOrderNumber="99505" aw:OrderDate="1999-10-22">
<aw:Address aw:Type="Shipping">
<aw:Name>Cristian Osorio</aw:Name>
<aw:Street>456 Main Street</aw:Street>
<aw:City>Buffalo</aw:City>
<aw:State>NY</aw:State>
<aw:Zip>98112</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:Address>
<aw:Address aw:Type="Billing">
<aw:Name>Cristian Osorio</aw:Name>
<aw:Street>456 Main Street</aw:Street>
<aw:City>Buffalo</aw:City>
<aw:State>NY</aw:State>
<aw:Zip>98112</aw:Zip>
<aw:Country>USA</aw:Country>
<aw:Country>USA</aw:Country>
</aw:Address>
<aw:DeliveryNotes>Please notify me before shipping.</aw:DeliveryNotes>
<aw:Items>
<aw:Item aw:PartNumber="456-NM">
<aw:ProductName>Power Supply</aw:ProductName>
<aw:Quantity>1</aw:Quantity>
<aw:USPrice>45.99</aw:USPrice>
</aw:Item>
</aw:Items>
</aw:PurchaseOrder>
<aw:PurchaseOrder aw:PurchaseOrderNumber="99504" aw:OrderDate="1999-10-22">
<aw:Address aw:Type="Shipping">
<aw:Name>Jessica Arnold</aw:Name>
<aw:Street>4055 Madison Ave</aw:Street>
<aw:City>Seattle</aw:City>
<aw:State>WA</aw:State>
<aw:Zip>98112</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:Address>
<aw:Address aw:Type="Billing">
<aw:Name>Jessica Arnold</aw:Name>
<aw:Street>4055 Madison Ave</aw:Street>
<aw:City>Buffalo</aw:City>
<aw:State>NY</aw:State>
<aw:Zip>98112</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:Address>
<aw:Items>
<aw:Item aw:PartNumber="898-AZ">
<aw:ProductName>Computer Keyboard</aw:ProductName>
<aw:Quantity>1</aw:Quantity>
<aw:USPrice>29.99</aw:USPrice>
</aw:Item>
<aw:Item aw:PartNumber="898-AM">
<aw:ProductName>Wireless Mouse</aw:ProductName>
<aw:Quantity>1</aw:Quantity>
<aw:USPrice>14.99</aw:USPrice>
</aw:Item>
</aw:Items>
</aw:PurchaseOrder>
</aw:PurchaseOrders>
Пример XML-файла: конфигурация тестирования
(LINQ to XML)
23.10.2019 • 2 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Это тестовый
файл конфигурации.
TestConfig.xml
<?xml version="1.0"?>
<Tests>
<Test TestId="0001" TestType="CMD">
<Name>Convert number to string</Name>
<CommandLine>Examp1.EXE</CommandLine>
<Input>1</Input>
<Output>One</Output>
</Test>
<Test TestId="0002" TestType="CMD">
<Name>Find succeeding characters</Name>
<CommandLine>Examp2.EXE</CommandLine>
<Input>abc</Input>
<Output>def</Output>
</Test>
<Test TestId="0003" TestType="GUI">
<Name>Convert multiple numbers to strings</Name>
<CommandLine>Examp2.EXE /Verbose</CommandLine>
<Input>123</Input>
<Output>One Two Three</Output>
</Test>
<Test TestId="0004" TestType="GUI">
<Name>Find correlated key</Name>
<CommandLine>Examp3.EXE</CommandLine>
<Input>a1</Input>
<Output>b1</Output>
</Test>
<Test TestId="0005" TestType="GUI">
<Name>Count characters</Name>
<CommandLine>FinalExamp.EXE</CommandLine>
<Input>This is a test</Input>
<Output>14</Output>
</Test>
<Test TestId="0006" TestType="GUI">
<Name>Another Test</Name>
<CommandLine>Examp2.EXE</CommandLine>
<Input>Test Input</Input>
<Output>10</Output>
</Test>
</Tests>
Пример XML-файла: конфигурация тестирования в
пространстве имен
23.10.2019 • 2 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Это тестовый
файл конфигурации. XML располагается в пространстве имен.
TestConfigInNamespace.xml
<?xml version="1.0"?>
<Tests xmlns="http://www.adatum.com">
<Test TestId="0001" TestType="CMD">
<Name>Convert number to string</Name>
<CommandLine>Examp1.EXE</CommandLine>
<Input>1</Input>
<Output>One</Output>
</Test>
<Test TestId="0002" TestType="CMD">
<Name>Find succeeding characters</Name>
<CommandLine>Examp2.EXE</CommandLine>
<Input>abc</Input>
<Output>def</Output>
</Test>
<Test TestId="0003" TestType="GUI">
<Name>Convert multiple numbers to strings</Name>
<CommandLine>Examp2.EXE /Verbose</CommandLine>
<Input>123</Input>
<Output>One Two Three</Output>
</Test>
<Test TestId="0004" TestType="GUI">
<Name>Find correlated key</Name>
<CommandLine>Examp3.EXE</CommandLine>
<Input>a1</Input>
<Output>b1</Output>
</Test>
<Test TestId="0005" TestType="GUI">
<Name>Count characters</Name>
<CommandLine>FinalExamp.EXE</CommandLine>
<Input>This is a test</Input>
<Output>14</Output>
</Test>
<Test TestId="0006" TestType="GUI">
<Name>Another Test</Name>
<CommandLine>Examp2.EXE</CommandLine>
<Input>Test Input</Input>
<Output>10</Output>
</Test>
</Tests>
Пример XML-файла: заказчики и заказы (LINQ
to XML)
23.10.2019 • 4 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Этот файл
содержит сведения о заказчиках и заказах.
Раздел Пример XSD -файла. Заказчики и заказы содержит XSD -файл, с помощью которого можно
проверить этот документ. Здесь используются функции XSD xs:key и xs:keyref для установления
того, что атрибут CustomerID элемента Customer является ключом, а также для установления связи
между элементом CustomerID каждого из элементов Order и атрибутом CustomerID каждого из
элементов Customer .
Пример написания запросов LINQ с использованием преимущества такой связи в предложении Join
см. в разделе Практическое руководство. Объединение двух коллекций (C#) (LINQ to XML ) (C#).
CustomersOrders.xml
<?xml version="1.0" encoding="utf-8"?>
<Root>
<Customers>
<Customer CustomerID="GREAL">
<CompanyName>Great Lakes Food Market</CompanyName>
<ContactName>Howard Snyder</ContactName>
<ContactTitle>Marketing Manager</ContactTitle>
<Phone>(503) 555-7555</Phone>
<FullAddress>
<Address>2732 Baker Blvd.</Address>
<City>Eugene</City>
<Region>OR</Region>
<PostalCode>97403</PostalCode>
<Country>USA</Country>
</FullAddress>
</Customer>
<Customer CustomerID="HUNGC">
<CompanyName>Hungry Coyote Import Store</CompanyName>
<ContactName>Yoshi Latimer</ContactName>
<ContactTitle>Sales Representative</ContactTitle>
<Phone>(503) 555-6874</Phone>
<Fax>(503) 555-2376</Fax>
<FullAddress>
<Address>City Center Plaza 516 Main St.</Address>
<City>Elgin</City>
<Region>OR</Region>
<PostalCode>97827</PostalCode>
<Country>USA</Country>
</FullAddress>
</Customer>
<Customer CustomerID="LAZYK">
<CompanyName>Lazy K Kountry Store</CompanyName>
<ContactName>John Steel</ContactName>
<ContactTitle>Marketing Manager</ContactTitle>
<Phone>(509) 555-7969</Phone>
<Fax>(509) 555-6221</Fax>
<FullAddress>
<Address>12 Orchestra Terrace</Address>
<City>Walla Walla</City>
<Region>WA</Region>
<Region>WA</Region>
<PostalCode>99362</PostalCode>
<Country>USA</Country>
</FullAddress>
</Customer>
<Customer CustomerID="LETSS">
<CompanyName>Let's Stop N Shop</CompanyName>
<ContactName>Jaime Yorres</ContactName>
<ContactTitle>Owner</ContactTitle>
<Phone>(415) 555-5938</Phone>
<FullAddress>
<Address>87 Polk St. Suite 5</Address>
<City>San Francisco</City>
<Region>CA</Region>
<PostalCode>94117</PostalCode>
<Country>USA</Country>
</FullAddress>
</Customer>
</Customers>
<Orders>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>6</EmployeeID>
<OrderDate>1997-05-06T00:00:00</OrderDate>
<RequiredDate>1997-05-20T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-05-09T00:00:00">
<ShipVia>2</ShipVia>
<Freight>3.35</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>8</EmployeeID>
<OrderDate>1997-07-04T00:00:00</OrderDate>
<RequiredDate>1997-08-01T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-07-14T00:00:00">
<ShipVia>2</ShipVia>
<Freight>4.42</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>1</EmployeeID>
<OrderDate>1997-07-31T00:00:00</OrderDate>
<RequiredDate>1997-08-28T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-08-05T00:00:00">
<ShipVia>2</ShipVia>
<Freight>116.53</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<CustomerID>GREAL</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1997-07-31T00:00:00</OrderDate>
<RequiredDate>1997-08-28T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-08-04T00:00:00">
<ShipVia>2</ShipVia>
<Freight>18.53</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>6</EmployeeID>
<OrderDate>1997-09-04T00:00:00</OrderDate>
<RequiredDate>1997-10-02T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-09-10T00:00:00">
<ShipVia>1</ShipVia>
<Freight>57.15</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>3</EmployeeID>
<OrderDate>1997-09-25T00:00:00</OrderDate>
<RequiredDate>1997-10-23T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-09-30T00:00:00">
<ShipVia>3</ShipVia>
<Freight>76.13</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1998-01-06T00:00:00</OrderDate>
<RequiredDate>1998-02-03T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1998-02-04T00:00:00">
<ShipVia>2</ShipVia>
<Freight>719.78</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>3</EmployeeID>
<OrderDate>1998-03-09T00:00:00</OrderDate>
<RequiredDate>1998-04-06T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1998-03-18T00:00:00">
<ShipVia>2</ShipVia>
<ShipVia>2</ShipVia>
<Freight>33.68</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>3</EmployeeID>
<OrderDate>1998-04-07T00:00:00</OrderDate>
<RequiredDate>1998-05-05T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1998-04-15T00:00:00">
<ShipVia>2</ShipVia>
<Freight>25.19</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1998-04-22T00:00:00</OrderDate>
<RequiredDate>1998-05-20T00:00:00</RequiredDate>
<ShipInfo>
<ShipVia>3</ShipVia>
<Freight>18.84</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1998-04-30T00:00:00</OrderDate>
<RequiredDate>1998-06-11T00:00:00</RequiredDate>
<ShipInfo>
<ShipVia>3</ShipVia>
<Freight>14.01</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>HUNGC</CustomerID>
<EmployeeID>3</EmployeeID>
<OrderDate>1996-12-06T00:00:00</OrderDate>
<RequiredDate>1997-01-03T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1996-12-09T00:00:00">
<ShipVia>2</ShipVia>
<Freight>20.12</Freight>
<ShipName>Hungry Coyote Import Store</ShipName>
<ShipAddress>City Center Plaza 516 Main St.</ShipAddress>
<ShipCity>Elgin</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97827</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>HUNGC</CustomerID>
<EmployeeID>1</EmployeeID>
<OrderDate>1996-12-25T00:00:00</OrderDate>
<RequiredDate>1997-01-22T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-01-03T00:00:00">
<ShipVia>3</ShipVia>
<Freight>30.34</Freight>
<ShipName>Hungry Coyote Import Store</ShipName>
<ShipAddress>City Center Plaza 516 Main St.</ShipAddress>
<ShipCity>Elgin</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97827</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>HUNGC</CustomerID>
<EmployeeID>3</EmployeeID>
<OrderDate>1997-01-15T00:00:00</OrderDate>
<RequiredDate>1997-02-12T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-01-24T00:00:00">
<ShipVia>1</ShipVia>
<Freight>0.2</Freight>
<ShipName>Hungry Coyote Import Store</ShipName>
<ShipAddress>City Center Plaza 516 Main St.</ShipAddress>
<ShipCity>Elgin</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97827</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>HUNGC</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1997-07-16T00:00:00</OrderDate>
<RequiredDate>1997-08-13T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-07-21T00:00:00">
<ShipVia>1</ShipVia>
<Freight>45.13</Freight>
<ShipName>Hungry Coyote Import Store</ShipName>
<ShipAddress>City Center Plaza 516 Main St.</ShipAddress>
<ShipCity>Elgin</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97827</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>HUNGC</CustomerID>
<EmployeeID>8</EmployeeID>
<OrderDate>1997-09-08T00:00:00</OrderDate>
<RequiredDate>1997-10-06T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-10-15T00:00:00">
<ShipVia>1</ShipVia>
<Freight>111.29</Freight>
<ShipName>Hungry Coyote Import Store</ShipName>
<ShipAddress>City Center Plaza 516 Main St.</ShipAddress>
<ShipCity>Elgin</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97827</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LAZYK</CustomerID>
<EmployeeID>1</EmployeeID>
<OrderDate>1997-03-21T00:00:00</OrderDate>
<RequiredDate>1997-04-18T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-04-10T00:00:00">
<ShipVia>3</ShipVia>
<Freight>7.48</Freight>
<ShipName>Lazy K Kountry Store</ShipName>
<ShipAddress>12 Orchestra Terrace</ShipAddress>
<ShipCity>Walla Walla</ShipCity>
<ShipRegion>WA</ShipRegion>
<ShipPostalCode>99362</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LAZYK</CustomerID>
<EmployeeID>8</EmployeeID>
<OrderDate>1997-05-22T00:00:00</OrderDate>
<RequiredDate>1997-06-19T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-06-26T00:00:00">
<ShipVia>2</ShipVia>
<Freight>11.92</Freight>
<ShipName>Lazy K Kountry Store</ShipName>
<ShipAddress>12 Orchestra Terrace</ShipAddress>
<ShipCity>Walla Walla</ShipCity>
<ShipRegion>WA</ShipRegion>
<ShipPostalCode>99362</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LETSS</CustomerID>
<EmployeeID>1</EmployeeID>
<OrderDate>1997-06-25T00:00:00</OrderDate>
<RequiredDate>1997-07-23T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-07-04T00:00:00">
<ShipVia>2</ShipVia>
<Freight>13.73</Freight>
<ShipName>Let's Stop N Shop</ShipName>
<ShipAddress>87 Polk St. Suite 5</ShipAddress>
<ShipCity>San Francisco</ShipCity>
<ShipRegion>CA</ShipRegion>
<ShipPostalCode>94117</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LETSS</CustomerID>
<EmployeeID>8</EmployeeID>
<OrderDate>1997-10-27T00:00:00</OrderDate>
<RequiredDate>1997-11-24T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-11-05T00:00:00">
<ShipVia>2</ShipVia>
<Freight>51.44</Freight>
<ShipName>Let's Stop N Shop</ShipName>
<ShipAddress>87 Polk St. Suite 5</ShipAddress>
<ShipCity>San Francisco</ShipCity>
<ShipRegion>CA</ShipRegion>
<ShipPostalCode>94117</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LETSS</CustomerID>
<EmployeeID>6</EmployeeID>
<OrderDate>1997-11-10T00:00:00</OrderDate>
<RequiredDate>1997-12-08T00:00:00</RequiredDate>
<RequiredDate>1997-12-08T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-11-21T00:00:00">
<ShipVia>2</ShipVia>
<Freight>45.97</Freight>
<ShipName>Let's Stop N Shop</ShipName>
<ShipAddress>87 Polk St. Suite 5</ShipAddress>
<ShipCity>San Francisco</ShipCity>
<ShipRegion>CA</ShipRegion>
<ShipPostalCode>94117</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LETSS</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1998-02-12T00:00:00</OrderDate>
<RequiredDate>1998-03-12T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1998-02-13T00:00:00">
<ShipVia>2</ShipVia>
<Freight>90.97</Freight>
<ShipName>Let's Stop N Shop</ShipName>
<ShipAddress>87 Polk St. Suite 5</ShipAddress>
<ShipCity>San Francisco</ShipCity>
<ShipRegion>CA</ShipRegion>
<ShipPostalCode>94117</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
</Orders>
</Root>
Образец XSD-файла: заказчики и заказы
23.10.2019 • 2 minutes to read • Edit Online
Следующий файл XSD используется в различных примерах в документации LINQ to XML. Этот файл
содержит определение схемы для статьи Пример XML -файла. Заказчики и заказы (LINQ to XML ). В схеме
используются функции XSD xs:key и xs:keyref для определения того, что атрибут CustomerID элемента
Customer является ключом, а также для установления связей между элементом CustomerID в каждом
элементе Order и атрибутом CustomerID в каждом элементе Customer .
Пример написания запросов LINQ с использованием преимущества такой связи в предложении Join см.
в разделе Практическое руководство. Объединение двух коллекций (C#) (LINQ to XML ) (C#).
CustomersOrders.xsd
<?xml version="1.0" encoding="utf-8" ?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name='Root'>
<xs:complexType>
<xs:sequence>
<xs:element name='Customers'>
<xs:complexType>
<xs:sequence>
<xs:element name='Customer' type='CustomerType' minOccurs='0' maxOccurs='unbounded' />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name='Orders'>
<xs:complexType>
<xs:sequence>
<xs:element name='Order' type='OrderType' minOccurs='0' maxOccurs='unbounded' />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:key name='CustomerIDKey'>
<xs:selector xpath='Customers/Customer'/>
<xs:field xpath='@CustomerID'/>
</xs:key>
<xs:keyref name='CustomerIDKeyRef' refer='CustomerIDKey'>
<xs:selector xpath='Orders/Order'/>
<xs:field xpath='CustomerID'/>
</xs:keyref>
</xs:element>
<xs:complexType name='CustomerType'>
<xs:sequence>
<xs:element name='CompanyName' type='xs:string'/>
<xs:element name='ContactName' type='xs:string'/>
<xs:element name='ContactTitle' type='xs:string'/>
<xs:element name='Phone' type='xs:string'/>
<xs:element name='Fax' minOccurs='0' type='xs:string'/>
<xs:element name='FullAddress' type='AddressType'/>
</xs:sequence>
<xs:attribute name='CustomerID' type='xs:token'/>
</xs:complexType>
<xs:complexType name='AddressType'>
<xs:sequence>
<xs:element name='Address' type='xs:string'/>
<xs:element name='City' type='xs:string'/>
<xs:element name='Region' type='xs:string'/>
<xs:element name='Region' type='xs:string'/>
<xs:element name='PostalCode' type='xs:string' />
<xs:element name='Country' type='xs:string'/>
</xs:sequence>
<xs:attribute name='CustomerID' type='xs:token'/>
</xs:complexType>
<xs:complexType name='OrderType'>
<xs:sequence>
<xs:element name='CustomerID' type='xs:token'/>
<xs:element name='EmployeeID' type='xs:token'/>
<xs:element name='OrderDate' type='xs:dateTime'/>
<xs:element name='RequiredDate' type='xs:dateTime'/>
<xs:element name='ShipInfo' type='ShipInfoType'/>
</xs:sequence>
</xs:complexType>
<xs:complexType name='ShipInfoType'>
<xs:sequence>
<xs:element name='ShipVia' type='xs:integer'/>
<xs:element name='Freight' type='xs:decimal'/>
<xs:element name='ShipName' type='xs:string'/>
<xs:element name='ShipAddress' type='xs:string'/>
<xs:element name='ShipCity' type='xs:string'/>
<xs:element name='ShipRegion' type='xs:string'/>
<xs:element name='ShipPostalCode' type='xs:string'/>
<xs:element name='ShipCountry' type='xs:string'/>
</xs:sequence>
<xs:attribute name='ShippedDate' type='xs:dateTime'/>
</xs:complexType>
</xs:schema>
Пример XML-файла: заказчики и заказы в
пространстве имен
23.10.2019 • 3 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Этот файл
содержит сведения о заказчиках и заказах. XML располагается в пространстве имен.
CustomersOrdersInNamespace.xml
<?xml version="1.0" encoding="utf-8"?>
<Root xmlns="http://www.adventure-works.com">
<Customers>
<Customer CustomerID="GREAL">
<CompanyName>Great Lakes Food Market</CompanyName>
<ContactName>Howard Snyder</ContactName>
<ContactTitle>Marketing Manager</ContactTitle>
<Phone>(503) 555-7555</Phone>
<FullAddress>
<Address>2732 Baker Blvd.</Address>
<City>Eugene</City>
<Region>OR</Region>
<PostalCode>97403</PostalCode>
<Country>USA</Country>
</FullAddress>
</Customer>
<Customer CustomerID="HUNGC">
<CompanyName>Hungry Coyote Import Store</CompanyName>
<ContactName>Yoshi Latimer</ContactName>
<ContactTitle>Sales Representative</ContactTitle>
<Phone>(503) 555-6874</Phone>
<Fax>(503) 555-2376</Fax>
<FullAddress>
<Address>City Center Plaza 516 Main St.</Address>
<City>Elgin</City>
<Region>OR</Region>
<PostalCode>97827</PostalCode>
<Country>USA</Country>
</FullAddress>
</Customer>
<Customer CustomerID="LAZYK">
<CompanyName>Lazy K Kountry Store</CompanyName>
<ContactName>John Steel</ContactName>
<ContactTitle>Marketing Manager</ContactTitle>
<Phone>(509) 555-7969</Phone>
<Fax>(509) 555-6221</Fax>
<FullAddress>
<Address>12 Orchestra Terrace</Address>
<City>Walla Walla</City>
<Region>WA</Region>
<PostalCode>99362</PostalCode>
<Country>USA</Country>
</FullAddress>
</Customer>
<Customer CustomerID="LETSS">
<CompanyName>Let's Stop N Shop</CompanyName>
<ContactName>Jaime Yorres</ContactName>
<ContactTitle>Owner</ContactTitle>
<Phone>(415) 555-5938</Phone>
<FullAddress>
<Address>87 Polk St. Suite 5</Address>
<Address>87 Polk St. Suite 5</Address>
<City>San Francisco</City>
<Region>CA</Region>
<PostalCode>94117</PostalCode>
<Country>USA</Country>
</FullAddress>
</Customer>
</Customers>
<Orders>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>6</EmployeeID>
<OrderDate>1997-05-06T00:00:00</OrderDate>
<RequiredDate>1997-05-20T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-05-09T00:00:00">
<ShipVia>2</ShipVia>
<Freight>3.35</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>8</EmployeeID>
<OrderDate>1997-07-04T00:00:00</OrderDate>
<RequiredDate>1997-08-01T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-07-14T00:00:00">
<ShipVia>2</ShipVia>
<Freight>4.42</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>1</EmployeeID>
<OrderDate>1997-07-31T00:00:00</OrderDate>
<RequiredDate>1997-08-28T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-08-05T00:00:00">
<ShipVia>2</ShipVia>
<Freight>116.53</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1997-07-31T00:00:00</OrderDate>
<RequiredDate>1997-08-28T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-08-04T00:00:00">
<ShipVia>2</ShipVia>
<Freight>18.53</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>6</EmployeeID>
<OrderDate>1997-09-04T00:00:00</OrderDate>
<RequiredDate>1997-10-02T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-09-10T00:00:00">
<ShipVia>1</ShipVia>
<Freight>57.15</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>3</EmployeeID>
<OrderDate>1997-09-25T00:00:00</OrderDate>
<RequiredDate>1997-10-23T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-09-30T00:00:00">
<ShipVia>3</ShipVia>
<Freight>76.13</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1998-01-06T00:00:00</OrderDate>
<RequiredDate>1998-02-03T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1998-02-04T00:00:00">
<ShipVia>2</ShipVia>
<Freight>719.78</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>3</EmployeeID>
<OrderDate>1998-03-09T00:00:00</OrderDate>
<RequiredDate>1998-04-06T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1998-03-18T00:00:00">
<ShipVia>2</ShipVia>
<Freight>33.68</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<CustomerID>GREAL</CustomerID>
<EmployeeID>3</EmployeeID>
<OrderDate>1998-04-07T00:00:00</OrderDate>
<RequiredDate>1998-05-05T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1998-04-15T00:00:00">
<ShipVia>2</ShipVia>
<Freight>25.19</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1998-04-22T00:00:00</OrderDate>
<RequiredDate>1998-05-20T00:00:00</RequiredDate>
<ShipInfo>
<ShipVia>3</ShipVia>
<Freight>18.84</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>GREAL</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1998-04-30T00:00:00</OrderDate>
<RequiredDate>1998-06-11T00:00:00</RequiredDate>
<ShipInfo>
<ShipVia>3</ShipVia>
<Freight>14.01</Freight>
<ShipName>Great Lakes Food Market</ShipName>
<ShipAddress>2732 Baker Blvd.</ShipAddress>
<ShipCity>Eugene</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97403</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>HUNGC</CustomerID>
<EmployeeID>3</EmployeeID>
<OrderDate>1996-12-06T00:00:00</OrderDate>
<RequiredDate>1997-01-03T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1996-12-09T00:00:00">
<ShipVia>2</ShipVia>
<Freight>20.12</Freight>
<ShipName>Hungry Coyote Import Store</ShipName>
<ShipAddress>City Center Plaza 516 Main St.</ShipAddress>
<ShipCity>Elgin</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97827</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>HUNGC</CustomerID>
<EmployeeID>1</EmployeeID>
<OrderDate>1996-12-25T00:00:00</OrderDate>
<RequiredDate>1997-01-22T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-01-03T00:00:00">
<ShipVia>3</ShipVia>
<Freight>30.34</Freight>
<ShipName>Hungry Coyote Import Store</ShipName>
<ShipAddress>City Center Plaza 516 Main St.</ShipAddress>
<ShipCity>Elgin</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97827</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>HUNGC</CustomerID>
<EmployeeID>3</EmployeeID>
<OrderDate>1997-01-15T00:00:00</OrderDate>
<RequiredDate>1997-02-12T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-01-24T00:00:00">
<ShipVia>1</ShipVia>
<Freight>0.2</Freight>
<ShipName>Hungry Coyote Import Store</ShipName>
<ShipAddress>City Center Plaza 516 Main St.</ShipAddress>
<ShipCity>Elgin</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97827</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>HUNGC</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1997-07-16T00:00:00</OrderDate>
<RequiredDate>1997-08-13T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-07-21T00:00:00">
<ShipVia>1</ShipVia>
<Freight>45.13</Freight>
<ShipName>Hungry Coyote Import Store</ShipName>
<ShipAddress>City Center Plaza 516 Main St.</ShipAddress>
<ShipCity>Elgin</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97827</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>HUNGC</CustomerID>
<EmployeeID>8</EmployeeID>
<OrderDate>1997-09-08T00:00:00</OrderDate>
<RequiredDate>1997-10-06T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-10-15T00:00:00">
<ShipVia>1</ShipVia>
<Freight>111.29</Freight>
<ShipName>Hungry Coyote Import Store</ShipName>
<ShipAddress>City Center Plaza 516 Main St.</ShipAddress>
<ShipCity>Elgin</ShipCity>
<ShipRegion>OR</ShipRegion>
<ShipPostalCode>97827</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LAZYK</CustomerID>
<EmployeeID>1</EmployeeID>
<OrderDate>1997-03-21T00:00:00</OrderDate>
<RequiredDate>1997-04-18T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-04-10T00:00:00">
<ShipVia>3</ShipVia>
<Freight>7.48</Freight>
<ShipName>Lazy K Kountry Store</ShipName>
<ShipAddress>12 Orchestra Terrace</ShipAddress>
<ShipCity>Walla Walla</ShipCity>
<ShipRegion>WA</ShipRegion>
<ShipPostalCode>99362</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LAZYK</CustomerID>
<EmployeeID>8</EmployeeID>
<OrderDate>1997-05-22T00:00:00</OrderDate>
<RequiredDate>1997-06-19T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-06-26T00:00:00">
<ShipVia>2</ShipVia>
<Freight>11.92</Freight>
<ShipName>Lazy K Kountry Store</ShipName>
<ShipAddress>12 Orchestra Terrace</ShipAddress>
<ShipCity>Walla Walla</ShipCity>
<ShipRegion>WA</ShipRegion>
<ShipPostalCode>99362</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LETSS</CustomerID>
<EmployeeID>1</EmployeeID>
<OrderDate>1997-06-25T00:00:00</OrderDate>
<RequiredDate>1997-07-23T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-07-04T00:00:00">
<ShipVia>2</ShipVia>
<Freight>13.73</Freight>
<ShipName>Let's Stop N Shop</ShipName>
<ShipAddress>87 Polk St. Suite 5</ShipAddress>
<ShipCity>San Francisco</ShipCity>
<ShipRegion>CA</ShipRegion>
<ShipPostalCode>94117</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LETSS</CustomerID>
<EmployeeID>8</EmployeeID>
<OrderDate>1997-10-27T00:00:00</OrderDate>
<RequiredDate>1997-11-24T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-11-05T00:00:00">
<ShipVia>2</ShipVia>
<Freight>51.44</Freight>
<ShipName>Let's Stop N Shop</ShipName>
<ShipAddress>87 Polk St. Suite 5</ShipAddress>
<ShipCity>San Francisco</ShipCity>
<ShipRegion>CA</ShipRegion>
<ShipPostalCode>94117</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
<Order>
<CustomerID>LETSS</CustomerID>
<EmployeeID>6</EmployeeID>
<OrderDate>1997-11-10T00:00:00</OrderDate>
<RequiredDate>1997-12-08T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1997-11-21T00:00:00">
<ShipVia>2</ShipVia>
<Freight>45.97</Freight>
<ShipName>Let's Stop N Shop</ShipName>
<ShipAddress>87 Polk St. Suite 5</ShipAddress>
<ShipCity>San Francisco</ShipCity>
<ShipRegion>CA</ShipRegion>
<ShipPostalCode>94117</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
</Order>
<Order>
<CustomerID>LETSS</CustomerID>
<EmployeeID>4</EmployeeID>
<OrderDate>1998-02-12T00:00:00</OrderDate>
<RequiredDate>1998-03-12T00:00:00</RequiredDate>
<ShipInfo ShippedDate="1998-02-13T00:00:00">
<ShipVia>2</ShipVia>
<Freight>90.97</Freight>
<ShipName>Let's Stop N Shop</ShipName>
<ShipAddress>87 Polk St. Suite 5</ShipAddress>
<ShipCity>San Francisco</ShipCity>
<ShipRegion>CA</ShipRegion>
<ShipPostalCode>94117</ShipPostalCode>
<ShipCountry>USA</ShipCountry>
</ShipInfo>
</Order>
</Orders>
</Root>
Пример XML-файла: числовые данные (LINQ to
XML)
23.10.2019 • 2 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Этот файл
содержит числовые данные для суммирования, вычисления средних значений и группирования.
Data.xml
<Root>
<TaxRate>7.25</TaxRate>
<Data>
<Category>A</Category>
<Quantity>3</Quantity>
<Price>24.50</Price>
</Data>
<Data>
<Category>B</Category>
<Quantity>1</Quantity>
<Price>89.99</Price>
</Data>
<Data>
<Category>A</Category>
<Quantity>5</Quantity>
<Price>4.95</Price>
</Data>
<Data>
<Category>A</Category>
<Quantity>3</Quantity>
<Price>66.00</Price>
</Data>
<Data>
<Category>B</Category>
<Quantity>10</Quantity>
<Price>.99</Price>
</Data>
<Data>
<Category>A</Category>
<Quantity>15</Quantity>
<Price>29.00</Price>
</Data>
<Data>
<Category>B</Category>
<Quantity>8</Quantity>
<Price>6.99</Price>
</Data>
</Root>
Пример XML-файла: числовые данные в
пространстве имен
23.10.2019 • 2 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Этот файл
содержит числовые данные для суммирования, вычисления средних значений и группирования. XML
располагается в пространстве имен.
Данные
<Root xmlns='http://www.adatum.com'>
<TaxRate>7.25</TaxRate>
<Data>
<Category>A</Category>
<Quantity>3</Quantity>
<Price>24.50</Price>
</Data>
<Data>
<Category>B</Category>
<Quantity>1</Quantity>
<Price>89.99</Price>
</Data>
<Data>
<Category>A</Category>
<Quantity>5</Quantity>
<Price>4.95</Price>
</Data>
<Data>
<Category>A</Category>
<Quantity>3</Quantity>
<Price>66.00</Price>
</Data>
<Data>
<Category>B</Category>
<Quantity>10</Quantity>
<Price>.99</Price>
</Data>
<Data>
<Category>A</Category>
<Quantity>15</Quantity>
<Price>29.00</Price>
</Data>
<Data>
<Category>B</Category>
<Quantity>8</Quantity>
<Price>6.99</Price>
</Data>
</Root>
Пример XML-файла: книги (LINQ to XML)
23.10.2019 • 2 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Этот файл
содержит сведения о книгах.
books.xml
<?xml version="1.0"?>
<Catalog>
<Book id="bk101">
<Author>Garghentini, Davide</Author>
<Title>XML Developer's Guide</Title>
<Genre>Computer</Genre>
<Price>44.95</Price>
<PublishDate>2000-10-01</PublishDate>
<Description>An in-depth look at creating applications
with XML.</Description>
</Book>
<Book id="bk102">
<Author>Garcia, Debra</Author>
<Title>Midnight Rain</Title>
<Genre>Fantasy</Genre>
<Price>5.95</Price>
<PublishDate>2000-12-16</PublishDate>
<Description>A former architect battles corporate zombies,
an evil sorceress, and her own childhood to become queen
of the world.</Description>
</Book>
</Catalog>
Пример XML-файла: консолидированные заказы
на покупку
23.10.2019 • 2 minutes to read • Edit Online
Следующий XML -файл используется в различных примерах в документации LINQ to XML. Этот файл
представляет собой набор заказов на покупку в различных формах от нескольких компаний. Заказы на
покупку от каждой компании располагаются в отдельном пространстве имен.
ConsolidatedPurchaseOrders.xml
<?xml version="1.0"?>
<PurchaseOrders xmlns="www.contoso.com">
<PurchaseOrder
PurchaseOrderNumber="99503"
OrderDate="1999-10-20">
<Address Type="Shipping">
<Name>Ellen Adams</Name>
<Street>123 Maple Street</Street>
<City>Mill Valley</City>
<State>CA</State>
<Zip>10999</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Tai Yee</Name>
<Street>8 Oak Avenue</Street>
<City>Old Town</City>
<State>PA</State>
<Zip>95819</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please leave packages in shed by driveway.</DeliveryNotes>
<Items>
<Item PartNumber="872-AA">
<ProductName>Lawnmower</ProductName>
<Quantity>1</Quantity>
<USPrice>148.95</USPrice>
<Comment>Confirm this is electric</Comment>
</Item>
<Item PartNumber="926-AA">
<ProductName>Baby Monitor</ProductName>
<Quantity>2</Quantity>
<USPrice>39.98</USPrice>
<ShipDate>1999-05-21</ShipDate>
</Item>
</Items>
</PurchaseOrder>
<PurchaseOrder PurchaseOrderNumber="99505" OrderDate="1999-10-22">
<Address Type="Shipping">
<Name>Cristian Osorio</Name>
<Street>456 Main Street</Street>
<City>Buffalo</City>
<State>NY</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Cristian Osorio</Name>
<Street>456 Main Street</Street>
<City>Buffalo</City>
<City>Buffalo</City>
<State>NY</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please notify by email before shipping.</DeliveryNotes>
<Items>
<Item PartNumber="456-NM">
<ProductName>Power Supply</ProductName>
<Quantity>1</Quantity>
<USPrice>45.99</USPrice>
</Item>
</Items>
</PurchaseOrder>
<PurchaseOrder PurchaseOrderNumber="99504" OrderDate="1999-10-22">
<Address Type="Shipping">
<Name>Jessica Arnold</Name>
<Street>4055 Madison Ave</Street>
<City>Seattle</City>
<State>WA</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<Address Type="Billing">
<Name>Jessica Arnold</Name>
<Street>4055 Madison Ave</Street>
<City>Buffalo</City>
<State>NY</State>
<Zip>98112</Zip>
<Country>USA</Country>
</Address>
<DeliveryNotes>Please do not deliver on Saturday.</DeliveryNotes>
<Items>
<Item PartNumber="898-AZ">
<ProductName>Computer Keyboard</ProductName>
<Quantity>1</Quantity>
<USPrice>29.99</USPrice>
</Item>
<Item PartNumber="898-AM">
<ProductName>Wireless Mouse</ProductName>
<Quantity>1</Quantity>
<USPrice>14.99</USPrice>
</Item>
</Items>
</PurchaseOrder>
<aw:PurchaseOrder
PONumber="11223"
Date="2000-01-15"
xmlns:aw="http://www.adventure-works.com">
<aw:ShippingAddress>
<aw:Name>Chris Preston</aw:Name>
<aw:Street>123 Main St.</aw:Street>
<aw:City>Seattle</aw:City>
<aw:State>WA</aw:State>
<aw:Zip>98113</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:ShippingAddress>
<aw:BillingAddress>
<aw:Name>Chris Preston</aw:Name>
<aw:Street>123 Main St.</aw:Street>
<aw:City>Seattle</aw:City>
<aw:State>WA</aw:State>
<aw:Zip>98113</aw:Zip>
<aw:Country>USA</aw:Country>
</aw:BillingAddress>
<aw:DeliveryInstructions>Ship only complete order.</aw:DeliveryInstructions>
<aw:Item PartNum="LIT-01">
<aw:ProductID>Litware Networking Card</aw:ProductID>
<aw:Qty>1</aw:Qty>
<aw:Price>20.99</aw:Price>
</aw:Item>
<aw:Item PartNum="LIT-25">
<aw:ProductID>Litware 17in LCD Monitor</aw:ProductID>
<aw:Qty>1</aw:Qty>
<aw:Price>199.99</aw:Price>
</aw:Item>
</aw:PurchaseOrder>
</PurchaseOrders>
Ссылка (LINQ to XML)
23.10.2019 • 2 minutes to read • Edit Online
В этом разделе
Справочную информацию по классам LINQ to XML см. в разделе System.Xml.Linq.
Справочную информацию по методам расширений, которые помогают при проверке XML -деревьев XSD -
файла, см. в разделе System.Xml.Schema.Extensions.
Справочную информацию по методам расширений, которые позволяют выполнить запросы XPath к XML -
дереву, см. в разделе System.Xml.XPath.Extensions.
См. также
LINQ to XML (C#)
LINQ to ADO.NET (Страница портала)
23.10.2019 • 3 minutes to read • Edit Online
LINQ to ADO.NET позволяет запрашивать любой перечисляемый объект в ADO.NET с помощью модели
программирования LINQ.
NOTE
Документация по LINQ to ADO.NET находится в разделе ADO.NET пакета SDK для .NET Framework: LINQ и ADO.NET.
Существуют три отдельные технологии ADO.NET LINQ: LINQ to DataSet, LINQ to SQL и LINQ to Entities.
Технология LINQ to DataSet обеспечивает расширенные и оптимизированные запросы к DataSet, LINQ to
SQL позволяет запрашивать непосредственно схемы базы данных SQL Server, а LINQ to Entities —
выполнять запросы к EDM.
LINQ to DataSet
DataSet является одним из наиболее широко используемых компонентов в ADO.NET и ключевым элементом
модели автономного программирования, на основе которой создан ADO.NET. Несмотря на все это, объект
DataSet имеет ограниченные возможности запросов.
LINQ to DataSet позволяет использовать расширенные возможности запросов в DataSet — те же функции,
что и для многих других источников данных.
Дополнительные сведения см. в разделе LINQ to DataSet.
LINQ to SQL
LINQ to SQL предоставляет инфраструктуру времени выполнения для управления реляционными данными в
виде объектов. В LINQ to SQL модель данных реляционной базы данных сопоставляется объектной модели,
выраженной в языке программирования разработчика. При выполнении приложения LINQ to SQL
преобразует интегрированные в язык запросы из объектной модели в SQL и отправляет их в базу данных для
выполнения. Когда база данных возвращает результаты, LINQ to SQL переводит их обратно в объекты,
которыми можно управлять.
LINQ to SQL включает поддержку хранимых процедур, определяемых пользователем функций в базе
данных и наследования в объектной модели.
Дополнительные сведения см. в разделе LINQ to SQL.
LINQ to Entities
В модели EDM реляционные данные представлены в виде объектов в среде .NET. Благодаря этому
поддержка LINQ эффективно реализуется на уровне объектов, что позволяет составлять запросы баз данных
на языке, используемом для сборки бизнес-логики. Эта функция называется LINQ to Entities.
Дополнительные сведения см. в разделе LINQ to Entities.
См. также
LINQ и ADO.NET
LINQ (C#)
Включение источника данных для запросов LINQ
23.10.2019 • 6 minutes to read • Edit Online
Существуют различные способы расширения LINQ для поддержки запросов LINQ к любому источнику
данных. Источник данных может быть, например, структурой данных, веб-службой, файловой системой или
базой данных. Шаблон LINQ упрощает для клиентов осуществление запросов к источникам данных, для
которых задействована поддержка запросов LINQ, поскольку синтаксис и шаблон запроса не меняется.
Далее перечислены способы, с помощью которых LINQ может быть расширен для этих источников данных.
Реализация интерфейса IEnumerable<T> в типе для выполнения запросов LINQ к объектам этого типа.
Создание методов стандартных операторов запроса, таких как Where и Select, расширяющих тип, для
поддержки настраиваемых запросов LINQ этого типа.
Создание поставщика для источника данных, который реализует интерфейс IQueryable<T>.
Поставщик, реализующий этот интерфейс, получает запросы LINQ в виде деревьев выражения,
которые он может выполнять любым способом, например удаленно.
Создание поставщика для источника данных, который использует преимущества существующей
технологии LINQ. Такой поставщик позволит выполнять не только запросы, но и операции вставки,
обновления, удаления и сопоставления для определяемых пользователем типов.
Эти варианты рассматриваются далее.
См. также
IQueryable<T>
IEnumerable<T>
Enumerable
Общие сведения о стандартных операторах запроса (C#)
LINQ to Objects (C#)
Интегрированная среда разработки Visual Studio и
поддержка средств для LINQ (C#)
23.10.2019 • 2 minutes to read • Edit Online
См. также
LINQ (C#)
Объектно ориентированное программирование
(C#)
04.11.2019 • 16 minutes to read • Edit Online
Классы и объекты
Термины класс и объект часто взаимозаменяемы, но в действительности классы описывают типы
объектов, а объекты — это используемые экземпляры классов. Поэтому процесс создания объекта
называется созданием экземпляра. Если использовать сравнение с чертежом, то класс является чертежом, а
объект является зданием, построенным по нему.
Определение класса:
class SampleClass
{
}
struct SampleStruct
{
}
class SampleClass
{
public string sampleField;
}
Для работы со свойствами используются процедуры "Get" и "Set", которые расширяют возможности
управления способом задания и возврата значений.
В языке C# для хранения значения свойства можно создать частное поле (private) или использовать так
называемые автоматически реализуемые свойства, которые автоматически создают это поле и
обеспечивают базовую логику для процедур свойств.
Определение автоматически реализуемого свойства:
class SampleClass
{
public int SampleProperty { get; set; }
}
Если требуется выполнить дополнительные операции чтения и записи применительно к значению свойства,
определите поле для хранения значения свойства и затем реализуйте базовую логику для хранения и
извлечения этого значения:
class SampleClass
{
private int _sample;
public int Sample
{
// Return the value stored in a field.
get { return _sample; }
// Store the value in the field.
set { _sample = value; }
}
}
У большинства свойств есть методы или процедуры для задания и возврата значения свойства. Однако
можно создать свойства, доступные только для чтения или только на запись, чтобы запретить изменение или
чтение значений свойств. В C# можно опустить метод свойства get или set . Следует отметить, что
автоматически реализуемые свойства нельзя сделать доступными только для чтения или только для записи.
Дополнительные сведения можно найти в разделе
get
set
Методы
Действие, которое выполняет объект, называется методом.
Определение метода класса:
class SampleClass
{
public int sampleMethod(string sampleParam)
{
// Insert code here
}
}
Класс может иметь несколько реализаций или перегрузок одного и того же метода, которые отличаются от
других числом или типами параметров.
Перегрузка метода:
Как правило, метод объявляется при определении класса. Однако C# также поддерживает методы
расширения, которые позволяют добавлять методы в существующий класс вне определения класса.
Дополнительные сведения можно найти в разделе
Методы
Методы расширения
Конструкторы
Конструкторы — это методы классов, выполняемые автоматически при создании объекта заданного типа.
Обычно конструкторы выполняют инициализацию членов данных нового объекта. Конструктор можно
запустить только один раз при создании класса. Кроме того, код конструктора всегда выполняется раньше
всех остальных частей кода в классе. Следует отметить, что так же, как и для других методов, можно создать
несколько перегрузок конструктора.
Определение конструктора для класса:
class Container
{
class Nested
{
// Add code here.
}
}
Чтобы создать экземпляр вложенного класса, укажите имя класса контейнера и имя вложенного класса,
используя в качестве разделителя точку:
МОДИФИКАТОР C# ОПРЕДЕЛЕНИЕ
protected internal Доступ к типу или члену возможен из любого кода в той
же сборке, или из производного класса в другой сборке.
private protected Доступ к типу или члену можно получить из кода в том же
классе или в производном классе в сборке базового класса.
После создания экземпляра класса можно присваивать значения свойствам и полям экземпляра и вызывать
методы класса.
Чтобы назначить значения свойствам в процессе создания экземпляра класса, используйте инициализаторы
объектов:
Чтобы получить доступ к статическому члену, используйте имя класса без создания объекта этого класса:
Console.WriteLine(SampleClass.SampleString);
Статические классы в C# имеют только статические члены и не могут быть созданы. Статические члены
также не могут обращаться к нестатическим свойствам, полям или методам.
Дополнительные сведения см. в разделе static.
Анонимные типы
Анонимные типы позволяют создавать объекты без написания определения класса для типа данных. Вместо
этого компилятор создает класс для вас. Данный класс не имеет имени и содержит свойства, которые
указаны при объявлении объекта.
Создание экземпляра анонимного типа:
Наследование
Наследование позволяет создавать новые классы, которые повторно используют, расширяют и изменяют
поведение, определенное в другом классе. Класс, члены которого наследуются, называется базовым классом,
а класс, который наследует эти члены, называется производным классом. Следует учитывать, что все классы
в C# неявно наследуются от класса Object, который поддерживает иерархию классов .NET и предоставляет
низкоуровневые службы для всех классов.
NOTE
C# не поддерживает множественное наследование. То есть можно указать только один базовый класс для
производного класса.
class DerivedClass:BaseClass {}
По умолчанию унаследовать класс можно от любого класса. Однако можно указать, должен ли класс
использоваться в качестве базового класса, или создать класс, который может использоваться только в
качестве базового.
Указание, что класс не может использоваться в качестве базового класса:
МОДИФИКАТОР C# ОПРЕДЕЛЕНИЕ
Интерфейсы
Интерфейсы, как и классы, определяют набор свойств, методов и событий. Но, в отличие от классов,
интерфейсы не предоставляют реализацию. Они реализуются классами, но определяются как отдельные от
классов сущности. Интерфейс представляет собой контракт, в котором класс, реализующий интерфейс,
должен реализовывать каждый аспект этого интерфейса в точном соответствии с его определением.
Определение интерфейса:
interface ISampleInterface
{
void doSomething();
}
Универсальные шаблоны
Классы, структуры, интерфейсы и методы в платформе .NET Framework могут иметь параметры типа,
которые определяют типы объектов, которые они могут хранить или использовать. Наиболее
распространенным примером универсального шаблона является коллекция, в которой можно указать тип
объектов, которые могут в ней храниться.
Определение универсального класса:
Делегаты
Делегат — это тип, который определяет сигнатуру метода и может обеспечивать связь с любым методом с
совместимой сигнатурой. Метод можно запустить (или вызвать) с помощью делегата. Делегаты
используются для передачи методов в качестве аргументов к другим методам.
NOTE
Обработчики событий — это ничто иное, как методы, вызываемые с помощью делегатов. Дополнительные сведения
об использовании делегатов при обработке событий см. в разделе События.
Создание делегата:
См. также
Руководство по программированию на C#
Отражение (C#)
23.10.2019 • 2 minutes to read • Edit Online
Механизм отражения позволяет получать объекты (типа Type), которые описывают сборки, модули и
типы. Отражение можно использовать для динамического создания экземпляра типа, привязки типа к
существующему объекту, а также получения типа из существующего объекта и вызова его методов или
доступа к его полям и свойствам. Если в коде используются атрибуты, отражение обеспечивает доступ к
ним. Дополнительные сведения см. в разделе Атрибуты.
Вот простой пример отражения, в котором для получения типа переменной используется статический
метод GetType , наследуемый всеми типами от базового класса Object .
Результат.
System.Int32
В этом примере отражение используется для получения полного имени загруженной сборки.
Результат.
mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
NOTE
Ключевые слова C# protected и internal не имеют никакого значения в промежуточном языке и не
используются в интерфейсах API отражения. Соответствующими терминами в промежуточном языке являются
Family и Assembly. Для идентификации метода internal с помощью отражения используйте свойство
IsAssembly. Для идентификации метода protected internal используйте IsFamilyOrAssembly.
См. также
Руководство по программированию на C#
Сборки в .NET
Сериализация (C#)
23.10.2019 • 6 minutes to read • Edit Online
Сериализация — это процесс преобразования объекта в поток байтов для сохранения или передачи в
память, базу данных или файл. Эта операция предназначена для того, чтобы сохранить состояния объекта
для последующего воссоздания при необходимости. Обратный процесс называется десериализацией.
Объект сериализуется в поток, который содержит не только данные, но и сведения о типе объекта,
например номер версии, язык и региональные параметры, имя сборки. В этом формате потока объект
можно сохранить в базе данных, файле или памяти.
Применение сериализации
Сериализация позволяет разработчику сохранять состояние объекта и воссоздавать его при необходимости.
Это полезно для длительного хранения объектов или для обмена данными. Используя сериализацию,
разработчик может, например, отправить объект удаленному приложению через веб-службу, передать
объект из одного домена в другой, передать объект через брандмауэр в виде XML -строки, обеспечить
защиту или сохранность сведений о пользователях в разных приложениях.
Превращение объекта в сериализуемый
Чтобы сериализовать объект, вам нужен сам этот объект, поток, который будет содержать объект, и
класс Formatter. Классы для сериализации и десериализации объектов содержатся в
System.Runtime.Serialization.
Примените к типу атрибут SerializableAttribute, чтобы указать возможность сериализации экземпляров этого
типа. Если в типе нет атрибута SerializableAttribute, при попытке сериализации выдается исключение.
Если вы не хотите, чтобы поле в классе было сериализуемым, примените атрибут NonSerializedAttribute. Если
поле сериализуемого типа содержит указатель, дескриптор или специальные структуры данных для
определенной среды, и содержимое этого поле невозможно разумно воссоздать в другой среде, такое поле
лучше сделать несериализуемым.
Если сериализуемый класс содержит ссылки на объекты других классов, имеющие пометку
SerializableAttribute, эти объекты тоже будут сериализованы.
Сериализация конструктора
Сериализация конструктора — это особая форма сериализации, при которой применяется способ
постоянного хранения объектов, используемый в средствах разработки. Сериализация конструктора
выполняет преобразование графа объекта в файл исходного кода, с помощью которого впоследствии можно
восстановить граф объекта. Этот файл исходного кода может содержать программный код, разметку или
даже информацию из таблицы SQL.
Показывает, как записать объект из класса в XML -файл с помощью класса XmlSerializer.
Пример
public class XMLWrite
{
writer.Serialize(file, overview);
file.Close();
}
}
Компиляция кода
В сериализуемом классе должен быть открытый конструктор без параметров.
Отказоустойчивость
При следующих условиях возможно возникновение исключения:
В сериализуемом классе нет открытого конструктора без параметров.
Файл существует и является файлом только для чтения (IOException).
Слишком длинный путь (PathTooLongException).
Диск заполнен (IOException).
См. также
StreamWriter
Практическое руководство. Чтение данных объекта из XML -файла (C#)
Сериализация (C#)
Практическое руководство. Чтение данных объекта
из XML-файла (C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом примере демонстрируется считывание данных объекта, которые ранее были записаны в XML -файл с
помощью класса XmlSerializer.
Пример
public class Book
{
public String title;
}
Console.WriteLine(overview.title);
Компиляция кода
Замените имя файла "c:\temp\SerializationOverview.xml" на имя файла, в котором содержатся
сериализованные данные. Дополнительные сведения о сериализации данных см. в разделе Практическое
руководство. Запись данных объекта в XML -файл (C#).
У класса должен быть открытый конструктор без параметров.
Десериализуются только открытые свойства и поля.
Отказоустойчивость
При следующих условиях возможно возникновение исключения:
В сериализуемом классе нет открытого конструктора без параметров.
Данные в файле не являются данными из класса, который десериализуется.
Файл не существует (IOException).
Безопасность платформы .NET Framework
Всегда проверяйте входные данные и никогда не десериализуйте данные из непроверенных источников.
Созданный заново объект выполняется на локальном компьютере с разрешениями кода, который его
десериализовал. Следует проверять все входные данные перед использованием их в приложении.
См. также
StreamWriter
Практическое руководство. Запись данных объекта в XML -файл (C#)
Сериализация (C#)
Руководство по программированию на C#
Пошаговое руководство. Сохранение объекта с
помощью C#
23.10.2019 • 8 minutes to read • Edit Online
С помощью сериализации можно сохранить данные объекта между экземплярами, что позволит сохранять
значения и извлекать их при следующем создании экземпляра объекта.
В этом пошаговом руководстве будет создан базовый объект Loan , а его данные сохранены в файл. Затем
при повторном создании объекта вы получите данные из файла.
IMPORTANT
В этом примере создается файл (если файл отсутствует). Если приложение должно создать файл, ему необходимо
разрешение Create для папки. Для задания разрешений используются списки управления доступом. Если файл уже
существует, приложению требуется лишь разрешение Write (с более низким уровнем). Если возможно, безопаснее
создать файл во время развертывания и предоставить только разрешения Read для одного файла (вместо
разрешения Create для папки). По тем же соображениям рекомендуется записывать данные в пользовательские
папки, а не в корневую папку или папку Program Files.
IMPORTANT
В этом примере данные сохраняются в двоичном формате. Эти форматы не следует использовать для
конфиденциальных данных, таких как пароли или сведения о кредитных картах.
Предварительные требования
Для сборки и запуска установите пакет SDK для .NET Core.
Установите любой привычный для вас редактор кода, если еще не сделали этого.
TIP
Нужно ли устанавливать редактор кода? Попробуйте использовать Visual Studio.
Этот пример требует C# версии 7.3. См. сведения о выборе версии языка C#.
Можно просмотреть образец кода онлайн в репозитории GitHub с примерами .NET.
[field:NonSerialized()]
public DateTime TimeLastLoaded { get; set; }
[field: NonSerialized()]
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
Добавьте обработчик событий для события PropertyChanged и несколько строк, чтобы изменить объект Loan
и отобразить изменения. Дополнения отображаются в следующем коде:
При повторном запуске приложения выводятся одни и те же значения. При каждом запуске программы
создается новый объект Loan. В реальной жизни процентная ставка время от времени меняется, но не при
каждом запуске этого приложения. Код сериализации позволяет сохранять последние процентные ставки
между экземплярами приложения. На следующем шаге для этого в класс Loan будет добавлена
сериализация.
[Serializable()]
Атрибут SerializableAttribute сообщает компилятору, что все находящееся в классе может быть сохранено в
файле. Поскольку событие PropertyChanged не представляет часть графа объекта, который должен быть
сохранен, оно не подлежит сериализации. В противном случае будут сериализованы все объекты,
присоединенные к этому событию. Можно добавить NonSerializedAttribute к объявлению поля для
обработчика событий PropertyChanged .
[field: NonSerialized()]
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
Начиная с C# 7.3 можно прикреплять атрибуты к резервному полю автоматически реализуемого свойства с
помощью значения целевого объекта field . Следующий код добавляет свойство TimeLastLoaded и
помечает его как не подлежащее сериализации:
[field:NonSerialized()]
public DateTime TimeLastLoaded { get; set; }
Следующим шагом будет добавление кода сериализации в приложение LoanApp. Чтобы выполнить
сериализацию класса и записать данные в файл, используйте пространства имен System.IO и
System.Runtime.Serialization.Formatters.Binary. Во избежание ввода полных имен можно добавить ссылки на
необходимые пространства имен, как показано в следующем коде:
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
Следующим шагом является добавление кода для десериализации объекта из файла при создании объекта.
Добавьте в класс константу для имени файла сериализованных данных, как показано в следующем коде:
Затем добавьте следующий код после строки, которая создает объект TestLoan :
if (File.Exists(FileName))
{
Console.WriteLine("Reading saved file");
Stream openFileStream = File.OpenRead(FileName);
BinaryFormatter deserializer = new BinaryFormatter();
TestLoan = (Loan)deserializer.Deserialize(openFileStream);
TestLoan.TimeLastLoaded = DateTime.Now;
openFileStream.Close();
}
Прежде всего убедитесь, что файл существует. Если он существует, создайте класс Stream для чтения
двоичного файла и класс BinaryFormatter для преобразования файла. Кроме того, необходимо
преобразовать тип потока в тип объекта Loan.
Далее необходимо добавить код для сериализации класса в файл. Добавьте следующий код после
существующего кода в методе Main :
Теперь можно снова собрать и запустить приложение. При первом запуске вы заметите, что процентная
ставка имеет значение 7,5, а затем меняется на 7,1. Закройте приложение, а затем снова запустите его.
Теперь приложение выводит сообщение о том, что сохраненный файл прочитан и процентная ставка
составляет 7,1, даже раньше кода, который меняет ее.
См. также
Сериализация (C#)
Руководство по программированию на C#
Операторы и выражения (Руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Приведение и преобразование типов
Инструкции (Руководство по программированию
на C#)
23.10.2019 • 9 minutes to read • Edit Online
// Assignment statement.
counter = 1;
Инструкции объявления
В следующем коде приведены примеры объявления переменных с начальным значением и без него, а
также пример объявления константы с необходимой инициализацией.
Инструкции выражений
В следующем коде показаны примеры инструкций выражений, включая присваивание, создание объекта с
помощью присваивания и вызов метода.
// Expression statement (assignment).
area = 3.14 * (radius * radius);
Пустая инструкция
В следующих примерах показаны два способа использования пустой инструкции:
void ProcessMessages()
{
while (ProcessMessage())
; // Statement needed here.
}
void F()
{
//...
if (done) goto exit;
//...
exit:
; // Statement needed here.
}
Внедренные инструкции
В некоторых инструкциях, включая do, while, for и foreach, всегда есть внедренная инструкция, следующая за
ними. Эта внедренная инструкция может быть либо одной инструкцией, либо несколькими инструкциями,
заключенными в скобки {} в блоке инструкций. Даже однострочные внедренные инструкции могут быть
заключены в скобки {}, как показано в следующем примере:
// Not recommended.
foreach (string s in System.IO.Directory.GetDirectories(
System.Environment.CurrentDirectory))
System.Console.WriteLine(s);
Внедренная инструкция, не заключенная в скобки {}, не может быть инструкцией объявления или
инструкцией с меткой. Это показано в следующем примере:
if(pointB == true)
//Error CS1023:
int radius = 5;
Чтобы устранить ошибку, поместите внедренную инструкцию в блок:
if (b == true)
{
// OK:
System.DateTime d = System.DateTime.Now;
System.Console.WriteLine(d.ToLongDateString());
}
}
return "Not found.";
Недостижимые инструкции
Если компилятор определяет, что поток управления ни при каких обстоятельствах не сможет достичь
определенной инструкции, то он выдаст предупреждение CS0162, как показано в следующем примере:
Спецификация языка C#
Дополнительные сведения см. в разделе Операторы в спецификации языка C#.
См. также
Руководство по программированию на C#
Ключевые слова операторов
Выражения
Выражения (Руководство по программированию в
C#)
04.11.2019 • 8 minutes to read • Edit Online
((x < 10) && ( x > 5)) || ((x > 20) && (x < 25));
System.Convert.ToInt32("35");
Значения выражений
В большинстве контекстов, в которых используются выражения, например, в операторах или в параметрах
методов, предполагается, что результатом вычисления выражения будет какое-то значение. Если x и y —
целые числа, результатом вычисления выражения x + y будет числовое значение. Результатом вычисления
выражения new MyClass() будет ссылка на новый экземпляр класса MyClass . Результатом вычисления
выражения myClass.ToString() является строка, так как она является возвращаемым типом метода. Однако
хотя имя пространства имен классифицируется как выражение, значение не будет результатом его
вычисления и поэтому никогда не станет конечным результатом какого-либо выражения. Имя пространства
имен нельзя передать параметру метода, или использовать его в новом выражении, или присвоить его
переменной. Его можно использовать только как часть выражения в более крупном выражении. Это также
относится к типам (в отличие от объектов System.Type), именам групп методов (в отличие от отдельных
методов) и к методам доступа к событиям add и remove.
У каждого значения есть связанный с ним тип. Например, если x и y — переменные типа int , значение
выражения x + y также типизируется как int . Если значение присвоено переменной другого типа или x и
y принадлежат к разным типам, то применяются правила преобразования типов. Дополнительные сведения
о работе таких преобразований см. в разделе Приведение и преобразование типов.
Переполнения
Числовые выражения могут привести к переполнениям, если значение больше максимального значения
типа значения. Дополнительные сведения см. в разделах Проверяемые и непроверяемые и Явные числовые
преобразования в статье Встроенные числовые преобразования.
// Expression statements.
int i = 5;
string s = "Hello World";
int num = 5;
System.Console.WriteLine(num); // Output: 5
num = 6;
System.Console.WriteLine(num); // Output: 6
Выражения вызова
В следующем примере кода вызов метода DoWork является выражением вызова.
DoWork();
При вызове метода необходимо указать имя метода в явном виде, как было показано в предыдущем
примере, или в виде результата другого выражения, после чего в скобках указываются все параметры этого
метода. Дополнительные сведения см. в статье Методы. При вызове делегата указывается имя делегата и
параметры метода в скобках. Дополнительные сведения см. в разделе Делегаты. Результатом вызова
метода или делегата является возвращаемое методом значение, если метод вообще возвращает значение. В
качестве значений в выражениях нельзя использовать методы, возвращающие значение типа void.
Выражения запросов
Аналогичные правила в общем случае применяются и к выражениям запроса. Дополнительные сведения
см. в статье LINQ.
Лямбда-выражения
Лямбда-выражения представляют собой "встроенные методы", у которых нет имен, но которые могут иметь
входные параметры и несколько инструкций. Они активно используются в LINQ для передачи аргументов
методам. Лямбда-выражения компилируются в делегаты или в деревья выражений в зависимости от
условий, при которых они используются. Дополнительные сведения см. в разделе Лямбда-выражения.
Деревья выражений
Деревья выражения позволяют представлять выражения в виде структур данных. Они широко
используются поставщиками LINQ для преобразования выражений запросов в код, имеющий смысл в
других контекстах, например в базе данных SQL. Дополнительные сведения см. в статье Деревья
выражений (C#).
Примечания
Если в выражении присутствует переменная, свойство объекта или индексатор объекта, для вычисления
выражения используется значение этого элемента. В C# выражение можно использовать везде, где
требуется значение или объект, если результат вычисления выражения соответствует требуемому типу.
Спецификация языка C#
Дополнительные сведения см. в разделе о выражениях в спецификации языка C#.
См. также
Руководство по программированию на C#
Инструкции
Методы
Делегаты
Типы
LINQ
Члены, воплощающие выражения (руководство
по программированию на C#)
25.11.2019 • 5 minutes to read • Edit Online
Метод C# 6
Property C# 7.0
Конструктор C# 7.0
Индексатор C# 7.0
Методы
Метод, воплощающий выражение, состоит из одного выражения, возвращающего значение, тип которого
соответствует возвращаемому типу метода, или методов, возвращающих void , который выполняет
некоторые операции. Например, типы, переопределяющие метод ToString, обычно содержат одно
выражение, которое возвращает строковое представление текущего объекта.
В следующем примере определяется класс Person , который переопределяет метод ToString с помощью
определения тела выражения. Он также определяет метод DisplayName , который отображает имя в
консоли. Обратите внимание, что ключевое слово return не используется в определении тела выражения
ToString .
using System;
class Example
{
static void Main()
{
Person p = new Person("Mandy", "Dejesus");
Console.WriteLine(p);
p.DisplayName();
}
}
В следующем примере определяется класс Location , свойство Name только для чтения которого
реализуется как определение тела выражения, возвращающее значение закрытого поля locationName :
Свойства
Начиная с C# 7.0 определения тела выражения можно использовать для реализации методов доступа get
и set свойства. В следующем примере показано, как это сделать:
public class Location
{
private string locationName;
Конструкторы
Определение тела выражения для конструктора обычно состоит из одного выражения присваивания или
вызова метода, который обрабатывает аргументы конструктора или инициализирует состояние экземпляра.
В следующем примере определяется класс Location , конструктор которого имеет один строковый
параметр name. Определение тела выражения присваивает аргумент свойству Name .
Методы завершения
Определение тела выражения для метода завершения обычно содержит операторы очистки, например
операторы, высвобождающие неуправляемые ресурсы.
В следующем примере определяется метод завершения, который использует определение тела выражения
для указания того, что был вызван метод завершения.
using System;
Индексаторы
Как при использовании свойств, методы доступа get и set индексатора состоят из определений тела
выражений, если метод доступа get состоит из одного выражения, которое возвращает значение, или
метод доступа set выполняет простое присваивание.
В следующем примере определяется класс с именем Sports , включающий внутренний массив String,
который содержит названия нескольких видов спорта. Методы доступа get и set индексатора
реализуются как определения тела выражений.
using System;
using System.Collections.Generic;
Анонимная функция — это "встроенный" оператор или выражение, которое может использоваться, когда
тип делегата неизвестен. Ее можно использовать для инициализации именованного делегата или передать
вместо типа именованного делегата в качестве параметра метода.
Для создания анонимной функции можно использовать лямбда-выражение или анонимный метод. Мы
советуем использовать лямбда-выражения, так как это более лаконичный способ написания встроенного
кода. В отличие от анонимных методов некоторые типы лямбда-выражений можно преобразовывать в
типы дерева выражений.
Эволюция делегатов в C#
В C# 1.0 экземпляр делегата создавался путем его явной инициализации с помощью метода, который был
определен в другом месте кода. В C# 2.0 введена концепция анонимных методов как способа написания
неименованных встроенных блоков операторов, которые могут быть выполнены в вызове делегата. В C#
3.0 введены лямбда-выражения, по сути аналогичные анонимным методам, но более выразительные и
четкие. Эти две функции собирательно называют анонимными функциями. Как правило, приложения,
предназначенные для версии 3.5 и более поздних версий платформы .NET Framework, должны
использовать лямбда-выражения.
В следующем примере демонстрируется эволюция создания делегата от C# 1.0 до C# 3.0:
class Test
{
delegate void TestDelegate(string s);
static void M(string s)
{
Console.WriteLine(s);
}
Спецификация языка C#
Дополнительные сведения см. в разделе Выражения анонимных функций в спецификации языка C#.
См. также
Инструкции, выражения и операторы
Лямбда-выражения
Делегаты
Expression Trees (C#) (Деревья выражений (C#))
Лямбда-выражения (руководство по
программированию на C#)
25.11.2019 • 17 minutes to read • Edit Online
Лямбда выражений также можно преобразовать в типы дерева выражения, как показано в
следующем примере:
Лямбда-выражения можно использовать в любом коде, для которого требуются экземпляры типов
делегатов или деревьев выражений, например в качестве аргумента метода Task.Run(Action) для
передачи кода, который должен выполняться в фоновом режиме. Можно также использовать
лямбда-выражения при применении LINQ в C#, как показано в следующем примере:
int[] numbers = { 2, 3, 4, 5 };
var squaredNumbers = numbers.Select(x => x * x);
Console.WriteLine(string.Join(" ", squaredNumbers));
// Output:
// 4 9 16 25
При использовании синтаксиса на основе методов для вызова метода Enumerable.Select в классе
System.Linq.Enumerable (например, в LINQ to Objects и LINQ to XML ) параметром является тип
делегата System.Func<T,TResult>. При вызове метода Queryable.Select в классе
System.Linq.Queryable (например, в LINQ to SQL ) типом параметра является тип дерева выражения
Expression<Func<TSource,TResult>> . В обоих случаях можно использовать одно и то же лямбда-
выражение для указания значения параметра. Поэтому оба вызова Select выглядят одинаково,
хотя на самом деле объект, созданный из лямбда-выражения, имеет другой тип.
Выражения-лямбды
Лямбда-выражение с выражением с правой стороны оператора => называется выражением
лямбда. Выражения-лямбды широко используются при конструировании деревьев выражений.
Выражения-лямбды возвращают результат выражения и принимают следующую основную форму.
Если лямбда-выражение имеет только один входной параметр, скобки можно не ставить; во всех
остальных случаях они обязательны.
Нулевое количество входных параметры задается пустыми скобками:
Иногда компилятору не удается определить входные типы. Вы можете указать типы данных в
явном виде, как показано в следующем примере:
Для входных параметров все типы нужно задать либо в явном, либо в неявном виде. В противном
случае компилятор выдает ошибку CS0748.
Текст выражения лямбды может состоять из вызова метода. Но при создании деревьев выражений,
которые вычисляются вне контекста поддержки общеязыковой среды выполнения .NET, например
в SQL Server, вызовы методов не следует использовать в лямбда-выражениях. Эти методы не
имеют смысла вне контекста среды CLR .NET.
Лямбды операторов
Лямбда оператора напоминает выражение-лямбду, за исключением того, что оператор (или
операторы) заключается в фигурные скобки:
(input-parameters) => { <sequence-of-statements> }
Тело лямбды оператора может состоять из любого количества операторов; однако на практике
обычно используется не более двух-трех.
Асинхронные лямбда-выражения
С помощью ключевых слов async и await можно легко создавать лямбда-выражения и операторы,
включающие асинхронную обработку. Например, в следующем примере Windows Forms
содержится обработчик событий, который вызывает асинхронный метод ExampleMethodAsync и
ожидает его.
Лямбда-выражения и кортежи
В C# 7.0 представлена встроенная поддержка кортежей. Кортеж можно ввести в качестве
аргумента лямбда-выражения, и лямбда-выражение также может возвращать кортеж. В некоторых
случаях компилятор C# использует определение типа для определения типов компонентов
кортежа.
Кортеж определяется путем заключения в скобки списка его компонентов с разделителями-
запятыми. В следующем примере кортеж с тремя компонентами используется для передачи
последовательности чисел в лямбда-выражение. Оно удваивает каждое значение и возвращает
кортеж с тремя компонентами, содержащий результат операций умножения.
Func<(int, int, int), (int, int, int)> doubleThem = ns => (2 * ns.Item1, 2 * ns.Item2, 2 * ns.Item3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
// Output:
// The set (2, 3, 4) doubled: (4, 6, 8)
Как правило, поля кортежи именуются как Item1 , Item2 и т. д. Тем не менее кортеж с
именованными компонентами можно определить, как показано в следующем примере:
Func<(int n1, int n2, int n3), (int, int, int)> doubleThem = ns => (2 * ns.n1, 2 * ns.n2, 2 * ns.n3);
var numbers = (2, 3, 4);
var doubledNumbers = doubleThem(numbers);
Console.WriteLine($"The set {numbers} doubled: {doubledNumbers}");
Экземпляр этого делегата можно создать как Func<int, bool> , где int — входной параметр, а
bool — возвращаемое значение. Возвращаемое значение всегда указывается в последнем
параметре типа. Например, Func<int, string, bool> определяет делегат с двумя входными
параметрами, int и string , и типом возвращаемого значения bool . Следующий делегат Func
при вызове возвращает логическое значение, которое показывает, равен ли входной параметр 5:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
int oddNumbers = numbers.Count(n => n % 2 == 1);
Console.WriteLine($"There are {oddNumbers} odd numbers in {string.Join(" ", numbers)}");
Компилятор может вывести тип входного параметра ввода; но его также можно определить явным
образом. Данное лямбда-выражение подсчитывает указанные целые значения ( n ), которые при
делении на два дают остаток 1.
В следующем примере кода показано, как создать последовательность, которая содержит все
элементы массива numbers , предшествующие 9, так как это первое число последовательности, не
удовлетворяющее условию:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstNumbersLessThanSix = numbers.TakeWhile(n => n < 6);
Console.WriteLine(string.Join(" ", firstNumbersLessThanSix));
// Output:
// 5 4 1 3
В следующем примере показано, как указать несколько входных параметров путем их заключения
в скобки. Этот метод возвращает все элементы в массиве numbers до того числа, значение которого
меньше его порядкового номера в массиве:
int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
var firstSmallNumbers = numbers.TakeWhile((n, index) => n >= index);
Console.WriteLine(string.Join(" ", firstSmallNumbers));
// Output:
// 5 4
updateCapturedLocalVariable = x =>
{
j = x;
bool result = j > input;
Console.WriteLine($"{j} is greater than {input}: {result}");
};
isEqualToCapturedLocalVariable = x => x == j;
int gameInput = 5;
game.Run(gameInput);
int anotherJ = 3;
game.updateCapturedLocalVariable(anotherJ);
Спецификация языка C#
Дополнительные сведения см. в разделе Выражения анонимных функций в спецификации языка
C#.
См. также
Руководство по программированию на C#
Встроенный язык запросов LINQ
Деревья выражений
Локальные функции в сравнении с лямбда-выражениями
Неявно типизированные лямбда-выражения
Примеры C# в Visual Studio 2008 (см. файлы примеров запросов LINQ и программу XQuery)
Рекурсивные лямбда-выражения
Практическое руководство. Использование
лямбда-выражений в запросах (Руководство по
программированию на C#)
04.11.2019 • 3 minutes to read • Edit Online
Пример
Следующий пример демонстрирует, как можно применить лямбда-выражение в запросе, основанном на
методе, с помощью стандартного оператора запросов Enumerable.Where. Обратите внимание на то, что
метод Where в этом примере имеет входной параметр с типом делегата Func<T,TResult>, а этот делегат
принимает на вход целое число и возвращает логическое значение. Лямбда-выражение может быть
преобразовано в этот делегат. Если вы примените запрос LINQ to SQL, в котором используется метод
Queryable.Where, параметр будет иметь тип Expression<Func<int,bool>> , но лямбда-выражение останется
точно таким же. Дополнительные сведения о типах выражений см. в статье
System.Linq.Expressions.Expression.
class SimpleLambda
{
static void Main()
{
// Data source.
int[] scores = { 90, 71, 82, 93, 75, 82 };
Пример
В приведенном ниже примере показано, как использовать лямбда-выражение в вызове метода выражения
запроса. Мы вынуждены использовать лямбда-выражения, поскольку синтаксис запросов не позволяет
вызвать стандартный оператор запроса Sum.
Сначала запрос группирует учащихся по успеваемости, как задано в перечислении GradeLevel . Затем для
каждой группы он добавляет итоговые оценки каждого учащегося. Для этого требуются две операции Sum .
Внутренняя операция Sum вычисляет общий результат для каждого учащегося, а внешняя операция Sum
рассчитывает промежуточный общий результат по всем учащимся в группе.
private static void TotalsByGradeLevel()
{
// This query retrieves the total scores for First Year students, Second Years, and so on.
// The outer Sum method uses a lambda in order to specify which numbers to add together.
var categories =
from student in students
group student by student.Year into studentGroup
select new { GradeLevel = studentGroup.Key, TotalScore = studentGroup.Sum(s => s.ExamScores.Sum()) };
Компиляция кода
Чтобы выполнить этот код, скопируйте и вставьте метод в StudentClass , описанный в практическом
руководстве по созданию запроса коллекции объектов, и вызовите его из метода Main .
См. также
Лямбда-выражения
Expression Trees (C#) (Деревья выражений (C#))
Проверка равенства (руководство по
программированию на C#)
23.10.2019 • 5 minutes to read • Edit Online
Иногда возникает необходимость проверить равенство двух значений. В некоторых случаях проверяется
равенство значений, которое также называют их эквивалентностью, что означает, что содержащиеся в
двух переменных значения равны. В других случаях требуется определить, ссылаются ли две переменные на
один и тот же объект в памяти. Такой тип равенства называется равенством ссылок или идентичностью. В
этом разделе описаны эти два типа равенства и приведены ссылки на разделы, содержащие дополнительные
сведения.
Равенство ссылок
Равенство ссылок означает, что два объекта ссылаются на один и тот же объект. Это может произойти при
простом присваивании, как показано в следующем примере.
using System;
class Test
{
public int Num { get; set; }
public string Str { get; set; }
// Assign b to a.
b = a;
В этом фрагменте кода создается два объекта, но после инструкции присваивания они оба ссылаются на
один и тот же объект. Поэтому между ними устанавливается равенство ссылок. Метод ReferenceEquals
позволяет определить, ссылаются ли две ссылки на один и тот же объект.
Концепция равенства ссылок применима только к ссылочным типам. Объекты типа значения не могут иметь
равенство ссылок, поскольку переменной присваивается экземпляр типа значения, создается копия этого
значения. Поэтому две распакованные структуры никогда не могут ссылаться на одно и то же расположение
в памяти. Более того, для сравнения двух типов значений ReferenceEquals всегда будет возвращаться
результат false , даже если содержащиеся в объектах значения идентичны. Это связано с тем, что каждая
переменная запакована в отдельный экземпляр объекта. Дополнительные сведения см. в разделе
Практическое руководство. Проверка на ссылочное равенство (идентичность).
Равенство значений
Равенство значений означает, что два объекта содержат одинаковое значение или одинаковые значения.
Для примитивных типов значений, таких как int или bool, проверка на равенство значений представляет
собой очевидную задачу. Можно использовать оператор ==, как показано в следующем примере.
int a = GetOriginalValue();
int b = GetCurrentValue();
Для большинства других типов проверка равенства значений является более сложной, поскольку при этом
необходимо понимать, как равенство определено в типе. Для всех классов и структур, имеющих несколько
полей или свойств, равенство значений часто определяется как равенство значений всех полей и свойств.
Например, два объекта Point могут быть определены как равные, если значение pointA.X равняется
pointB.X, а значение pointA.Y равняется pointB.Y.
Однако общего требования о том, что равенство должно определяться на основе всех полей в типе, нет. Оно
может определяться и на основе части полей. При сравнении значений типов сторонних разработчиков
необходимо понимать, как именно реализовано равенство для таких типов. Дополнительные сведения об
определении равенства значений в собственных классах и структурах см. в практическом руководстве по
определению равенства значений для типа.
Равенство значений с плавающей запятой
Проверка равенства значений с плавающей запятой (double и float) представляет проблему из-за неточности
арифметических операций с плавающей запятой на двоичных компьютерах. Дополнительные сведения
можно найти в заметках в разделе System.Double.
См. также
ЗАГОЛОВОК ОПИСАНИЕ
Практическое руководство. Проверка на ссылочное Описание процедуры проверки равенства ссылок двух
равенство (идентичность) переменных.
См. также
Руководство по программированию на C#
Как определить равенства значений для типа
(руководство по программированию на C#)
23.10.2019 • 11 minutes to read • Edit Online
При определении класса или структуры необходимо решить, имеет ли смысл создавать пользовательское
определение равенства значений (или эквивалентности) для этого типа. Обычно правила определения
равенства реализуются, если объекты этого типа будут добавляться в коллекции или если они в первую
очередь предназначены для хранения набора полей или свойств. В основу определения равенства значений
можно положить сравнение всех полей и свойств в типе или только их части. Но в любом случае (как для
классов, так и для структур) реализация должна соответствовать следующим пяти гарантиям равенства.
1. x.Equals(x) возвращает true . Это называется свойством рефлексивности.
2. x.Equals(y) возвращает то же значение, что и y.Equals(x) . Это называется свойством симметрии.
3. Если (x.Equals(y) && y.Equals(z)) возвращает true , x.Equals(z) возвращает true . Это называется
свойством транзитивности.
4. Последовательные вызовы x.Equals(y) возвращают одно и то же значение до тех пор, пока объекты,
на которые ссылаются x и y, не будут изменены.
5. x.Equals(null)возвращает false . Однако null.Equals(null) вызывает исключение. Эта инструкция
не удовлетворяет приведенному выше правилу номер два.
Любая определяемая вами структура имеет заданную по умолчанию реализацию равенства значений,
которая наследуется от переопределения System.ValueType метода Object.Equals(Object). Эта реализация
использует отражение для проверки всех полей и свойств в типе. Хотя эта реализация возвращает верный
результат, она отличается невысокой скоростью по сравнению с пользовательской реализацией, которую
можно написать специально для конкретного типа.
Детали реализации равенства значений для классов и структур различаются. Однако для реализации
равенства как для классов, так и для структур, необходимо выполнить одни и те же базовые действия.
1. Переопределите виртуальный метод Object.Equals(Object). В большинстве случаев пользовательская
реализация bool Equals( object obj ) должна вызывать относящийся к конкретному типу метод
Equals , который является реализацией интерфейса System.IEquatable<T>. ( См. шаг 2.)
Пример
В следующем примере показана реализация равенства значений в классе (ссылочный тип).
namespace ValueEquality
{
using System;
class TwoDPoint : IEquatable<TwoDPoint>
{
// Readonly auto-implemented properties.
public int X { get; private set; }
public int Y { get; private set; }
class Program
{
static void Main(string[] args)
{
ThreeDPoint pointA = new ThreeDPoint(3, 4, 5);
ThreeDPoint pointB = new ThreeDPoint(3, 4, 5);
ThreeDPoint pointC = null;
int i = 5;
/* Output:
pointA.Equals(pointB) = True
pointA == pointB = True
null comparison = False
Compare to some other type = False
Two null TwoDPoints are equal: True
(pointE == pointA) = False
(pointA == pointE) = False
(pointA != pointE) = True
pointE.Equals(list[0]): False
*/
}
Пример
В следующем примере показана реализация равенства значений в структуре (тип значения).
class Program
class Program
{
static void Main(string[] args)
{
TwoDPoint pointA = new TwoDPoint(3, 4);
TwoDPoint pointB = new TwoDPoint(3, 4);
int i = 5;
pointD = temp;
// True:
Console.WriteLine("pointD == (pointC = 3,4) = {0}", pointD == pointC);
/* Output:
pointA.Equals(pointB) = True
pointA == pointB = True
Object.Equals(pointA, pointB) = True
pointA.Equals(null) = False
(pointA == null) = False
(pointA != null) = True
pointA.Equals(i) = False
pointE.Equals(list[0]): True
pointA == (pointC = null) = False
pointC == pointD = True
pointA == (pointC = 3,4) = True
pointD == (pointC = 3,4) = True
*/
*/
}
См. также
Сравнения на равенство
Руководство по программированию на C#
Практическое руководство. Проверка на
равенство (идентичность) ссылок (Руководство по
программированию на C#)
23.10.2019 • 4 minutes to read • Edit Online
Вам не требуется реализовывать настраиваемую логику, чтобы обеспечить поддержку проверки ссылок на
равенство в типах. Эту возможность для всех типов реализует метод Object.ReferenceEquals.
В следующем примере показано, как проверить две переменные на равенство ссылок, то есть как
определить, ссылаются ли они на один и тот же объект в памяти.
В этом примере также показано, почему Object.ReferenceEquals всегда возвращает false для типов
значений и почему не следует использовать ReferenceEquals для проверки строк на равенство.
Пример
namespace TestReferenceEquality
{
struct TestStruct
{
public int Num { get; private set; }
public string Name { get; private set; }
class TestClass
{
public int Num { get; set; }
public string Name { get; set; }
}
class Program
{
static void Main()
{
// Demonstrate reference equality with reference types.
#region ReferenceTypes
// Demonstrate that two value type instances never have reference equality.
#region ValueTypes
#region stringRefEquality
// Constant strings within the same assembly are always interned by the runtime.
// This means they are stored in the same location in memory. Therefore,
// the two strings have reference equality although no assignment takes place.
string strA = "Hello world!";
string strB = "Hello world!";
Console.WriteLine("ReferenceEquals(strA, strB) = {0}",
Object.ReferenceEquals(strA, strB)); // true
#endregion
/* Output:
ReferenceEquals(tcA, tcB) = False
After asignment: ReferenceEquals(tcA, tcB) = True
tcB.Name = TestClass 42 tcB.Num: 42
After asignment: ReferenceEquals(tsC, tsD) = False
ReferenceEquals(strA, strB) = True
strA = "Goodbye world!" strB = "Hello world!"
After strA changes, ReferenceEquals(strA, strB) = False
*/
Реализация Equals в универсальном базовом классе System.Object также выполняет проверку на равенство
ссылок, однако использовать такой подход не рекомендуется, поскольку в случае переопределения метода
в классе результат может отличаться от ожидаемого. Это справедливо также для операторов == и != . При
работе со ссылочными типами операторы == и != по умолчанию выполняют проверку на равенство
ссылок. Тем не менее производные классы могут перегружать оператор для проверки на равенство
значений. Чтобы свести к минимуму вероятность появления ошибки, рекомендуется всегда использовать
ReferenceEquals в тех случаях, когда требуется проверить два объекта на равенство ссылок.
Константные строки в рамках одной сборки всегда интернируются во время выполнения. Таким образом,
всегда присутствует только один экземпляр каждого уникального строкового литерала. Тем не менее во
время выполнения не гарантируется интернирование строк, созданных во время выполнения, а также
интернирование двух равных константных строк из разных сборок.
См. также
Сравнения на равенство
Типы (Руководство по программированию на C#)
07.11.2019 • 20 minutes to read • Edit Online
int a = 5;
int b = a + 2; //OK
// Error. Operator '+' cannot be applied to operands of type 'int' and 'bool'.
int c = a + test;
NOTE
Тем, кто ранее использовал C и C++, нужно обратить внимание на то, что в C# тип bool нельзя преобразовать в int.
Компилятор внедряет сведения о типе в исполняемый файл в виде метаданных. Среда CLR использует эти
метаданные во время выполнения для дальнейшего обеспечения безопасности типа при выделении и
освобождении памяти.
Задание типов в объявлениях переменных
При объявлении переменной или константы в программе необходимо либо задать ее тип, либо
использовать ключевое слово var, чтобы дать возможность компилятору определить его. В следующем
примере показаны некоторые объявления переменных, использующие встроенные числовые типы и
сложные пользовательские типы:
// Declaration only:
float temperature;
string name;
MyClass myClass;
Типы для параметров и возвращаемых значений метода задаются в сигнатуре метода. Далее представлена
сигнатура метода, который требует значение int в качестве входного аргумента и возвращает строку:
После объявления переменной она не может быть повторно объявлена с новым типом, и ей нельзя
присвоить значение, несовместимое с ее объявленным типом. Например, нельзя объявить переменную
типа int и затем присвоить ей логическое значение true. Однако значения могут быть преобразованы в
другие типы, например при сохранении в другие переменные или при передаче в качестве аргументов
метода. Если преобразование типов не приводит к потере данных, она выполняется компилятором
автоматически. Для преобразования, которое может привести к потере данных, необходимо выполнить
приведение в исходном коде.
Дополнительные сведения см. в разделе Приведение и преобразование типов.
Встроенные типы
C# предоставляет стандартный набор встроенных числовых типов для представления целых чисел,
значений с плавающей запятой, логических выражений, текстовых символов, десятичных значений и
других типов данных. Также существуют встроенные типы string и object . Они доступны для
использования в любой программе C#. Дополнительные сведения о встроенных типах см. в Справочных
таблицах по встроенным типам.
Пользовательские типы
Вы можете создавать собственные типы, используя конструкции структура, класс, интерфейс и
перечисление. Сама библиотека классов .NET — это коллекция пользовательских типов, предоставленных
корпорацией Майкрософт, которые вы можете свободно использовать в приложениях. По умолчанию в
любой программе C# доступны наиболее часто используемые типы из библиотеки классов. Чтобы сделать
доступными другие типы, нужно явным образом добавить в проект ссылку на сборку, в которой они
определены. Если компилятору предоставлена ссылка на сборку, то вы можете объявлять в коде
переменные (и константы) любых типов, объявленных в этой сборке. См. дополнительные сведения о
библиотеке классов .NET.
NOTE
Как видно, все наиболее часто используемые типы организованы в пространство имен System. Но само по себе
пространство имен, в котором размещен тип, никак не зависит от того, является ли он типом значения или
ссылочным типом.
Типы значений
Типы значений являются производными от System.ValueType, который является производным от
System.Object. Типы, производные от System.ValueType, имеют особое поведение в среде CLR.
Переменные типа значения напрямую содержат свои значения, что означает, что память выделяется в
любом контексте объявления переменной. Нет раздельного размещения в куче или накладных расходов
при сборке мусора для переменных типа значения.
Существует две категории типов значений: структура и перечисление.
Встроенные числовые типы являются структурами и имеют свойства и методы, к которым можно
обращаться.
Но объявление и присвоение значений вы выполняете для них так, как если бы они были простыми
нестатистическими типами.
Типы значений являются запечатанными, что означает, например, что нельзя наследовать тип от
System.Int32 и нельзя определить структуру для наследования от любого пользовательского класса или
структуры, поскольку структура может наследовать только от System.ValueType. Тем не менее структура
может реализовывать один или несколько интерфейсов. Вы можете привести тип структуры к любому
типу интерфейса, который она реализует. Это приведет к операции упаковки-преобразования, которая
упакует структуру внутри объекта ссылочного типа в управляемой куче. Операции упаковки-
преобразования выполняются при передаче типа значения в метод, принимающий System.Object или
любой тип интерфейса в качестве входного параметра. Дополнительные сведения см. в разделе Упаковка-
преобразование и распаковка-преобразование.
Используйте ключевое слово struct, чтобы создать собственные пользовательские типы значений. Как
правило, структура используется как контейнер для небольшого набора связанных переменных, как
показано в следующем примере:
Дополнительные сведения о структурах см. в статье Structs (Структуры). См. дополнительные сведения о
типах значений.
Еще одна категория типов значений — это перечисления. Перечисление определяет набор именованных
целочисленных констант. Например, перечисление System.IO.FileMode из библиотеки классов .NET
содержит набор именованных целочисленных констант, которые определяют правила открытия файла. В
следующем примере представлено определение этого типа:
public enum FileMode
{
CreateNew = 1,
Create = 2,
Open = 3,
OpenOrCreate = 4,
Truncate = 5,
Append = 6,
}
Интерфейс должен быть инициализирован вместе с объектом класса, который его реализует. Если MyClass
реализует IMyInterface , экземпляр IMyInterface создается так, как показано в следующем примере:
При создании объекта выделяется память в управляемой куче, и переменная хранит только ссылку на
расположение объекта. Хранение типов в управляемой куче требует дополнительных действий как при
выделении памяти, так и при удалении, которое выполняется функцией автоматического управления
памятью в среде CLR, известной как сборка мусора. Сборка мусора является высоко оптимизированным
процессом и в большинстве случаев она не создает помех для производительности. Дополнительные
сведения о сборке мусора см. в статье Автоматическое управление памятью.
Все массивы являются ссылочными типами, даже если их элементы являются типами значений. Массивы
являются неявно производными от класса System.Array, но в C# их можно объявлять и использовать с
упрощенным синтаксисом, как показано в следующем примере:
универсальные типы;
Тип может быть объявлен с одним или несколькими параметрами типа, служащими в качестве
местозаполнителя для фактического типа (конкретного типа), который клиентский код предоставит при
создании экземпляра типа. Такие типы называются универсальными типами. Например, тип .NET
System.Collections.Generic.List<T> имеет один параметр типа, которому в соответствии с соглашением
присвоено имя T. При создании экземпляра этого типа необходимо указать тип объектов, которые будут
содержаться в списке. Например, так используется список строковых значений:
Использование параметра типа позволяет повторно использовать один же класс для хранения элементов
любого типа, не преобразовывая каждый элемент в объект. Универсальные классы коллекций называются
строго типизированными коллекциями, так как компилятору известен конкретный тип элементов
коллекции и он может выдать ошибку во время компиляции, если, к примеру, вы попытаетесь добавить
целое число в объект stringList , созданный в предыдущем примере. Дополнительные сведения см. в
статье Универсальные шаблоны.
Связанные разделы
Дополнительные сведения см. в следующих разделах:
Приведение и преобразование типов
Упаковка-преобразование и распаковка-преобразование
Использование типа dynamic
Типы значений
Ссылочные типы
Классы и структуры
Анонимные типы
Универсальные шаблоны
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Преобразование типов XML -данных
Целочисленные типы
Руководство по программированию на C#.
Приведение и преобразование типов
25.11.2019 • 8 minutes to read • Edit Online
int i;
i = "Hello"; // error CS0029: Cannot implicitly convert type 'string' to 'int'
Тем не менее иногда может потребоваться скопировать значение в переменную или параметр метода
другого типа. Например, может потребоваться передать целочисленную переменную в метод, параметр
которого имеет тип double . Или может понадобиться присвоить переменную класса переменной типа
интерфейса. Такого рода операции называются преобразованиями типа. В C# можно выполнять
следующие виды преобразований.
Неявные преобразования. Специальный синтаксис не требуется, так как преобразование
является строго типизированным и данные не будут потеряны. Примеры включают
преобразования из меньших в большие целочисленные типы и преобразования из производных
классов в базовые классы.
Явные преобразования (приведения) . Для явных преобразований требуется оператор
приведения () . Приведение требуется, если в ходе преобразования данные могут быть утрачены
или преобразование может завершиться сбоем по другим причинам. Типичными примерами
являются числовое преобразование в тип с меньшей точностью или меньшим диапазоном и
преобразование экземпляра базового класса в производный класс.
Пользовательские преобразования. Такие преобразования выполняются специальными
методами, которые можно определить для включения явных и неявных преобразований между
пользовательскими типами без связи "базовый класс — производный класс". Дополнительные
сведения см. в разделе Операторы пользовательского преобразования.
Преобразования с использованием вспомогательных классов. Чтобы выполнить
преобразование между несовместимыми типами, например целыми числами и объектами
System.DateTime или шестнадцатеричными строками и массивами байтов, можно использовать
классы System.BitConverter и System.Convert, а также методы Parse встроенных числовых типов,
такие как Int32.Parse. Дополнительные сведения см. в разделе Практическое руководство.
преобразованию массива байтов в значение типа int, преобразованию строки в число и
преобразованию из шестнадцатеричных строк в числовые типы.
Неявные преобразования
Для встроенных числовых типов неявное преобразование можно выполнить, если сохраняемое значение
может уместиться в переменной без усечения или округления. При использовании целочисленных типов
это означает, что диапазон исходного типа является надлежащим подмножеством диапазона для
целевого типа. Например, переменная типа long (64-разрядное целое число) может хранить любое
значение, которое может хранить переменная int (32-разрядное целое число). В следующем примере
компилятор неявно преобразует значение num справа в тип long перед назначением bigNum .
Полный список всех неявных числовых преобразований см. в разделе Таблица неявных числовых
преобразований в статье Встроенные числовые преобразования.
Для ссылочных типов неявное преобразование всегда предусмотрено из класса в любой из его прямых
или косвенных базовых классов или интерфейсов. Никакой специальный синтаксис не требуется,
поскольку производный класс всегда содержит все члены базового класса.
Явные преобразования
Тем не менее если преобразование нельзя выполнить без риска потери данных, компилятор требует
выполнения явного преобразования, которое называется приведением. Приведение — это способ явно
указать компилятору, что необходимо выполнить преобразование и что вам известно, что может
произойти потеря данных. Чтобы выполнить приведение, укажите тип, в который производится
приведение, в круглых скобках перед преобразуемым значением или переменной. В следующей
программе выполняется приведение типа double в int. Программа не будет компилироваться без
приведения.
class Test
{
static void Main()
{
double x = 1234.7;
int a;
// Cast double to int.
a = (int)x;
System.Console.WriteLine(a);
}
}
// Output: 1234
Полный список всех поддерживаемых явных числовых преобразований см. в разделе Таблица явных
числовых преобразований в статье Встроенные числовые преобразования.
Для ссылочных типов явное приведение является обязательным, если необходимо преобразовать
базовый тип в производный тип:
// Create a new derived type.
Giraffe g = new Giraffe();
Операция приведения между ссылочными типами не меняет тип времени выполнения базового объекта;
изменяется только тип значения, который используется в качестве ссылки на этот объект.
Дополнительные сведения см. в разделе Полиморфизм.
using System;
class Animal
{
public void Eat() { Console.WriteLine("Eating."); }
public override string ToString()
{
return "I am an animal.";
}
}
class Reptile : Animal { }
class Mammal : Animal { }
class UnSafeCast
{
static void Main()
{
Test(new Mammal());
C# предоставляет оператор is, чтобы можно было проверить совместимость перед фактическим
выполнением приведения. Дополнительные сведения см. в статье Практическое руководство. Безопасное
приведение с помощью сопоставления шаблонов, а также операторов is и as.
Спецификация языка C#
Дополнительные сведения см. в разделе Преобразования спецификация языка C#.
См. также
Руководство по программированию на C#
Типы
Оператор приведения ()
Операторы пользовательского преобразования
Обобщенное преобразование типов
Практическое руководство. Преобразование строки в число
Упаковка-преобразование и распаковка-
преобразование (Руководство по
программированию на C#)
23.10.2019 • 8 minutes to read • Edit Online
Упаковка представляет собой процесс преобразования типа значения в тип object или в любой другой
тип интерфейса, реализуемый этим типом значения. Когда тип значения упаковывается средой CLR, он
инкапсулирует значение внутри экземпляра System.Object и сохраняет его в управляемой куче. Операция
распаковки извлекает тип значения из объекта. Упаковка является неявной; распаковка является явной.
Понятия упаковки и распаковки лежат в основе единой системы типов C#, в которой значение любого
типа можно рассматривать как объект.
В следующем примере выполнена операция i упаковки целочисленной переменной, которая присвоена
объекту o .
int i = 123;
// The following line boxes i.
object o = i;
Затем можно выполнить операцию распаковки объекта o и присвоить его целочисленной переменной i
:
o = 123;
i = (int)o; // unboxing
// String.Concat example.
// String.Concat has many versions. Rest the mouse pointer on
// Concat in the following statement to verify that the version
// that is used here takes three object arguments. Both 42 and
// true must be boxed.
Console.WriteLine(String.Concat("Answer", 42, true));
// List example.
// Create a list of objects to hold a heterogeneous collection
// of elements.
List<object> mixedList = new List<object>();
// The following loop sums the squares of the first group of boxed
// integers in mixedList. The list elements are objects, and cannot
// be multiplied or added to the sum until they are unboxed. The
// unboxing must be done explicitly.
var sum = 0;
for (var j = 1; j < 5; j++)
{
// The following statement causes a compiler error: Operator
// '*' cannot be applied to operands of type 'object' and
// 'object'.
//sum += mixedList[j] * mixedList[j]);
// Output:
// Answer42True
// First Group:
// 1
// 2
// 3
// 4
// Second Group:
// 5
// 6
// 7
// 8
// 9
// Sum: 30
Производительность
По сравнению с простыми операциями присваивания операции упаковки и распаковки являются весьма
затратными процессами с точки зрения вычислений. При выполнении упаковки типа значения
необходимо создать и разместить новый объект. Объем вычислений при выполнении операции
распаковки, хотя и в меньшей степени, но тоже весьма значителен. Дополнительные сведения см. в
разделе Производительность.
Упаковка
Упаковка используется для хранения типов значений в куче со сбором мусора. Упаковка представляет
собой неявное преобразование типа значения в тип object или в любой другой тип интерфейса,
реализуемый этим типом значения. При упаковке типа значения в куче выделяется экземпляр объекта и
выполняется копирование значения в этот новый объект.
Рассмотрим следующее объявление переменной типа значения.
int i = 123;
Результат этого оператора создает ссылку на объект o в стеке, которая ссылается на значение типа int в
куче. Это значение является копией значения типа значения, присвоенного переменной i . Разница
между этими двумя переменными, i и o , показана на рисунке упаковки-преобразования ниже:
Можно также выполнять упаковку явным образом, как в следующем примере, однако явная упаковка не
является обязательной.
int i = 123;
object o = (object)i; // explicit boxing
ОПИСАНИЕ
В этом примере целочисленная переменная i преобразуется в объект o при помощи упаковки. Затем
значение, хранимое переменной i , меняется с 123 на 456 . В примере показано, что исходный тип
значения и упакованный объект используют отдельные ячейки памяти, а значит могут хранить разные
значения.
Пример
class TestBoxing
{
static void Main()
{
int i = 123;
Распаковка
Распаковка является явным преобразованием из типа object в тип значения или из типа интерфейса в тип
значения, реализующего этот интерфейс. Операция распаковки состоит из следующих действий:
проверка экземпляра объекта на то, что он является упакованным значением заданного типа
значения;
копирование значения из экземпляра в переменную типа значения.
В следующем коде показаны операции по упаковке и распаковке.
Для успешной распаковки типов значений во время выполнения необходимо, чтобы экземпляр, который
распаковывается, был ссылкой на объект, предварительно созданный с помощью упаковки экземпляра
этого типа значения. Попытка распаковать null создает исключение NullReferenceException. Попытка
распаковать ссылку на несовместимый тип значения создает исключение InvalidCastException.
Пример
В следующем примере показан случай недопустимой распаковки, в результате чего создается исключение
InvalidCastException . В случае использования try и catch при возникновении ошибки выводится
сообщение.
class TestUnboxing
{
static void Main()
{
int i = 123;
object o = i; // implicit boxing
try
{
int j = (short)o; // attempt to unbox
System.Console.WriteLine("Unboxing OK.");
}
catch (System.InvalidCastException e)
{
System.Console.WriteLine("{0} Error: Incorrect unboxing.", e.Message);
}
}
}
int j = (short) o;
на:
int j = (int) o;
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
Связанные разделы
Дополнительные сведения:
Ссылочные типы
Типы значений
См. также
Руководство по программированию на C#
Практическое руководство. Преобразование
массива байтов в значение типа int (руководство
по программированию на C#)
25.11.2019 • 2 minutes to read • Edit Online
В этом примере демонстрируется использование класса BitConverter для преобразования массива байтов в
значение типа int и обратно в массив байтов. Например, может потребоваться преобразование из байтов во
встроенный тип данных после чтения байтов из сети. В дополнение к методу ToInt32(Byte[], Int32),
показанному в примере, для преобразования байтов (из массива байтов) в другие встроенные типы данных
можно использовать и другие методы класса BitConverter, представленные в таблице ниже.
Пример
Этот пример инициализирует массив байтов, обращает порядок расположения элементов в массиве, если в
архитектуре компьютера используется прямой порядок байтов (т. е. первым сохраняется наименее
значащий байт), и затем вызывает метод ToInt32(Byte[], Int32) для преобразования четырех байтов массива
в значение типа int . Второй аргумент ToInt32(Byte[], Int32) указывает начальный индекс массива байтов.
NOTE
Результат зависит от порядка следования байтов в архитектуре компьютера.
byte[] bytes = { 0, 0, 0, 25 };
Пример
В этом примере вызывается метод GetBytes(Int32) класса BitConverter для преобразования значения int в
массив байтов.
NOTE
Результат зависит от порядка следования байтов в архитектуре компьютера.
См. также
BitConverter
IsLittleEndian
Типы
Практическое руководство. Преобразование
строки в число (руководство по
программированию на C#)
04.11.2019 • 6 minutes to read • Edit Online
Вы можете преобразовывать строку в число с помощью вызова метода Parse или TryParse , который
можно найти в различных числовых типах ( int , long , double и т. д.), или используя методы в классе
System.Convert.
Если имеется строка, то немного эффективнее и проще вызвать метод TryParse (например,
int.TryParse("11", out number) ) или метод Parse (например, var number = int.Parse("11") ). Использование
метода Convert более удобно для общих объектов, реализующих IConvertible.
Вы можете использовать методы Parse или TryParse в числовом типе, который предположительно
содержит строка, таком как тип System.Int32. Метод Convert.ToInt32 использует Parse внутри себя. Метод
Parse возвращает преобразованное число; метод TryParse возвращает значение Boolean, которое
указывает, успешно ли выполнено преобразование, и возвращает преобразованное число в параметр out .
Если строка имеет недопустимый формат, Parse создает исключение, а TryParse возвращает значение
false. В случае сбоя операции синтаксического анализа при вызове метода Parse вы всегда должны
использовать обработку исключений, чтобы перехватить FormatException.
try
{
int numVal = Int32.Parse("-105");
Console.WriteLine(numVal);
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
// Output: -105
try
{
int m = Int32.Parse("abc");
}
catch (FormatException e)
{
Console.WriteLine(e.Message);
}
// Output: Input string was not in a correct format.
В следующем примере показан один из подходов к анализу строки, которая, как ожидается, будет включать
начальные числовые символы (включая шестнадцатеричные символы) и конечные нечисловые символы.
Он назначает допустимые символы в начале новой строки перед вызовом метода TryParse. Поскольку
анализируемые строки содержат небольшое количество символов, в примере вызывается метод
String.Concat для назначения допустимых символов новой строке. Для большей строки можете
использовать класс StringBuilder.
using System;
decimal ToDecimal(String)
float ToSingle(String)
double ToDouble(String)
short ToInt16(String)
int ToInt32(String)
long ToInt64(String)
ЧИСЛОВОЙ ТИП МЕТОД
ushort ToUInt16(String)
uint ToUInt32(String)
ulong ToUInt64(String)
В данном примере вызывается метод Convert.ToInt32(String) для преобразования входных данных string в
значение типа int. Пример перехватывает два наиболее распространенных исключения, которые могут
создаваться этим методом, — FormatException и OverflowException. Если итоговое число можно увеличить,
не превышая Int32.MaxValue, пример добавляет 1 к результату и отображает вывод.
using System;
while (repeat)
{
Console.Write("Enter a number between −2,147,483,648 and +2,147,483,647 (inclusive): ");
См. также
Типы
Практическое руководство. Определение представления числового значения в строке
Пример: служебная программа форматирования .NET Core WinForms (C#)
Практическое руководство. Преобразование из
шестнадцатеричных строк в числовые типы
(руководство по программированию на C#)
25.11.2019 • 4 minutes to read • Edit Online
Пример
Результатом следующего примера является шестнадцатеричное значение каждого символа в string .
Сначала выполняется разбор string до массива символов. Затем, чтобы получить числовое значение
каждого символа, для каждого из них вызывается метод ToInt32(Char). В конце формат числа меняется на
шестнадцатеричный в string .
Пример
В этом примере анализируется string шестнадцатеричных значений и выводится символ,
соответствующий каждому шестнадцатеричному значению. Сначала вызывается метод Split(Char[]) для
получения каждого шестнадцатеричного значения как отдельной string в массиве. Затем вызывается
метод ToInt32(String, Int32), который преобразует шестнадцатеричное значение в десятичное,
представленное в целое число. В примере показано два разных способа получения символа,
соответствующего этому коду символа. В первом случае используется ConvertFromUtf32(Int32), который
возвращает символ, соответствующий целочисленному аргументу, в виде string . По второму способу
выполняется явное приведение int к char.
Пример
В этом примере показан еще один способ преобразования шестнадцатеричного string в целое число — с
помощью метода Parse(String, NumberStyles) .
Пример
В следующем примере показано преобразование шестнадцатеричного string в число с плавающей
запятой с помощью класса System.BitConverter и метод UInt32.Parse.
// Output: 200.0056
Пример
В следующем примере показано преобразование массива байтов в шестнадцатеричную строку с помощью
класса System.BitConverter.
/*Output:
01-AA-B1-DC-10-DD
01AAB1DC10DD
*/
См. также
Standard Numeric Format Strings
Типы
Практическое руководство. Определение представления числового значения в строке
Использование типа dynamic (руководство по
программированию на C#)
04.11.2019 • 8 minutes to read • Edit Online
C# 4 добавляет новый тип dynamic . Этот тип является статическим, но объект типа dynamic обходит
проверку статического типа. В большинстве случаев он работает как тип object . Во время компиляции
предполагается, что элемент, типизированный как dynamic , поддерживает любые операции. Это значит,
что вам не придется задумываться о том, получает ли объект значение из API COM, из динамического
языка, такого как IronPython, из модели DOM HTML, из отражения или из другой части программы. При
этом если код недопустимый, ошибки перехватываются во время выполнения.
Например, если метод экземпляра exampleMethod1 в следующем коде имеет только один параметр,
компилятор определяет, что первый вызов метода ec.exampleMethod1(10, 4) недопустим, поскольку
содержит два аргумента. Этот вызов приводит к ошибке компилятора. Второй вызов метода
dynamic_ec.exampleMethod1(10, 4) компилятором не проверяется, поскольку dynamic_ec имеет тип dynamic .
В результате ошибка компилятора не возникает. Тем не менее ошибка не может оставаться незамеченной
неограниченное время. Она перехватывается во время выполнения и вызывает исключение времени
выполнения.
class ExampleClass
{
public ExampleClass() { }
public ExampleClass(int v) { }
Роль компилятора в этих примерах заключается в том, чтобы объединить информацию о том, какие
действия каждый оператор будет выполнять с объектом или выражением, типизированным как dynamic .
Во время выполнения сохраненная информация проверяется, а все недействительные операторы вызывают
исключение времени выполнения.
Результатом большинства динамических операций является сам dynamic . Например, при наведении
указателя мыши на testSum в следующем примере IntelliSense отображает тип (локальная переменная)
dynamic testSum.
dynamic d = 1;
var testSum = d + 3;
// Rest the mouse pointer over testSum in the following statement.
System.Console.WriteLine(testSum);
Преобразования
Преобразования динамических объектов в другие типы выполнять очень просто. Это позволяет
разработчику переключаться между динамическим и нединамическим поведением.
Любой объект можно преобразовать в динамический тип неявно, как показано в следующих примерах.
dynamic d1 = 7;
dynamic d2 = "a string";
dynamic d3 = System.DateTime.Today;
dynamic d4 = System.Diagnostics.Process.GetProcesses();
И наоборот, явное преобразование можно динамически применить к любому выражению типа dynamic .
int i = d1;
string str = d2;
DateTime dt = d3;
System.Diagnostics.Process[] procs = d4;
// The following statement does not cause a compiler error, even though ec is not
// dynamic. A run-time exception is raised because the run-time type of d1 is int.
ec.exampleMethod2(d1);
// The following statement does cause a compiler error.
//ec.exampleMethod2(7);
Среда DLR
Среда выполнения динамического языка (DLR ) — это новый API в .NET Framework 4. Она предоставляет
инфраструктуру, которая поддерживает тип dynamic в C#, а также реализацию динамических языков
программирования, таких как IronPython и IronRuby. Дополнительные сведения о DLR см. в разделе
Общие сведения о среде DLR.
взаимодействие COM
C# 4 включает несколько функций, улучшающих взаимодействие с интерфейсами API COM, такими как API
автоматизации Office. К таким усовершенствованиям относятся применение типа dynamic и именованных
и необязательных аргументов.
Многие методы COM допускают вариативность в типах аргументов и возвращают тип, назначая типы как
object . В связи с этим для согласования со строго типизированными переменными в C# необходимо явно
приводить значения. При компиляции с использованием параметра -link (параметры компилятора C#)
введение типа dynamic позволяет обрабатывать вхождения object в сигнатурах COM, как если бы они
имели тип dynamic , и таким образом избежать большей части приведения. Например, следующие
инструкции показывают, как получить доступ к ячейке в электронной таблице Microsoft Office Excel с
типом dynamic и без типа dynamic .
// After the introduction of dynamic, the access to the Value property and
// the conversion to Excel.Range are handled by the run-time COM binder.
excelApp.Cells[1, 1].Value = "Name";
Excel.Range range2010 = excelApp.Cells[1, 1];
См. также
ЗАГОЛОВОК ОПИСАНИЕ
Общие сведения о среде DLR Содержит общие сведения о среде DLR — среде
выполнения, которая добавляет в общую среду языковой
среды исполнения (CLR) набор служб для динамических
языков.
Динамические объекты предоставляют такие элементы, как свойства и методы, во время выполнения, а не
во время компиляции. Это позволяет создавать объекты для работы со структурами, не соответствующими
статическому типу или формату. Например, можно использовать динамический объект для ссылки на
модель DOM HTML, которая может содержать любую комбинацию допустимых элементов и атрибутов
разметки HTML. Поскольку каждый документ HTML является уникальным, элементы для конкретного
документа HTML определяются во время выполнения. Наиболее распространенный способ ссылки на
атрибут элемента HTML заключается в передаче имени этого атрибута в метод GetProperty элемента. Для
ссылки на атрибут id элемента HTML <div id="Div1"> следует сначала получить ссылку на элемент <div> ,
а затем использовать divElement.GetProperty("id") . При использовании динамического объекта можно
сослаться на атрибут id в виде divElement.id .
Динамические объекты обеспечивают удобный доступ к динамическим языкам, таким как IronPython и
IronRuby. С помощью динамического объекта можно ссылаться на динамический скрипт,
интерпретируемый во время выполнения.
Ссылка на динамический объект выполняется с помощью позднего связывания. В C# тип объектов с
поздним связыванием указывается как dynamic . В Visual Basic тип объектов с поздним связыванием
указывается как Object . Дополнительные сведения см. в разделах dynamic и Раннее и позднее связывание.
Вы можете создавать настраиваемые динамические объекты, используя классы из пространства имен
System.Dynamic. Например, можно создать объект ExpandoObject и задать члены этого объекта во время
выполнения. Также можно создать собственный тип, наследующий класс DynamicObject. Затем для
обеспечения динамических функциональных возможностей во время выполнения можно переопределить
члены класса DynamicObject.
В данном пошаговом руководстве демонстрируется выполнение следующих задач:
Создание пользовательского объекта, который динамически предоставляет содержимое текстового
файла в виде свойств объекта.
Создание проекта, использующего библиотеку IronPython .
Предварительные требования
Для выполнения инструкций этого пошагового руководства потребуется установить IronPython для .NET.
Перейдите на страницу загрузки для получения последней версии.
NOTE
Отображаемые на компьютере имена или расположения некоторых элементов пользовательского интерфейса Visual
Studio могут отличаться от указанных в следующих инструкциях. Это зависит от имеющегося выпуска Visual Studio и
используемых параметров. Дополнительные сведения см. в разделе Персонализация среды IDE.
4. Щелкните правой кнопкой мыши проект DynamicSample, выберите пункт Добавить, а затем
щелкните Класс. В поле Имя введите ReadOnlyFile и нажмите кнопку ОК. Будет добавлен новый
файл, содержащий класс ReadOnlyFile.
5. В верхней части файла ReadOnlyFile.cs или ReadOnlyFile.vb добавьте следующий код для импорта
пространств имен System.IO и System.Dynamic.
using System.IO;
using System.Dynamic;
Imports System.IO
Imports System.Dynamic
8. Добавьте в класс ReadOnlyFile следующий код, чтобы задать закрытое поле для пути к файлу и
конструктор для класса ReadOnlyFile .
// Store the path to the file and the initial line count value.
private string p_filePath;
// Public constructor. Verify that file exists and store the path in
// the private variable.
public ReadOnlyFile(string filePath)
{
if (!File.Exists(filePath))
{
throw new Exception("File path does not exist.");
}
p_filePath = filePath;
}
' Store the path to the file and the initial line count value.
Private p_filePath As String
' Public constructor. Verify that file exists and store the path in
' the private variable.
Public Sub New(ByVal filePath As String)
If Not File.Exists(filePath) Then
Throw New Exception("File path does not exist.")
End If
p_filePath = filePath
End Sub
try
{
sr = new StreamReader(p_filePath);
while (!sr.EndOfStream)
{
line = sr.ReadLine();
switch (StringSearchOption)
{
case StringSearchOption.StartsWith:
if (testLine.StartsWith(propertyName.ToUpper())) { results.Add(line); }
break;
case StringSearchOption.Contains:
if (testLine.Contains(propertyName.ToUpper())) { results.Add(line); }
break;
case StringSearchOption.EndsWith:
if (testLine.EndsWith(propertyName.ToUpper())) { results.Add(line); }
break;
}
}
}
catch
{
// Trap any exception that occurs in reading the file and return null.
results = null;
}
finally
{
if (sr != null) {sr.Close();}
}
return results;
}
Public Function GetPropertyValue(ByVal propertyName As String,
Optional ByVal StringSearchOption As StringSearchOption =
StringSearchOption.StartsWith,
Optional ByVal trimSpaces As Boolean = True) As List(Of String)
Try
sr = New StreamReader(p_filePath)
Return results
End Function
10. После метода GetPropertyValue добавьте следующий код, чтобы переопределить метод
TryGetMember класса DynamicObject. Метод TryGetMember вызывается при запросе члена
динамического класса без указания аргументов. Аргумент binder содержит сведения об элементе, на
который дается ссылка, а аргумент result ссылается на результат, возвращенный для указанного
элемента. Метод TryGetMember возвращает логическое значение true , если запрошенный элемент
существует. В противном случае возвращается false .
// Implement the TryGetMember method of the DynamicObject class for dynamic member calls.
public override bool TryGetMember(GetMemberBinder binder,
out object result)
{
result = GetPropertyValue(binder.Name);
return result == null ? false : true;
}
' Implement the TryGetMember method of the DynamicObject class for dynamic member calls.
Public Overrides Function TryGetMember(ByVal binder As GetMemberBinder,
ByRef result As Object) As Boolean
result = GetPropertyValue(binder.Name)
Return If(result Is Nothing, False, True)
End Function
11. После метода TryGetMember добавьте следующий код, чтобы переопределить метод TryInvokeMember
класса DynamicObject. Метод TryInvokeMember вызывается при запросе члена динамического класса с
аргументами. Аргумент binder содержит сведения об элементе, на который дается ссылка, а аргумент
result ссылается на результат, возвращенный для указанного элемента. Аргумент args содержит
массив аргументов, передаваемых в элемент. Метод TryInvokeMember возвращает логическое
значение true , если запрошенный элемент существует. В противном случае возвращается false .
Пользовательская версия метода TryInvokeMember ожидает, что первый аргумент будет значением из
перечисления StringSearchOption , заданного на предыдущем шаге. Метод TryInvokeMember ожидает,
что второй аргумент будет логическим значением. Если один или оба элемента имеют допустимые
значения, они передаются в метод GetPropertyValue для получения результатов.
try
{
if (args.Length > 0) { StringSearchOption = (StringSearchOption)args[0]; }
}
catch
{
throw new ArgumentException("StringSearchOption argument must be a StringSearchOption enum
value.");
}
try
{
if (args.Length > 1) { trimSpaces = (bool)args[1]; }
}
catch
{
throw new ArgumentException("trimSpaces argument must be a Boolean value.");
}
Try
If args.Length > 0 Then StringSearchOption = CType(args(0), StringSearchOption)
Catch
Throw New ArgumentException("StringSearchOption argument must be a StringSearchOption enum
value.")
End Try
Try
If args.Length > 1 Then trimSpaces = CType(args(1), Boolean)
Catch
Throw New ArgumentException("trimSpaces argument must be a Boolean value.")
End Try
3. Сохраните файл и нажмите сочетание клавиш CTRL+F5 для сборки и запуска приложения.
3. Если используется Visual Basic, щелкните правой кнопкой мыши проект DynamicIronPythonSample и
выберите пункт Свойства. Перейдите на вкладку Ссылки. Нажмите кнопку Добавить. Если
используется Visual C#, в обозревателе решений щелкните правой кнопкой мыши папку Ссылки и
выберите пункт Добавить ссылку.
4. На вкладке Обзор перейдите к папке, в которой установлены библиотеки IronPython. Например,
C:\Program Files\IronPython 2.6 for .NET 4.0. Выберите библиотеки IronPython.dll,
IronPython.Modules.dll, Microsoft.Scripting.dll и Microsoft.Dynamic.dll. Нажмите кнопку ОК.
5. Если используется Visual Basic, отредактируйте файл Module1.vb. Если используется Visual C#,
отредактируйте файл Program.cs.
6. В верхней части файла добавьте следующий код для импорта пространств имен
Microsoft.Scripting.Hosting и IronPython.Hosting из библиотек IronPython.
using Microsoft.Scripting.Hosting;
using IronPython.Hosting;
Imports Microsoft.Scripting.Hosting
Imports IronPython.Hosting
8. После указания в коде необходимости загрузки модуля random.py добавьте следующий код, чтобы
создать массив целых чисел. Массив передается методу shuffle модуля random.py, который
произвольно сортирует значения в массиве.
9. Сохраните файл и нажмите сочетание клавиш CTRL+F5 для сборки и запуска приложения.
См. также
System.Dynamic
System.Dynamic.DynamicObject
Использование типа dynamic
Раннее и позднее связывание
dynamic
Реализация динамических интерфейсов (документ в формате PDF на сайте Microsoft TechNet)
Классы и структуры (Руководство по
программированию в C#)
23.10.2019 • 11 minutes to read • Edit Online
Классы и структуры являются двумя основными конструкциями системы общих типов CTS,
используемой в платформе .NET Framework. Оба они являются структурами данных, которые
инкапсулируют набор данных и поведений в одной логической сущности. Данные и поведение
являются членами класса или структуры. К ним относятся методы, свойства, события и другие элементы,
которые описаны далее в этой статье.
Объявление класса или структуры служит своего рода чертежом для создания экземпляров или
объектов во время выполнения. Если вы определите класс или структур с именем Person , то Person
здесь обозначает имя типа. Если вы объявите и инициализируете переменную p типа Person , принято
говорить, что p является объектом (или экземпляром) Person . Можно создать несколько экземпляров
одного типа Person , и каждый экземпляр будет иметь разные значения свойств и полей.
Класс является ссылочным типом. Когда вы создаете объект класса и сохраняете его в переменную, эта
переменная содержит только ссылку на память объекта. Если ссылка на объект сохраняется в новую
переменную, эта переменная также ссылается на исходный объект. Изменения, внесенные через одну
переменную, отражаются и в другой переменной, поскольку обе они ссылаются на одни и те же
данные.
Структура (struct) является типом значения. При создании структуры переменная, которой присвоена
структура, содержит фактические данные этой структуры. Если структура присваивается новой
переменной, все данные копируются. Таким образом, новая переменная и исходная переменная
содержат две отдельные копии одинаковых данных. Изменения, внесенные в одну копию, не влияют на
другую.
В общем случае классы используются для моделирования более сложного поведения или для таких
данных, которые будут изменяться после создания объекта класса. Структуры лучше подходят для
небольших структур данных, информация в которых не должна изменяться после создания структуры.
Подробную информацию можно получить в описаниях классов, объектов и структур.
Пример
В следующем примере в пространстве имен ProgrammingGuide определен класс CustomClass с тремя
членами: конструктор экземпляра, свойство с именем Number и метод с именем Multiply . Метод Main
в классе Program создает экземпляр (объект) класса CustomClass . Обращение к методам и свойствам
объекта осуществляется с использованием точечной нотации.
using System;
namespace ProgrammingGuide
{
// Class definition.
public class CustomClass
{
// Class members.
//
// Property.
public int Number { get; set; }
// Method.
public int Multiply(int num)
{
return num * Number;
}
// Instance Constructor.
public CustomClass()
{
Number = 0;
}
}
// Another class definition that contains Main, the program entry point.
class Program
{
static void Main(string[] args)
{
// Create an object of type CustomClass.
CustomClass custClass = new CustomClass();
Инкапсуляция
Концепцию инкапсуляции признают одним из основополагающих принципов объектно-
ориентированного программирования. Принцип инкапсуляции заключается в том, что в классе или
структуре можно указать уровень доступности для обращения к каждому из его членов из кода,
расположенного вне этого класса или структуры. Вы можете скрыть методы и переменные, которые не
предназначены для использования вне класса или сборки. Это позволяет снизить риск ошибок в коде и
вредоносных действий.
Дополнительные сведения о классах вы найдете в статьях Классы и Объекты.
Участники
Все методы, поля, константы, свойства и события должны объявляться внутри типа. Совокупно они
называются членами типа. В C# не существует глобальных переменных или методов, как в некоторых
других языках. Даже точка входа программы, то есть метод Main , должна объявляться внутри класса
или структуры. Ниже приведен полный список возможных членов, которые можно объявить в классе
или структуре.
Поля
Константы
Свойства
Методы
Конструкторы
События
Методы завершения
Индексаторы
Инструкции
Вложенные типы
Специальные возможности
Некоторые методы и свойства специально предназначены для того, чтобы их вызов или доступ к ним
осуществлялся из клиентского кода, то есть из кода за пределами этого класса или структуры. Другие
методы и свойства могут использоваться только в самом классе или структуре. Важно ограничить
доступность кода так, чтобы только нужные элементы клиентского кода получали к нему доступ.
Уровень доступности для типов и их членов вы можете задать с помощью модификаторов доступа
public, protected, internal, protected internal, private и private protected. По умолчанию используется
режим доступа private . Дополнительные сведения см. в статье Модификаторы доступа.
Наследование
Классы (но не структуры) поддерживают наследование. Класс, производный от другого класса (базового
класса), автоматически включает все открытые, защищенные и внутренние члены базового класса за
исключением конструкторов и методов завершения. Дополнительные сведения см. в статьях о
наследовании и полиморфизме.
Классы могут быть объявлены как абстрактные. Это означает, что один или несколько их членов не
имеют реализации. Из абстрактных классов нельзя напрямую создать экземпляры. Они выполняют
роль базовых классов для других классов, которые предоставляют реализацию недостающих членов.
Также классы можно объявить запечатанными, чтобы запретить наследование от них других классов.
Дополнительные сведения см. в статье Абстрактные и запечатанные классы и члены классов.
Интерфейсы
Классы и структуры могут наследовать несколько интерфейсов. Наследование интерфейса означает, что
тип реализует все методы, определенные в этом интерфейсе. Дополнительные сведения см. в статье
Интерфейсы.
Универсальные типы
Для класса или структуры можно определить один или несколько параметров типа. Клиентский код
назначает тип при создании экземпляра типа. Например, класс List<T> в пространстве имен
System.Collections.Generic определен с помощью одного параметра типа. Клиентский код создает
экземпляр List<string> или List<int> , информируя о том, сущности какого типа будут храниться в
этом списке. Дополнительные сведения см. в статье Универсальные шаблоны.
Статические типы
Классы (но не структуры) могут быть объявлены как статические. Статический класс может содержать
только статические члены. Для него нельзя создать экземпляр с помощью ключевого слова new. При
запуске программы в память загружается одна копия такого класса. Доступ к его членам
осуществляется через имя класса. Классы и структуры могут содержать статические члены.
Дополнительные сведения см. в статье Статические классы и члены статических классов.
Вложенные типы
Класс или структура могут быть вложены в другой класс или структуру. Дополнительные сведения см. в
разделе Вложенные типы.
Разделяемые типы
Вы можете разделить определение класса, структуры или метода на несколько файлов с кодом.
Дополнительные сведения см. в статье Разделяемые классы и методы.
Инициализаторы объектов
Вы можете создавать и инициализировать экземпляры класса или структуры, а также коллекции
объектов, без явного вызова соответствующих конструкторов. Дополнительные сведения см. в статье
Инициализаторы объектов и коллекций.
Анонимные типы
В некоторых ситуациях нет смысла или пользы создавать именованный класс, например при заполнении
списка структурами данных, которые не будут сохраняться или передаваться другому методу. В таких
случаях можно использовать анонимные типы. Дополнительные сведения см. в статье Анонимные типы.
Методы расширения
Вы можете "расширить" класс, даже не создавая производного класса. Для этого можно создать
отдельный тип и вызывать его методы так, как если бы они принадлежали исходному типу.
Дополнительные сведения см. в разделе Методы расширения.
Неявно типизированные локальные переменные
Внутри метода класса или структуры можно использовать неявное типизирование, чтобы компилятор
самостоятельно определял правильный тип во время компиляции. Дополнительные сведения см. в
статье Implicitly Typed Local Variables (Неявно типизированные локальные переменные).
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Классы (Руководство по программированию на
C#)
23.10.2019 • 8 minutes to read • Edit Online
Ссылочные типы
Тип, который определен как класс, является ссылочным типом. Когда во время выполнения вы
объявляете переменную ссылочного типа, такая переменная будет содержать значение NULL, пока вы
явным образом не создадите экземпляр класса с помощью оператора new или не назначите его объекту
совместимого типа, созданному в другом месте, как показано в следующем примере:
//Declaring another object of the same type, assigning it the value of the first object.
MyClass mc2 = mc;
При создании объекта выделяется достаточный объем памяти для этого объекта в управляемой куче, и
переменная хранит только ссылку на расположение данного объекта. Хранение типов в управляемой
куче требует дополнительных действий как при выделении памяти, так и при удалении, которое
выполняется функцией автоматического управления памятью в среде CLR, известной как сборка
мусора. Сборка мусора является хорошо оптимизированным процессом и в большинстве случаев не
создает помех для производительности. Дополнительные сведения о сборке мусора см. в разделе
Автоматическое управление памятью и сборка мусора.
Объявление классов
Классы объявляются с помощью ключевого слова class, за которым следует уникальный идентификатор,
как показано в следующем примере:
Ключевому слову class предшествует уровень доступа. Так как в этом случае используется открытый
класс (public), любой пользователь может создавать его экземпляры. За именем класса следует ключевое
слово class . Имя класса должно быть допустимым именем идентификатора C#. Оставшаяся часть
определения — это тело класса, в котором задаются данные и поведение. Поля, свойства, методы и
события в классе собирательно называются членами класса.
Создание объектов
Несмотря на то, что они иногда взаимозаменяемы, класс и объект — разные вещи. Класс определяет тип
объекта, но не является объектом. Объект — это конкретная сущность, основанная на классе, которую
иногда называют экземпляром класса.
Объекты можно создавать с помощью ключевого слова new, за которым следует имя класса, на котором
будет основан объект, например следующим образом:
Customer object1 = new Customer();
При создании экземпляра класса ссылка на объект передается программисту. В предыдущем примере
object1 представляет собой ссылку на объект, который основан на Customer . Эта ссылка указывает на
новый объект, но не содержит данные этого объекта. Фактически, можно создать ссылку на объект без
создания собственно объекта:
Customer object2;
Создание таких ссылок, которые не указывают на объект, не рекомендуется, так как попытка доступа к
объекту по такой ссылке приведет к сбою во время выполнения. Однако такую ссылку можно сделать
указывающей на объект, создав новый объект или назначив ее существующему объекту, как показано
далее:
В этом коде создаются две ссылки на объект, которые указывают на один и тот же объект. Таким
образом, любые изменения объекта, выполненные посредством object3 , отражаются при
последующем использовании object4 . Поскольку на объекты, основанные на классах, указывают
ссылки, классы называют ссылочными типами.
Наследование классов
Классы полностью поддерживают наследование, фундаментальный механизм объектно
ориентированного программирования. Создаваемый класс может наследовать от любого другого
интерфейса или класса, который не определен как запечатанный, а другие классы могут наследовать от
этого класса и переопределять его виртуальные методы.
При наследовании создается производный класс, то есть класс объявляется с помощью базового класса,
от которого он наследует данные и поведение. Базовый класс задается добавлением после имени
производного класса двоеточия и имени базового класса, как показано далее:
Когда класс объявляет базовый класс, он наследует все члены базового класса, за исключением
конструкторов. Дополнительные сведения см. в разделе Наследование.
В отличие от C++, класс в C# может только напрямую наследовать от одного базового класса. Тем не
менее, поскольку базовый класс может сам наследовать от другого класса, класс может косвенно
наследовать от нескольких базовых классов. Кроме того, класс может напрямую реализовать несколько
интерфейсов. Дополнительные сведения см. в разделе Интерфейсы.
Класс может быть объявлен абстрактным. Абстрактный класс содержит абстрактные методы, которые
имеют определение сигнатуры, но не имеют реализации. Нельзя создавать экземпляры абстрактных
классов. Они могут использоваться только через производные классы, реализующие абстрактные
методы. И наоборот, запечатанный класс не позволяет другим классам быть от него производными.
Дополнительные сведения см. в статье Абстрактные и запечатанные классы и члены классов.
Определения классов можно разделить между различными исходными файлами. Дополнительные
сведения см. в разделе Разделяемые классы и методы.
Пример
В следующем примере определяется открытый класс, содержащий автоматически реализуемое
свойство, метод и специальный метод, который называется конструктором. См. дополнительные
сведения о свойствах, методах и конструкторах. Затем создаются экземпляры этого класса с помощью
ключевого слова new .
using System;
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Объектно-ориентированное программирование
Полиморфизм
Имена идентификаторов
Члены
Методы
Конструкторы
Методы завершения
Объекты
Объекты (Руководство по программированию на
C#)
04.11.2019 • 8 minutes to read • Edit Online
Определение класса или структуры подобно чертежу, на котором указаны действия, выполняемые типом. В
сущности, объект является блоком памяти, выделенной и настроенной в соответствии с чертежом.
Программа может создать множество объектов одного класса. Объекты также называют экземплярами.
Они могут храниться либо в именованной переменной, либо в массиве или коллекции. Клиентский код —
это код, использующий эти переменные для вызова методов и доступа к открытым свойствам объекта. В
объектно-ориентированном языке, таком как C#, стандартная программа состоит из нескольких
динамически взаимодействующих объектов.
NOTE
Поведение статических типов отличается от описанного здесь поведения. Дополнительные сведения см. в статье
Статические классы и члены статических классов.
class Program
{
static void Main()
{
Person person1 = new Person("Leopold", 6);
Console.WriteLine("person1 Name = {0} Age = {1}", person1.Name, person1.Age);
}
}
/*
Output:
person1 Name = Leopold Age = 6
person2 Name = Molly Age = 16
person1 Name = Molly Age = 16
*/
Так как структуры являются типами значений, в переменной объекта структуры хранится копия всего
объекта. Экземпляры структур также можно создавать с помощью оператора new , однако он не является
обязательным, как показано в следующем примере:
public struct Person
{
public string Name;
public int Age;
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
Память для p1 и p2 выделена в стеке потока. Эта память освобождается вместе с типом или методом, в
котором она объявляется. Эта одна из причин того, почему структуры копируются при присваивании.
Напротив, при выходе всех ссылок на объект из области действия среда CLR автоматически освобождает
память (выполняет сборку мусора), выделенную для экземпляра класса. Возможность детерминированного
уничтожения объекта класса, имеющаяся в C++, в данном случае отсутствует. Дополнительные сведения о
сборке мусора в .NET Framework см. в разделе Сборка мусора.
NOTE
В среде CLR процесс выделения и освобождения памяти в управляемой куче значительно оптимизирован. В
большинстве случаев нет существенной разницы в затратах производительности на выделение экземпляра класса в
куче и выделение экземпляра структуры в стеке.
if (p2.Equals(p1))
Console.WriteLine("p2 and p1 have the same values.");
В реализации System.ValueType Equals используется отражение, так как необходимо определить поля,
имеющиеся в любой структуре. При создании собственных структур переопределите метод Equals для
предоставления эффективного алгоритма равенства, соответствующего вашему типу.
Чтобы определить, равны ли значения полей в двух экземплярах класса, можно воспользоваться
методом Equals или оператором ==. Однако их следует использовать, только если они переопределены
или перегружены классом с целью предоставления пользовательского определение равенства для
объектов этого типа. Класс может также реализовывать интерфейс IEquatable<T> или интерфейс
IEqualityComparer<T>. Оба интерфейса предоставляют методы, которые можно использовать для
проверки равенства значений. При создании собственных классов, переопределяющих Equals , следуйте
рекомендациям из практического руководства по определению равенства значений для типа и раздела
Object.Equals(Object).
Связанные разделы
Дополнительные сведения:
Классы
Структуры
Конструкторы
Методы завершения
События
См. также
Руководство по программированию на C#
object
Наследование
class
struct
Оператор new
Система общих типов CTS
Структуры (Руководство по программированию
на C#)
25.11.2019 • 2 minutes to read • Edit Online
Структуры используют большую часть того же синтаксиса, что и классы. Имя структуры должно быть
допустимым именем идентификатора C#. Структуры более ограничены по сравнению с классами по
следующим причинам.
В объявлении структуры поля не могут быть инициализированы до тех пор, пока они будут объявлены
как const или static.
Структура не может объявлять конструктор без параметров или метод завершения.
Структуры копируются при присваивании. При присваивании структуры к новой переменной
выполняется копирование всех данных, а любое изменение новой копии не влияет на данные в
исходной копии. Это важно помнить при работе с коллекциями типов значений, такими как
Dictionary<string, myStruct> .
Структуры являются типами значений, а классы — ссылочными типами.
В отличие от классов структуры можно создавать без использования оператора new .
Структуры могут объявлять конструкторы, имеющие параметры.
Структура не может наследовать от другой структуры или класса и не может быть базовой для класса.
Все структуры наследуют непосредственно от ValueType, который наследует от Object.
Структуры могут реализовывать интерфейсы.
Структура не может быть null , а переменная структуры не может быть назначена null , если
переменная не объявлена как тип, допускающий значение NULL.
См. также
Руководство по программированию на C#
Классы и структуры
Классы
Типы значений, допускающие значение NULL
Имена идентификаторов
Использование структур
Определение различия между передачей структуры и ссылки класса в метод
Использование структур (руководство по
программированию на C#)
31.10.2019 • 5 minutes to read • Edit Online
Тип struct подходит для представления простых объектов, таких как Point , Rectangle и Color . Хотя point
удобно представить в виде класса с автоматически реализуемыми свойствами, в некоторых сценариях
структура может оказаться более эффективной. Например, при объявлении массива из 1000 объектов
Point потребуется выделить дополнительную память для ссылки на каждый объект. В этом случае
использование структуры будет более экономичным вариантом. Поскольку платформа .NET Framework
содержит объект с именем Point, структура в этом примере называется Coords .
Определение конструктора без параметров для структуры является ошибочным. Кроме того, ошибкой будет
и инициализация поля экземпляра в основной части структуры. Члены структуры, доступные извне, можно
инициализировать только с помощью параметризованного конструктора, неявного конструктора без
параметров, инициализатора объекта или путем доступа к членам по отдельности после объявления
структуры. Любые закрытые или недоступные члены требуют исключительного использования
конструкторов.
При создании объекта структуры с помощью оператора new вызывается соответствующий конструктор в
соответствии с сигнатурой конструктора. В отличие от классов, создавать структуры можно без оператора
new . В этом случае вызов конструктора не выполняется, что способствует более эффективному выделению
ресурсов. Однако поля остаются неназначенными и объект нельзя использовать до инициализации всех
полей. Например, невозможно получить или задать значения с помощью свойств.
При создании экземпляра объекта структуры с помощью конструктора без параметров все члены
назначаются согласно их значениям по умолчанию.
При создании конструктора с параметрами для структуры необходимо явно инициализировать все члены. В
противном случае один или несколько членов остаются неназначенными и такую структуру использовать
невозможно: это вызывает ошибку компилятора CS0171.
В отличие от классов, структуры не поддерживают наследование. Структура не может наследовать от
другой структуры или класса и не может быть базовой для класса. Однако структуры наследуют от базового
класса Object. Структуры могут реализовывать интерфейсы именно так, как это делают классы.
Нельзя объявить класс с помощью ключевого слова struct . В C# классы и структуры имеют разную
семантику. Структура является типом значения, а класс — ссылочным типом. Для получения
дополнительной информации см. Типы значений и Типы ссылок.
Если не требуется использовать семантику ссылочного типа, система может более эффективно
обрабатывать небольшой класс, объявленный как структура.
Пример 1
В этом примере показана инициализация struct с использованием конструктора без параметров и
параметризованного конструктора.
// Display results.
Console.Write("Coords 1: ");
Console.WriteLine($"x = {coords1.x}, y = {coords1.y}");
Console.Write("Coords 2: ");
Console.WriteLine($"x = {coords2.x}, y = {coords2.y}");
Пример 2
В этом примере демонстрируется уникальная возможность структур. Здесь создается объект CoOrds без
использования оператора new . Если заменить слово struct на слово class , программа не будет
скомпилирована.
// Initialize.
coords1.x = 10;
coords1.y = 20;
// Display results.
Console.Write("Coords 1: ");
Console.WriteLine($"x = {coords1.x}, y = {coords1.y}");
См. также
Руководство по программированию на C#
Классы и структуры
Структуры
Наследование (Руководство по
программированию на C#)
23.10.2019 • 10 minutes to read • Edit Online
NOTE
Структуры не поддерживают наследование, но могут реализовывать интерфейсы. Дополнительные сведения
см. в разделе Интерфейсы.
//Properties.
protected int ID { get; set; }
protected string Title { get; set; }
protected string Description { get; set; }
protected TimeSpan jobLength { get; set; }
// Method Update enables you to update the title and job length of an
// existing WorkItem object.
public void Update(string title, TimeSpan joblen)
{
this.Title = title;
this.jobLength = joblen;
}
class Program
{
static void Main()
{
// Create an instance of WorkItem by using the constructor in the
// base class that takes three arguments.
WorkItem item = new WorkItem("Fix Bugs",
"Fix all bugs in my code branch",
new TimeSpan(3, 4, 0, 0));
Интерфейсы
Интерфейс является ссылочным типом, в чем-то схожим с абстрактным базовым классом, который
состоит только из абстрактных членов. Если класс реализует интерфейс, этот класс должен
предоставлять реализацию для всех членов интерфейса. В классе может быть реализовано несколько
интерфейсов, хотя производным он может быть только от одного прямого базового класса.
Интерфейсы используются для определения определенных возможностей для классов, которые не
обязательно имеют отношения тождественности. Например, интерфейс System.IEquatable<T> может
быть реализован любым классом или структурой, включающими клиентский код для определения
эквивалентности двух объектов типа (однако тип определяет эквивалентность). IEquatable<T> не
подразумевает тот же вид отношений тождественности, который существует между базовым и
производным классами (например, Mammal является Animal ). Дополнительные сведения см. в разделе
Интерфейсы.
См. также
Руководство по программированию на C#
Классы и структуры
class
struct
Полиморфизм (Руководство по
программированию на C#)
25.11.2019 • 12 minutes to read • Edit Online
using System;
using System.Collections.Generic;
// Virtual method
// Virtual method
public virtual void Draw()
{
Console.WriteLine("Performing base class drawing tasks");
}
}
class Program
{
static void Main(string[] args)
{
// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used whereever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
new Rectangle(),
new Triangle(),
new Circle()
};
/* Output:
Drawing a rectangle
Performing base class drawing tasks
Drawing a triangle
Performing base class drawing tasks
Drawing a circle
Drawing a circle
Performing base class drawing tasks
*/
В C# каждый тип является полиморфным, так как все типы, включая пользовательские, наследуют Object.
Обзор полиморфизма
Виртуальные члены
Если производный класс наследуется из базового, он получает все методы, поля, свойства и события
базового класса. Разработчик производного класса может выбрать следующее:
переопределение виртуальных членов в базовом классе;
наследование метода ближайшего базового класса без переопределения;
определение новой, невиртуальной реализации тех членов, которые скрывают реализации
базового класса.
Производный класс может переопределить член базового класса, только если последний будет объявлен
виртуальным или абстрактным. Производный член должен использовать ключевое слово override,
указывающее, что метод предназначен для участия в виртуальном вызове. Примером является
следующий код:
Поля не могут быть виртуальными. Виртуальными могут быть только методы, свойства, события и
индексаторы. Когда производный класс переопределяет виртуальный член, он вызывается даже в то
случае, если доступ к экземпляру этого класса осуществляется в качестве экземпляра базового класса.
Примером является следующий код:
BaseClass A = (BaseClass)B;
A.DoWork(); // Also calls the new method.
Виртуальные методы и свойства позволяют производным классам расширять базовый класс без
необходимости использовать реализацию базового класса метода. Дополнительные сведения см. в
разделе Управление версиями с помощью ключевых слов Override и New. Еще одну возможность
определения метода или набора методов, реализация которых оставлена производным классам, дает
интерфейс. Дополнительные сведения см. в разделе Интерфейсы.
Сокрытие членов базового класса новыми членами
Если вам нужно, чтобы производный член имел такое же имя, как и член в базовом классе, но вы не
хотите, чтобы он участвовал в виртуальном вызове, используйте ключевое слово new. Ключевое слово
new вставляется перед типом возвращаемого значения замещаемого члена класса. Примером является
следующий код:
Доступ к скрытым членам базового класса можно по-прежнему осуществлять из клиентского кода
приведением экземпляра производного класса к экземпляру базового класса. Например:
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}
В предыдущем примере метод DoWork больше не является виртуальным для любого класса,
производного от C. Он по-прежнему будет виртуальным для экземпляров C, даже если они будут
приведены к типу B или A. Запечатанные методы можно заменить производными классами с помощью
ключевого слова new , как показано в следующем примере:
public class D : C
{
public new void DoWork() { }
}
В этом случае, если DoWork вызывается на D с помощью переменной типа D, вызывается новый DoWork .
Если переменная типа C, B или A используется для доступа к экземпляру D, вызов DoWork будет
выполняться по правилам виртуального наследования и направлять эти вызовы на реализацию DoWork на
классе C.
Доступ к виртуальным членам базового класса из производных классов
Производный класс, который заменил или переопределил метод или свойство, может получить доступ к
методу или свойству на базовом классе с помощью ключевого слова base . Примером является
следующий код:
NOTE
Рекомендуется, чтобы виртуальные члены использовали base для вызова реализации базового класса этого
члена в их собственной реализации. Разрешение поведения базового класса позволяет производному классу
концентрироваться на реализации поведения, характерного для производного класса. Если реализация базового
класса не вызывается, производный класс сопоставляет свое поведение с поведением базового класса по своему
усмотрению.
В этом разделе
Управление версиями с помощью ключевых слов Override и New
Использование ключевых слов Override и New
Практическое руководство. Переопределение метода ToString
См. также
Руководство по программированию на C#
Наследование
Абстрактные и запечатанные классы и члены классов
Методы
События
Свойства
Индексаторы
Типы
Практическое руководство. Управление версиями
с помощью ключевых слов "Override" и "New"
(Руководство по программированию в C#)
04.11.2019 • 8 minutes to read • Edit Online
Язык C# построен таким образом, что управление версиями базовых и производных классов в различных
библиотеках может включать сохранение и расширение обратной совместимости. Это означает, например,
то, что C# полностью поддерживает введение в базовый класс нового члена с таким же именем, как у
члена производного класса, и никакое непредвиденное поведение при этом не возникает. Кроме того, это
значит, что класс должен прямо указывать, в каких случаях метод будет перезаписывать унаследованный
метод, а в каких он будет становиться новым методом, скрывающим одноименный унаследованный метод.
В C# производные классы могут содержать методы с такими же именами, как у методов базового класса.
Метод базового класса должен быть определен как виртуальный.
Если методу в производном классе не предшествуют ключевые слова new или override, компилятор
выдаст предупреждение, а метод будет вести себя так, как если бы имелось ключевое слово new .
Если методу в производном классе предшествует ключевое слово new , он определяется как
независимый от метода в базовом классе.
Если методу в производном классе предшествует ключевое слово override , объекты производного
класса вызывают этот метод вместо метода базового класса.
Метод базового класса можно вызывать из производного класса с помощью ключевого слова base .
Ключевые слова override , virtual и new можно также применять к свойствам, индексаторам и
событиям.
По умолчанию методы C# не являются виртуальными. Если метод объявлен как виртуальный, любой
наследующий его класс может реализовать свою собственную версию. Чтобы сделать метод виртуальным,
в объявление метода базового класса добавляется модификатор virtual . После этого производный класс
может переопределить базовый виртуальный метод с помощью ключевого слова override или скрыть
виртуальный метод в базовом классе с помощью ключевого слова new . Если ключевое слово override или
new не указано, компилятор выдает предупреждение, а метод в производном классе скрывает метод в
базовом классе.
Чтобы продемонстрировать это на практике, предположим, что компания А создала класс с именем
GraphicsClass , который использует ваша программа. Ниже показан GraphicsClass :
class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
}
Ваша компания использует этот класс, на основе которого вы создаете собственный производный класс,
добавляя новый метод:
class YourDerivedGraphicsClass : GraphicsClass
{
public void DrawRectangle() { }
}
Ваше приложение работает, пока компания А не выпускает новую версию GraphicsClass , напоминающую
следующий код:
class GraphicsClass
{
public virtual void DrawLine() { }
public virtual void DrawPoint() { }
public virtual void DrawRectangle() { }
}
Новая версия GraphicsClass содержит метод с именем DrawRectangle . Сначала ничего не происходит.
Новая версия по-прежнему совместима со старой версией на уровне двоичного кода. Любая развернутая
вами программа будет работать как раньше, даже если в системе соответствующего компьютера
установлен новый класс. Существующие вызовы метода DrawRectangle будут и дальше ссылаться на вашу
версию в производном классе.
Однако, как только вы выполните повторную компиляцию приложения с новой версией GraphicsClass ,
компилятор выдаст предупреждение CS0108. В предупреждении будет сказано, что вам необходимо
указать, каким образом метод DrawRectangle будет вести себя в приложении.
Если метод должен переопределять новый метод базового класса, используйте ключевое слово override :
Ключевое слово override гарантирует, что все объекты, производные от YourDerivedGraphicsClass , будут
использовать версию производного класса DrawRectangle . Объекты, производные от
YourDerivedGraphicsClass , сохраняют доступ к версии базового класса DrawRectangle за счет ключевого
слова base:
base.DrawRectangle();
Если вы не хотите, чтобы ваш метод переопределял новый метод базового класса, воспользуйтесь
следующими рекомендациями. Чтобы избежать путаницы между двумя методами, вы можете
переименовать свой метод. Эта работа требует времени и подвержена ошибкам, а в некоторых случаях
непрактична. В то же время, если проект относительно небольшой, метод можно переименовать,
используя параметры рефакторинга в Visual Studio. Дополнительные сведения см. в разделе Рефакторинг
классов и типов (конструктор классов).
Кроме того, предупреждения можно избежать с помощью ключевого слова new в определении
производного класса:
При вызове DoWork к экземпляру Derived компилятор C#, в первую очередь, пытается сделать вызов
совместимым с версиями DoWork , изначально объявленными в Derived . Методы переопределения не
считаются объявленными в классе, они представляют собой новые реализации метода, объявленного в
базовом классе. Если же компилятор C# не может сопоставить вызов метода с исходным методом в
Derived , он пытается сопоставить его с переопределенным методом, имеющим такое же имя и
совместимые параметры. Например:
int val = 5;
Derived d = new Derived();
d.DoWork(val); // Calls DoWork(double).
Поскольку переменная val может быть преобразована в значение double неявно, компилятор C#
вызывает DoWork(double) , а не DoWork(int) . Избежать этого можно двумя способами. Во-первых, избегайте
объявления новых методов, имена которых совпадают с виртуальными методами. Во-вторых, можно
настроить компилятор C# на вызов виртуального метода, заставив его выполнить поиск метода базового
класса путем приведения экземпляра Derived к Base . Поскольку метод виртуальный, будет вызвана
реализация DoWork(int) в Derived . Например:
Дополнительные примеры new и override см. в разделе Использование ключевых слов Override и New.
См. также
Руководство по программированию на C#
Классы и структуры
Методы
Наследование
Использование ключевых слов "Override" и "New"
(Руководство по программированию в C#)
23.10.2019 • 14 minutes to read • Edit Online
В C# метод в производном классе может иметь то же имя, что и метод в базовом классе. Можно задать
способ взаимодействия методов, воспользовавшись ключевыми словами new и override. Модификатор
override расширяет метод virtual базового класса, а модификатор new скрывает доступный метод
базового класса. Эта разница показана в примере в этой статье.
В консольном приложении объявите два класса — BaseClass и DerivedClass . Тип DerivedClass наследуется
от типа BaseClass .
class BaseClass
{
public void Method1()
{
Console.WriteLine("Base - Method1");
}
}
bc.Method1();
dc.Method1();
dc.Method2();
bcdc.Method1();
}
// Output:
// Base - Method1
// Base - Method1
// Derived - Method2
// Base - Method1
}
Далее добавьте следующий метод Method2 в класс BaseClass . Сигнатура этого метода соответствует
сигнатуре метода Method2 в DerivedClass .
Поскольку BaseClass теперь имеет метод Method2 , можно добавить второй оператор вызова для
переменных bc и bcdc класса BaseClass , как показано в следующем коде.
bc.Method1();
bc.Method2();
dc.Method1();
dc.Method2();
bcdc.Method1();
bcdc.Method2();
При построении проекта видно, что добавление метода Method2 в BaseClass вызывает предупреждение.
Предупреждение говорит, что метод Method2 в DerivedClass скрывает метод Method2 в BaseClass .
Рекомендуется использовать ключевое слово new в определении Method2 , если требуется получить такой
результат. Другой вариант — переименовать один из методов Method2 для разрешения предупреждения,
но это не всегда целесообразно.
Прежде чем добавлять new , запустите программу, чтобы увидеть выходные данные дополнительных
операторов вызова. Отобразятся следующие результаты.
// Output:
// Base - Method1
// Base - Method2
// Base - Method1
// Derived - Method2
// Base - Method1
// Base - Method2
Ключевое слово new сохраняет связи, дающие этот результат, однако подавляет предупреждение.
Переменные, имеющие тип BaseClass , продолжают обращаться к элементам класса BaseClass , а
переменная, имеющая тип DerivedClass , продолжает обращаться к элементам класса DerivedClass в
первую очередь, а затем к элементам, унаследованным от BaseClass .
Чтобы подавить предупреждение, добавьте модификатор new в определение Method2 в классе
DerivedClass , как показано в следующем коде. Модификатор можно добавить перед или после public .
Запустите программу еще раз, чтобы убедиться, что выходные данные не изменились. Также убедитесь,
что предупреждение больше не появляется. Используя ключевое слово new , вы утверждаете, что вам
известно, что модифицируемый им член скрывается членом, который наследуется от базового класса.
Дополнительные сведения о скрытии имен через наследование см. в разделе Модификатор new.
Чтобы противопоставить это поведение эффекту использования override , добавьте в DerivedClass
следующий метод. Модификатор override можно добавить перед или после public .
Снова запустите проект. Обратите особое внимание на последние две строки следующих выходных данных.
// Output:
// Base - Method1
// Base - Method2
// Derived - Method1
// Derived - Method2
// Derived - Method1
// Base - Method2
Использование модификатора override позволяет bcdc осуществлять доступ к методу Method1 , который
определен в DerivedClass . Как правило, это требуемое поведение в иерархиях наследования. Требуется,
чтобы объекты со значениями, созданными из производного класса, использовали определенные в
производном классе методы. Это поведение достигается с использованием override для расширения
метода базового класса.
Следующий код содержит полный пример.
using System;
using System.Text;
namespace OverrideAndNew
{
class Program
{
static void Main(string[] args)
{
BaseClass bc = new BaseClass();
DerivedClass dc = new DerivedClass();
BaseClass bcdc = new DerivedClass();
// The following two calls do what you would expect. They call
// the methods that are defined in BaseClass.
bc.Method1();
bc.Method2();
// Output:
// Base - Method1
// Base - Method2
// The following two calls do what you would expect. They call
// the methods that are defined in DerivedClass.
dc.Method1();
dc.Method2();
// Output:
// Derived - Method1
// Derived - Method2
class BaseClass
{
public virtual void Method1()
{
Console.WriteLine("Base - Method1");
}
// Define the base class, Car. The class defines two methods,
// DescribeCar and ShowDetails. DescribeCar calls ShowDetails, and each derived
// class also defines a ShowDetails method. The example tests which version of
// ShowDetails is selected, the base class method or the derived class method.
class Car
{
public void DescribeCar()
{
System.Console.WriteLine("Four wheels and an engine.");
ShowDetails();
}
// Notice the output from this test case. The new modifier is
// used in the definition of ShowDetails in the ConvertibleCar
// class.
TestCars1 формирует следующие выходные данные. Обратите особое внимание на результаты для car2 ,
который, вероятно, не соответствует ожидаемым. Тип объекта — ConvertibleCar , но DescribeCar не
получает доступа к версии ShowDetails , определенной в классе ConvertibleCar , так как этот метод
объявлен с модификатором new , а не override . В результате объект ConvertibleCar отображает то же
описание, что и объект Car . Сравните результаты для car3 , который является объектом Minivan . В этом
случае метод ShowDetails , объявленный в классе Minivan , переопределяет метод ShowDetails ,
объявленный в классе Car , и отображается описание микроавтобуса.
// TestCars1
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------
TestCars2 создает список объектов, имеющих тип Car . Значения объектов создаются из классов Car ,
ConvertibleCar и Minivan . DescribeCar вызывается для каждого элемента списка. В следующем коде
показано определение TestCars2 .
// TestCars2
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Standard transportation.
// ----------
// Four wheels and an engine.
// Carries seven people.
// ----------
Методы TestCars3 и TestCars4 завершают пример. Эти методы вызывают ShowDetails напрямую: сначала
из объектов, объявленных с типом ConvertibleCar и Minivan ( TestCars3 ), а затем из объектов, объявленных
с типом Car ( TestCars4 ). Следующий код определяет эти два метода.
Методы производят следующий результат, который соответствует результатам из первого примера в этой
теме.
// TestCars3
// ----------
// A roof that opens up.
// Carries seven people.
// TestCars4
// ----------
// Standard transportation.
// Carries seven people.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq;
using System.Text;
namespace OverrideAndNew2
{
class Program
{
static void Main(string[] args)
{
// Declare objects of the derived classes and test which version
// of ShowDetails is run, base or derived.
TestCars1();
// Notice the output from this test case. The new modifier is
// used in the definition of ShowDetails in the ConvertibleCar
// class.
ConvertibleCar car2 = new ConvertibleCar();
car2.DescribeCar();
System.Console.WriteLine("----------");
// Define the base class, Car. The class defines two virtual methods,
// DescribeCar and ShowDetails. DescribeCar calls ShowDetails, and each derived
// class also defines a ShowDetails method. The example tests which version of
// ShowDetails is used, the base class method or the derived class method.
class Car
{
public virtual void DescribeCar()
{
System.Console.WriteLine("Four wheels and an engine.");
ShowDetails();
}
См. также
Руководство по программированию на C#
Классы и структуры
Управление версиями с помощью ключевых слов Override и New
base
abstract
Практическое руководство. Переопределение
метода ToString (руководство по
программированию на C#)
25.11.2019 • 2 minutes to read • Edit Online
Каждый класс или структура в языке C# неявно наследует класс Object. Поэтому каждый объект в языке C#
получает метод ToString, который возвращает строковое представление данного объекта. Например, все
переменные типа int имеют метод ToString , который позволяет им возвращать их содержимое в виде
строки:
int x = 42;
string strx = x.ToString();
Console.WriteLine(strx);
// Output:
// 42
При создании пользовательского класса или структуры необходимо переопределить метод ToString, чтобы
передать информацию о типе клиентскому коду.
Дополнительные сведения об использовании строк форматирования и других типов пользовательского
форматирования с методом ToString см. в разделе Типы форматирования.
IMPORTANT
При принятии решения относительно того, какая информация должна будет предоставляться посредством этого
метода, подумайте, будет ли создаваемый класс или структура когда-либо использоваться ненадежным кодом.
Постарайтесь не предоставлять информацию, которая может быть использована вредоносным кодом.
Чтобы переопределить метод ToString в классе или структуре, выполните указанные ниже действия.
1. Объявите метод ToString со следующими модификаторами и типом возвращаемого значения:
class Person
{
public string Name { get; set; }
public int Age { get; set; }
См. также
IFormattable
Руководство по программированию на C#
Классы и структуры
Строки
string
override
virtual
Типы форматирования
Члены (Руководство по программированию на C#)
23.10.2019 • 3 minutes to read • Edit Online
В классах и структурах есть члены, представляющие их данные и поведение. Члены класса включают все
члены, объявленные в этом классе, а также все члены (кроме конструкторов и методов завершения),
объявленные во всех классах в иерархии наследования данного класса. Закрытые члены в базовых классах
наследуются, но недоступны из производных классов.
В следующей таблице перечислены виды членов, которые содержатся в классе или в структуре.
ЧЛЕН ОПИСАНИЕ
См. также
Руководство по программированию на C#
Классы
Абстрактные и запечатанные классы и члены
классов (Руководство по программированию на
C#)
25.11.2019 • 4 minutes to read • Edit Online
Ключевое слово abstract позволяет создавать классы и члены классов, которые являются неполными и
должны быть реализованы в производном классе.
Ключевое слово sealed позволяет предотвратить наследование класса или определенных членов класса,
помеченных ранее как virtual.
Абстрактные методы не имеют реализации, поэтому определение такого метода заканчивается точкой
с запятой вместо обычного блока метода. Классы, производные от абстрактного класса, должны
реализовывать все абстрактные методы. Если абстрактный класс наследует виртуальный метод из
базового класса, абстрактный класс может переопределить виртуальный метод с помощью
абстрактного метода. Например:
// compile with: -target:library
public class D
{
public virtual void DoWork(int i)
{
// Original implementation.
}
}
public class F : E
{
public override void DoWork(int i)
{
// New implementation.
}
}
Если метод virtual объявляется как abstract , он все равно считается виртуальным по отношению к
любому классу, наследующему от абстрактного класса. Класс, наследующий абстрактный метод, не
может получать доступ к исходной реализации метода (так, в предыдущем примере DoWork для
класса F не может вызывать DoWork для класса D ). Таким образом, абстрактный класс может
принудительно задавать необходимость предоставлять новые реализации виртуальных методов в
производных классах.
Запечатанный класс не может использоваться в качестве базового класса. Поэтому он также не может
быть абстрактным классом. Запечатанные классы предотвращают наследование. Поскольку их нельзя
использовать в качестве базовых классов, определенная оптимизация во время выполнения позволяет
несколько ускорить вызов членов запечатанных классов.
Метод, индексатор, свойство или событие для производного класса, переопределяющего виртуальный
член базового класса, может объявлять этот член как запечатанный. Это делает бесполезным
виртуальный аспект члена для каждого последующего производного класса. Для этого в объявлении
члена класса необходимо перед ключевым словом override поместить ключевое слово sealed .
Например:
public class D : C
{
public sealed override void DoWork() { }
}
См. также
Руководство по программированию на C#
Классы и структуры
Наследование
Методы
Поля
Определение абстрактных свойств
Статические классы и члены статических классов
(Руководство по программированию в C#)
04.11.2019 • 9 minutes to read • Edit Online
Статический класс в основном такой же, как и нестатический класс, но имеется одно отличие: нельзя
создавать экземпляры статического класса. Другими словами, нельзя использовать оператор new для
создания переменной типа класса. Поскольку нет переменной экземпляра, доступ к членам статического
класса осуществляется с использованием самого имени класса. Например, если есть статический класс,
называемый UtilityClass , имеющий открытый статический метод с именем MethodA , вызов метода
выполняется, как показано в следующем примере:
UtilityClass.MethodA();
Статический класс может использоваться как обычный контейнер для наборов методов, работающих на
входных параметрах, и не должен возвращать или устанавливать каких-либо внутренних полей
экземпляра. Например, в библиотеке классов .NET Framework статический класс System.Math содержит
методы, выполняющие математические операции, без требования сохранять или извлекать данные,
уникальные для конкретного экземпляра класса Math. Это значит, что члены класса применяются путем
задания имени класса и имени метода, как показано в следующем примере.
// Output:
// 3.14
// -4
// 3
Как и в случае со всеми типами классов, сведения о типе для статического класса загружаются средой
.NET Framework CLR, когда загружается программа, которая ссылается на класс. Программа не может
точно указать, когда загружается класс. Однако гарантируется загрузка этого класса, инициализация его
полей и вызов статического конструктора перед первым обращением к классу в программе. Статический
конструктор вызывается только один раз, и статический класс остается в памяти на время существования
домена приложения, в котором находится программа.
NOTE
Создание нестатического класса, который допускает создание только одного экземпляра самого себя, см. в
документе Реализация Singleton в C#.
Пример
Ниже приведен пример статического класса, содержащего два метода, преобразующих температуру по
Цельсию в температуру по Фаренгейту и наоборот.
return fahrenheit;
}
return celsius;
}
}
class TestTemperatureConverter
{
static void Main()
{
Console.WriteLine("Please select the convertor direction");
Console.WriteLine("1. From Celsius to Fahrenheit.");
Console.WriteLine("2. From Fahrenheit to Celsius.");
Console.Write(":");
switch (selection)
{
case "1":
Console.Write("Please enter the Celsius temperature: ");
F = TemperatureConverter.CelsiusToFahrenheit(Console.ReadLine());
Console.WriteLine("Temperature in Fahrenheit: {0:F2}", F);
break;
case "2":
Console.Write("Please enter the Fahrenheit temperature: ");
C = TemperatureConverter.FahrenheitToCelsius(Console.ReadLine());
Console.WriteLine("Temperature in Celsius: {0:F2}", C);
break;
default:
Console.WriteLine("Please select a convertor.");
break;
}
Статический члены
Нестатический класс может содержать статические методы, поля, свойства или события. Статический
член вызывается для класса даже в том случае, если не создан экземпляр класса. Доступ к статическому
члены всегда выполняется по имени класса, а не экземпляра. Существует только одна копия статического
члена, независимо от того, сколько создано экземпляров класса. Статические методы и свойства не могут
обращаться к нестатическим полям и событиям в их содержащем типе, и они не могут обращаться к
переменной экземпляра объекта, если он не передается явно в параметре метода.
Более привычно объявление нестатического класса с несколькими статическими членами, чем
объявление всего класса как статического. Статические поля обычно используются для следующих двух
целей: хранение счетчика числа созданных объектов или хранение значения, которое должно совместно
использоваться всеми экземплярами.
Статические методы могут быть перегружены, но не переопределены, поскольку они относятся к классу,
а не к экземпляру класса.
Несмотря на то, что поле не может быть объявлено как static const , поле const по своему поведению
является статическим. Он относится к типу, а не к экземплярам типа. Поэтому к полям const можно
обращаться с использованием той же нотации ClassName.MemberName , что и для статических полей.
Экземпляр объекта не требуется.
C# не поддерживает статические локальные переменные (переменные, объявленные в области действия
метода).
Для объявления статических методов класса используется ключевое слово static перед возвращаемым
типом члена, как показано в следующем примере:
public class Automobile
{
public static int NumberOfWheels = 4;
public static int SizeOfGasTank
{
get
{
return 15;
}
}
public static void Drive() { }
public static event EventType RunOutOfGas;
Статические члены инициализируются перед первым доступом к статическому члену и перед вызовом
статического конструктора, если таковой имеется. Для доступа к члену статического класса следует
использовать имя класса, а не имя переменной, указывая расположение члена, как показано в
следующем примере:
Automobile.Drive();
int i = Automobile.NumberOfWheels;
Если класс содержит статические поля, должен быть указан статический конструктор, который
инициализирует эти поля при загрузке класса.
Вызов статического метода генерирует инструкцию вызова в промежуточном языке Microsoft (MSIL ), в
то время как вызов метода экземпляра генерирует инструкцию callvirt , которая также проверяет
наличие ссылок на пустые объекты. Однако в большинстве случаев разница в производительности двух
видов вызовов несущественна.
Спецификация языка C#
Дополнительные сведения см. в разделах Статические классы и Члены экземпляра и статические члены в
документации Предварительная спецификация C# 6.0. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
static
Классы
class
Статические конструкторы
Конструкторы экземпляров
Модификаторы доступа (Руководство по
программированию в C#)
23.10.2019 • 8 minutes to read • Edit Online
Не все модификаторы доступа могут использоваться всеми типами или членами типов во всех
контекстах, а в некоторых случаях доступность члена типа ограничивается доступностью типа, в
котором он содержится. Следующие подразделы содержат дополнительные сведения о доступности.
// public class:
public class Tricycle
{
// protected method:
protected void Pedal() { }
// private field:
private int wheels = 3;
NOTE
Защищенный внутренний уровень доступности означает защищенный ИЛИ внутренний доступ, а не
защищенный И внутренний. Другими словами, защищенный внутренний член доступен из любого класса в той
же сборке, включая производные классы. Чтобы сделать его доступным только для производных классов в той
же сборке, объявите сам класс как внутренний, а его члены как защищенные. Кроме того, начиная с C# 7.2,
можно использовать защищенные закрытые модификаторы доступа для достижения такого же результата без
необходимости преобразования содержащего класса во внутренний.
Другие типы
Интерфейсы, объявляемые непосредственно в пространстве имен, могут быть объявлены как открытые
или внутренние. Равно как и в случае с классами и структурами, для интерфейсов по умолчанию
задается внутренний доступ. Члены интерфейса всегда открыты, поскольку интерфейс как раз и создан
для того, чтобы обеспечить доступ к классу или структуре для других типов. Модификаторы доступа к
членам интерфейса не применяются.
Члены перечисления всегда открыты, и модификаторы доступа к ним не применяются.
Делегаты ведут себя как классы и структуры. По умолчанию они имеют внутренний доступ, если
объявляются непосредственно в пространстве имен, и закрытый доступ, если являются вложенными.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Классы и структуры
Интерфейсы
private
public
internal
protected
protected internal
private protected
class
struct
interface
Поля (Руководство по программированию в C#)
25.11.2019 • 6 minutes to read • Edit Online
Поле является переменной любого типа, которая объявлена непосредственно в классе или структуре. Поля
являются членами содержащих их типов.
Класс или структура может иметь поля экземпляра или статические поля. Поля экземпляра относятся только
к экземпляру типа. Если имеется класс T с полем экземпляра F, можно создать два объекта типа T и
изменить значение F в каждом объекте, не влияя на значение в другом объекте. Напротив, статическое
поле принадлежит к самому классу и является общим для всех экземпляров этого класса. Изменения,
выполненные из экземпляра A, будут немедленно доступны экземплярам B и C, если они получат доступ к
полю.
Как правило, следует использовать поля только для переменных, являющихся закрытыми или
защищенными. Данные, которые класс представляет клиентскому коду, должны предоставляться через
методы, свойства и индексаторы. Используя эти конструкции для косвенного доступа к внутренним полям,
можно предотвратить использование недопустимых входных значений. Закрытое поле, которое хранит
данные, представленные открытым свойством, называется резервным хранилищем или резервным полем.
Как правило, поля хранят данные, которые должны быть доступны нескольким методам класса и храниться
дольше, чем время существования любого отдельного метода. Например, класс, представляющий
календарную дату, может иметь три целочисленных поля: одно для месяца, одно для числа и одно для года.
Переменные, не используемые вне области одного метода, должны быть объявлены как локальные
переменные в самом теле метода.
Поля объявляются в блоке класса путем указания уровня доступа поля, за которым следует тип поля, а
затем имя поля. Например:
public class CalendarEntry
{
// private field
private DateTime date;
}
}
Для доступа к полю в объекте добавьте точку после имени объекта, за которой следует имя поля, как в
objectname.fieldname . Например:
Полю можно присвоить начальное значение с помощью оператора присваивания при объявлении поля.
Чтобы автоматически назначить поле day , например полю "Monday" , нужно объявить day , как в
следующем примере:
Поля инициализируются непосредственно перед вызовом конструктора для экземпляра объекта. Если
конструктор присваивает значение поля, он заменит значения, присвоенные при объявлении поля.
Дополнительные сведения см. в разделе Использование конструкторов.
NOTE
Инициализатор поля не может ссылаться на другие поля экземпляров.
Поля могут иметь пометку public, private, protected, internal, protected internal или private protected. Эти
модификаторы доступа определяют, каким образом пользователи класса смогут получать доступ к полю.
Дополнительные сведения см. в статье Модификаторы доступа.
При необходимости можно объявить поле статическим (static). Это делает поле доступным для
вызывающих объектов в любое время, даже если экземпляр класса не существует. Дополнительные
сведения см. в статье Статические классы и члены статических классов.
Поле может быть объявлено доступным только для чтения (readonly). Полю только для чтения можно
присвоить значение только во время инициализации или в конструкторе. Поле static readonly очень
похоже на константу, за исключением того, что компилятор C# не имеет доступа к значению статического
поля только для чтения во время компиляции, но только во время выполнения. Дополнительные сведения
см. в разделе Константы.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Классы и структуры
Использование конструкторов
Наследование
Модификаторы доступа
Абстрактные и запечатанные классы и члены классов
Константы (Руководство по программированию на
C#)
23.10.2019 • 4 minutes to read • Edit Online
Константы — это постоянные значения, которые известны во время компиляции и не изменяются во время
выполнения программы. Константы должны объявляться с модификатором const. Только встроенные типы
C# (за исключением System.Object) можно объявлять как const . Список встроенных типов см. в таблице
встроенных типов. Пользовательские типы, включая классы, структуры и массивы, не могут объявляться как
const . Модификатор readonly позволяет создать класс, структуру или массив, которые инициализируются
один раз (например, в конструкторе), и впоследствии изменить их нельзя.
C# не поддерживает методы, свойства или события const .
Тип перечисления позволяет определять именованные константы для целочисленных встроенных типов
(например int , uint , long и т. д.). Дополнительные сведения см. в разделе Перечисление.
Константы должны инициализироваться сразу после объявления. Например:
class Calendar1
{
public const int Months = 12;
}
В этом примере константа months всегда имеет значение 12, и его не может изменить даже сам класс. На
самом деле в случае, если компилятор встречает идентификатор константы в исходном коде C# (например,
months ), он подставляет значение литерала непосредственно в создаваемый им промежуточный язык ( IL ).
Поскольку с константой в среде выполнения не связан адрес ни одной переменной, поля const не могут
передаваться по ссылке и отображаться в выражении как левостороннее значение.
NOTE
Будьте внимательны, ссылаясь на постоянные значения, определенные в другом коде, например, в DLL. Если в новой
версии DLL для константы определяется новое значение, старое значение литерала хранится в вашей программе
вплоть до повторной компиляции для новой версии.
class Calendar2
{
public const int Months = 12, Weeks = 52, Days = 365;
}
Выражение, которое используется для инициализации константы, может ссылаться на другую константу,
если не создает циклическую ссылку. Например:
class Calendar3
{
public const int Months = 12;
public const int Weeks = 52;
public const int Days = 365;
Константы могут иметь пометку public, private, protected, internal, protected internal или private protected. Эти
модификаторы доступа определяют, каким образом пользователи класса смогут получать доступ к
константе. Дополнительные сведения см. в разделе Модификаторы доступа.
Доступ к константам осуществляется так, как если бы они были статическими полями, поскольку значение
константы одинаково для всех экземпляров типа. Для их объявления используйте ключевое слово static .
Выражения, которые не относятся к классу, определяющему константу, должны включать имя класса,
период и имя константы для доступа к этой константе. Например:
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Классы и структуры
Свойства
Типы
readonly
Immutability in C# Part One: Kinds of Immutability (Неизменность в C#, часть 1. Виды неизменности)
Практическое руководство. Определение
абстрактных свойств (руководство по
программированию на C#)
25.11.2019 • 3 minutes to read • Edit Online
В следующем примере показано, как определять абстрактные свойства. В объявлении абстрактного свойства
не предоставляется реализация методов доступа к свойству. В нем объявляется, что класс поддерживает
свойства, однако реализация методов доступа к ним передается в производные классы. В следующем
примере показано, как реализовать абстрактные свойства, наследуемые от базового класса.
Этот пример включает три файла, каждый из которых компилируется отдельно в сборку, на которую
задаются ссылки при последующей компиляции:
abstractshape.cs: класс Shape , который содержит абстрактное свойство Area .
shapes.cs: подклассы класса Shape .
shapetest.cs: тестовая программа для отображения областей некоторых объектов, производных от
Shape .
Пример
В этом файле объявляется класс Shape , который содержит свойство Area типа double .
// compile with: csc -target:library abstractshape.cs
public abstract class Shape
{
private string name;
public Shape(string s)
{
// calling the set accessor of the Id property.
Id = s;
}
public string Id
{
get
{
return name;
}
set
{
name = value;
}
}
При объявлении абстрактного свойства, такого как Area в этом примере, вы просто указываете
используемые методы доступа, но не реализуете их. В этом примере используется только метод
доступа get, поэтому свойство будет доступно только для чтения.
Пример
В следующем коде представлены три подкласса класса Shape и демонстрируется, как они переопределяют
свойство Area для получения его собственной реализации.
// compile with: csc -target:library -reference:abstractshape.dll shapes.cs
public class Square : Shape
{
private int side;
Пример
В следующем коде показана тестовая программа, которая создает несколько производных от Shape
объектов и печатает их области.
System.Console.WriteLine("Shapes Collection");
foreach (Shape s in shapes)
{
System.Console.WriteLine(s);
}
}
}
/* Output:
Shapes Collection
Square #1 Area = 25.00
Circle #1 Area = 28.27
Rectangle #1 Area = 20.00
*/
См. также
Руководство по программированию на C#
Классы и структуры
Абстрактные и запечатанные классы и члены классов
Свойства
Определение констант в C#
25.11.2019 • 2 minutes to read • Edit Online
NOTE
В C# с помощью директивы препроцессора #define нельзя определять константы так, как это было реализовано в C и
C++.
Чтобы определить значения константы целочисленного типа ( int , byte и т. д.), используйте
перечисляемый тип. Дополнительные сведения см. в разделе Перечисление.
Чтобы определить нецелочисленные константы, можно сгруппировать их в статический класс с именем
Constants . В этом случае перед любыми ссылками на константы будет необходимо указывать имя класса,
как показано в следующем примере.
Пример
static class Constants
{
public const double Pi = 3.14159;
public const int SpeedOfLight = 300000; // km per sec.
}
class Program
{
static void Main()
{
double radius = 5.3;
double area = Constants.Pi * (radius * radius);
int secsFromSun = 149476000 / Constants.SpeedOfLight; // in km
}
}
Используя квалификатор имени класса, вы гарантируете, что вы сами и другие разработчики будете
понимать, что имеете дело с константой, которую нельзя изменить.
См. также
Классы и структуры
Свойства (Руководство по программированию
в C#)
04.11.2019 • 8 minutes to read • Edit Online
Свойство — это член, предоставляющий гибкий механизм для чтения, записи или вычисления
значения частного поля. Свойства можно использовать, как если бы они были членами общих
данных, но фактически они представляют собой специальные методы, называемые методами
доступа. Это позволяет легко получать доступ к данным и помогает повысить безопасность и
гибкость методов.
class TimePeriod
{
private double _seconds;
class Program
{
static void Main()
{
TimePeriod t = new TimePeriod();
// The property assignment causes the 'set' accessor to be called.
t.Hours = 24;
Начиная с C# 7.0 методы доступа get и set можно реализовывать в виде членов, воплощающих
выражения. В этом случае необходимо указывать ключевые слова get и set . В следующем
примере показано использование определений текста выражений для обоих методов доступа.
Обратите внимание, что ключевое слово return не используется с методом доступа get .
using System;
class Program
{
static void Main(string[] args)
{
var item = new SaleItem("Shoes", 19.95m);
Console.WriteLine($"{item.Name}: sells for {item.Price:C2}");
}
}
// The example displays output like the following:
// Shoes: sells for $19.95
class Program
{
static void Main(string[] args)
{
var item = new SaleItem{ Name = "Shoes", Price = 19.95m };
Console.WriteLine($"{item.Name}: sells for {item.Price:C2}");
}
}
// The example displays output like the following:
// Shoes: sells for $19.95
Связанные разделы
Использование свойств
Свойства интерфейса
Сравнение свойств и индексаторов
Ограничение доступности методов доступа
Автоматически реализуемые свойства
Спецификация языка C#
Дополнительные сведения см. в разделе Свойства в Спецификации языка C#. Спецификация языка
является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Использование свойств
Индексаторы
Ключевое слово get
Ключевое слово set
Использование свойств (Руководство по
программированию в C#)
24.10.2019 • 13 minutes to read • Edit Online
Свойства сочетают в себе возможности полей и методов. Пользователю объекта свойство представляется
как поле, и для доступа к нему применяется тот же синтаксис. При реализации класса свойство
представляется в виде одного или двух блоков кода для методов доступа get и (или) set. Блок кода для
метода доступа get выполняется только при считывании свойства, а для метода set — при присвоении
свойству нового значения. Свойство без метода доступа set доступно только для чтения. Свойство без
метода доступа get доступно только для записи. Свойство, для которого определены оба этих метода,
доступно для чтения и записи.
В отличие от полей, свойства не классифицируются как переменные. Соответственно, нельзя передать
свойство в качестве параметра ref или out.
Свойства нашли широкое применение в программировании. Их можно использовать для проверки данных
перед подтверждением изменения; для прозрачного предоставления доступа к данным класса, которые
фактически извлекаются из других источников, например из базы данных; для выполнения действия при
изменении данных (например, в этом случае может создаваться событие или изменяться значение других
полей).
При объявлении свойств в блоке класса указывается уровень доступа поля, затем тип и имя свойства, а
после этого блок кода, в котором объявляются методы доступа get и (или) set . Например:
В этом примере Month объявляется как свойство, а метод доступа set обеспечивает установку значения
Month в диапазоне от 1 до 12. Для отслеживания фактического значения свойство Month использует
частное поле. Фактическое местоположение данных свойства часто называется "резервным хранилищем".
Как правило, в качестве резервного хранилища свойства используют частные поля. Поле помечается как
частное для того, чтобы гарантировать возможность его изменения только посредством вызова свойства.
Дополнительные сведения об ограничениях открытого и закрытого доступа см. в разделе Модификаторы
доступа.
Автоматически реализуемые свойства поддерживают упрощенный синтаксис для простых объявлений
свойств. Дополнительные сведения см. в разделе Автоматически реализуемые свойства.
Метод доступа get
Тело метода доступа get похоже на тело метода. Оно должно возвращать значение заданного типа
свойства. Выполнение метода доступа get эквивалентно считыванию значения поля. Например, если
включена оптимизация и метод доступа get возвращает частную переменную, вызов метода доступа get
определяется компилятором как встроенный, что позволяет исключить затраты ресурсов на вызов метода.
Тем не менее виртуальный метод доступа get не может определяться как встроенный, поскольку во время
компиляции компилятору не известно, как метод может быть фактически вызван во время выполнения.
Ниже показан метод доступа get , возвращающий значение частного поля _name :
class Person
{
private string _name; // the name field
public string Name => _name; // the Name property
}
При ссылке на свойство (кроме случаев, когда свойство является целью присваивания) вызывается метод
доступа get , который считывает значение свойства. Например:
Метод доступа get должен завершаться инструкцией return или throw, при этом управление не может
передаваться из тела метода доступа.
Изменение состояния объекта с помощью метода доступа get считается ошибочным стилем
программирования. Например, побочным эффектом следующего метода доступа является изменение
состояния объекта каждый раз при доступе к полю _number .
Метод доступа get можно использовать для возврата значения поля напрямую или после вычисления.
Например:
class Employee
{
private string _name;
public string Name => _name != null ? _name : "NA";
}
Если в предыдущем фрагменте кода свойству Name не присвоено значение, будет возвращено значение NA
.
При присвоении значения свойству вызывается метод доступа set с аргументом, содержащим новое
значение. Например:
Использование имени явного параметра ( value ) для объявления локальной переменной в методе доступа
set является ошибкой.
Примечания
Свойства могут быть помечены как public , private , protected , internal , protected internal или
private protected . Эти модификаторы доступа определяют, каким образом пользователи класса смогут
получать доступ к свойству. Методы доступа get и set для одного свойства могут иметь разные
модификаторы доступа. Например, метод доступа get может иметь модификатор public , разрешающий
доступ из-за пределов типа только для чтения, а метод доступа set — модификатор private или
protected . Дополнительные сведения см. в статье Модификаторы доступа.
Свойство может быть объявлено как статическое с помощью ключевого слова static . Это делает свойство
доступным для вызывающих объектов в любое время, даже если экземпляр класса не существует.
Дополнительные сведения см. в статье Статические классы и члены статических классов.
Свойство может быть помечено как виртуальное с помощью ключевого слова virtual. Это позволяет
производным классам переопределять поведение свойства с помощью ключевого слова override.
Дополнительные сведения об этих параметрах см. в разделе Наследование.
Свойство, переопределяющее виртуальное свойство, также может быть запечатанным (sealed). Это
указывает, что для производных классов оно больше не является виртуальным. Наконец, свойство можно
объявить абстрактным (abstract). Это означает, что в классе не будет определена реализация такого
свойства, и в производных классах должны использоваться собственные реализации. Дополнительные
сведения об этих параметрах см. в разделе Абстрактные и запечатанные классы и члены классов.
NOTE
Использование модификаторов virtual, abstract или override в методе доступа статического (static) свойства является
ошибкой.
Пример
В этом примере демонстрируются свойства экземпляра, а также статические и доступные только для чтения
свойства. Этот метод принимает введенное с клавиатуры имя сотрудника, увеличивает значение
NumberOfEmployees на 1, после чего отображает имя и номер сотрудника.
public class Employee
{
public static int NumberOfEmployees;
private static int _counter;
private string _name;
// A Constructor:
public Employee() => _counter = ++NumberOfEmployees; // Calculate the employee's number:
}
class TestEmployee
{
static void Main()
{
Employee.NumberOfEmployees = 107;
Employee e1 = new Employee();
e1.Name = "Claude Vige";
Пример
В этом примере демонстрируется доступ к свойству базового класса, которое скрыто в производном классе
другим свойством с таким же именем:
public class Employee
{
private string _name;
public string Name
{
get => _name;
set => _name = value;
}
}
class TestHiding
{
static void Main()
{
Manager m1 = new Manager();
((Employee)m1).Name = "Mary";
Пример
В этом примере два класса ( Cube и Square ) реализуют абстрактный класс Shape и переопределяют его
абстрактное свойство Area . Обратите внимание на использование модификатора override в свойствах.
Программа принимает введенную длину стороны, на основании которой рассчитывает площади квадрата и
куба. Также принимается введенное значение площади, на основании которой рассчитываются длины
сторон квадрата и куба.
//constructor
public Square(double s) => side = s;
//constructor
public Cube(double s) => side = s;
class TestShapes
{
static void Main()
{
// Input the side:
System.Console.Write("Enter the side: ");
double side = double.Parse(System.Console.ReadLine());
См. также
Руководство по программированию на C#
Свойства
Свойства интерфейса
Автоматически реализуемые свойства
Свойства интерфейса (Руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Свойства можно объявлять для интерфейса. Ниже показан пример метода доступа к свойству интерфейса:
Метод доступа свойства интерфейса не имеет тела. Таким образом, методы доступа нужны, чтобы указать,
доступно ли свойство для чтения и записи, только для чтения или только для записи.
Пример
В этом примере интерфейс IEmployee содержит доступное для чтения и записи свойство Name , а также
свойство Counter , предназначенное только для чтения. Класс Employee реализует интерфейс IEmployee и
использует эти два свойства. Программа считывает имя нового сотрудника и текущее число сотрудников,
после чего отображает имя сотрудника и его рассчитанный номер.
Вы можете использовать полное имя свойства, которое ссылается на интерфейс, где был объявлен член.
Например:
string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}
Это называется явной реализацией интерфейса. Например, если класс Employee реализует два интерфейса (
ICitizen и IEmployee ), оба из которых содержат свойство Name , потребуется явная реализация члена
интерфейса. Это значит, что потребуется следующее объявление свойства:
string IEmployee.Name
{
get { return "Employee Name"; }
set { }
}
interface IEmployee
{
string Name
{
get;
set;
}
int Counter
{
get;
}
}
// constructor
public Employee() => _counter = ++numberOfEmployees;
}
class TestEmployee
{
static void Main()
{
System.Console.Write("Enter number of employees: ");
Employee.numberOfEmployees = int.Parse(System.Console.ReadLine());
См. также
Руководство по программированию на C#
Свойства
Использование свойств
Сравнение свойств и индексаторов
Индексаторы
Интерфейсы
Ограничение доступности методов доступа
(Руководство по программированию на C#)
23.10.2019 • 6 minutes to read • Edit Online
Выражения get и set свойства или индексатора называются методами доступа. По умолчанию они имеют
такие же уровни видимости или доступа, что и свойство или индексатор, которым они принадлежат.
Дополнительные сведения см. в разделе Уровни доступа. Тем не менее в некоторых случаях рекомендуется
ограничить уровни доступа для этих методов. Как правило, в этом случае ограничивается уровень доступа
для метода set , тогда как метод get остается общедоступным. Например:
В этом примере свойство Name определяет методы доступа get и set . Метод доступа get получает
уровень доступа самого свойства (в этом случае public ), а к методу set явным образом применяется
модификатор доступа protected.
Реализация интерфейсов
Методы доступа, которые используются для реализации интерфейса, не могут иметь модификаторы
доступа. Тем не менее при реализации интерфейса с использованием одного метода доступа (например,
get ) другой метод доступа может иметь модификатор доступа, как показано в следующем примере:
public string Id
{
get { return _id; }
set { }
}
}
b1.Name = "Mary";
d1.Name = "John";
b1.Id = "Mary123";
d1.Id = "John123"; // The BaseClass.Id property is called.
Комментарии
Обратите внимание, что если заменить объявление new private string Id на new public string Id , будут
получены следующие выходные данные:
Name and ID in the base class: Name-BaseClass, ID-BaseClass
См. также
Руководство по программированию на C#
Свойства
Индексаторы
Модификаторы доступа
Практическое руководство. Объявление и
использование свойств чтения и записи
(руководство по программированию на C#)
25.11.2019 • 3 minutes to read • Edit Online
Свойства имеют все преимущества открытых членов данных, но не связаны с рисками незащищенного,
неконтролируемого и несанкционированного доступа к данным объекта. Это достигается благодаря
применению методов доступа, которые представляют собой особые методы для присвоения и извлечения
значений базового члена данных. Метод доступа set присваивает значения членам данных, а метод доступа
get извлекает их.
Это можно продемонстрировать на примере класса Person , который содержит два свойства: Name (string) и
Age (int). Для обоих свойств реализованы методы доступа get и set , благодаря чему они доступны и для
чтения, и для записи.
Пример
class Person
{
private string _name = "N/A";
private int _age = 0;
set
{
_age = value;
}
}
class TestPerson
{
{
static void Main()
{
// Create a new Person object:
Person person = new Person();
// Print out the name and the age associated with the person:
Console.WriteLine("Person details - {0}", person);
Отказоустойчивость
В предыдущем примере свойства Name и Age являются открытыми и содержат одновременно методы
доступа get и set . Благодаря этому объект может считывать эти свойства и записывать их. Тем не менее в
некоторых случаях необходимо исключить один из методов доступа. Например, свойство без метода
доступа set будет доступно только для чтения:
Кроме того, можно сделать один метод доступа открытым, а другой оставить частным или защищенным.
Дополнительные сведения см. в разделе Асимметричные методы доступа.
После объявления свойств их можно использовать так же, как поля класса. Это позволяет получить
естественный синтаксис извлечения и установки значения свойства, как показано на примере следующих
операторов:
person.Name = "Joe";
person.Age = 99;
Обратите внимание, что в методе set свойства доступна специальная переменная value . Она содержит
значение, заданное пользователем, например:
_name = value;
Обратите внимание на простой синтаксис приращения свойства Age для объекта Person :
person.Age += 1;
Если для моделирования свойств использовать отдельные методы set и get , аналогичный код может
иметь следующий вид:
person.SetAge(person.GetAge() + 1);
Обратите внимание, что метод ToString не используется в этой программе явно. Он вызывается по
умолчанию при вызове WriteLine .
См. также
Руководство по программированию на C#
Свойства
Классы и структуры
Автоматически реализуемые свойства
(Руководство по программированию на C#)
25.11.2019 • 2 minutes to read • Edit Online
В C# 3.0 и более поздних версиях автоматически реализуемые свойства делают объявление свойств
более лаконичным, когда в методах доступа к свойствам не требуется дополнительная логика. Они
также позволяют клиентскому коду создавать объекты. При объявлении свойства, как показано в
следующем примере, компилятор создает закрытое анонимное резервное поле, которое может быть
доступно только через методы доступа get и set свойства.
Пример
В следующем примере показан простой класс, имеющий несколько автоматически реализуемых
свойств.
// Constructor
public Customer(double purchases, string name, int ID)
{
TotalPurchases = purchases;
Name = name;
CustomerID = ID;
}
// Methods
public string GetContactInfo() { return "ContactInfo"; }
public string GetTransactionHistory() { return "History"; }
class Program
{
static void Main()
{
// Intialize a new object.
Customer cust1 = new Customer(4987.63, "Northwind", 90108);
// Modify a property.
cust1.TotalPurchases += 499.99;
}
}
См. также
Свойства
Модификаторы
Практическое руководство. Реализация
облегченного класса с автоматически
реализуемыми свойствами (руководство по
программированию на C#)
25.11.2019 • 3 minutes to read • Edit Online
В этом примере показано, как создать неизменяемый упрощенный класс, служащий исключительно для
инкапсуляции набора автоматически реализуемых свойств. Используйте такую конструкцию вместо
структуры, когда требуется использовать семантику ссылочного типа.
Неизменяемое свойство можно создать двумя способами.
Можно объявить метод доступа set как private. Свойство можно задать только в типе, но оно является
неизменяемым для потребителей.
При объявлении закрытого метода доступа set для инициализации свойства нельзя использовать
инициализатор объекта. Необходимо использовать конструктор или фабричный метод.
Вы можете объявить только метод доступа get, который делает свойство неизменяемым везде, кроме
конструктора типа.
Пример
В следующем примере показаны два способа реализации неизменяемого класса с автоматически
реализуемыми свойствами. Каждый способ объявляет одно из свойств с закрытым методом доступа set и
одно из свойств только с методом доступа get . Первый класс использует конструктор только для
инициализации свойства, а второй класс использует статический фабричный метод, вызывающий
конструктор.
// Public constructor.
public Contact(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}
}
// Private constructor.
private Contact2(string contactName, string contactAddress)
{
Name = contactName;
Address = contactAddress;
}
/* Output:
Terry Adams, 123 Main St.
Fadi Fakhouri, 345 Cypress Ave.
Hanying Feng, 678 1st Ave
Cesar Garcia, 12 108th St.
Debra Garcia, 89 E. 42nd St.
*/
Компилятор создает резервные поля для каждого автоматически реализуемого свойства. Эти поля
недоступны непосредственно из исходного кода.
См. также
Свойства
struct
Инициализаторы объектов и коллекций
Методы (Руководство по программированию
на C#)
23.10.2019 • 16 minutes to read • Edit Online
Метод — это блок кода, содержащий ряд инструкций. Программа инициирует выполнение
инструкций, вызывая метод и указывая все аргументы, необходимые для этого метода. В C# все
инструкции выполняются в контексте метода. Метод Main является точкой входа для каждого
приложения C# и вызывается общеязыковой средой выполнения (CLR ) при запуске программы.
NOTE
В этой статье рассматриваются названные методы. Сведения об анонимных функциях см. в разделе
Анонимные функции.
Сигнатуры методов
Методы объявляются в классе или в структуре путем указания уровня доступа, такого как public или
private , необязательных модификаторов, таких как abstract или sealed , возвращаемого значения,
имени метода и всех параметров этого метода. Все эти части вместе представляют собой сигнатуру
метода.
NOTE
Тип возврата метода не является частью сигнатуры метода в целях перегрузки метода. Однако он является
частью сигнатуры метода при определении совместимости между делегатом и методом, на который он
указывает.
Параметры метода заключаются в скобки и разделяются запятыми. Пустые скобки указывают, что
параметры методу не требуются. Этот класс содержит четыре метода:
Доступ к методу
Вызов метода в объекте аналогичен доступу к полю. После имени объекта добавьте точку, имя
метода и круглые скобки. Аргументы перечисляются в этих скобках и разделяются запятыми. Таким
образом, методы класса Motorcycle могут вызываться, как показано в следующем примере:
class TestMotorcycle : Motorcycle
{
moto.StartEngine();
moto.AddGas(15);
moto.Drive(5, 20);
double speed = moto.GetTopSpeed();
Console.WriteLine("My top speed is {0}", speed);
}
}
int Square(int i)
{
// Store input argument in a local variable.
int input = i;
return input * input;
}
Теперь, если передать объект, основанный на этом типе, в метод, то будет передана ссылка на объект.
В следующем примере объект типа SampleRefType передается в метод ModifyObject :
В этом примере, в сущности, делается то же, что и в предыдущем примере, — аргумент по значению
передается в метод. Но поскольку здесь используется ссылочный тип, результат будет другим. В
данном случае в методе ModifyObject изменено поле value параметра obj , а также изменено поле
value аргумента, rt в методе TestRefType . В качестве выходных данных метод TestRefType
отображает 33.
Дополнительные сведения о передаче ссылочных типов по ссылке и по значению см. в разделах
Передача параметров ссылочного типа и Ссылочные типы.
Возвращаемые значения
Методы могут возвращать значение вызывающему объекту. Если тип возврата, указываемый перед
именем метода, не void , этот метод может возвращать значение с помощью ключевого слова
return . Инструкция с ключевым словом return , за которым следует значение, соответствующее
типу возврата, будет возвращать это значение объекту, вызвавшему метод.
Значение может возвращаться вызывающему объекту по значению или, начиная с C# версии 7.0, по
ссылке. Значения возвращаются вызывающему объекту по ссылке, если ключевое слово ref
используется в сигнатуре метода и указывается после каждого ключевого слова return . Например,
следующая сигнатура метода и оператор return указывают, что метод возвращает имена переменных
estDistance вызывающему объекту по ссылке.
Ключевое слове return также останавливает выполнение метода. Если тип возврата — void ,
инструкцию return без значения по-прежнему можно использовать для завершения выполнения
метода. Без ключевого слова return этот метод будет останавливать выполнение при достижении
конца блока кода. Методы с типом возврата, отличным от void, должны использовать ключевое
слово return для возврата значения. Например, в следующих двух методах ключевое слово return
используется для возврата целочисленных значений.
class SimpleMath
{
public int AddTwoNumbers(int number1, int number2)
{
return number1 + number2;
}
Чтобы использовать значение, возвращаемое из метода, вызывающий метод может применять сам
вызов метода везде, где будет достаточно значения того же типа. Можно также назначить
возвращаемое значение переменной. Например, следующие два примера кода достигают одной и
той же цели.
Использование локальной переменной, в данном случае result , для сохранения значения является
необязательным. Это может улучшить читаемость кода или может оказаться необходимым, если
нужно сохранить исходное значение аргумента для всей области метода.
Чтобы использовать значение, возвращаемое по ссылке из метода, необходимо объявить локальную
ссылочную переменную, если планируется изменение значения. Например, если метод
Planet.GetEstimatedDistance возвращает значение Double по ссылке, можно определить его как
локальную ссылочную переменную с использованием кода следующего вида:
Асинхронные методы
С помощью функции async можно вызывать асинхронные методы, не прибегая к использованию
явных обратных вызовов или ручному разделению кода между несколькими методами или лямбда-
выражениями.
Если пометить метод с помощью модификатора async , можно использовать в этом методе
инструкцию await . Когда управление достигает выражения await в асинхронном методе, управление
возвращается вызывающему объекту и выполнение метода приостанавливается до завершения
выполнения ожидающей задачи. После завершения задачи можно возобновить выполнение в
методе.
NOTE
Асинхронный метод возвращается в вызывающий объект, когда он встречает первый ожидаемый объект,
выполнение которого еще не завершено, или когда выполнение асинхронного метода доходит до конца — в
зависимости от того, что происходит раньше.
Асинхронный метод может иметь тип возврата Task<TResult>, Taskили void. Тип возврата void в
основном используется для определения обработчиков событий, где требуется тип возврата void.
Асинхронный метод, который возвращает тип void, не может быть ожидающим. Вызывающий объект
метода, возвращающего значение типа void, не может перехватывать исключения, которые выдает
этот метод.
В следующем примере DelayAsync является асинхронным методом с типом возврата Task<TResult>.
DelayAsync имеет инструкцию return , которая возвращает целое число. Поэтому объявление
метода DelayAsync должно иметь тип возврата Task<int> . Поскольку тип возврата — Task<int> ,
вычисление выражения await в DoSomethingAsync создает целое число, как показывает следующая
инструкция: int result = await delayTask .
В следующем примере метод startButton_Click служит примером асинхронного метода с типом
возврата void. Поскольку DoSomethingAsync является асинхронным методом, задача для вызова
DoSomethingAsync должна быть ожидаемой, как показывает следующая инструкция:
await DoSomethingAsync(); . Метод startButton_Click должен быть определен с модификатором async
, так как этот метод имеет выражение await .
// using System.Diagnostics;
// using System.Threading.Tasks;
// Output:
// Result: 5
Асинхронный метод не может объявить все параметры ref или out , но может вызывать методы,
которые имеют такие параметры.
Дополнительные сведения об асинхронных методах см. в разделах Асинхронное программирование с
использованием ключевых слов async и await, Поток управления в асинхронных программах и
Асинхронные типы возврата.
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
метода доступа.
Iterators
Итератор выполняет настраиваемую итерацию по коллекции, например по списку или массиву.
Итератор использует инструкцию yield return для возврата всех элементов по одному. Когда
достигается инструкция yield return , текущее расположение в коде запоминается. При следующем
вызове итератора выполнение возобновляется с этого места.
Итератор вызывается из клиентского кода с помощью инструкции foreach .
Тип возврата итератора может быть IEnumerable, IEnumerable<T>, IEnumeratorили IEnumerator<T>.
Дополнительные сведения см. в разделе Итераторы.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Классы и структуры
Модификаторы доступа
Статические классы и члены статических классов
Наследование
Абстрактные и запечатанные классы и члены классов
params
return
out
ref
Передача параметров
Локальные функции (руководство по
программированию на C#)
25.11.2019 • 8 minutes to read • Edit Online
Начиная с версии 7.0 в языке C# поддерживаются локальные функции. Локальные функции представляют
собой частные методы типа, вложенные в другой элемент. Они могут вызываться только из того элемента, в
который вложены. Ниже перечислены элементы, в которых можно объявлять и из которых можно вызывать
локальные функции:
Методы, в частности методы итератора и асинхронные методы
Конструкторы
Методы доступа свойств
Методы доступа событий
Анонимные методы
Лямбда-выражения
Методы завершения
Другие локальные функции
Тем не менее локальные функции нельзя объявлять внутри элемента, воплощающего выражение.
NOTE
В некоторых случаях для реализации возможностей, поддерживаемых локальными функциями, также можно
использовать лямбда-выражения. Дополнительные сведения см. в разделе Сравнение локальных функций и лямбда-
выражений.
Применение локальных функций позволяет сделать предназначение кода более понятным. Другие
пользователи, читающие ваш код, смогут видеть, что соответствующий метод вызывается только внутри того
метода, в который он вложен. В случае с командными проектами это также гарантирует, что другой
разработчик не сможет ошибочно вызвать метод напрямую из любого другого места в классе или структуре.
Кроме того, к локальной функции, а также ее параметрам и параметрам типа, нельзя применять атрибуты.
В следующем примере определяется локальная функция AppendPathSeparator , которая является частной для
метода GetText :
using System;
using System.IO;
class Example
{
static void Main()
{
string contents = GetText(@"C:\temp", "example.txt");
Console.WriteLine("Contents of the file:\n" + contents);
}
return filepath;
}
}
}
class Example
{
static void Main()
{
IEnumerable<int> ienum = OddSequence(50, 110);
Console.WriteLine("Retrieved enumerator...");
Вместо этого исключение может быть вызвано при выполнении проверки до того, как будет извлечен
итератор, путем возврата итератора из локальной функции, как показано в следующем примере.
using System;
using System.Collections.Generic;
class Example
{
static void Main()
{
IEnumerable<int> ienum = OddSequence(50, 110);
Console.WriteLine("Retrieved enumerator...");
return GetOddSequenceEnumerator();
IEnumerable<int> GetOddSequenceEnumerator()
{
for (int i = start; i <= end; i++)
{
if (i % 2 == 1)
yield return i;
}
}
}
}
// The example displays the following output:
// Unhandled Exception: System.ArgumentOutOfRangeException: Specified argument was out of the range of valid
values.
// Parameter name: end must be less than or equal to 100.
// at Sequence.<GetNumericRange>d__1.MoveNext() in Program.cs:line 23
// at Example.Main() in Program.cs:line 43
Локальные функции можно использовать аналогичным образом для обработки исключений вне
асинхронной операции. Как правило, при возникновении исключения в асинхронном методе требуется
проверить внутренние исключения в AggregateException. Локальная функция реализует моментальный сбой
кода, синхронно обеспечивая вызов исключения и наблюдение за ним.
В следующем примере асинхронный метод GetMultipleAsync выполняет приостановку на указанное число
секунд, возвращая значение, представляющее собой произведение случайного множителя на это число
секунд. Максимальная задержка составляет 5 с. Результат ArgumentOutOfRangeException возвращается в
том случае, если значение больше 5. Как видно из следующего примера, исключение, которое возникает при
передаче в метод GetMultipleAsync значения 6, инкапсулируется в AggregateException после того, как
начинается выполнение метода GetMultipleAsync .
using System;
using System.Threading.Tasks;
class Example
{
static void Main()
{
int result = GetMultipleAsync(6).Result;
Console.WriteLine($"The returned value is {result:N0}");
}
Как и в случае с итератором метода, можно выполнить рефакторинг кода из этого примера таким образом,
чтобы реализовать проверку перед вызовом асинхронного метода. Как видно из результатов следующего
примера, ArgumentOutOfRangeException не инкапсулируется в AggregateException.
using System;
using System.Threading.Tasks;
class Example
{
static void Main()
{
int result = GetMultiple(6).Result;
Console.WriteLine($"The returned value is {result:N0}");
}
return GetValueAsync();
См. также
Методы
Возвращаемые ссылочные значения и ссылочные
локальные переменные
11.11.2019 • 11 minutes to read • Edit Online
Начиная с версии 7.0 язык C# поддерживает значения, возвращаемые по ссылке (возвращаемые ссылочные
значения). Возвращаемое ссылочное значение позволяет методу вернуть вызывающей стороне ссылку на
переменную, а не фиксированное значение. После этого вызывающий может самостоятельно решить, как
обрабатывать полученную переменную: по значению или по ссылке. Вызывающий может создать новую
переменную и присвоить ей ссылку на полученное значение (локальная ссылочная переменная).
При назначении по значению считывается значение переменной и присваивается это значение новой
переменной:
Ключевое слово ref используется перед объявлением локальной переменной и перед значением во
втором примере. Если не указать ключевые слова ref одновременно в объявлении и присвоении
переменной в обоих примерах, возникает ошибка компилятора CS8172 "Невозможно инициализировать
значением переменную по ссылке".
До версии C# 7.3 ссылочные локальные переменные нельзя было переназначить после инициализации так,
чтобы они ссылались на другое хранилище. Это ограничение было снято. В следующем примере
демонстрируется переназначение:
using System;
class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };
Без использования возвращаемых ссылочных значений такая операция выполняется путем возврата
индекса элемента массива вместе с его значением. После этого вызывающий объект использует индекс для
изменения значения в отдельном вызове метода. Тем не менее вызывающий объект также может изменить
индекс для доступа к другим значениям массива и их изменения.
В следующем примере показано, как можно изменить метод FindNumber в версии C# 7.3 или более
поздней, чтобы использовать переназначение ссылочной локальной переменной:
using System;
class NumberStore
{
int[] numbers = { 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023 };
Вторая версия более эффективна в случаях, когда искомое число находится ближе к концу большого
массива.
См. также
Ключевое слово ref
Написание безопасного и эффективного кода
Передача параметров (Руководство по
программированию в C#)
04.11.2019 • 2 minutes to read • Edit Online
В C# аргументы могут передаваться параметрам либо по значению, либо по ссылке. Передача по ссылке
позволяет изменять и сохранять измененные значения параметров членов функций, методов, свойств,
индексаторов, операторов и конструкторов в вызывающей среде. Чтобы передать параметр по ссылке,
намереваясь изменить значение, используйте ключевое слово ref или out . Чтобы передать по ссылке,
намереваясь предотвратить копирование, но не изменение значения, используйте модификатор in . Для
простоты в этих примерах используется только ключевое слово ref . Дополнительные сведения о различиях
между ключевыми словами in , ref и out см. в разделах in, ref, out.
В приведенном ниже примере показано различие между параметрами-значениями и ссылочными
параметрами.
class Program
{
static void Main(string[] args)
{
int arg;
// Passing by value.
// The value of arg in Main is not changed.
arg = 4;
squareVal(arg);
Console.WriteLine(arg);
// Output: 4
// Passing by reference.
// The value of arg in Main is changed.
arg = 4;
squareRef(ref arg);
Console.WriteLine(arg);
// Output: 16
}
// Passing by reference
static void squareRef(ref int refParameter)
{
refParameter *= refParameter;
}
}
Спецификация языка C#
Дополнительные сведения см. в разделе Список аргументов в Спецификации языка C#. Спецификация
языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Методы
Передача параметров типа значения (Руководство
по программированию в C#)
23.10.2019 • 5 minutes to read • Edit Online
Переменная типа значения напрямую содержит данные, в отличие от переменной ссылочного типа, которая
содержит только ссылку на данные. Передавая в метод переменную типа значения, вы передаете ему
копию этой переменной. Любые изменения параметра, которые происходят внутри метода, не влияют на
исходные данные, хранимые в переменной. Если вы хотите, чтобы вызываемый метод изменял значение
аргумента, его необходимо передать по ссылке с помощью ключевых слов ref или out. Можно также
использовать ключевое слово in для передачи параметра значения по ссылке, чтобы избежать копирования
и гарантировать неизменность значения. В следующих примерах мы для простоты используем ref .
class PassingValByVal
{
static void SquareIt(int x)
// The parameter x is passed by value.
// Changes to x will not affect the original value of x.
{
x *= x;
System.Console.WriteLine("The value inside the method: {0}", x);
}
static void Main()
{
int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);
Переменная n имеет тип значения. Она содержит данные, в нашем примере это значение 5 . Когда
вызывается SquareIt , содержимое n копируется в параметр x , который используется исключительно
внутри метода. Но в Main значение n всегда останется прежним после выполнения метода SquareIt .
Изменения, выполненные внутри метода, влияют только на локальную переменную x .
class PassingValByRef
{
static void SquareIt(ref int x)
// The parameter x is passed by reference.
// Changes to x will affect the original value of x.
{
x *= x;
System.Console.WriteLine("The value inside the method: {0}", x);
}
static void Main()
{
int n = 5;
System.Console.WriteLine("The value before calling the method: {0}", n);
В этом примере передается не значение n , а ссылка на n . Теперь параметр x будет не значением типа int,
а ссылкой на int . В нашем примере это ссылка на n . Таким образом, при передаче x в метод передается
только информация о том, что x ссылается на n .
При вызове метода SwapByRef используйте ключевое слово ref , как показано в следующем примере.
static void Main()
{
int i = 2, j = 3;
System.Console.WriteLine("i = {0} j = {1}" , i, j);
См. также
Руководство по программированию на C#
Передача параметров
Передача параметров ссылочного типа
Передача параметров ссылочного типа
(Руководство по программированию в C#)
04.11.2019 • 6 minutes to read • Edit Online
Переменная ссылочного типа содержит не сами данные, а ссылку на них. При передаче параметра
ссылочного типа по значению можно изменять данные, относящиеся к объекту, на который указывает
ссылка, например, значение члена класса. Тем не менее вы не можете изменить значение самой ссылки.
Например, вы не можете использовать одну и ту же ссылку, чтобы выделить память для нового объекта и
сохранить его вне этого метода. Для этого необходимо передать параметр с использованием ключевого
слова ref или out. В следующих примерах мы для простоты используем ref .
class PassingRefByVal
{
static void Change(int[] pArray)
{
pArray[0] = 888; // This change affects the original element.
pArray = new int[5] {-3, -1, -2, -3, -4}; // This change is local.
System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
Change(arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr
[0]);
}
}
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside the method, the first element is: -3
Inside Main, after calling the method, the first element is: 888
*/
В предыдущем примере массив arr имеет ссылочный тип и передается в метод без параметра ref . В
таком случае в метод будет передана копия ссылки, которая указывает на arr . Как видно из выходных
данных, метод может изменять содержимое элемента массива (в данном случае с 1 на 888 ). Тем не менее
при выделении нового блока памяти с помощью оператора new внутри метода Change переменная pArray
будет ссылаться на новый массив. Таким образом, любые выполненные после этого изменения не будут
отражаться в исходном массиве arr , который был создан внутри Main . Фактически, в этом примере
создается два массива: один внутри Main , а другой — в методе Change .
Передача ссылочных типов по ссылке
Следующий пример аналогичен предыдущему, однако в нем в заголовок и вызов метода добавляется
ключевое слово ref . Любые изменения, выполняемые в методе, влияют на исходную переменную в
вызывающей программе.
class PassingRefByRef
{
static void Change(ref int[] pArray)
{
// Both of the following changes will affect the original variables:
pArray[0] = 888;
pArray = new int[5] {-3, -1, -2, -3, -4};
System.Console.WriteLine("Inside the method, the first element is: {0}", pArray[0]);
}
Change(ref arr);
System.Console.WriteLine("Inside Main, after calling the method, the first element is: {0}", arr[0]);
}
}
/* Output:
Inside Main, before calling the method, the first element is: 1
Inside the method, the first element is: -3
Inside Main, after calling the method, the first element is: -3
*/
Все изменения, выполняемые внутри метода, влияют на исходный массив в Main . Фактически, происходит
перемещение исходного массива с помощью оператора new . Таким образом, после вызова метода Change
любые ссылки на arr будут указывать на массив из пяти элементов, который создается в методе Change .
В этом примере параметры должны передаваться по ссылке, чтобы обеспечить изменение переменных в
вызывающей программе. Если удалить ключевое слово ref из заголовка и вызова метода, в вызывающей
программе не будут выполнены никакие изменения.
Дополнительные сведения о строках см. в этом разделе.
См. также
Руководство по программированию на C#
Передача параметров
ref
in
out
Ссылочные типы
Практическое руководство. Определение
различия между передачей структуры и ссылки
класса в метод (руководство по
программированию на C#)
25.11.2019 • 4 minutes to read • Edit Online
Пример
class TheClass
{
public string willIChange;
}
struct TheStruct
{
public string willIChange;
}
class TestClassAndStruct
{
static void ClassTaker(TheClass c)
{
c.willIChange = "Changed";
}
ClassTaker(testClass);
StructTaker(testStruct);
См. также
Руководство по программированию на C#
Классы
Структуры
Передача параметров
Неявно типизированные локальные
переменные (руководство по
программированию на C#)
25.11.2019 • 8 minutes to read • Edit Online
Локальные переменные можно объявлять без указания конкретного типа. Ключевое слово var
указывает, что компилятор должен вывести тип переменной из выражения справа от оператора
инициализации. Выведенный тип может быть встроенным, анонимным, определяемым
пользователем либо типом, определяемым в библиотеке классов .NET Framework. Дополнительные
сведения об инициализации массивов с var см. в разделе Неявно типизированные массивы.
В приведенных ниже примерах показаны различные способы объявления локальных переменных с
помощью var :
// i is compiled as an int
var i = 5;
// s is compiled as a string
var s = "Hello";
// a is compiled as int[]
var a = new[] { 0, 1, 2 };
Важно понимать, что ключевое слово var не означает "variant" и не означает, что переменная
является слабо типизированной или имеет позднее связывание. Он указывает только на то, что
компилятор определяет и назначает наиболее подходящий тип.
Ключевое слово var можно использовать в следующих контекстах:
С локальными переменными (переменными, объявленными в области метода), как показано в
предыдущем примере.
В операторе инициализации for.
В операторе using.
class ImplicitlyTypedLocals2
{
static void Main()
{
string[] words = { "aPPLE", "BlUeBeRrY", "cHeRry" };
Примечания
В объявлениям неявно типизированных переменных применяются следующие ограничения:
var можно использовать только в том случае, если локальная переменная объявляется и
инициализируется в одном и том же операторе; переменная не может инициализироваться в
нулевое значение, в группу методов или в анонимную функцию.
var нельзя применять к полям в области видимости класса.
Переменные, объявленные с помощью var , нельзя использовать в выражении инициализации.
Другими словами, это выражение является допустимым выражением: int i = (i = 20); , но
вызывает ошибку времени компиляции: var i = (i = 20);
Инициализировать сразу несколько неявно типизированных переменных в одном и том же
операторе нельзя.
Если тип с именем var входит в область видимости, то ключевое слово var разрешится в это
имя типа и не будет обрабатываться как часть объявления неявно типизированной локальной
переменной.
Неявное типизирование с ключевым словом var может применяться только к переменным в области
локального метода. Неявное типизирование недоступно для полей класса C#, так как при обработке
кода компилятор столкнется с логическим парадоксом: компилятор должен знать тип поля, но он не
может определить тип, пока не проанализирует выражение присваивания, и не может вычислить
выражение, не зная тип. Рассмотрим следующий код.
bookTitles — это поле класса, которому присваивается тип var . Так как поле не имеет выражения
для оценки, то компилятор не сможет вывести тип bookTitles . Кроме того, добавления выражения в
поле (так же, как для локальной переменной) тоже недостаточно:
Когда компилятор обнаруживает поля во время компиляции кода, он записывает тип каждого поля
перед обработкой любого выражения, связанного с ним. Компилятор обнаруживает тот же парадокс
при попытке анализа bookTitles : он должен знать тип поля, но обычно он определяет тип var путем
анализа выражения, который невозможно определить, если заранее не знать тип.
Переменную var можно также использовать в выражениях запросов, где точный
сконструированный тип переменной запроса определить непросто. Подобная ситуация может
возникнуть в операциях группировки и сортировки.
Кроме того, ключевое слово var может пригодиться, если конкретный тип переменной сложно
набрать с клавиатуры, а также если он очевиден либо затрудняет чтение кода. Использовать var
таким образом можно, например, с вложенными универсальными типами — подобные типы
применяются в групповых операциях. В следующем запросе переменная запроса имеет тип
IEnumerable<IGrouping<string, Student>> . Если вы и другие пользователи, которые работают с кодом,
это понимаете, использовать неявный ввод для удобства и краткости кода можно без проблем.
// Same as previous example except we use the entire last name as a key.
// Query variable is an IEnumerable<IGrouping<string, Student>>
var studentQuery3 =
from student in students
group student by student.Last;
При этом использование ключевого слова var может усложнить понимание вашего кода для других
разработчиков. В связи с этим в документации по C# var применяется только в тех случаях, когда это
необходимо.
См. также
Справочник по C#
Неявно типизированные массивы
Практическое руководство. Использование неявно типизированных локальных переменных и
массивов в выражении запроса
Анонимные типы
Инициализаторы объектов и коллекций
var
LINQ в C#
Встроенный язык запросов LINQ
for
foreach, in
Оператор using
Практическое руководство. Использование неявно
типизированных локальных переменных и
массивов в выражении запроса (руководство по
программированию на C#)
25.11.2019 • 3 minutes to read • Edit Online
Неявно типизированные локальные переменные можно использовать в тех случаях, когда требуется, чтобы
компилятор определял тип локальной переменной. Неявно типизированные локальные переменные
необходимо использовать для хранения анонимных типов, которые часто используются в выражениях
запроса. В следующих примерах демонстрируется обязательное и необязательное использование неявно
типизированных локальных переменных в запросах.
Неявно типизированные локальные переменные объявляются с помощью контекстного ключевого слова
var. Дополнительные сведения см. в разделах Неявно типизированные локальные переменные и Неявно
типизированные массивы.
Пример
В следующем примере показан общий сценарий, в котором использование ключевого слова var является
обязательным: выражение запроса, создающее последовательность анонимных типов. В этом случае
переменная запроса и переменная итерации в операторе foreach должны быть неявно типизированы с
помощью ключевого слова var , поскольку доступ к имени анонимного типа отсутствует. Дополнительные
сведения об анонимных типах см. в разделе Анонимные типы.
Пример
В следующем примере ключевое слово var используется в схожей ситуации, однако при этом ключевое
слово var является необязательным. Поскольку student.LastName является строкой, при выполнении
запроса возвращается последовательность строк. Таким образом, queryID можно объявить как
System.Collections.Generic.IEnumerable<string> вместо var . Ключевое слово var используется для удобства.
В этом примере переменная итерации в операторе foreach явно типизируется как строка, но вместо этого
может быть объявлена с помощью ключевого слова var . Так как тип переменной итерации не является
анонимным, использование ключевого слова var возможно, но не обязательно. Обратите внимание, что
само по себе ключевое слово var является не типом, а инструкцией, предписывающей компилятору
определение и присвоение типа.
См. также
Руководство по программированию на C#
Методы расширения
Встроенный язык запросов LINQ
var
LINQ в C#
Методы расширения (Руководство по
программированию в C#)
25.11.2019 • 11 minutes to read • Edit Online
Методы расширения позволяют "добавлять" методы в существующие типы без создания нового
производного типа, перекомпиляции и иного изменения первоначального типа. Методы расширения
представляют собой особую разновидность статического метода, но вызываются так же, как методы
экземпляра в расширенном типе. Для клиентского кода, написанного на языках C#, F# и Visual Basic,
нет видимого различия между вызовом метода расширения и вызовом методов, фактически
определенных в типе.
Самые обычные методы расширения — стандартные операторы запросов LINQ, которые добавляют
функции запросов в существующие типы System.Collections.IEnumerable и
System.Collections.Generic.IEnumerable<T>. Для использования стандартных операторов запросов их
необходимо ввести в область действия с помощью директивы using System.Linq . Тогда каждый тип,
реализующий тип IEnumerable<T>, будет иметь методы экземпляра, в частности GroupBy, OrderBy,
Average и т. д. Эти дополнительные методы можно видеть в завершении операторов IntelliSense при
вводе точки после экземпляра типа IEnumerable<T>, например List<T> или Array.
В следующем примере показано, как вызывать метод стандартного оператора запроса OrderBy для
массива целых чисел. Выражение в скобках называется лямбда-выражением. Многие стандартные
операторы запроса принимают лямбда-выражения в качестве параметров, но это необязательно для
методов расширения. Дополнительные сведения см. в разделе Лямбда-выражения.
class ExtensionMethods2
{
В приведенном ниже примере показан метод расширения, определенный для класса System.String.
Обратите внимание, что этот метод определяется внутри невложенного, неуниверсального
статического класса.
namespace ExtensionMethods
{
public static class MyExtensions
{
public static int WordCount(this String str)
{
return str.Split(new char[] { ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}
Метод расширения WordCount можно ввести в область действия с помощью следующей директивы
using :
using ExtensionMethods;
using System.Linq;
(Также может потребоваться добавить ссылку на библиотеку System.Core.dll.) Обратите внимание, что
стандартные операторы запроса теперь появляются в IntelliSense в виде дополнительных методов,
доступных для большинства типов IEnumerable<T>.
Пример
В следующем примере демонстрируются правила, которые компилятор C# соблюдает при
определении того, к чему необходимо привязать вызов метода — к методу экземпляра типа или к
методу расширения. Статический класс Extensions содержит методы расширения, определяемые для
любого типа, реализующего интерфейс IMyInterface . Все три класса — A , B и C — реализуют этот
интерфейс.
Метод расширения MethodB никогда не вызывается, потому что его имя и сигнатура точно совпадают
с методами, уже реализованными этими классами.
Если компилятор не может найти метод экземпляра с совпадающей сигнатурой, он выполняет
привязку к совпадающему методу расширения, если такой существует.
// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
namespace ExtensionMethodsDemo1
{
using System;
using Extensions;
using DefineIMyInterface;
class A : IMyInterface
{
public void MethodB() { Console.WriteLine("A.MethodB()"); }
}
class B : IMyInterface
{
public void MethodB() { Console.WriteLine("B.MethodB()"); }
public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
}
class C : IMyInterface
{
public void MethodB() { Console.WriteLine("C.MethodB()"); }
public void MethodA(object obj)
{
Console.WriteLine("C.MethodA(object obj)");
}
}
class ExtMethodDemo
{
static void Main(string[] args)
{
// Declare an instance of class A, class B, and class C.
A a = new A();
B b = new B();
C c = new C();
Общие рекомендации
В общем, методы расширения рекомендуется реализовывать в ограниченном количестве и только
при необходимости. Когда это возможно, клиентский код, служащий для расширения существующего
типа, должен осуществлять расширение путем создания нового типа, производного от
существующего. Дополнительные сведения см. в разделе Наследование.
При использовании метода расширения для расширения типа, исходный код которого невозможно
изменить, возникает риск того, что изменение в реализации типа вызовет сбой метода расширения.
В случае реализации методов расширения для какого-либо типа необходимо помнить о следующих
фактах:
Метод расширения никогда не будет вызван, если он имеет ту же сигнатуру, что и метод,
определенный в типе.
Методы расширения вводятся в область действия на уровне пространства имен. Например, при
наличии нескольких статических классов, содержащих методы расширения в единственном
пространстве имен с именем Extensions , все они будут введены в область действия директивой
using Extensions; .
См. также
Руководство по программированию на C#
Parallel Programming Samples (Образцы параллельного программирования, включают множество
примеров методов расширения)
Лямбда-выражения
Общие сведения о стандартных операторах запроса
Conversion Rules for Instance Parameters and their Impact (Правила преобразования для параметров
экземпляра и их влияние)
Extension Methods Interoperability between Languages (Взаимодействие между языками с помощью
методов расширения)
Extension Methods and Curried Delegates (Методы расширения и каррированные делегаты)
Extension method Binding and Error reporting
Практическое руководство. Реализация и вызов
пользовательского метода расширения
(руководство по программированию на C#)
25.11.2019 • 3 minutes to read • Edit Online
Этот раздел описывает, как реализовать свои методы расширения для любого типа .NET Framework.
Клиентский код может использовать методы расширения путем добавления ссылки на содержащую их
библиотеку DLL и добавления директивы using, которая указывает пространство имен, в котором
определены методы расширения.
Пример
В приведенном ниже примере реализуется метод расширения с именем WordCount в классе
CustomExtensions.StringExtension . Метод работает с классом String класса, который указан как первый
параметр метода. Пространство имен CustomExtensions импортируется в пространство имен приложения, и
метод вызывается внутри метода Main .
using System.Linq;
using System.Text;
using System;
namespace CustomExtensions
{
// Extension methods must be defined in a static class.
public static class StringExtension
{
// This is the extension method.
// The first parameter takes the "this" modifier
// and specifies the type for which the method is defined.
public static int WordCount(this String str)
{
return str.Split(new char[] {' ', '.','?'}, StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}
namespace Extension_Methods_Simple
{
// Import the extension method namespace.
using CustomExtensions;
class Program
{
static void Main(string[] args)
{
string s = "The quick brown fox jumped over the lazy dog.";
// Call the method as if it were an
// instance method on the type. Note that the first
// parameter is not specified by the calling code.
int i = s.WordCount();
System.Console.WriteLine("Word count of s is {0}", i);
}
}
}
См. также
Руководство по программированию на C#
Методы расширения
Встроенный язык запросов LINQ
Статические классы и члены статических классов
protected
internal
public
this
namespace
Практическое руководство. Создание нового
метода для перечисления (руководство по
программированию на C#)
25.11.2019 • 2 minutes to read • Edit Online
Пример
В следующем примере перечисление Grades содержит возможные буквенные оценки, которые учащийся
может получить в классе. Метод расширения с именем Passing добавляется в тип Grades , чтобы каждый
экземпляр этого типа "знал", проходной это балл или нет.
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
namespace EnumExtension
{
// Define an extension method in a non-nested static class.
public static class Extensions
{
public static Grades minPassing = Grades.D;
public static bool Passing(this Grades grade)
{
return grade >= minPassing;
}
}
Extensions.minPassing = Grades.C;
Console.WriteLine("\r\nRaising the bar!\r\n");
Console.WriteLine("First {0} a passing grade.", g1.Passing() ? "is" : "is not");
Console.WriteLine("Second {0} a passing grade.", g2.Passing() ? "is" : "is not");
}
}
}
/* Output:
First is a passing grade.
Second is not a passing grade.
Обратите внимание на то, что класс Extensions также содержит статическую переменную, которая
обновляется динамически, а возвращаемое значение метода расширения отражает текущее значение этой
переменной. Это показывает, что в фоновом режиме методы расширения вызываются непосредственно для
статического класса, в котором они определены.
См. также
Руководство по программированию на C#
Методы расширения
Именованные и необязательные аргументы
(Руководство по программированию на C#)
25.11.2019 • 12 minutes to read • Edit Online
Именованные аргументы
Именованные аргументы освобождают разработчика от необходимости запоминать или уточнять
порядок параметров в списках параметров вызванных методов. Параметр для каждого аргумента можно
указать, используя имя параметра. Например, функция, которая выводит сведения о заказе (например,
имя продавца, номер заказа и наименование товара), может вызываться как обычно, путем передачи
аргументов по позиции в порядке, определяемом функцией.
PrintOrderDetails("Gift Shop", 31, "Red Mug");
Если вы не помните порядок параметров, но знаете их имена, можете передать аргументы в любом
порядке.
PrintOrderDetails(orderNum: 31, productName: "Red Mug", sellerName: "Gift Shop");
Именованные аргументы также делают код более удобным для чтения, поскольку указывают, чему
соответствует каждый аргумент. В приведенном ниже примере метода sellerName не может быть равен
NULL или пробелу. Так как и sellerName , и productName являются строковыми типами, вместо передачи
аргументов по позиции имеет смысл использовать именованные аргументы, чтобы устранить данную
неоднозначность и сделать код более удобочитаемым.
Именованные аргументы при использовании с позиционными аргументами допустимы при условии, что
за ними не следуют позиционные аргументы, либо,
PrintOrderDetails("Gift Shop", 31, productName: "Red Mug");
начиная с C# 7.2, они используются в правильной позиции. В примере ниже параметр orderNum
находится в правильной позиции, но не имеет явно заданного имени.
PrintOrderDetails(sellerName: "Gift Shop", 31, productName: "Red Mug");
Пример
Приведенный ниже код реализует как примеры из этого раздела, так и некоторые дополнительные
примеры.
class NamedExample
{
static void Main(string[] args)
{
// The method can be called in the normal way, by using positional arguments.
PrintOrderDetails("Gift Shop", 31, "Red Mug");
Необязательные аргументы
Определение метода, конструктора, индексатора или делегата может указывать, являются его параметры
обязательными или нет. Любой вызов должен содержать аргументы для всех обязательных параметров;
аргументы для необязательных параметров можно опустить.
Определение каждого необязательного параметра содержит его значение по умолчанию. Если аргумент
для параметра не передается, используется значение по умолчанию. Значением по умолчанию должен
быть один из следующих типов выражений:
константное выражение;
выражение в форме new ValType() , где ValType — это тип значения, например, enum или struct;
выражение в форме default(ValType), где ValType — это тип значения.
Необязательные параметры определяются в конце списка параметров после всех обязательных
параметров. Если вызывающий объект предоставляет аргумент для любого из последующих
необязательных параметров, он должен содержать аргументы для всех предыдущих необязательных
параметров. Пробелы, разделенные запятыми, в списке аргументов не поддерживаются. Например, в
следующем коде метод экземпляра ExampleMethod определяется одним обязательным и двумя
необязательными параметрами.
Следующий вызов ExampleMethod вызывает ошибку компилятора, поскольку аргумент предоставляется для
третьего параметра, но не для второго.
//anExample.ExampleMethod(3, ,4);
Если вы знаете имя третьего параметра, можете использовать для выполнения задачи именованный
аргумент.
anExample.ExampleMethod(3, optionalint: 4);
NOTE
Необязательные параметры также можно объявлять с помощью класса .NET OptionalAttribute. Для параметров
OptionalAttribute значение по умолчанию не требуется.
Пример
В следующем примере конструктор ExampleClass имеет один параметр, который является
необязательным. У метода экземпляра ExampleMethod есть один обязательный параметр, required , и два
необязательных параметра, optionalstr и optionalint . Код в Main демонстрирует различные способы,
которые можно использовать для вызова конструктора и метода.
namespace OptionalNamespace
{
class OptionalExample
{
static void Main(string[] args)
{
// Instance anExample does not send an argument for the constructor's
// optional parameter.
ExampleClass anExample = new ExampleClass();
anExample.ExampleMethod(1, "One", 1);
anExample.ExampleMethod(2, "Two");
anExample.ExampleMethod(3);
class ExampleClass
{
private string _name;
Интерфейсы COM
Именованные и необязательные аргументы, а также поддержка динамических объектов и другие
усовершенствования значительно улучшают взаимодействие с API COM, такими как API автоматизации
Office.
Например, метод AutoFormatв интерфейсе Microsoft Office Excel Range имеет семь параметров и все они
необязательные. Эти параметры показаны на следующем рисунке:
В C# 3.0 и более ранних версиях аргумент необходимо указывать для каждого параметра, как показано в
следующем примере.
var myFormat =
Microsoft.Office.Interop.Excel.XlRangeAutoFormat.xlRangeAutoFormatAccounting1;
При этом вызов AutoFormat можно значительно упростить, используя именованные и необязательные
аргументы, представленные в C# 4.0. Именованные и необязательные аргументы позволяют опускать
аргументы для необязательных параметров, если значение параметра по умолчанию менять не нужно. В
следующем вызове значение задается только для одного из семи параметров.
// The following code shows the same call to AutoFormat in C# 4.0. Only
// the argument for which you want to provide a specific value is listed.
excelApp.Range["A1", "B4"].AutoFormat( Format: myFormat );
Overload Resolution
Использование именованных и необязательных аргументов влияет на разрешение перегрузки описанным
ниже образом.
Метод, индексатор или конструктор является кандидатом на выполнение, если каждый из его
параметров необязателен либо по имени или позиции соответствует одному и тому же аргументу в
операторе вызова, и этот аргумент можно преобразовать в тип параметра.
Если найдено более одного кандидата, правила разрешения перегрузки для предпочтительных
преобразований применяются к аргументам, указанным явно. Опущенные аргументы для
необязательных параметров игнорируются.
Если два кандидата определяются как равно подходящие, предпочтение отдается кандидату без
необязательных параметров, аргументы которых в вызове были опущены. Это —
последовательность определения приоритетов в разрешении перегрузки для кандидатов с
меньшим числом параметров.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Использование именованных и необязательных аргументов в программировании приложений Office
Использование типа dynamic
Использование конструкторов
Использование индексаторов
Практическое руководство. Использование
именованных и необязательных аргументов в
программировании приложений Office
(руководство по программированию на C#)
25.11.2019 • 7 minutes to read • Edit Online
NOTE
Отображаемые на компьютере имена или расположения некоторых элементов пользовательского интерфейса Visual
Studio могут отличаться от указанных в следующих инструкциях. Это зависит от имеющегося выпуска Visual Studio и
используемых параметров. Дополнительные сведения см. в разделе Персонализация среды IDE.
Добавление ссылки
1. В обозревателе решений щелкните имя проекта правой кнопкой мыши и выберите пункт
Добавить ссылку. Откроется диалоговое окно Добавление ссылки.
2. На странице .NET выберите Microsoft.Office.Interop.Word в списке Имя компонента.
3. Нажмите кнопку ОК.
2. Добавьте в конец метода следующий код, чтобы определить место отображения текста в документе
и текст для отображения:
Запуск приложения
1. Добавьте в метод Main следующую инструкцию:
DisplayInWord();
2. Нажмите клавиши CTRL+F5, чтобы запустить проект. Появится документ Word, содержащий
указанный текст.
Именованные и необязательные аргументы позволяют задавать значения только для тех параметров,
которые требуется изменить. Добавьте в конец метода DisplayInWord следующий код, чтобы создать
простую таблицу. Аргумент указывает, что запятые в текстовой строке в range разделяют ячейки
таблицы.
// Convert to a simple table. The table will have a single row with
// three columns.
range.ConvertToTable(Separator: ",");
В более ранних версиях C# для вызова ConvertToTable каждому параметру требовался ссылочный
аргумент, как показано в следующем коде:
2. Чтобы использовать заранее определенный формат таблицы, замените последнюю строку метода
DisplayInWord следующей инструкцией и нажмите сочетание клавиш CTRL+F5. В качестве формата
можно использовать любую из констант WdTableFormat.
Пример
Следующий код включает полный пример:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Word = Microsoft.Office.Interop.Word;
namespace OfficeHowTo
{
class WordProgram
{
static void Main(string[] args)
{
DisplayInWord();
}
// Next, use the ConvertToTable method to put the text into a table.
// The method has 16 optional parameters. You only have to specify
// values for those you want to change.
// Convert to a simple table. The table will have a single row with
// three columns.
range.ConvertToTable(Separator: ",");
См. также
Именованные и необязательные аргументы
Конструкторы (Руководство по
программированию на C#)
25.11.2019 • 4 minutes to read • Edit Online
Каждый раз, когда создается класс или структура, вызывается конструктор. Класс или структура может
иметь несколько конструкторов, принимающих различные аргументы. Конструкторы позволяют
программисту задавать значения по умолчанию, ограничивать число установок и писать код, который
является гибким и удобным для чтения. Дополнительные сведения и примеры см. в разделах
Использование конструкторов и Конструкторы экземпляров.
Синтаксис конструктора
Конструктор — это метод, имя которого совпадает с именем его типа. Его сигнатура метода содержит
только имя метода и список параметров. Она не содержит возвращаемый тип. В приведенном ниже
примере демонстрируется конструктор для класса с именем Person .
Если конструктор поддерживает реализацию в виде оператора, можно использовать определение тела
выражения. В следующем примере определяется класс Location , конструктор которого имеет один
строковый параметр name. Определение тела выражения присваивает аргумент полю locationName .
public class Location
{
private string locationName;
Статические конструкторы
В приведенных выше примерах показаны конструкторы экземпляров, которые создают новый объект.
В классе или структуре также может быть статический конструктор, который инициализирует
статические члены типа. Статические конструкторы не имеют параметров. Если вы не предоставили
статический конструктор для инициализации статических полей, компилятор C# инициализирует
статические поля значениями по умолчанию, которые указаны в таблице значений по умолчанию.
В следующем примере статический конструктор используется для инициализации статического поля.
static Adult()
{
minimumAge = 18;
}
Можно также определить статический конструктор с помощью определения тела выражения, как
показано в следующем примере.
В этом разделе
Использование конструкторов
Конструкторы экземпляров
Закрытые конструкторы
Статические конструкторы
Практическое руководство. Создание конструктора копий
См. также
Руководство по программированию на C#
Классы и структуры
Методы завершения
static
Why Do Initializers Run In The Opposite Order As Constructors? Part One (Почему инициализаторы
выполняются в порядке, обратном действию конструкторов? Часть 1)
Использование конструкторов (Руководство по
программированию на C#)
04.11.2019 • 7 minutes to read • Edit Online
Каждый раз, когда создается класс или структура, вызывается конструктор. Конструкторы имеют имя,
совпадающее с именем класса или структуры, и обычно инициализируют члены данных нового объекта.
В следующем примере класс с именем Taxi определяется с помощью простого конструктора. Затем
оператор new создает экземпляр этого класса. Конструктор Taxi вызывается оператором new сразу после
того, как новому объекту будет выделена память.
class TestTaxi
{
static void Main()
{
Taxi t = new Taxi();
Console.WriteLine(t.IsInitialized);
}
}
class NLog
{
// Private Constructor:
private NLog() { }
В то же время следующий код вызывает ошибку компилятора, поскольку не использует new , и потому, что
использует не инициализированный объект:
int i;
Console.WriteLine(i);
Кроме того, объекты на основе structs (включая все встроенные числовые типы) можно
инициализировать или назначить, а затем использовать, как в следующем примере:
В связи с этим вызывать конструктор без параметров для типа значения необязательно.
Оба класса и structs могут определять конструкторы, принимающие параметры. Конструкторы,
принимающие параметры, необходимо вызывать с помощью оператора new или base. Классы и structs
могут определять также несколько конструкторов; для определения конструктора без параметров ни один
их них не требуется. Например:
Конструктор может использовать ключевое слово base для вызова конструктора базового класса.
Например:
public class Manager : Employee
{
public Manager(int annualSalary)
: base(annualSalary)
{
//Add further instructions here.
}
}
В этом примере конструктор базового класса вызывается перед выполнением соответствующего ему блока.
Ключевое слово base можно использовать как с параметрами, так и без них. Любые параметры для
конструктора можно использовать как параметры для base или как часть выражения. Дополнительные
сведения см. в разделе base.
В производном классе, если конструктор базового класса не вызывается явным образом с помощью
ключевого слова base , конструктор без параметров, если он существует, вызывается неявно. Это означает,
что следующие объявления конструкторов действуют одинаково:
Если базовый класс не предлагает конструктор без параметров, производный класс должен явно вызвать
конструктор базового класса с помощью base .
Конструктор может вызывать другой конструктор в том же объекте с помощью ключевого слова this. Как и
base , this можно использовать с параметрами или без, а все параметры в конструкторе доступны как
параметры this или как часть выражения. Например, второй конструктор в предыдущем примере можно
переписать, используя this :
Применение ключевого слова this в приведенном выше примере привело к вызову конструктора:
Конструкторы могут иметь пометку public, private, protected, internal, protected internal или private protected.
Эти модификаторы доступа определяют, каким образом пользователи класса смогут создавать класс.
Дополнительные сведения см. в статье Модификаторы доступа.
Конструктор можно объявить статическим, используя ключевое слово static. Статические конструкторы
вызываются автоматически непосредственно перед доступом к статическим полям и обычно используются
для инициализации членов статического класса. Дополнительные сведения см. в разделе Статические
конструкторы.
Спецификация языка C#
Дополнительные сведения см. в разделах Конструкторы экземпляров и Статические конструкторы в
Спецификации языка C#. Спецификация языка является предписывающим источником информации о
синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Классы и структуры
Конструкторы
Методы завершения
Конструкторы экземпляров (Руководство по
программированию в C#)
23.10.2019 • 6 minutes to read • Edit Online
class Coords
{
public int x, y;
// constructor
public Coords()
{
x = 0;
y = 0;
}
}
NOTE
Для ясности этот класс содержит открытые поля. Открытые поля не рекомендуется использовать на практике,
поскольку в этом случае любой метод в любом месте программы получает неограниченный и неконтролируемый
доступ к внутренней работе объекта. Члены данных обычно должны быть закрытыми, а доступ к ним должен
осуществляться только посредством методов и свойства класса.
Этот конструктор экземпляра вызывается каждый раз при создании объекта на базе класса Coords . Такой
конструктор без аргументов называется конструктором без параметров. Зачастую такие конструкторы
используются для предоставления дополнительных конструкторов. Например, можно добавить
конструктор в класс Coords , позволяющий указывать начальные значения для членов данных:
Это позволяет создавать объекты Coords с начальными значениями по умолчанию или с другими
начальными значениями:
Если класс не имеет конструктора, автоматически создается конструктор без параметров и для
инициализации полей объекта используются значения по умолчанию. Например, int инициализируется
значением 0. Дополнительные сведения о значениях по умолчанию см. в разделе Таблица значений по
умолчанию. Следовательно, поскольку конструктор без параметров класса Coords инициализирует все
члены данных с нулевыми значениями, его можно удалить. При этом работа класса не изменится. Полный
пример использования нескольких конструкторов см. в данном разделе в примере 1; пример
автоматически созданного конструктора см. в примере 2.
Конструкторы экземпляров также можно использовать для вызова конструкторов экземпляров базового
класса. Конструктор класса может вызвать конструктор базового класса с помощью инициализатора:
В этом примере класс Circle передает значения радиуса и высоты конструктору, предоставленному
классом Shape , для которого класс Circle является производным. Полный текст кода с использованием
классов Shape и Circle см. в данном разделе в примере 3.
Пример 1
В следующем примере демонстрируется класс с двумя конструкторами, один из которых не имеет
аргументов, а второй имеет два аргумента.
class Coords
{
public int x, y;
// Default constructor.
public Coords()
{
x = 0;
y = 0;
}
class MainClass
{
static void Main()
{
var p1 = new Coords();
var p2 = new Coords(5, 3);
Пример 2
В этом примере класс Person не имеет конструкторов, поэтому автоматически предоставляется
конструктор без параметров, а все поля инициализируются значениями по умолчанию.
public class Person
{
public int age;
public string name;
}
class TestPerson
{
static void Main()
{
var person = new Person();
Обратите внимание, что значение по умолчанию age равно 0 , а значение по умолчанию name равно
null . Дополнительные сведения о значениях по умолчанию см. в разделе Таблица значений по
умолчанию.
Пример 3
В следующем примере демонстрируется использование инициализатора базового класса. Класс Circle
является производным от основного класса Shape , а класс Cylinder является производным от класса
Circle . Конструктор каждого производного класса использует инициализатор своего базового класса.
abstract class Shape
{
public const double pi = Math.PI;
protected double x, y;
class TestShapes
{
static void Main()
{
double radius = 2.5;
double height = 3.0;
Дополнительные примеры вызова конструкторов базовых классов см. в разделах virtual, override и base.
См. также
Руководство по программированию на C#
Классы и структуры
Конструкторы
Методы завершения
static
Закрытые конструкторы (Руководство по
программированию на C#)
04.11.2019 • 2 minutes to read • Edit Online
class NLog
{
// Private Constructor:
private NLog() { }
Пример
Ниже приведен пример класса с закрытым конструктором.
public class Counter
{
private Counter() { }
public static int currentCount;
public static int IncrementCount()
{
return ++currentCount;
}
}
class TestCounter
{
static void Main()
{
// If you uncomment the following statement, it will generate
// an error because the constructor is inaccessible:
// Counter aCounter = new Counter(); // Error
Counter.currentCount = 100;
Counter.IncrementCount();
Console.WriteLine("New count: {0}", Counter.currentCount);
Обратите внимание, что если в примере раскомментировать следующий оператор, возникнет ошибка, так
как конструктор недоступен из-за уровня защиты:
Спецификация языка C#
Дополнительные сведения см. в разделе Закрытые конструкторы в Спецификации языка C#. Спецификация
языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Классы и структуры
Конструкторы
Методы завершения
private
public
Статические конструкторы (Руководство по
программированию в C#)
23.10.2019 • 7 minutes to read • Edit Online
Статический конструктор используется для инициализации любых статических данных или для
выполнения определенного действия, которое требуется выполнить только один раз. Он вызывается
автоматически перед созданием первого экземпляра или ссылкой на какие-либо статические члены.
class SimpleClass
{
// Static variable that must be initialized at run time.
static readonly long baseline;
Примечания
Статические конструкторы обладают следующими свойствами.
Статический конструктор не принимает модификаторы доступа и не имеет параметров.
Класс или структура могут иметь только один статический конструктор.
Статические конструкторы не могут быть унаследованы или перегружены.
Статический конструктор нельзя вызывать напрямую. Он предназначен только для вызова из
общеязыковой среды выполнения (CLR ). Он запускается автоматически.
Пользователь не управляет временем, в течение которого статический конструктор выполняется в
программе.
Статический конструктор вызывается автоматически для инициализации класса перед созданием
первого экземпляра типа или ссылкой на какие-либо статические члены. Статический конструктор
выполняется раньше, чем конструктор экземпляра. Обратите внимание на то, что статический
конструктор типа вызывается при вызове статического метода, назначенного делегату или событию,
а не при его назначении. Если в классе со статическим конструктором существуют переменные
инициализаторы статических полей, они поочередно выполняются в том порядке, в котором
включены в объявление класса, и сразу после этого выполняется статический конструктор.
Если вы не предоставили статический конструктор для инициализации статических полей, все
статические поля инициализируются значениями по умолчанию, которые указаны в таблице
значений по умолчанию.
Если статический конструктор инициирует исключение, среда выполнения не вызывает его во
второй раз, и тип остается неинициализированным на время существования домена приложения, в
котором выполняется программа. Чаще всего исключение TypeInitializationException вызывается,
когда статический конструктор не может создать экземпляр типа или в статическом конструкторе
создается необработанное исключение. Для устранения неполадок с неявными статическими
конструкторами, которые не определены в исходном коде, может потребоваться проверка кода на
промежуточном языке (IL ).
Наличие статического конструктора не позволяет добавлять атрибут типа BeforeFieldInit. Это
ограничивает возможности оптимизации во время выполнения.
Поле, объявленное как static readonly , может быть присвоено только при его объявлении или в
статическом конструкторе. Если явный статический конструктор не является обязательным,
инициализируйте статические поля в объявлении, а не через статический конструктор, чтобы
сохранить больше возможностей для оптимизации в среде выполнения.
NOTE
Наличие явного статического конструктора следует документировать, несмотря на отсутствие прямого доступа к
нему, поскольку это важно для устранения неполадок с исключениями при инициализации.
Использование
Типичным использованием статических конструкторов является случай, когда класс использует
файл журнала и конструктор применяется для добавления записей в этот файл.
Статические конструкторы также полезны при создании классов-оболочек для неуправляемого
кода, когда конструктор может вызвать метод LoadLibrary .
Статические конструкторы очень удобны для применения в среде выполнения проверок параметра
типа, которые невозможно проверить с помощью ограничений во время компиляции (ограничения
параметров типа).
Пример
В этом примере класс Bus имеет статический конструктор. При создании первого экземпляра класса Bus
( bus1 ) для инициализации класса вызывается статический конструктор. В выходных данных этого
примера можно увидеть, что статический конструктор выполняется только один раз, несмотря на то, что
создается два экземпляра класса Bus . Кроме того, этот конструктор вызывается до выполнения
конструктора экземпляра.
// Instance constructor.
public Bus(int routeNum)
{
RouteNumber = routeNum;
Console.WriteLine("Bus #{0} is created.", RouteNumber);
}
// Instance method.
public void Drive()
{
TimeSpan elapsedTime = DateTime.Now - globalStartTime;
class TestBus
{
static void Main()
{
// The creation of this instance activates the static constructor.
Bus bus1 = new Bus(71);
Спецификация языка C#
Дополнительные сведения см. в разделе Статические конструктор в спецификации языка C#.
См. также
Руководство по программированию на C#
Классы и структуры
Конструкторы
Статические классы и члены статических классов
Методы завершения
Рекомендации по разработке конструкторов
Предупреждение о безопасности CA2121. Статические конструкторы должны быть частными
Практическое руководство. Создание конструктора
копий (руководство по программированию на C#)
25.11.2019 • 2 minutes to read • Edit Online
В C# не предусмотрен конструктор копии для объектов, однако его можно написать самостоятельно.
Пример
В следующем примере класс Person определяет конструктор копии, который использует экземпляр Person в
качестве аргумента. Значения свойств аргумента присваиваются свойствам нового экземпляра Person . Код
содержит дополнительный конструктор копии, который отправляет свойства Name и Age экземпляра,
который необходимо скопировать конструктору экземпляра класса.
class Person
{
// Copy constructor.
public Person(Person previousPerson)
{
Name = previousPerson.Name;
Age = previousPerson.Age;
}
// Instance constructor.
public Person(string name, int age)
{
Name = name;
Age = age;
}
class TestPerson
{
static void Main()
{
// Create a Person object by using the instance constructor.
Person person1 = new Person("George", 40);
// Show details to verify that the name and age fields are distinct.
Console.WriteLine(person1.Details());
Console.WriteLine(person2.Details());
См. также
ICloneable
Руководство по программированию на C#
Классы и структуры
Конструкторы
Методы завершения
Методы завершения (руководство по
программированию в C#)
04.11.2019 • 6 minutes to read • Edit Online
Примечания
В структурах определение методов завершения невозможно. Они применяются только в классах.
Каждый класс может иметь только один метод завершения.
Методы завершения не могут быть унаследованы или перегружены.
Методы завершения невозможно вызвать. Они запускаются автоматически.
Метод завершения не принимает модификаторов и не имеет параметров.
Например, ниже показано объявление метода завершения для класса Car .
class Car
{
~Car() // finalizer
{
// cleanup statements...
}
}
Метод завершения можно также реализовать как определение тела выражения, как показано в
следующем примере.
using System;
Метод завершения неявно вызывает метод Finalize для базового класса объекта. В связи с этим вызов
метода завершения неявно преобразуется в следующий код:
protected override void Finalize()
{
try
{
// Cleanup statements...
}
finally
{
base.Finalize();
}
}
Это означает, что метод Finalize вызывается рекурсивно для всех экземпляров цепочки наследования
начиная с самого дальнего и заканчивая самым первым.
NOTE
Пустые методы завершения использовать нельзя. Если класс содержит метод завершения, то в очереди метода
Finalize создается запись. При вызове метода завершения вызывается сборщик мусора, выполняющий
обработку очереди. Если метод завершения пустой, это приводит только к ненужному снижению
производительности.
Программист не может управлять моментом вызова метода завершения, потому что этот момент
определяется сборщиком мусора. Сборщик мусора проверяет наличие объектов, которые больше не
используются приложением. Если он считает, что какой-либо объект требует уничтожения, то вызывает
метод завершения (при наличии) и освобождает память, используемую для хранения этого объекта.
В приложениях .NET Framework (но не в приложениях .NET Core) методы завершения также вызываются
при выходе из программы.
Сборку мусора можно выполнить принудительно, вызвав метод Collect, но в большинстве случаев этого
следует избегать из-за возможных проблем с производительностью.
Пример
В приведенном ниже примере создаются три класса, образующих цепочку наследования. Класс First
является базовым, класс Second является производным от класса First , а класс Third является
производным от класса Second . Все три класса имеют методы завершения. В методе Main создается
экземпляр самого дальнего в цепочке наследования класса. При выполнении программы обратите
внимание на то, что методы завершения для всех трех классов вызываются автоматически в порядке от
самого дальнего до первого в цепочке наследования.
class First
{
~First()
{
System.Diagnostics.Trace.WriteLine("First's destructor is called.");
}
}
class TestDestructors
{
static void Main()
{
Third t = new Third();
}
}
/* Output (to VS Output Window):
Third's destructor is called.
Second's destructor is called.
First's destructor is called.
*/
Спецификация языка C#
Дополнительные сведения см. в разделе Деструкторы спецификация языка C# 6.0.
См. также
IDisposable
Руководство по программированию на C#
Конструкторы
Сборка мусора
Инициализаторы объектов и коллекций
(Руководство по программированию в C#)
25.11.2019 • 11 minutes to read • Edit Online
C# позволяет создать экземпляр объекта или коллекции и выполнять присваивания его членов в одной
инструкции.
Инициализаторы объектов
Инициализаторы объектов позволяют присваивать значения всем доступным полям и свойствам
объекта во время создания без вызова конструктора, за которым следуют строки операторов
присваивания. Синтаксис инициализатора объекта позволяет задавать аргументы конструктора или
опускать их (и синтаксис в скобках). В приведенном ниже примере демонстрируется использование
инициализатора объекта с именованным типом Cat и вызов конструктора без параметров. Обратите
внимание на использование в классе Cat автоматически внедренных свойств. Дополнительные
сведения см. в разделе Автоматически реализуемые свойства.
public Cat()
{
}
[1, 0] = 0.0,
[1, 1] = 1.0,
[1, 2] = 0.0,
[2, 0] = 0.0,
[2, 1] = 0.0,
[2, 2] = 1.0,
};
Любой доступный индексатор, который содержит доступный метод задания, можно использовать как
одно из выражений в инициализаторе объекта независимо от количества или типов аргументов.
Аргументы индекса формируют левую часть присваивания, а значение стоит в его правой части.
Например, все нижеприведенные конструкции допустимы, если IndexersExample имеет
соответствующие индексаторы:
Для компиляции приведенного выше кода тип IndexersExample должен иметь следующие члены:
var productInfos =
from p in products
select new { p.ProductName, p.UnitPrice };
foreach(var p in productInfos){...}
Каждый объект нового анонимного типа содержит два общедоступных свойства, имеющих те же
имена, что и у свойств или полей исходного объекта. Кроме того, при создании анонимного типа
можно переименовать поле; в следующем примере выполняется переименование поля UnitPrice в
Price .
Инициализаторы коллекций
Инициализаторы коллекций позволяют задавать один или несколько инициализаторов элементов при
инициализации типа коллекции, который реализует интерфейс IEnumerable и включает Add с
соответствующей сигнатурой как метод экземпляра или метод расширения. Инициализаторами
элементов могут быть простые значения, выражения и инициализаторы объектов. При использовании
инициализатора коллекции не требуется указывать несколько вызовов; эти вызовы добавляет
компилятор.
Ниже приведен пример двух простых инициализаторов коллекций.
В качестве элемента инициализатора коллекции можно указать значение null, если метод Add
коллекции допускает это.
В предыдущем примере создается код, который вызывает Item[TKey] для задания значений. В версиях
C# ниже 6 можно было инициализировать словари и другие ассоциативные контейнеры, используя
указанный ниже синтаксис. Обратите внимание, что вместо синтаксиса индексатора с круглыми
скобками и присваиванием он использует объект с несколькими значениями:
В этом примере инициализатор вызывает Add(TKey, TValue) для добавления трех элементов в словарь.
Эти два разных способа инициализации ассоциативных коллекций немного отличаются из-за вызовов
методов, которые создает компилятор. Оба варианта могут работать с классом Dictionary . Другие
типы могут поддерживать только один из них в зависимости от своего открытого API.
Примеры
В следующем примере объединяются понятия инициализаторов коллекций и объектов.
public class InitializationSample
{
public class Cat
{
// Auto-implemented properties.
public int Age { get; set; }
public string Name { get; set; }
public Cat() { }
// Display results.
System.Console.WriteLine(cat.Name);
Console.WriteLine("Address Entries:");
/*
* Prints:
Address Entries:
John Doe
123 Street
Topeka, KS 00000
Jane Smith
456 Street
Topeka, KS 00000
*/
}
В методах Add можно использовать ключевое слово params , чтобы принимать переменное число
аргументов, как показано в приведенном ниже примере. Здесь также демонстрируется
пользовательская реализация индексатора, которая также применяется для инициализации коллекции
с помощью индексов.
public void Add(TKey key, params TValue[] values) => Add(key, (IEnumerable<TValue>)values);
storedValues.AddRange(values);
}
}
/*
* Prints:
Using second multi-valued dictionary created with a collection initializer using indexing:
Using third multi-valued dictionary created with a collection initializer using indexing:
См. также
Руководство по программированию на C#
LINQ в C#
Анонимные типы
Практическое руководство. Инициализация
объектов с помощью инициализатора объектов
(руководство по программированию на C#)
25.11.2019 • 3 minutes to read • Edit Online
Инициализаторы объектов можно использовать для декларативной инициализации объектов типов без
явного вызова конструктора для типа.
Следующие примеры демонстрируют использование инициализаторов объектов с именованными
объектами. Компилятор выполняет обработку инициализаторов объектов, сначала получая доступ к
конструктору экземпляра по умолчанию, а затем обрабатывая инициализации членов. Таким образом, если
конструктор без параметров объявляется как private в классе, произойдет сбой инициализаторов объектов,
требующих открытого доступа.
При определении анонимного типа использовать инициализатор объекта обязательно. Дополнительные
сведения см. в разделе Практическое руководство. Возвращение поднаборов свойств элементов в запросе.
Пример
В следующем примере показана инициализация нового типа StudentName с помощью инициализаторов
объектов. В этом примере задаются свойства в типе StudentName :
Console.WriteLine(student1.ToString());
Console.WriteLine(student2.ToString());
Console.WriteLine(student3.ToString());
Console.WriteLine(student4.ToString());
}
// Output:
// Craig 0
// Craig 0
// 183
// Craig 116
// Properties.
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
Console.WriteLine(team["2B"]);
}
}
См. также
Руководство по программированию на C#
Инициализаторы объектов и коллекций
Практическое руководство. Инициализация
словаря с помощью инициализатора коллекции
(Руководство по программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
В следующем примере кода Dictionary<TKey,TValue> инициализируется экземплярами типа StudentName .
Первая инициализация использует метод Add с двумя аргументами. Компилятор создает вызов метода Add
для каждой пары ключа int и значения StudentName . Вторая инициализация использует общедоступный
метод индексатора чтения и записи класса Dictionary :
public class HowToDictionaryInitializer
{
class StudentName
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int ID { get; set; }
}
Обратите внимание на две пары фигурных скобок в каждом элементе коллекции в первом объявлении.
Внутренняя пара фигурных скобок заключает инициализатор объекта StudentName , а внешняя пара —
инициализатор пары "ключ – значение", которая будет добавлена в коллекцию students
Dictionary<TKey,TValue>. И наконец, весь инициализатор коллекции для словаря заключается в фигурные
скобки. Во второй инициализации левая часть оператора присваивания является ключом, а правая часть —
значением, использующим инициализатор объекта для StudentName .
См. также
Руководство по программированию на C#
Инициализаторы объектов и коллекций
Вложенные типы (Руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Тип, определенный внутри класса или структуры, называется вложенным типом. Например:
class Container
{
class Nested
{
Nested() { }
}
}
Независимо от того, является ли внешний тип классом или структурой, вложенным типам по умолчанию
присваивается модификатор private, из-за чего они доступны только из содержащего их типа. В
предыдущем примере класс Nested недоступен для внешних типов.
Также можно указать модификатор доступа, определяющий доступность вложенного типа, как показано
ниже:
Вложенные типы класса могут иметь модификаторы public, protected, internal, protected internal,
private или private protected.
Тем не менее при определении вложенного класса protected , protected internal или
private protected внутри запечатанного класса возникает предупреждение компилятора CS0628
"Новый защищенный член объявлен в запечатанном классе".
Вложенные типы структуры могут иметь модификаторы public, internal или private.
В следующем примере класс Nested определяется как открытый:
class Container
{
public class Nested
{
Nested() { }
}
}
Вложенный (внутренний) тип может получить доступ к вмещающему (внешнему) типу. Чтобы получить
доступ к вмещающему типу, передайте его в качестве аргумента в конструктор вложенного типа. Пример:
public class Container
{
public class Nested
{
private Container parent;
public Nested()
{
}
public Nested(Container parent)
{
this.parent = parent;
}
}
}
Вложенный тип имеет доступ ко всем членам, которые доступны вмещающему типу. Он может получать
доступ к закрытым и защищенным членам вмещающего типа, включая любые наследуемые защищенные
члены.
В предыдущем объявлении полным именем класса Nested является Container.Nested . Это имя используется
для создания нового экземпляра вложенного класса, как показано ниже:
См. также
Руководство по программированию на C#
Классы и структуры
Модификаторы доступа
Конструкторы
Разделяемые классы и методы (Руководство по
программированию в C#)
04.11.2019 • 11 minutes to read • Edit Online
Можно разделить определение класса, структуры, интерфейса или метода между двумя или более
исходными файлами. Каждый исходный файл содержит часть определения класса или метода, а во время
компиляции приложения все части объединяются.
Разделяемые классы
Существует несколько ситуаций, когда желательно разделение определения класса.
При работе над большими проектами распределение класса между различными файлами позволяет
нескольким программистам работать с ним одновременно.
При работе с использованием автоматически создаваемого источника код можно добавлять в класс
без повторного создания файла источника. Visual Studio использует этот подход при создании форм
Windows Forms, кода оболочки веб-службы и т. д. Можно создать код, который использует эти
классы, без необходимости изменения файла, созданного в Visual Studio.
Чтобы разделить определение класса, используйте модификатор ключевого слова partial, как
показано ниже:
Ключевое слово partial указывает, что другие части класса, структуры или интерфейса могут быть
определены в пространстве имен. Все части должны использовать ключевое слово partial . Для
формирования окончательного типа все части должны быть доступны во время компиляции. Все части
должны иметь одинаковые модификаторы доступа, например public , private и т. д.
Если какая-либо из частей объявлена абстрактной, то весь тип будет считаться абстрактным. Если какая-
либо из частей объявлена запечатанной, то весь тип будет считаться запечатанным. Если какая-либо из
частей объявляет базовый тип, то весь тип будет наследовать данный класс.
Все части, указывающие базовый класс, должны быть согласованы друг с другом, а части, не использующие
базовый класс, все равно наследуют базовый тип. Части могут указывать различные базовые интерфейсы, и
окончательный тип будет реализовывать все интерфейсы, перечисленные во всех разделяемых
объявлениях. Любые члены класса, структуры или интерфейса, объявленные в разделяемом объявлении,
доступны для всех остальных частей. Окончательный тип представляет собой комбинацию всех частей,
выполненную во время компиляции.
NOTE
Модификатор partial недоступен в объявлениях делегатов или перечислений.
В следующем примере показано, что вложенные типы могут быть разделяемыми, даже если тип, в который
они вложены, не является разделяемым.
class Container
{
partial class Nested
{
void Test() { }
}
partial class Nested
{
void Test2() { }
}
}
[SerializableAttribute]
partial class Moon { }
[ObsoleteAttribute]
partial class Moon { }
[SerializableAttribute]
[ObsoleteAttribute]
class Moon { }
Ограничения
Имеется несколько правил, которые необходимо выполнять при работе с определениями разделяемого
класса.
Все определения разделяемого типа, являющиеся частями одного типа, должны изменяться с
использованием типа partial . Например, следующие объявления класса приведут к появлению
ошибки:
Все определения разделяемого типа, являющиеся частями одного и того же типа, должны быть
определены в одной сборке и в одном модуле (EXE -файл или DLL -файл). Разделяемые определения
не могут находиться в разных модулях.
Имя класса и параметры универсального типа должны соответствовать всем определениям
разделяемого типа. Универсальные типы могут быть разделяемыми. Все объявления разделяемого
типа должны использовать одинаковые имена параметров в одном и том же порядке.
Приведенные ниже ключевые слова необязательно должны присутствовать в определении
разделяемого типа, но если они присутствуют в одном определении разделяемого типа, то не
должны конфликтовать с ключевыми словами, указанными в других определениях того же
разделяемого типа.
public
private
protected
internal
abstract
sealed
базовый класс
модификатор new (вложенные части)
универсальные ограничения
Дополнительные сведения см. в разделе Ограничения параметров типа.
Пример 1
ОПИСАНИЕ
В следующем примере поля и конструктор класса Coords объявлены в одном определении разделяемого
класса, а член PrintCoords — в другом определении разделяемого класса.
Код
class TestCoords
{
static void Main()
{
Coords myCoords = new Coords(10, 15);
myCoords.PrintCoords();
Пример 2
ОПИСАНИЕ
В следующем примере показано, что можно также разработать разделяемые структуры и интерфейсы.
Код
partial interface ITest
{
void Interface_Test();
}
partial struct S1
{
void Struct_Test() { }
}
partial struct S1
{
void Struct_Test2() { }
}
Разделяемые методы
Разделяемый класс или структура могут содержать разделяемый метод. Одна часть класса содержит
сигнатуру метода. В той же или в другой части можно определить дополнительную реализацию. Если
реализация не предоставлена, метод и все вызовы метода удаляются во время компиляции.
Разделяемые методы позволяют разработчику одной части класса определить метод, схожий с событием.
Разработчик другой части класса может решить, реализовывать этот метод или нет. Если метод не
реализован, то компилятор удаляет сигнатуру метода и все вызовы этого метода. Вызовы метода, включая
любые результаты, которые могли бы произойти от оценки аргументов в вызовах, не имеют эффекта во
время выполнения. Таким образом, любой код в разделяемом классе может свободно использовать
разделяемый метод, даже если реализация не предоставлена. Во время компиляции и выполнения
программы не возникнут никакие ошибки, если метод будет вызван, но не реализован.
Разделяемые методы особенно полезны для настройки автоматически созданного кода. Они позволяют
зарезервировать имя и сигнатуру метода, чтобы автоматически созданный код мог вызвать метод, а
разработчик мог сам решить, реализовывать этот метод или нет. Как и разделяемые классы, разделяемые
методы позволяют организовать совместную работу автоматически созданного кода и кода, созданного
человеком, без дополнительных затрат во время выполнения.
Объявление разделяемого метода состоит из двух частей: определения и реализации. Они могут
находиться в разных частях или в одной и той же части разделяемого класса. Если объявление реализации
отсутствует, то компилятор оптимизирует код, удаляя как объявление определения, так и все вызовы
метода.
// Definition in file1.cs
partial void onNameChanged();
// Implementation in file2.cs
partial void onNameChanged()
{
// method body
}
Спецификация языка C#
Дополнительные сведения см. в разделе Разделяемые типы в Спецификации языка C#. Спецификация
языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Классы
Структуры
Интерфейсы
partial (тип)
Анонимные типы (Руководство по
программированию в C#)
25.11.2019 • 6 minutes to read • Edit Online
Анонимные типы позволяют легко инкапсулировать свойства только для чтения в один объект без
необходимости предварительного определения типа. Имя типа создается компилятором и
недоступно на уровне исходного кода. Тип каждого свойства выводится компилятором.
Анонимные типы создаются с помощью оператора new и инициализатора объекта.
Дополнительные сведения об инициализаторах объектов см. в статье Инициализаторы объектов и
коллекций.
В следующем примере показан анонимный тип, инициализированный с помощью двух свойств —
Amount и Message .
// Rest the mouse pointer over v.Amount and v.Message in the following
// statement to verify that their inferred types are int and string.
Console.WriteLine(v.Amount + v.Message);
var productQuery =
from prod in products
select new { prod.Color, prod.Price };
var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};
Примечания
Анонимные типы являются типами class, прямыми производными от типа object, и не могут быть
приведены ни к какому иному типу, кроме object. Компилятор назначает имя для каждого
анонимного типа, несмотря на то что для вашего приложения он недоступен. С точки зрения среды
CLR анонимный тип не отличается от других ссылочных типов.
Если два или несколько инициализаторов анонимных объектов в сборке указывают на
последовательность свойств, идущих в том же порядке и имеющих те же типы и имена, компилятор
обрабатывает объекты как экземпляры одного типа. Они используют одни и те же сведения типа,
созданные компилятором.
Никакое поле, свойство, событие или тип возвращаемого значения метода невозможно объявить,
используя анонимный тип. Точно так же нельзя объявить с помощью анонимного типа ни один
формальный параметр метода, свойства, конструктора или индексатора. Для передачи анонимного
типа или коллекции, содержащей анонимные типы, как аргумента для метода можно использовать
этот параметр в качестве объекта типа. Однако подобное решение противоречит цели строгой
типизации. Если вам нужно сохранить результаты запроса или передать их за пределы метода,
используйте вместо анонимного типа структуру или класс, названные обычным образом.
Так как методы Equals и GetHashCode в анонимных типах определяются через методы Equals и
GetHashCode свойств, два экземпляра одного и того же анонимного типа равны, только если равны
их свойства.
См. также
Руководство по программированию на C#
Инициализаторы объектов и коллекций
Приступая к работе с LINQ в C#
LINQ в C#
Практическое руководство. Возвращение
поднаборов свойств элементов в запросе
(руководство по программированию на C#)
25.11.2019 • 2 minutes to read • Edit Online
Используйте анонимный тип в выражении запроса, если выполняются оба следующих условия:
требуется возвращать только некоторые свойства каждого исходного элемента;
не требуется хранить результаты запроса за пределами области метода, в котором выполняется
запрос.
Если требуется возвращать только одно свойство или поле из каждого исходного элемента, вы можете
использовать оператор "точка" в предложении select . Например, чтобы вернуть только ID для каждого
элемента student , напишите предложение select следующим образом:
select student.ID;
Пример
В следующем примере показано, как использовать анонимный тип для возвращения только конкретного
набора свойств каждого исходного элемента, соответствующего указанному условию.
Обратите внимание, что анонимный тип использует имена исходных элементов для соответствующих
свойств, если имена не заданы. Чтобы присваивать новые имена свойствам в анонимном типе, напишите
инструкцию select следующим образом:
select new { First = student.FirstName, Last = student.LastName };
Если вы попробуете сделать это в предыдущем примере, также необходимо изменить инструкцию
Console.WriteLine :
Компиляция кода
Чтобы выполнить этот код, скопируйте и вставьте класс в консольное приложение C# с директивой using
для пространства имен System.Linq.
См. также
Руководство по программированию на C#
Анонимные типы
LINQ в C#
Интерфейсы (Руководство по
программированию в C#)
01.11.2019 • 7 minutes to read • Edit Online
interface IEquatable<T>
{
bool Equals(T obj);
}
Имя структуры должно быть допустимым именем идентификатора C#. По соглашению имена
интерфейсов начинаются с заглавной буквы I .
Любой объект (класс или структура), реализующий интерфейс IEquatable<T>, должен содержать
определение для метода Equals, соответствующее сигнатуре, которую задает интерфейс. В результате
вы можете быть уверены, что класс, реализующий IEquatable<T> , содержит метод Equals , с помощью
которого экземпляр этого класса может определить, равен ли он другому экземпляру того же класса.
Определение IEquatable<T> не предоставляет реализацию для метода Equals . Класс или структура
может реализовывать несколько интерфейсов, но класс может наследовать только от одного класса.
Дополнительные сведения об абстрактных классах см. в разделе Абстрактные и запечатанные классы и
члены классов.
Интерфейсы могут содержать методы, свойства, события, индексаторы, а также любое сочетание этих
четырех типов членов. Ссылки на примеры см. в разделе Связанные разделы. Интерфейс не может
содержать константы, поля, операторы, конструкторы экземпляров, методы завершения или типы.
Члены интерфейса автоматически являются открытыми, и они не могут включать модификаторы
доступа. Члены также не могут быть статическими.
Для реализации члена интерфейса соответствующий член реализующего класса должен быть
открытым и не статическим, а также иметь такое же имя и сигнатуру, что и член интерфейса.
Если класс (или структура) реализует интерфейс, этот класс (или структура) должен предоставлять
реализацию для всех членов, которые определяет этот интерфейс. Сам интерфейс не предоставляет
функциональность, которую класс или структура может наследовать таким же образом, как можно
наследовать функциональность базового класса. Однако если базовый класс реализует интерфейс, то
любой класс, производный от базового класса, наследует эту реализацию.
В следующем примере показана реализация интерфейса IEquatable<T>. Реализующий класс Car
должен предоставлять реализацию метода Equals.
public class Car : IEquatable<Car>
{
public string Make {get; set;}
public string Model { get; set; }
public string Year { get; set; }
Свойства и индексаторы класса могут определять дополнительные методы доступа для свойства или
индексатора, определенного в интерфейсе. Например, интерфейс может объявлять свойство, имеющее
акцессор get. Класс, реализующий этот интерфейс, может объявлять это же свойство с обоими
акцессорами ( get и set). Однако если свойство или индексатор использует явную реализацию,
методы доступа должны совпадать. Дополнительные сведения о явной реализации см. в статьях Явная
реализация интерфейса и Свойства интерфейса.
Интерфейсы могут наследовать от других интерфейсов. Класс может включать интерфейс несколько
раз через наследуемые базовые классы или через интерфейсы, которые наследуются другими
интерфейсами. Однако класс может предоставить реализацию интерфейса только однократно и
только если класс объявляет интерфейс как часть определения класса ( class ClassName : InterfaceName
). Если интерфейс наследуется, поскольку наследуется базовый класс, реализующий этот интерфейс, то
базовый класс предоставляет реализацию членов этого интерфейса. Но производный класс может
повторно реализовать любые члены виртуального интерфейса и не использовать наследованную
реализацию.
Базовый класс также может реализовывать члены интерфейса с помощью виртуальных членов. В
таком случае производный класс может изменять поведение интерфейса путем переопределения
виртуальных членов. Дополнительные сведения о виртуальных членах см. в статье Полиморфизм.
Сводка по интерфейсам
Интерфейс имеет следующие свойства.
Интерфейс подобен абстрактному базовому классу, имеющему только абстрактные члены. Любой
класс (или структура), реализующий интерфейс, должен реализовывать все его члены.
Невозможно создать экземпляр интерфейса напрямую. Его члены реализуются любым классом
(или структурой), реализующим интерфейс.
Интерфейсы могут содержать события, индексаторы, методы и свойства.
Интерфейсы не содержат реализацию методов.
Класс или структура может реализовывать несколько интерфейсов. Класс может наследовать
базовому классу и также реализовывать один или несколько интерфейсов.
Содержание раздела
Явная реализация интерфейса
В этом разделе объясняется, как создать член класса, который относится к интерфейсу.
Практическое руководство. Явная реализация элементов интерфейса
В этом разделе содержится пример явной реализации членов интерфейсов.
Практическое руководство. Явная реализация элементов двух интерфейсов
В этом разделе содержится пример явной реализации членов интерфейсов с помощью наследования.
Связанные разделы
Свойства интерфейса
Индексаторы в интерфейсах
Практическое руководство. Реализация событий интерфейса
Классы и структуры
Наследование
Методы
Полиморфизм
Абстрактные и запечатанные классы и члены классов
Свойства
События
Индексаторы
См. также
Руководство по программированию на C#
Наследование
Имена идентификаторов
Явная реализация интерфейса (Руководство по
программированию в C#)
23.10.2019 • 2 minutes to read • Edit Online
Если класс реализует два интерфейса, содержащих член с одинаковой сигнатурой, при реализации такого
члена в классе оба интерфейса будут использовать этот член в качестве собственной реализации. В
следующем примере все вызовы Paint приводят к вызову одного и того же метода.
class Test
{
static void Main()
{
SampleClass sc = new SampleClass();
IControl ctrl = sc;
ISurface srfc = sc;
interface IControl
{
void Paint();
}
interface ISurface
{
void Paint();
}
class SampleClass : IControl, ISurface
{
// Both ISurface.Paint and IControl.Paint call this method.
public void Paint()
{
Console.WriteLine("Paint method in SampleClass");
}
}
// Output:
// Paint method in SampleClass
// Paint method in SampleClass
// Paint method in SampleClass
Если два члена интерфейса выполняют разные функции, это может привести к некорректной реализации
одного или обоих интерфейсов. Член интерфейса можно реализовать явно, создав член класса, который
вызывается только через этот интерфейс и относится только к нему. Для этого в имени члена класса
указывается имя интерфейса, после которого следует точка. Например:
public class SampleClass : IControl, ISurface
{
void IControl.Paint()
{
System.Console.WriteLine("IControl.Paint");
}
void ISurface.Paint()
{
System.Console.WriteLine("ISurface.Paint");
}
}
Член класса IControl.Paint доступен только через интерфейс IControl , а ISurface.Paint — только через
интерфейс ISurface . Обе реализации метода будут разделены, и ни одна из них не будет доступна в классе
напрямую. Например:
IControl c = obj;
c.Paint(); // Calls IControl.Paint on SampleClass.
ISurface s = obj;
s.Paint(); // Calls ISurface.Paint on SampleClass.
// Output:
// IControl.Paint
// ISurface.Paint
Явная реализация также применяется в случаях, когда в каждом из двух интерфейсов объявляются разные
члены (например, свойство и метод) с одинаковыми именами:
interface ILeft
{
int P { get;}
}
interface IRight
{
int P();
}
Чтобы реализовать оба интерфейса и избежать ошибок компилятора, класс должен использовать явную
реализацию либо свойства P, либо метода P, либо одновременно обоих этих членов. Например:
См. также
Руководство по программированию на C#
Классы и структуры
Интерфейсы
Наследование
Практическое руководство. Руководство по
программированию на C#. Явная реализация
членов интерфейса
23.10.2019 • 2 minutes to read • Edit Online
В этом примере объявляются интерфейс IDimensions и класс Box , который явно реализует члены
интерфейса getLength и getWidth . Доступ к членам осуществляется посредством экземпляра интерфейса
dimensions .
Пример
interface IDimensions
{
float getLength();
float getWidth();
}
Отказоустойчивость
Обратите внимание, что следующие строки в методе Main закомментированы, поскольку при их
компиляции возникнут ошибки. Член интерфейса, реализованный явным образом, недоступен из
экземпляра класса:
Также обратите внимание, что следующие строки в методе Main успешно выводят на печать
размеры поля, поскольку методы вызываются из экземпляра интерфейса:
System.Console.WriteLine("Length: {0}", dimensions.getLength());
System.Console.WriteLine("Width: {0}", dimensions.getWidth());
См. также
Руководство по программированию на C#
Классы и структуры
Интерфейсы
Практическое руководство. Явная реализация элементов двух интерфейсов
Как выполнить явную реализацию членов двух
интерфейсов (руководство по программированию
на C#)
23.10.2019 • 2 minutes to read • Edit Online
Пример
// Declare the English units interface:
interface IEnglishDimensions
{
float Length();
float Width();
}
Отказоустойчивость
Если требуется использовать единицы английской системы мер по умолчанию, реализуйте методы Length и
Width обычным способом, после чего явно реализуйте методы Length и Width из интерфейса
IMetricDimensions:
// Normal implementation:
public float Length() => lengthInches;
public float Width() => widthInches;
// Explicit implementation:
float IMetricDimensions.Length() => lengthInches * 2.54f;
float IMetricDimensions.Width() => widthInches * 2.54f;
В этом случае единицы английской системы мер будут доступны из экземпляра класса, а метрические
единицы — из экземпляра интерфейса:
См. также
Руководство по программированию на C#
Классы и структуры
Интерфейсы
Практическое руководство. Явная реализация элементов интерфейса
Типы перечислений (Руководство по
программированию в C#)
25.11.2019 • 7 minutes to read • Edit Online
По умолчанию базовым типом каждого элемента перечисления является int. Можно задать другой
целочисленный тип, используя двоеточие, как показано в предыдущем примере. Полный список
возможных типов см. в разделе enum (справочник по C#).
Чтобы проверить основные числовые значения путем приведения в базовый тип, как показано в
следующем примере.
// Output:
// Monday is day number #1.
// Dec is month number #11.
NOTE
Переменной meetingDay можно присвоить любое произвольное целое значение. Например, эта строка кода не
создает ошибку: meetingDay = (Day) 42 . Но этого делать нельзя, поскольку неявно ожидается, что переменная
перечисления принимает одно из значений, определяемых перечислением. Присвоение переменной типа
перечисления произвольного значения связано с большим риском возникновения ошибок.
Элементам списка перечислителя типа перечисления можно присвоить любые значения, и можно также
использовать вычисленные значения:
enum MachineState
{
PowerOff = 0,
Running = 5,
Sleeping = 10,
Hibernating = Sleeping + 5
}
[Flags]
enum Days
{
None = 0b_0000_0000, // 0
Sunday = 0b_0000_0001, // 1
Monday = 0b_0000_0010, // 2
Tuesday = 0b_0000_0100, // 4
Wednesday = 0b_0000_1000, // 8
Thursday = 0b_0001_0000, // 16
Friday = 0b_0010_0000, // 32
Saturday = 0b_0100_0000 // 64
}
class MyClass
{
Days meetingDays = Days.Tuesday | Days.Thursday;
}
Чтобы определить, установлен ли конкретный флаг, используйте побитовый оператор AND , как показано в
следующем примере:
Дополнительные сведения о том, что необходимо учитывать при определении типов перечислений при
помощи атрибута System.FlagsAttribute, см. в статье System.Enum.
См. также
System.Enum
Руководство по программированию на C#
enum
Делегаты (Руководство по программированию
на C#)
04.11.2019 • 4 minutes to read • Edit Online
Делегат — это тип, который представляет ссылки на методы с определенным списком параметров и
типом возвращаемого значения. При создании экземпляра делегата этот экземпляр можно связать с
любым методом с совместимой сигнатурой и типом возвращаемого значения. Метод можно вызвать
(активировать) с помощью экземпляра делегата.
Делегаты используются для передачи методов в качестве аргументов к другим методам. Обработчики
событий — это ничто иное, как методы, вызываемые с помощью делегатов. При создании
пользовательского метода класс (например, элемент управления Windows) может вызывать этот метод
при появлении определенного события. В следующем примере показано объявление делегата:
Делегату можно назначить любой метод из любого доступного класса или структуры, соответствующей
типу делегата. Этот метод должен быть статическим методом или методом экземпляра. Это позволяет
программно изменять вызовы метода, а также включать новый код в существующие классы.
NOTE
В контексте перегрузки метода его сигнатура не содержит возвращаемое значение. Однако в контексте делегатов
сигнатура метода содержит возвращаемое значение. Другими словами, метод должен иметь тот же
возвращаемый тип, что и делегат.
Благодаря возможности ссылаться на метод как на параметр делегаты идеально подходят для
определения методов обратного вызова. Например, ссылка на метод, сравнивающий два объекта, может
быть передана в качестве аргумента алгоритму сортировки. Поскольку код сравнения находится в
отдельной процедуре, алгоритм сортировки может быть написан более общим способом.
В этом разделе
Использование делегатов
Использование делегатов вместо интерфейсов (руководство по программированию на C#)
Делегаты с именованными методами и делегаты с анонимными методами
Использование расхождения в делегатах
Практическое руководство. Объединение делегатов (многоадресные делегаты)
Практическое руководство. Объявление, создание и использование делегата
Спецификация языка C#
Дополнительные сведения см. в разделе Делегаты в Спецификации языка C#. Спецификация языка
является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Delegate
Руководство по программированию на C#
События
Использование делегатов (Руководство по
программированию на C#)
04.11.2019 • 9 minutes to read • Edit Online
Делегат — это тип, который безопасно инкапсулирует метод, схожий с указателем функции в C и C++. В
отличие от указателей функций в C делегаты объектно-ориентированы, типобезопасны и безопасны. Тип
делегата задается его именем. В следующем примере объявляется делегат с именем Del , который может
инкапсулировать метод, использующий в качестве аргумента значение string и возвращающий значение
void:
Объект делегата обычно создается путем указания имени метода, для которого делегат будет служить
оболочкой, или с помощью анонимной функции. После создания экземпляра делегата вызов метода,
выполненный в делегате передается делегатом в этот метод. Параметры, передаваемые делегату
вызывающим объектом, передаются в метод, а возвращаемое методом значение (при его наличии)
возвращается делегатом в вызывающий объект. Эта процедура называется вызовом делегата. Делегат, для
которого создан экземпляр, можно вызвать, как если бы это был метод, для которого создается оболочка.
Например:
Типы делегатов являются производными от класса Delegate в платформа.NET Framework. Типы делегатов
являются запечатанными — от них нельзя наследовать, а от Delegate нельзя создавать производные
пользовательские классы. Поскольку созданный экземпляр делегата является объектом, его можно
передавать как параметр или назначать свойству. Это позволяет методу принимать делегат в качестве
параметра и вызывать делегат в дальнейшем. Эта процедура называется асинхронным обратным вызовом и
обычно используется для уведомления вызывающего объекта о завершении длительной операции. Когда
делегат используется таким образом, коду, использующему делегат, не требуются сведения о реализации
используемого метода. Данные функциональные возможности аналогичны возможностям,
предоставляемым интерфейсами инкапсуляции.
Обратный вызов также часто используется для задания настраиваемого метода сравнения и передачи этого
делегата в метод сортировки. Это позволяет сделать коду вызывающего объекта частью алгоритма
сортировки. В следующем примере метод использует тип Del тип в качестве параметра.
public static void MethodWithCallback(int param1, int param2, Del callback)
{
callback("The number is: " + (param1 + param2).ToString());
}
MethodWithCallback(1, 2, handler);
Вместе с рассмотренным ранее статическим методом DelegateMethod есть три метода, для которых можно
создать оболочку с помощью экземпляра Del .
При вызове делегат может вызывать сразу несколько методов. Это называется многоадресностью. Чтобы
добавить в список методов делегата (список вызова) дополнительный метод, необходимо просто добавить
два делегата с помощью оператора сложения или назначения сложения ("+" или "+="). Например:
На данном этапе список вызова делегата allMethodsDelegate содержит три метода — Method1 , Method2 и
DelegateMethod . Три исходных делегата d1 , d2 и d3 остаются без изменений. При вызове
allMethodsDelegate все три метода вызываются по порядку. Если делегат использует параметры,
передаваемые по ссылке, эта ссылка передается после каждого из трех методов, а все изменения одного из
методов становятся видны в следующем методе. Если любой из методов вызывает неперехваченное
исключение, это исключение передается в вызывающий делегат объект, а последующие методы в списке
вызова не вызываются. Если делегат имеет возвращаемое значение и (или) выходные параметры, он
возвращает возвращаемое значение и параметры последнего вызванного метода. Чтобы удалить метод из
списка вызовов, используйте вычитание или операторы присваивания вычитания ( - или -= ). Например:
//remove Method1
allMethodsDelegate -= d1;
Поскольку типы делегата являются производными от System.Delegate , в делегате можно вызывать методы и
свойства, определенные этим классом. Например, чтобы определить число методов в списке вызова делегата,
можно использовать код:
См. также
Руководство по программированию на C#
Делегаты
Использование расхождения в делегатах
Расхождение в делегатах
Использование расхождения в универсальных методах-делегатах Func и Action
События
Делегаты с именованными методами и Анонимные
методы (Руководство по программированию в C#)
04.11.2019 • 3 minutes to read • Edit Online
Делегат можно связать с именованным методом. При создании экземпляра делегата с использованием
именованного метода этот метод передается в качестве параметра, например:
// Declare a delegate.
delegate void Del(int x);
Примечания
Метод, который передается как параметр делегата, должен иметь такую же сигнатуру, что и объявление
делегата.
Экземпляр делегата может инкапсулировать статический метод или метод экземпляра.
Несмотря на то, что делегат может использовать параметр out, не рекомендуется делать это для делегатов
многоадресных событий, поскольку в этом случае невозможно определить, какой делегат будет вызван.
Пример 1
Ниже приведен простой пример объявления и использования делегата. Обратите внимание, что делегат Del
и связанный с ним метод MultiplyNumbers имеют одинаковые сигнатуры.
// Declare a delegate
delegate void Del(int i, double j);
class MathClass
{
static void Main()
{
MathClass m = new MathClass();
Пример 2
В следующем примере делегат, сопоставленный одновременно со статическим методом и методом
экземпляра, возвращает информацию из каждого из них.
// Declare a delegate
delegate void Del();
class SampleClass
{
public void InstanceMethod()
{
Console.WriteLine("A message from the instance method.");
}
class TestSampleClass
{
static void Main()
{
var sc = new SampleClass();
См. также
Руководство по программированию на C#
Делегаты
Практическое руководство. Объединение делегатов (многоадресные делегаты)
События
Практическое руководство. Руководство по
программированию на C#. Объединение
делегатов (многоадресные делегаты)
04.11.2019 • 2 minutes to read • Edit Online
В этом примере показано создание многоадресных делегатов. Объекты делегатов обладают полезным
свойством, благодаря которому одному экземпляру делегата с помощью оператора + можно присвоить
несколько объектов. Многоадресный делегат содержит список присвоенных ему делегатов. При вызове
многоадресного делегата поочередно вызываются представленные в его списке делегаты. Объединять
можно только делегаты одного типа.
С помощью оператора - можно удалить делегат, входящий в состав многоадресного делегата.
Пример
using System;
// Define a custom delegate that has a string parameter and returns void.
delegate void CustomDel(string s);
class TestClass
{
// Define two methods that have the same signature as CustomDel.
static void Hello(string s)
{
Console.WriteLine($" Hello, {s}!");
}
В C# 1.0 и более поздних версий делегаты можно объявлять так, как показано в следующем примере.
// Declare a delegate.
delegate void Del(string str);
В C# 2.0 предоставлен более простой способ для записи предыдущего объявления, который показан в
следующем примере.
В C# 2.0 и более поздних версиях можно использовать анонимный метод для объявления и инициализации
делегата, как показано в следующем примере.
В C# 3.0 и более поздних версиях можно объявлять делегаты и создавать для них экземпляры с помощью
лямбда-выражения, как показано в следующем примере.
Пример
// A set of classes for handling a bookstore:
namespace Bookstore
{
using System.Collections;
Каждый тип делегата описывает количество аргументов и их типы, а также тип возвращаемого
значения для всех методов, которые он может инкапсулировать. Каждый раз, когда требуется новый
набор типов аргументов или новый тип возвращаемого значения, нужно объявить новый тип
делегата.
Создания экземпляра делегата.
После объявления типа делегата нужно создать объект этого делегата и связать его с определенным
методом. Продолжая предыдущий пример, вы можете передать метод PrintTitle в метод
ProcessPaperbackBooks , как показано в следующем примере:
bookDB.ProcessPaperbackBooks(PrintTitle);
bookDB.ProcessPaperbackBooks(totaller.AddBookToTotal);
processBook(b);
Делегат можно вызвать синхронно, как показано в этом примере, или асинхронно при помощи
методов BeginInvoke и EndInvoke .
См. также
Руководство по программированию на C#
События
Делегаты
Массивы (Руководство по программированию на
C#)
24.10.2019 • 2 minutes to read • Edit Online
В структуре данных массива можно хранить несколько переменных одного типа. Чтобы объявить массив,
следует указать тип его элементов. Если требуется, чтобы массив мог хранить элементы любого типа,
можно указать object в качестве его типа. В унифицированной системе типов C# все типы, стандартные и
определяемые пользователем, ссылочные типы и типы значений напрямую или косвенно наследуются из
Object.
type[] arrayName;
Пример
В следующих примерах создаются одномерные массивы, многомерные массивы и массивы массивов:
class TestArraysClass
{
static void Main()
{
// Declare a single-dimensional array.
int[] array1 = new int[5];
// Alternative syntax.
int[] array3 = { 1, 2, 3, 4, 5, 6 };
// Set the values of the first array in the jagged array structure.
jaggedArray[0] = new int[4] { 1, 2, 3, 4 };
}
}
Связанные разделы
Массивы как объекты
Использование оператора foreach с массивами
Передача массивов в качестве аргументов
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Коллекции
Массивы как объекты (Руководство по
программированию на C#)
31.10.2019 • 2 minutes to read • Edit Online
В C# массивы представляют собой реальные объекты, а не просто адресуемые области непрерывной памяти,
как в C и C++. Array — это абстрактный базовый тип для всех типов массивов. Вы можете использовать
свойства и другие члены класса, входящие в Array. Например, с помощью свойства Length можно получить
длину массива. В следующем коде значение длины массива numbers ( 5 ) присваивается переменной с
именем lengthOfNumbers :
int[] numbers = { 1, 2, 3, 4, 5 };
int lengthOfNumbers = numbers.Length;
В классе Array представлено множество других полезных методов и свойств для сортировки, поиска и
копирования массивов.
Пример
В этом примере используется свойство Rank, позволяющее отобразить число измерений массива.
class TestArraysClass
{
static void Main()
{
// Declare and initialize an array.
int[,] theArray = new int[5, 10];
System.Console.WriteLine("The array has {0} dimensions.", theArray.Rank);
}
}
// Output: The array has 2 dimensions.
См. также
Руководство по программированию на C#
Массивы
Одномерные массивы
Многомерные массивы
Массивы массивов
Одномерные массивы (Руководство по
программированию на C#)
04.11.2019 • 2 minutes to read • Edit Online
Вы можете объявить одномерный массив, содержащий пять целых чисел, как показано в следующем
примере:
Этот массив содержит элементы с array[0] по array[4] . С помощью оператора new можно создать массив
и инициализировать его элементы, используя значения по умолчанию. В этом примере при инициализации
всем элементам массива присваиваются нулевые значения.
Таким же образом можно объявить массив, в котором хранятся строковые элементы. Например:
Инициализация массива
Массив можно инициализировать при объявлении. В этом случае не требуется спецификатор длины,
поскольку он уже задан по числу элементов в списке инициализации. Например:
Массив строк можно инициализировать таким же образом. Ниже приведено объявление массива строк, где
каждый элемент массива инициализируется с использованием названия дня:
string[] weekDays = new string[] { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
Если массив инициализируется при объявлении, вы можете использовать следующие сочетания клавиш:
int[] array2 = { 1, 3, 5, 7, 9 };
string[] weekDays2 = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
Переменную массива можно объявить без инициализации, однако при присвоении массива этой
переменной необходимо использовать оператор new . Например:
int[] array3;
array3 = new int[] { 1, 3, 5, 7, 9 }; // OK
//array3 = {1, 3, 5, 7, 9}; // Error
В C# 3.0 представлены неявно типизированные массивы. Дополнительные сведения см. в разделе Неявно
типизированные массивы.
Результат этого оператора зависит от того, является ли SomeType типом значения или ссылочным типом.
Если это тип значения, оператор создает массив из 10 элементов, каждый из которых имеет тип SomeType .
Если SomeType является ссылочным типом, этот оператор создает массив из 10 элементов, каждый из
которых инициализируется с использованием ссылки NULL.
Дополнительные сведения о типах значений и ссылочных типах см. в разделе Типы.
См. также
Array
Руководство по программированию на C#
Массивы
Многомерные массивы
Массивы массивов
Многомерные массивы (Руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Массивы могут иметь несколько измерений. Например, следующее объявление создает двухмерный массив
из четырех строк и двух столбцов.
Инициализация массива
Массив можно инициализировать при объявлении, как показано в следующем примере.
// Two-dimensional array.
int[,] array2D = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
// The same array with dimensions specified.
int[,] array2Da = new int[4, 2] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
// A similar array with string elements.
string[,] array2Db = new string[3, 2] { { "one", "two" }, { "three", "four" },
{ "five", "six" } };
// Three-dimensional array.
int[, ,] array3D = new int[,,] { { { 1, 2, 3 }, { 4, 5, 6 } },
{ { 7, 8, 9 }, { 10, 11, 12 } } };
// The same array with dimensions specified.
int[, ,] array3Da = new int[2, 2, 3] { { { 1, 2, 3 }, { 4, 5, 6 } },
{ { 7, 8, 9 }, { 10, 11, 12 } } };
// Output:
// 1
// 2
// 3
// 4
// 7
// three
// 8
// 12
// 12 equals 12
int[,] array4 = { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } };
Чтобы объявить переменную массива без инициализации, используйте оператор new для присвоения
массива переменной. Использование оператора new показано в следующем примере.
int[,] array5;
array5 = new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } }; // OK
//array5 = {{1,2}, {3,4}, {5,6}, {7,8}}; // Error
array5[2, 1] = 25;
См. также
Руководство по программированию на C#
Массивы
Одномерные массивы
Массивы массивов
Массивы массивов (Руководство по
программированию на C#)
23.10.2019 • 4 minutes to read • Edit Online
Массив массивов — это массив, элементы которого сами являются массивами. Элементы массива массивов
могут иметь различные измерения и размеры. Массив массивов иногда называется нерегулярным массивом.
В следующих примерах показано, как объявлять и инициализировать массивы массивов, а также получать
доступ к ним.
Ниже объявляется одномерный массив из трех элементов, каждый из которых является одномерным
массивом целых чисел:
Прежде чем использовать jaggedArray , его элементы необходимо инициализировать. Это можно сделать
следующим образом:
Каждый элемент представляет собой одномерный массив целых чисел. Первый из них содержит 5 целых
чисел, второй — 4, а третий — 2.
Кроме того, с помощью инициализаторов можно заполнять элементы массива значениями (при этом вам
не потребуется знать размер массива). Например:
Можно использовать следующую краткую форму. Обратите внимание, что при инициализации элементов
нельзя опускать оператор new , поскольку механизм инициализации по умолчанию для них не
предусмотрен:
int[][] jaggedArray3 =
{
new int[] { 1, 3, 5, 7, 9 },
new int[] { 0, 2, 4, 6 },
new int[] { 11, 22 }
};
В этом примере демонстрируется доступ к отдельным элементам, для чего отображается значение
элемента [1,0] первого массива ( 5 ):
Метод Length возвращает число массивов, содержащихся в массиве массивов. Допустим, предыдущий
массив был объявлен с использованием следующей строки:
System.Console.WriteLine(jaggedArray4.Length);
возвращает значение 3.
Пример
В этом примере создается массив, элементы которого являются массивами. Все элементы массива имеют
разный размер.
class ArrayTest
{
static void Main()
{
// Declare the array of two elements.
int[][] arr = new int[2][];
См. также
Array
Руководство по программированию на C#
Массивы
Одномерные массивы
Многомерные массивы
Использование оператора foreach с массивами
(Руководство по программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Оператор foreach обеспечивает простой и понятный способ итерации по элементам массива или любой
перечислимой коллекции.
Для одномерных массивов оператор foreach обрабатывает элементы в порядке возрастания индекса,
начиная с индекса 0 и заканчивая индексом Length - 1 :
Для многомерных массивов элементов обрабатываются так, что сначала увеличиваются индексы крайнего
правого измерения, а затем — следующего левого и т. д. влево:
Тем не менее для управления порядком обрабатываемых элементов в многомерных массивах можно
использовать вложенный цикл for.
См. также
Array
Руководство по программированию на C#
Массивы
Одномерные массивы
Многомерные массивы
Массивы массивов
Передача массивов в качестве аргументов
(руководство по программированию на C#)
23.10.2019 • 4 minutes to read • Edit Online
Массивы можно передавать в качестве аргументов в параметры метода. Поскольку массивы представляют
собой ссылочные типы, метод может изменять значения элементов.
int[] theArray = { 1, 3, 5, 7, 9 };
PrintArray(theArray);
Новый массив можно инициализировать и передать за один шаг, как показано в следующем примере.
Пример
В следующем примере массив строк инициализируется и передается в качестве аргумента в метод
DisplayArray для строк. Этот метод отображает элементы массива. Затем метод ChangeArray размещает
элементы массива в обратном порядке, а метод ChangeArrayElements изменяет первые три элемента массива.
После возврата каждого метода метод DisplayArray показывает, что передача массива по значению не
препятствует изменению элементов массива.
using System;
class ArrayExample
{
static void DisplayArray(string[] arr) => Console.WriteLine(string.Join(" ", arr));
int[,] theArray = { { 1, 2 }, { 2, 3 }, { 3, 4 } };
Print2DArray(theArray);
В следующем коде показано разделяемое объявление метода печати, который принимает в качестве
аргумента двухмерный массив.
void Print2DArray(int[,] arr)
{
// Method code.
}
Новый массив можно инициализировать и передать за один шаг, как показано в следующем примере.
Пример
В следующем примере инициализируется двухмерный массив целых чисел, который передается в метод
Print2DArray . Этот метод отображает элементы массива.
class ArrayClass2D
{
static void Print2DArray(int[,] arr)
{
// Display the array elements.
for (int i = 0; i < arr.GetLength(0); i++)
{
for (int j = 0; j < arr.GetLength(1); j++)
{
System.Console.WriteLine("Element({0},{1})={2}", i, j, arr[i, j]);
}
}
}
static void Main()
{
// Pass the array as an argument.
Print2DArray(new int[,] { { 1, 2 }, { 3, 4 }, { 5, 6 }, { 7, 8 } });
См. также
Руководство по программированию на C#
Массивы
Одномерные массивы
Многомерные массивы
Массивы массивов
Неявно типизированные массивы (Руководство по
программированию на C#)
04.11.2019 • 2 minutes to read • Edit Online
Вы можете создать неявно типизированный массив, тип экземпляра которого будет определяться на основе
элементов, указанных в инициализаторе массива. В отношении таких массивов действуют правила для
неявно типизированных переменных. Дополнительные сведения см. в статье Implicitly Typed Local Variables
(Неявно типизированные локальные переменные).
Неявно типизированные массивы обычно используются в выражениях запросов вместе с анонимными
типами, а также инициализаторами объектов и коллекций.
В следующих примерах демонстрируется создание неявно типизированного массива:
class ImplicitlyTypedArraySample
{
static void Main()
{
var a = new[] { 1, 10, 100, 1000 }; // int[]
var b = new[] { "hello", null, "world" }; // string[]
Обратите внимание, что в предыдущем примере с неявно типизированными массивами в левой части
выражения инициализации не используются квадратные скобки. Также обратите внимание, что массивы
массивов инициализируются с помощью выражения new [] , как и одномерные массивы.
См. также
Руководство по программированию на C#
Неявно типизированные локальные переменные
Массивы
Анонимные типы
Инициализаторы объектов и коллекций
var
LINQ в C#
Строки (Руководство по программированию на
C#)
25.11.2019 • 19 minutes to read • Edit Online
Строка — это объект типа String, значением которого является текст. Внутри программы текст хранится в
виде упорядоченной коллекции объектов Char только для чтения. В конце строки C# нет нуль-символов.
Поэтому строка C# может содержать любое число внедренных нуль-символов ('\0'). Свойство Length
строки соответствует числу содержащихся в ней объектов Char , но не числу символов Юникода. Для
доступа к отдельным кодовым точкам Юникода в строке используйте объект StringInfo.
// Initialize to null.
string message2 = null;
Обратите внимание, что вы не используете оператор new для создания объекта строки, за исключением
случаев инициализации строки с помощью массива символов.
Инициализируйте строку с константным значением Empty для создания нового объекта String, строка
которого имеет нулевую длину. Представлением строкового литерала строки с нулевой длиной является "".
Если вы инициализируете строки со значением Empty вместо NULL, вы снизите вероятность появления
исключения NullReferenceException. Используйте статический метод IsNullOrEmpty(String), чтобы
проверить значение строки, прежде чем пытаться получить к ней доступ.
System.Console.WriteLine(s1);
// Output: A string is more than the sum of its chars.
Так как "изменение" строки на самом деле является созданием новой строки, создавать ссылки на строки
следует с осторожностью. Если вы создадите ссылку на строку, а затем "измените" исходную строку, ссылка
будет по-прежнему указывать на исходный объект, а не на новый объект, который был создан при
изменении строки. Это поведение проиллюстрировано в следующем коде:
System.Console.WriteLine(s2);
//Output: Hello
Сведения о создании новых строк, основанных на таких изменениях, как операции поиска и замены
исходной строки, см. в инструкциях по изменению содержимого строки.
Буквальные строковые литералы используются для удобства и читабельности, если текст строки содержит
символы обратной косой черты, например в путях к файлам. Так как буквальные строки сохраняют
символы новой строки как часть текста строки, их можно использовать для инициализации многострочных
строк. Используйте двойные кавычки, чтобы вставить кавычки в буквальной строке. В следующем примере
показаны наиболее часто используемым буквальные строки:
string filePath = @"C:\Users\scoleridge\Documents\";
//Output: C:\Users\scoleridge\Documents\
Escape-последовательности строк
ESCAPE-ПОСЛЕДОВАТЕЛЬНОСТЬ ИМЯ СИМВОЛА КОДИРОВКА ЮНИКОД
\0 Null 0x0000
\a Предупреждение 0x0007
\b Backspace 0x0008
NOTE
Во время компиляции буквальные строки преобразуются в обычные строки с теми же escape-
последовательностями. Поэтому, если вы просматриваете буквальную строку в окне контрольных значений
отладчика, вы увидите escape-символы, добавленные компилятором, а не буквальную версию из исходного кода.
Например, буквальная строка @"C:\files.txt" будет отображаться в окне контрольных значений как "C:\\files.txt".
Строки формата
Строка формата — это строка, содержимое которой можно определить динамически во время
выполнения. Строки формата создаются путем внедрения интерполированных выражений или
заполнителей внутри фигурных скобок в строке. Весь код внутри фигурных скобок ( {...} ) будет
преобразован в значение и выходные данные как отформатированная строка во время выполнения.
Существует два способа создания строк формата: интерполяция строк и составное форматирование.
Интерполяция строк
В C# 6.0 и более поздних версий интерполированные строки определяются по специальному символу $ .
Они включают интерполированные выражения в фигурных скобках. См. дополнительные сведения в
интерактивном руководстве по интерполяции строк в C#.
Используйте интерполяцию для повышения удобства чтения и обслуживаемости кода. Интерполяция
строк позволяет достичь тех же результатов, что и использование метода String.Format , но более простым
и понятным способом.
// Output:
// Jupiter Hammon was an African American poet born in 1711.
// He was first published in 1761 at the age of 50.
// He'd be over 300 years old today.
Составное форматирование
String.Format использует заполнители в фигурных скобках, чтобы создать строку формата. В этом примере
результат аналогичен выходным данным, получаемым с помощью метода интерполяции строк, описанного
выше.
var pw = (firstName: "Phillis", lastName: "Wheatley", born: 1753, published: 1773);
Console.WriteLine("{0} {1} was an African American poet born in {2}.", pw.firstName, pw.lastName, pw.born);
Console.WriteLine("She was first published in {0} at the age of {1}.", pw.published, pw.published - pw.born);
Console.WriteLine("She'd be over {0} years old today.", Math.Round((2018d - pw.born) / 100d) * 100d);
// Output:
// Phillis Wheatley was an African American poet born in 1753.
// She was first published in 1773 at the age of 20.
// She'd be over 300 years old today.
Подстроки
Подстрока — это последовательность символов, содержащихся в строке. Используйте метод Substring,
чтобы создать новую строку из части исходной строки. Одно вхождение подстроки или несколько можно
найти с помощью метода IndexOf. Используйте метод Replace, чтобы заменить все вхождения указанной
подстроки новой строкой. Как и метод Substring, метод Replace фактически возвращает новую строку и не
изменяет исходную строку. См. дополнительные сведения о поиске строк и изменении содержимого строк.
System.Console.WriteLine(s3.Replace("C#", "Basic"));
// Output: "Visual Basic Express"
Если вам необходимо изменить отдельные символы в строке и функций методов String вам недостаточно,
используйте объект StringBuilder, чтобы изменить отдельные символы "на месте", а затем создайте новую
строку для сохранения результатов с помощью методов StringBuilder. В следующем примере предположим,
что необходимо определенным образом изменить исходную строку, а затем сохранить результаты для
дальнейшего использования:
string question = "hOW DOES mICROSOFT wORD DEAL WITH THE cAPS lOCK KEY?";
System.Text.StringBuilder sb = new System.Text.StringBuilder(question);
string s = String.Empty;
В отличие от пустых строк строка NULL не ссылается на экземпляр объекта System.String, поэтому любая
попытка вызвать метод для строки NULL приводит к исключению NullReferenceException. Но вы можете
использовать строки NULL в операциях объединения и сравнения с другими строками. В следующих
примерах показаны случаи, в которых ссылка на строку NULL вызывает и не вызывает исключение:
static void Main()
{
string str = "hello";
string nullStr = null;
string emptyStr = String.Empty;
// The null character can be displayed and counted, like other chars.
string s1 = "\x0" + "abc";
string s2 = "abc" + "\x0";
// Output of the following line: * abc*
Console.WriteLine("*" + s1 + "*");
// Output of the following line: *abc *
Console.WriteLine("*" + s2 + "*");
// Output of the following line: 4
Console.WriteLine(s2.Length);
}
В этом примере объект StringBuilder используется для создания строки из набора числовых типов:
using System;
using System.Text;
namespace CSRefStrings
{
class TestStringBuilder
{
static void Main()
{
var sb = new StringBuilder();
См. также
РАЗДЕЛ ОПИСАНИЕ
Определение представления числового значения в строке Объясняет, как безопасно проанализировать строку,
чтобы проверить, содержит ли она допустимое числовое
значение.
РАЗДЕЛ ОПИСАНИЕ
Базовые операции со строками в .NET Framework Содержит ссылки на статьи, в которых показаны базовые
операции над строками с помощью методов System.String
и System.Text.StringBuilder.
Синтаксический анализ строк даты и времени в .NET Показывает, как преобразовать строку, например
"01/24/2008", в объект System.DateTime.
Сравнение строк в .NET Framework Объясняет, как сравнивать строки, и содержит примеры
на языках C# и Visual Basic.
Using the StringBuilder class (Использование класса Описывает создание и изменение динамических
StringBuilder) строковых объектов с помощью класса StringBuilder.
int i = 0;
string s = "108";
bool result = int.TryParse(s, out i); //i now = 108
Если строка содержит нечисловые знаки либо числовое значение слишком велико или мало для указанного
типа, TryParse возвращает значение "false" и задает выходному параметру значение "0". В противном
случае возвращается значение "true", а выходному параметру задается числовое значение строки.
NOTE
Строка может содержать только числовые знаки и оставаться недопустимой для типа, где используется метод
TryParse . Например, "256" не является допустимым значением для byte , однако оно допустимо для int . "98,6"
не является допустимым значением для int , однако оно допустимо для decimal .
Пример
В следующем примере показано использование TryParse со строковыми представлениями значений long ,
byte и decimal .
string numString = "1287543"; //"1287543.0" will return false for a long
long number1 = 0;
bool canConvert = long.TryParse(numString, out number1);
if (canConvert == true)
Console.WriteLine("number1 now = {0}", number1);
else
Console.WriteLine("numString is not a valid long");
byte number2 = 0;
numString = "255"; // A value of 256 will return false
canConvert = byte.TryParse(numString, out number2);
if (canConvert == true)
Console.WriteLine("number2 now = {0}", number2);
else
Console.WriteLine("numString is not a valid byte");
decimal number3 = 0;
numString = "27.3"; //"27" is also a valid decimal
canConvert = decimal.TryParse(numString, out number3);
if (canConvert == true)
Console.WriteLine("number3 now = {0}", number3);
else
Console.WriteLine("number3 is not a valid decimal");
Отказоустойчивость
Простые числовые типы также реализуют статический метод Parse , который вызывает исключение, если
строка не является допустимым числом. В целом оператор TryParse более эффективен, поскольку если
число не является допустимым, он просто возвращает значение "false".
См. также
Практическое руководство. Преобразование массива байтов в значение типа int
Практическое руководство. Преобразование строки в число
Практическое руководство. Преобразование из шестнадцатеричных строк в числовые типы
Анализ числовых строк
Типы форматирования
Индексаторы (Руководство по
программированию в C#)
04.11.2019 • 4 minutes to read • Edit Online
Индексаторы позволяют индексировать экземпляры класса или структуры точно так же, как и
массивы. Индексированное значение можно задавать или получать без явного указания типа или
экземпляра элемента. Индексаторы действуют как свойства, за исключением того, что их акцессоры
принимают параметры.
В следующем примере определяется универсальный класс с простыми акцессорами get и set для
назначения и получения значений. Класс Program создает экземпляр этого класса для хранения строк.
using System;
class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection[0] = "Hello, World";
Console.WriteLine(stringCollection[0]);
}
}
// The example displays the following output:
// Hello, World.
NOTE
Дополнительные примеры см. в разделе Связанные разделы.
class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
int nextIndex = 0;
class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection.Add("Hello, World");
System.Console.WriteLine(stringCollection[0]);
}
}
// The example displays the following output:
// Hello, World.
Обратите внимание, что => представляет тело выражения, а ключевое слово get не используется.
Начиная с версии C# 7.0, методы доступа get и set можно реализовывать в виде членов с телом в
виде выражения. В этом случае необходимо указывать оба ключевых слова ( get и set ). Например:
using System;
class SampleCollection<T>
{
// Declare an array to store the data elements.
private T[] arr = new T[100];
class Program
{
static void Main()
{
var stringCollection = new SampleCollection<string>();
stringCollection[0] = "Hello, World.";
Console.WriteLine(stringCollection[0]);
}
}
// The example displays the following output:
// Hello, World.
Общие сведения об индексаторах
Индексаторы позволяют индексировать объекты так же, как и массивы.
Метод доступа get возвращает значение. Метод доступа set назначает значение.
Ключевое слово this используется для определения индексаторов.
Ключевое слово value set используется для определения значения, присваиваемого
индексатором .
Индексаторы не нужно индексировать по целому значению; пользователь может определить
конкретный механизм поиска на свое усмотрение.
Индексаторы могут быть перегружены.
Индексаторы могут иметь более одного формального параметра, например при доступе к
двумерному массиву.
Связанные разделы
Использование индексаторов
Индексаторы в интерфейсах
Сравнение свойств и индексаторов
Ограничение доступности методов доступа
Спецификация языка C#
Дополнительные сведения см. в разделе Индексаторы в Спецификации языка C#. Спецификация
языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Свойства
Использование индексаторов. Руководство по
программированию на C#
23.10.2019 • 7 minutes to read • Edit Online
Примечания
Тип индексатора и типы его параметров должны иметь по крайней мере такой же уровень доступности, как
и сам индексатор. Дополнительные сведения об уровнях доступа см. в разделе Модификаторы доступа.
Дополнительные сведения об использовании индексаторов с интерфейсом см. в разделе Индексаторы
интерфейса.
Сигнатура индексатора определяет число и типы его формальных параметров. В ней не указываются тип
индексатора или имена его формальных параметров. Если для одного класса объявляется несколько
индексаторов, они должны иметь разные сигнатуры.
Значение индексатора не классифицируется как переменная и, соответственно, не может передаваться в
качестве параметра ref или out.
Чтобы присвоить индексатору имя, которое можно использовать в других языках, используйте
System.Runtime.CompilerServices.IndexerNameAttribute, как показано в этом примере:
[System.Runtime.CompilerServices.IndexerName("TheItem")]
public int this[int index] // Indexer declaration
{
// get and set accessors
}
Этот индексатор будет иметь имя TheItem . Без атрибута имени по умолчанию будет использоваться имя
Item .
Пример 1
В следующем примере показано, как объявить частное поле массива temps и индексатор. Индексатор
обеспечивает прямой доступ к экземпляру tempRecord[i] . Вместо использования индексатора можно
объявить массив как элемент public и осуществлять доступ к его элементам напрямую ( tempRecord.temps[i] ).
Обратите внимание, что при определении прав доступа индексатора, например в инструкции Console.Write ,
вызывается метод доступа get. Таким образом, если метод доступа get отсутствует, возникает ошибка
времени компиляции.
class TempRecord
{
// Array of temperature values
private float[] temps = new float[10] { 56.2F, 56.7F, 56.5F, 56.9F, 58.8F,
61.3F, 65.9F, 62.1F, 59.2F, 57.5F };
set
{
temps[index] = value;
}
}
}
class MainClass
{
static void Main()
{
TempRecord tempRecord = new TempRecord();
// Use the indexer's set accessor
tempRecord[3] = 58.3F;
tempRecord[5] = 60.1F;
}
}
/* Output:
Element #0 = 56.2
Element #1 = 56.7
Element #2 = 56.5
Element #3 = 58.3
Element #4 = 58.8
Element #5 = 60.1
Element #6 = 65.9
Element #7 = 62.1
Element #8 = 59.2
Element #9 = 57.5
*/
Пример 2
Этот пример объявляет класс, который хранит названия дней недели. Метод доступа get принимает
название дня в виде строкового значения и возвращает соответствующее целое число. Например, для
Sunday возвращается значение 0, для Monday — 1 и т. д.
// This method finds the day or returns an Exception if the day is not found
private int GetDay(string testDay)
{
class Program
{
static void Main(string[] args)
{
DayCollection week = new DayCollection();
System.Console.WriteLine(week["Fri"]);
// Raises ArgumentOutOfRangeException
System.Console.WriteLine(week["Made-up Day"]);
Отказоустойчивость
Повысить безопасность и надежность индексаторов можно двумя способами:
Реализуйте стратегию обработки ошибок, предусматривающую действия в ситуациях, когда из
клиентского кода передается недопустимое значение индекса. В первом примере из этого раздела
класс TempRecord содержит свойство Length, с помощью которого клиентский код проверяет
введенное значение, прежде чем передать его в индексатор. Кроме того, код обработки ошибок
можно поместить в сам индексатор. Не забудьте задокументировать исключения, которые будут
вызываться в методе доступа индексатора, для других пользователей.
Настройте максимально ограничивающие уровни доступа для методов доступа get и set. Особенно
важно сделать это для метода доступа set . Дополнительные сведения см. в разделе Доступность
методов доступа.
См. также
Руководство по программированию на C#
Индексаторы
Свойства
Индексаторы в интерфейсах (Руководство по
программированию в C#)
23.10.2019 • 2 minutes to read • Edit Online
Индексаторы можно объявлять для интерфейса. Методы доступа индексаторов интерфейса отличаются от
методов доступа индексаторов класса следующим образом:
Методы доступа интерфейса не используют модификаторы.
Метод доступа интерфейса не имеет тела.
Таким образом, методы доступа нужны, чтобы указать, доступен ли индексатор для чтения и записи, только
для чтения или только для записи.
Ниже показан пример метода доступа индексатора для интерфейса:
// Indexer declaration:
string this[int index]
{
get;
set;
}
}
Сигнатура индексатора должна отличаться от сигнатур любых других индексаторов, объявленных в том же
интерфейсе.
Пример
Следующий пример демонстрирует реализацию индексаторов интерфейса.
// Indexer on an interface:
public interface ISomeInterface
{
// Indexer declaration:
int this[int index]
{
get;
set;
}
}
class MainClass
{
static void Main()
{
IndexerClass test = new IndexerClass();
System.Random rand = new System.Random();
// Call the indexer to initialize its elements.
for (int i = 0; i < 10; i++)
{
test[i] = rand.Next();
}
for (int i = 0; i < 10; i++)
{
System.Console.WriteLine("Element #{0} = {1}", i, test[i]);
}
В приведенном выше примере можно использовать явную реализацию члена интерфейса с помощью
полного имени члена интерфейса. Например:
string ISomeInterface.this[int index]
{
}
Тем не менее полное имя требуется только в целях устранения неоднозначности, если класс реализует
более одного интерфейса с одинаковой сигнатурой индексатора. Например, если класс Employee реализует
два интерфейса ( ICitizen и IEmployee ), оба из которых имеют одинаковую сигнатуру индексатора,
требуется явная реализация члена интерфейса. Это значит, что потребуется следующее объявление
индексатора:
См. также
Руководство по программированию на C#
Индексаторы
Свойства
Интерфейсы
Сравнение свойств и индексаторов (Руководство
по программированию в C#)
23.10.2019 • 2 minutes to read • Edit Online
СВОЙСТВО. ИНДЕКСАТОР
Позволяет вызывать методы как открытые члены данных. Обеспечивает доступ к элементам внутренней коллекции
объекта с использованием нотации массива для самого
объекта.
Может быть статическим членом или членом экземпляра. Должен быть членом экземпляра.
Метод доступа get свойства не имеет параметров. Метод доступа get индексатора имеет тот же список
формальных параметров, что и сам индексатор.
Метод доступа set свойства содержит неявный параметр Метод доступа set индексатора имеет тот же список
value . формальных параметров, что и сам индексатор, и также
должен содержать параметр value.
См. также
Руководство по программированию на C#
Индексаторы
Свойства
События (Руководство по программированию в
C#)
04.11.2019 • 3 minutes to read • Edit Online
События позволяют классу или объекту уведомлять другие классы или объекты о возникновении
каких-либо ситуаций. Класс, отправляющий (или порождающий) событие, называется издателем , а
классы, принимающие (или обрабатывающие) событие, называются подписчиками.
В типичном веб-приложении или приложении Windows Forms на C# вы подписываетесь на события,
вызываемые элементами управления, такими как кнопки и списки. Вы можете использовать
интегрированную среду разработки (IDE ) Visual C#, чтобы просмотреть события, публикуемые
элементом управления, и выбрать те из них, которые необходимо обрабатывать. IDE позволяет
автоматически добавлять пустой метод обработчика событий и код для подписки на событие.
Дополнительные сведения см. в разделе Практическое руководство. Подписка и отмена подписки на
события.
Связанные разделы
Дополнительные сведения можно найти в разделе
Практическое руководство. Подписка и отмена подписки на события
Практическое руководство. Публикация событий в соответствии с руководствами по .NET
Framework
Практическое руководство. Создание событий базового класса в производных классах
Практическое руководство. Реализация событий интерфейса
Практическое руководство. Реализация пользовательских методов доступа к событиям
Спецификация языка C#
Дополнительные сведения см. в разделе События в Спецификации языка C#. Спецификация языка
является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
EventHandler
Руководство по программированию на C#
Делегаты
Создание обработчиков событий в Windows Forms
Практическое руководство. Руководство по
программированию на C#. Подписка и отмена
подписки на события
23.10.2019 • 6 minutes to read • Edit Online
Необходимость подписки на событие, опубликованное другим классом, может возникнуть, когда требуется
написать пользовательский код, вызываемый при инициировании такого события. Например, можно
подписаться на событие кнопки click , чтобы приложение выполняло некоторое действие при нажатии
пользователем кнопки.
Подписка на события в интегрированной среде разработки Visual Studio
1. Если окно Свойства закрыто, в представлении Конструктор щелкните правой кнопкой мыши
форму или элемент управления, для которого требуется создать обработчик событий, и выберите
пункт Свойства.
2. Вверху окна Свойства щелкните значок События.
3. Дважды щелкните событие, которое требуется создать, например событие Load .
Visual C# создаст пустой метод обработчика событий и добавит его в код. Код можно также
добавить вручную в представлении Код. Например, приведенные ниже строки кода объявляют
метод обработчика событий, который будет выполнен при вызове классом Form события Load .
Строка кода, требуемая для подписки на событие, также создается автоматически в методе
InitializeComponent в файле Form1.Designer.cs проекта. Она имеет следующий вид:
Обратите внимание, что приведенный выше синтаксис появился только в C# 2.0. Он в точности
соответствует синтаксису C# 1.0, в котором с помощью ключевого слова new должен быть явно
создан инкапсулирующий делегат.
public Form1()
{
InitializeComponent();
this.Click += (s,e) =>
{
MessageBox.Show(((MouseEventArgs)e).Location.ToString());
};
}
Важно отметить, что отменить подписку на событие не так просто, если для подписки на него
использовалась анонимная функция. Чтобы отменить подписку в этом случае, необходимо
вернуться к коду, в котором была выполнена подписка на событие, сохранить анонимный метод в
переменной делегата, а затем добавить делегат к событию. Как правило, мы рекомендуем не
использовать анонимных функций для подписки на события, если предполагается, что в будущем
будет нужно отменять подписку на событие. Дополнительные сведения об анонимных функциях см.
в разделе Анонимные функции.
Отмена подписки
Чтобы предотвратить вызов обработчика событий при инициировании события, подписку на событие
необходимо отменить. Во избежание утечки ресурсов отменять подписку на события следует до удаления
объекта подписчика. До тех пор пока подписка на событие не отменена, делегат многоадресной рассылки,
лежащий в основе события в публикующем объекте, будет ссылаться на делегат, инкапсулирующий
обработчик событий подписчика. Если ссылка присутствует в публикующем объекте, объект подписчика не
будет удален при сборке мусора.
Отмена подписки на событие
Чтобы отменить подписку на событие, воспользуйтесь оператором присваивания вычитания ( -= ).
publisher.RaiseCustomEvent -= HandleCustomEvent;
Если подписка на событие отменена для всех подписчиков, экземпляр события в классе издателя
получает значение null .
См. также
События
event
Практическое руководство. Публикация событий в соответствии с руководствами по .NET Framework
Операторы - и -=
Операторы + и +=
Практическое руководство. Руководство по
программированию на C#. Публикация событий,
соответствующих рекомендациям .NET Framework
23.10.2019 • 5 minutes to read • Edit Online
Следующая процедура показывает, как добавлять события, которые соответствуют стандартному шаблону
.NET Framework для классов и структур. Все события в библиотеке классов .NET Framework основаны на
делегате EventHandler, который определен следующим образом:
NOTE
В .NET Framework 2.0 представлена универсальная версия этого делегата, EventHandler<TEventArgs>. В следующих
примерах демонстрируется использование обеих версий.
Хотя события в определяемых классах могут быть основаны на действительном типе делегата, даже на
делегатах, возвращающих значение, обычно рекомендуется основывать события на шаблоне .NET
Framework с помощью EventHandler, как показано в следующем примере.
Публикация событий, основанных на шаблоне EventHandler
1. (Пропустите этот шаг и перейдите к шагу 3a, если не нужно отправлять пользовательские данные с
определенным событием.) Объявите класс для пользовательских данных в области, видимой для
классов Publisher и Subscriber. Затем добавьте необходимые члены для хранения пользовательских
данных о событиях. В этом примере возвращается простая строка.
Пример
В следующем примере показана вышеописанная процедура с использованием пользовательского класса
EventArgs и EventHandler<TEventArgs> в качестве типа событий.
namespace DotNetEvents
{
using System;
using System.Collections.Generic;
}
// Wrap event invocations inside a protected virtual method
// to allow derived classes to override the event invocation behavior
protected virtual void OnRaiseCustomEvent(CustomEventArgs e)
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
EventHandler<CustomEventArgs> handler = RaiseCustomEvent;
class Program
{
static void Main(string[] args)
{
Publisher pub = new Publisher();
Subscriber sub1 = new Subscriber("sub1", pub);
Subscriber sub2 = new Subscriber("sub2", pub);
}
}
}
См. также
Delegate
Руководство по программированию на C#
События
Делегаты
Практическое руководство. Руководство по
программированию на C#. Создание событий
базового класса в производных классах
23.10.2019 • 4 minutes to read • Edit Online
В следующем простом примере показан стандартный способ объявления событий в базовом классе,
позволяющий вызывать их из производных классов. Этот шаблон широко используется в классах Windows
Forms в библиотеке классов .NET Framework.
При создании класса, который можно использовать в качестве базового для других классов, следует
учитывать тот факт, что события представляют собой особый тип делегатов, который может вызываться
только в том классе, который их объявил. Производные классы не могут создавать события, объявленные в
базовом классе, напрямую. Несмотря на то, что в некоторых обстоятельствах вам может потребоваться
событие, вызываемое только базовым классом, в большинстве случаев рекомендуется разрешать
производному классу вызывать события базового класса. Для этого следует создать в базовом классе
защищенный метод, предоставляющий оболочку для события. Вызывая или перезаписывая вызывающий
метод, производные классы могут вызывать событие косвенно.
NOTE
Не объявляйте виртуальные события в базовом классе и не переопределяйте их в производном классе. Компилятор
c# не обрабатывает их корректно, поэтому сложно сказать, будет ли подписчик производного события на самом деле
подписываться на событие базового класса.
Пример
namespace BaseClassEvents
{
using System;
using System.Collections.Generic;
public ShapeEventArgs(double d)
{
newArea = d;
}
public double NewArea
{
get { return newArea; }
}
}
public ShapeContainer()
{
_list = new List<Shape>();
}
class Test
{
См. также
Руководство по программированию на C#
События
Делегаты
Модификаторы доступа
Создание обработчиков событий в Windows Forms
Как реализовать события интерфейса
(руководство по программированию на C#)
23.10.2019 • 4 minutes to read • Edit Online
namespace ImplementInterfaceEvents
{
public interface IDrawingObject
{
event EventHandler ShapeChanged;
}
public class MyEventArgs : EventArgs
{
// class members
}
public class Shape : IDrawingObject
{
public event EventHandler ShapeChanged;
void ChangeShape()
{
// Do something here before the event…
OnShapeChanged(new MyEventArgs(/*arguments*/));
Пример
Следующий пример демонстрирует обработку менее распространенной ситуации, в которой класс
наследует от двух или более интерфейсов, каждый из которых имеет событие с одним и тем же именем.
Вам придется предоставить явную реализацию интерфейса по крайней мере для одного из этих событий.
Когда вы создаете для события явную реализацию интерфейса, нужно добавить еще и методы доступа
add и remove . Обычно они предоставляются компилятором, но в этой ситуации компилятор не сможет
предоставить их.
Предоставляя собственные методы доступа, вы можете указать одно или разные события вашего класса,
которые соответствуют этим двум событиям. Например, если события должны инициироваться в разное
время в соответствии со спецификациями интерфейса, можно связать каждое из них с отдельной
реализацией в классе. В следующем примере подписчики определяют, какое из событий OnDraw они
получат, указывая ссылку на фигуру IShape или IDrawingObject .
namespace WrapTwoInterfaceEvents
{
using System;
Console.WriteLine("Drawing a shape.");
}
/* Output:
Sub1 receives the IDrawingObject event.
Drawing a shape.
Drawing a shape.
Sub2 receives the IShape event.
*/
См. также
Руководство по программированию на C#
События
Делегаты
Явная реализация интерфейса
Практическое руководство. Создание событий базового класса в производных классах
Как реализовать пользовательские методы
доступа к событиям (руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Событие представляет собой особый вид многоадресного делегата, который можно вызвать только из
класса, где он объявлен. В клиентском коде создается подписка на событие. Для этого предоставляется
ссылка на метод, который должен вызываться при возникновении этого события. Эти методы добавляются в
список вызова делегата с помощью методов доступа к событиям, которые схожи с методами доступа к
свойствам и отличаются только именами ( add и remove ). В большинстве случаев реализовывать
настраиваемые методы доступа к событиям не требуется. Если в коде отсутствуют настраиваемые методы
доступа к событиям, компилятор добавляет их автоматически. Тем не менее в некоторых случаях требуется
реализовать настраиваемое поведение. Один из таких сценариев приводится в разделе Практическое
руководство. Реализация событий интерфейса.
Пример
В следующем примере показано, как реализовать настраиваемые методы доступа add и remove для
события. При необходимости вы можете заменять любой код в методах доступа, однако мы рекомендуем
заблокировать событие, прежде чем добавлять или удалять новый метод обработчика событий.
См. также
События
event
Универсальные шаблоны (Руководство по
программированию на C#)
30.10.2019 • 6 minutes to read • Edit Online
// constructor
public GenericList()
{
head = null;
}
class TestGenericList
{
static void Main()
{
// int is the type argument
GenericList<int> list = new GenericList<int>();
Связанные разделы
Параметры универсального типа
Ограничения параметров типа
Универсальные классы
Универсальные интерфейсы
Универсальные методы
Универсальные делегаты
Различия между шаблонами языка C++ и универсальными шаблонами языка C#
Универсальные типы и отражение
Универсальные типы во время выполнения
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#.
См. также
System.Collections.Generic
Руководство по программированию на C#
Типы
<typeparam>
<typeparamref>
Универсальные шаблоны в .NET
Руководство по программированию на C#.
Параметры универсального типа
04.11.2019 • 2 minutes to read • Edit Online
В определении универсального типа или метода параметр типа является заполнителем для определенного
типа, который клиент задает при создании экземпляра переменной универсального типа. Универсальный
класс, например GenericList<T> из раздела Общие сведения об универсальных шаблонах, нельзя
использовать в исходном виде, поскольку он представляет собой не фактически тип, а, скорее, заготовку
типа. Чтобы использовать класс GenericList<T> , в коде клиента необходимо объявить сконструированный
тип и создать его экземпляр, указав аргумент типа в угловых скобках. Аргумент типа для этого конкретного
класса может иметь любой тип, распознаваемый компилятором. Можно создать любое число экземпляров
сконструированного типа, каждый из которых будет использовать разные аргументы типа, как показано
ниже:
В каждом из этих экземпляров GenericList<T> каждое вхождение T в классе во время выполнения будет
заменяться аргументом типа. Посредством такой замены в этом случае создается три отдельных
типобезопасных эффективных объекта, использующих одно определение класса. Дополнительные сведения
о том, как в среде CLR реализуется эта замена, см. в разделе Универсальные типы во время выполнения.
Используйте имя параметра типа T для типов, содержащих только однобуквенный параметр типа.
См. также
System.Collections.Generic
Руководство по программированию на C#
Универсальные шаблоны
Различия между шаблонами языка C++ и универсальными шаблонами языка C#
Ограничения параметров типа (руководство по
программированию на C#)
04.11.2019 • 14 minutes to read • Edit Online
Ограничения сообщают компилятору о характеристиках, которые должен иметь аргумент типа. Без
ограничений аргумент типа может быть любым типом. Компилятор может только предполагать членов
System.Object, который является главным базовым классом для всех типов .NET. Дополнительные сведения
см. в статье Зачем использовать ограничения. Если в клиентском коде для создания экземпляра класса
используется недопустимый тип, возникает ошибка времени компиляции. Ограничения задаются с
помощью контекстного ключевого слова where . В следующей таблице описываются семь типов
ограничений:
ОГРАНИЧЕНИЕ ОПИСАНИЕ
where T : <имя базового класса> Аргумент типа должен иметь базовый класс или
производный от него класс.
Некоторые ограничения являются взаимоисключающими. Все типы значений должны иметь доступ к
конструктору без параметров. Ограничение struct подразумевает ограничение new() , а ограничение
new() не может использоваться с ограничением struct . Ограничение unmanaged подразумевает
ограничение struct . Ограничение unmanaged не может использоваться с ограничениями struct или
new() .
public T FindFirstOccurrence(string s)
{
Node current = head;
T t = null;
Компилятору известно только то, что T является ссылочным типом во время компиляции, и он должен
использовать операторы по умолчанию, которые действительны для всех ссылочных типов. Чтобы
проверить равенство значений, рекомендуется применить ограничение where T : IEquatable<T> или
where T : IComparable<T> и реализовать этот интерфейс в любом классе, который будет использоваться для
создания универсального класса.
class Base { }
class Test<T, U>
where U : struct
where T : Base, new()
{ }
Применение параметров типа в качестве ограничений для универсальных классов ограничено, поскольку в
отношении таких параметров типа компилятор может предполагать только то, что они являются
производными от System.Object . Параметры типа в качестве ограничений следует использовать в
универсальных классах в тех случаях, когда необходимо обеспечить отношение наследования между двумя
параметрами типа.
Ограничение не NULL
Начиная с C# 8.0, можно использовать ограничение notnull , чтобы указать, что аргумент типа должен
быть типом значения, не допускающим значения NULL, или ссылочным типом, не допускающим значения
NULL. Ограничение notnull может использоваться только в контексте nullable enable . Компилятор
генерирует предупреждение, если вы добавляете ограничение notnull в контексте, который игнорирует
допустимость значений NULL.
В отличие от других ограничений, когда аргумент типа нарушает ограничение notnull , компилятор
генерирует предупреждение, когда этот код компилируется в контексте nullable enable . Если код
скомпилирован в контексте, который игнорирует допустимость значений NULL, компилятор не генерирует
никаких предупреждений или ошибок.
Неуправляемое ограничение
Начиная с C# 7.3 можно использовать ограничение unmanaged , чтобы указать, что параметр типа должен
быть неуправляемым типом. Ограничение unmanaged позволяет создавать многократно используемые
подпрограммы для работы с типами, которые могут обрабатываться как блоки памяти, как показано в
следующем примере:
В примере выше метод необходимо компилировать в контексте unsafe , так как он использует оператор
sizeof для типа, не известного как встроенный тип. Без ограничения unmanaged оператор sizeof
недоступен.
Ограничения делегата
Кроме того, начиная с C# 7.3 можно использовать System.Delegate или System.MulticastDelegate как
ограничение базового класса. В среде CLR это ограничение всегда было разрешено, но в языке C# оно
было запрещено. Ограничение System.Delegate позволяет написать код, который работает с делегатами
типобезопасным образом. Следующий код определяет метод расширения, который объединяет два
делегата при условии, что они одного типа:
Приведенный выше метод можно использовать для объединения делегатов, которые относятся к одному
типу:
Если раскомментировать последнюю строку, она не будет компилироваться. first и test являются
типами делегатов, но это разные типы делегатов.
Ограничения перечисления
Начиная с C# 7.3 можно указать тип System.Enum в качестве ограничения базового класса. В среде CLR
это ограничение всегда было разрешено, но в языке C# оно было запрещено. Универсальные шаблоны с
System.Enum предоставляют типобезопасное программирование для кэширования результатов
использования статических методов в System.Enum . В следующем примере выполняется поиск всех
допустимых значений для типа перечисления и создается словарь, который сопоставляет эти значения с их
строковым представлением.
В этих методах используется отражение, которое влияет на производительность. Вы можете вызвать этот
метод для создания коллекции, которая кэшируется и используется повторно, вместо того, чтобы
повторять вызовы, требующие отражения.
Вы можете использовать его, как показано в следующем примере, для создания перечисления и словаря
имен и значений:
enum Rainbow
{
Red,
Orange,
Yellow,
Green,
Blue,
Indigo,
Violet
}
См. также
System.Collections.Generic
Руководство по программированию на C#
Введение в универсальные шаблоны
Универсальные классы
Ограничение new
Универсальные классы (Руководство по
программированию на C#)
23.10.2019 • 6 minutes to read • Edit Online
class BaseNode { }
class BaseNodeGeneric<T> { }
// concrete type
class NodeConcrete<T> : BaseNode { }
//No error
class Node1 : BaseNodeGeneric<int> { }
//Generates an error
//class Node2 : BaseNodeGeneric<T> {}
//Generates an error
//class Node3 : T {}
//No error
class Node4<T> : BaseNodeMultiple<T, int> { }
//No error
class Node5<T, U> : BaseNodeMultiple<T, U> { }
//Generates an error
//class Node6<T> : BaseNodeMultiple<T, U> {}
Универсальные типы могут использовать несколько параметров типа и ограничений, как показано ниже:
Если универсальный класс реализует интерфейс, все экземпляры такого класса можно привести к этому
интерфейсу.
Универсальные классы инвариантны. Другими словами, если входной параметр задает List<BaseClass> , при
попытке предоставить List<DerivedClass> возникает ошибка времени компиляции.
См. также
System.Collections.Generic
Руководство по программированию на C#
Универсальные шаблоны
Сохранение состояния перечислителей
Загадка по наследованию, часть 1
Универсальные интерфейсы. (Руководство по
программированию на C#)
23.10.2019 • 5 minutes to read • Edit Online
Часто требуется определить интерфейсы для универсальных классов коллекций или для универсальных
классов, представляющих элементы коллекции. Для универсальных классов предпочтительнее использовать
универсальные интерфейсы, такие как IComparable<T>, вместо IComparable, поскольку это позволяет
избежать выполнения операция упаковки-преобразования и распаковки-преобразования для типов
значений. В библиотеке классов .NET Framework в пространстве имен System.Collections.Generic
определяется несколько универсальных интерфейсов для работы с классами коллекций.
Если интерфейс задан в качестве ограничения для параметра типа, можно использовать только типы,
реализующие такой интерфейс. В следующем примере кода демонстрируется класс SortedList<T> , который
является производным от класса GenericList<T> . Дополнительные сведения см. в разделе Введение в
универсальные шаблоны. SortedList<T> добавляет ограничение where T : IComparable<T> . Это позволяет
методу BubbleSort в SortedList<T> использовать универсальный метод CompareTo в отношении элементов
списка. В этом примере элементы списка относятся к простому классу Person , который реализует
IComparable<Person> .
do
{
Node previous = null;
Node current = head;
swapped = false;
if (previous == null)
{
head = tmp;
}
else
{
previous.next = tmp;
}
previous = tmp;
swapped = true;
}
else
{
previous = current;
previous = current;
current = current.next;
}
}
} while (swapped);
}
}
class Program
{
static void Main()
{
//Declare and instantiate a new generic SortedList class.
//Person is the type argument.
SortedList<Person> list = new SortedList<Person>();
int[] ages = new int[] { 45, 19, 28, 23, 18, 9, 108, 72, 30, 35 };
В качестве ограничений для одного типа можно задать несколько интерфейсов, как показано ниже:
interface IMonth<T> { }
interface IBaseInterface<T> { }
См. также
Руководство по программированию на C#
Введение в универсальные шаблоны
interface
Универсальные шаблоны
Универсальные методы (Руководство по
программированию на C#)
23.10.2019 • 3 minutes to read • Edit Online
Универсальным называется метод, объявленный с использованием параметров типа, как показано ниже:
В следующем примере кода показан один из способов вызова метода с использованием int в качестве
аргумента типа:
Если опустить аргумент типа, компилятор определит его. Следующий вызов Swap эквивалентен
предыдущему:
Для статических методов и методов экземпляра применяются одинаковые правила определения типа.
Компилятор может определить параметры типа на основе переданных ему аргументов метода.
Определение параметров типа невозможно только для ограничения или возвращаемого значения.
Соответственно, механизм определения типа не работает с методами, не имеющими параметров.
Определение типа происходит во время компиляции до того, как компилятор попытается разрешить
сигнатуры перегруженных методов. Компилятор применяет логику определения типа ко всем
универсальным методам с одинаковым именем. На этапе разрешения перегрузки компилятор включает
только те универсальные методы, для которых был успешно определен тип.
В универсальном классе методы, не являющиеся универсальными, могут получать доступ к параметрам
типа на уровне класса следующим образом:
class SampleClass<T>
{
void Swap(ref T lhs, ref T rhs) { }
}
Если определить универсальный метод, принимающий те же параметры типа, что и содержащий его класс,
возникнет предупреждение компилятора CS0693. Это связано с тем, что в области действия метода
предоставленный для внутреннего типа T аргумент скрывает аргумент, предоставленный для внешнего
типа T . Для большей гибкости можно вызывать метод универсального класса с использованием
аргументов типа, отличающихся от предоставленных при создании экземпляра класса. В этом случае следует
предоставить другой идентификатор для параметра типа метода, как показано в GenericList2<T> в
следующем примере.
class GenericList<T>
{
// CS0693
void SampleMethod<T>() { }
}
class GenericList2<T>
{
//No warning
void SampleMethod<U>() { }
}
Универсальные методы могут быть перегружены в нескольких параметрах типа. Например, все
представленные ниже методы могут располагаться в одном классе:
void DoWork() { }
void DoWork<T>() { }
void DoWork<T, U>() { }
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#.
См. также
System.Collections.Generic
Руководство по программированию на C#
Введение в универсальные шаблоны
Методы
Универсальные методы и массивы (Руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
В C# 2.0 и более поздних версиях одномерные массивы с нулевой нижней границей автоматически
реализуют IList<T>. Это позволяет создавать универсальные методы, которые могут использовать один и тот
же код для выполнения итераций в массивах и других типах коллекций. Этот способ используется
преимущественно для чтения данных в коллекциях. Интерфейс IList<T> нельзя использовать для добавления
или удаления элементов массива. При попытке вызвать метод IList<T>, например RemoveAt, для массива в
этом контексте возникнет исключение.
В следующем примере кода показано, как отдельный универсальный метод, принимающий входной
параметр IList<T>, можно использовать для выполнения итераций в списке и массиве (в данном случае в
массиве целых чисел).
class Program
{
static void Main()
{
int[] arr = { 0, 1, 2, 3, 4 };
List<int> list = new List<int>();
ProcessItems<int>(arr);
ProcessItems<int>(list);
}
См. также
System.Collections.Generic
Руководство по программированию на C#
Универсальные шаблоны
Массивы
Универсальные шаблоны
Универсальные делегаты. (Руководство по
программированию на C#)
04.11.2019 • 2 minutes to read • Edit Online
Делегат может определять собственные параметры типа. В коде, который ссылается на универсальный
делегат, можно указать аргумент типа для создания закрытого сконструированного типа. Это будет
аналогично созданию экземпляра универсального класса или вызову универсального метода, как показано в
следующем примере:
В C# версии 2.0 представлена новая функция группового преобразования методов, которая применяется
как к конкретным, так и к универсальным типам делегатов, и позволяет записать приведенную выше строку
с использованием упрощенного синтаксиса:
Del<int> m2 = Notify;
class Stack<T>
{
T[] items;
int index;
В коде, который ссылается на делегат, необходимо указать аргумент типа содержащего класса, как показано
ниже:
Универсальные делегаты особенно полезны при определении событий, основанных на типовых шаблонах
разработки, поскольку аргумент отправителя может быть строго типизирован и больше не требует
приведения к Object и из него.
delegate void StackEventHandler<T, U>(T sender, U eventArgs);
class Stack<T>
{
public class StackEventArgs : System.EventArgs { }
public event StackEventHandler<Stack<T>, StackEventArgs> stackEvent;
class SampleClass
{
public void HandleStackChange<T>(Stack<T> stack, Stack<T>.StackEventArgs args) { }
}
См. также
System.Collections.Generic
Руководство по программированию на C#
Введение в универсальные шаблоны
Универсальные методы
Универсальные классы
Универсальные интерфейсы
Делегаты
Универсальные шаблоны
Различия между шаблонами языка C++ и
универсальными шаблонами языка C#
(Руководство по программированию в C#)
23.10.2019 • 3 minutes to read • Edit Online
См. также
Руководство по программированию на C#
Введение в универсальные шаблоны
Шаблоны
Универсальные типы во время выполнения
(Руководство по программированию в C#)
23.10.2019 • 5 minutes to read • Edit Online
Универсальный тип или метод, компилируемый на языке MSIL, будет содержать метаданные,
определяющие наличие в нем параметров типа. Способ использования MSIL для универсального типа
зависит от того, является ли предоставленный параметр типа ссылочным типом или типом значения.
Если в качестве параметра при первом построении универсального типа использовался тип значения, во
время выполнения создается специальный универсальный тип, для которого в соответствующих местах
кода MSIL заменяются предоставленные параметры. Специальные универсальные типы создаются один
раз для каждого уникального типа значения, используемого в качестве параметра.
Допустим, в коде программы объявляется стек, состоящий из целых чисел:
Stack<int> stack;
В этой точке среда выполнения создает специальную версию класса Stack<T>, параметр которого будет
соответствующим образом заменяться целым числом. После этого каждый раз, когда в коде программы
используется стек целых чисел, среда выполнения обращается к созданному специальному классу
Stack<T>. В следующем примере создаются два экземпляра стека целых чисел, которые совместно
используют один экземпляр кода Stack<int> :
Допустим, в другой точке кода создается другой класс Stack<T>, в качестве параметра которого
используется другой тип значения, например long , или определяемая пользователем структура. В
результате среда выполнения создает другую версию универсального типа и заменяет long в
соответствующих местах кода MSIL. Преобразования больше не требуются, поскольку каждый
специальный универсальный класс содержит собственный тип значения.
Для ссылочных типов универсальные шаблоны применяются несколько иным образом. При первом
создании универсального типа с любым ссылочным типом среда выполнения создает в коде MSIL
специальный универсальный тип, параметры которого заменяются ссылками на объекты. После этого
каждый раз при создании экземпляра такого сконструированного типа с использованием в качестве
параметра ссылочного типа (независимо от того, какой это тип) среда выполнения использует ранее
созданную специальную версию универсального типа. Это возможно, поскольку все ссылки имеют
одинаковый размер.
Допустим, у вас есть два ссылочных типа (классы Customer и Order ) и вы создаете стек типов Customer :
class Customer { }
class Order { }
Stack<Customer> customers;
В этой точке среда выполнения создает специальную версию класса Stack<T>, содержащую ссылки на
объекты, которые не хранят данные и будут заполнены позднее. Допустим, в следующей строке кода
создается стек другого ссылочного типа с именем Order :
В отличие от типов значений, в этом случае не создается другая специальная версия класса Stack<T> для
типа Order . Вместо этого создается экземпляр специальной версии класса Stack<T> и задается переменная
orders , ссылающаяся на него. Предположим, далее встречается строка кода, в которой создается стек типа
Customer :
Как и в случае предшествующего использования класса Stack<T>, созданного с помощью типа Order ,
создается другой специальный экземпляр класса Stack<T>. Содержащиеся в нем указатели будут ссылаться
на область памяти с размером типа Customer . Поскольку число ссылок в разных программах может
значительно различаться, реализация C# на основе универсальных шаблонов позволяет значительно
сократить объем кода за счет того, что компилятор создает для универсальных классов ссылочных типов
только один специальный класс.
Кроме того, при создании экземпляра универсального класса C# с использованием параметра типа
значения или ссылочного типа копия может выполнять запросы к нему во время выполнения, и при этом
могут быть установлены его фактический тип и параметр типа.
См. также
System.Collections.Generic
Руководство по программированию на C#
Введение в универсальные шаблоны
Универсальные шаблоны
Универсальные типы и отражение (Руководство по
программированию в C#)
23.10.2019 • 4 minutes to read • Edit Online
Поскольку среда CLR имеет доступ к данным универсальных типов во время выполнения, вы можете
использовать отражение для получения сведений об универсальных типах точно так же, как и для
неуниверсальных типов. Дополнительные сведения см. в разделе Универсальные типы во время выполнения.
В .NET Framework 2.0 в класс Type добавлено несколько новых членов для поддержки данных времени
выполнения для универсальных типов. Дополнительные сведения о том, как использовать эти методы и
свойства, см. в документации по этим классам. Пространство имен System.Reflection.Emit также содержит
новые члены, которые поддерживают универсальные типы. См. практическое руководство по Определение
универсального типа с порождаемым отражением
Список неизменяемых условий для терминов, используемых в отражении универсальных типов, см. в
примечаниях к описанию свойства IsGenericType.
Кроме того, члены класса MethodInfo обеспечивают данные времени выполнения для универсальных
методов. Список неизменяемых условий для терминов, используемых для отражения универсальных
методов, см. в примечаниях к описанию свойства IsGenericMethod.
См. также
Руководство по программированию на C#
Универсальные шаблоны
Отражение и универсальные типы
Универсальные шаблоны
Универсальные шаблоны и атрибуты (Руководство
по программированию в C#)
23.10.2019 • 2 minutes to read • Edit Online
Атрибуты можно применять к универсальным типам так же, как и к типам, не являющимся универсальными.
Дополнительные сведения о применении атрибутов см. в разделе Атрибуты.
Настраиваемые атрибуты могут ссылаться только на открытые универсальные типы (универсальные типы,
для которых не предоставлены аргументы типа) и закрытые сконструированные универсальные типы (типы,
для всех параметров типа которых предоставлены аргументы).
Такой настраиваемый атрибут используется в следующих примерах:
[CustomAttribute(info = typeof(GenericClass1<>))]
class ClassA { }
Укажите несколько параметров типа, используя соответствующее количество запятых. В этом примере
GenericClass2 имеет два параметра типа:
[CustomAttribute(info = typeof(GenericClass2<,>))]
class ClassB { }
Если атрибут ссылается на параметр универсального типа, возникнет ошибка времени компиляции:
См. также
Руководство по программированию на C#
Универсальные шаблоны
Атрибуты
Пространства имен (Руководство по
программированию в C#)
23.10.2019 • 2 minutes to read • Edit Online
System.Console.WriteLine("Hello World!");
System является пространством имен, а Console — это класс в нем. Можно использовать ключевое слово
using , и тогда полное имя не потребуется. См. следующий пример:
using System;
Console.WriteLine("Hello");
Console.WriteLine("World!");
namespace SampleNamespace
{
class SampleClass
{
public void SampleMethod()
{
System.Console.WriteLine(
"SampleMethod inside SampleNamespace");
}
}
}
См. также
Руководство по программированию на C#
Использование пространств имен
Практическое руководство. Использование пространства имен My
Имена идентификаторов
Директива using
:: Оператор
Использование пространств имен. (Руководство по
программированию в C#)
23.10.2019 • 6 minutes to read • Edit Online
using System;
Console.WriteLine("Hello, World!");
System.Console.WriteLine("Hello, World!");
namespace AliasExample
{
class TestClass
{
static void Main()
{
generics::Dictionary<string, int> dict = new generics::Dictionary<string, int>()
{
["A"] = 1,
["B"] = 2,
["C"] = 3
};
class Program
{
static void Main(string[] args)
{
// Displays "SampleMethod inside SampleNamespace."
SampleClass outer = new SampleClass();
outer.SampleMethod();
Полные имена
Пространства имен и типы имеют уникальные названия, которые описываются полными именами,
определяющими их место в логической иерархии. Например, инструкция A.B указывает, что A — это
пространство имен или тип, а B — вложенный в него элемент.
В следующем примере показаны вложенные классы и пространства имен. Полное имя каждого элемента
указано в комментарии рядом с ним.
namespace N1 // N1
{
class C1 // N1.C1
{
class C2 // N1.C1.C2
{
}
}
namespace N2 // N1.N2
{
class C2 // N1.N2.C2
{
}
}
}
Используя представленный выше фрагмент кода, вы можете добавить новый элемент класса C3 в
пространство имен N1.N2 , как показано выше:
namespace N1.N2
{
class C3 // N1.N2.C3
{
}
}
В общем случае следует использовать квалификатор псевдонима пространства имен :: для ссылки на
псевдоним пространства имен или global:: для ссылки на глобальное пространство имен, а также . для
определения типов или элементов.
Использование :: с псевдонимом, ссылающимся на тип вместо пространства имен, будет ошибкой.
Например:
// OK
Alias.WriteLine("Hi");
}
}
namespace Library
{
public class C : Alias::Exception { }
}
Этот код будет работать, однако если впоследствии определить тип с именем Alias , выражение Alias.
будет связываться с этим типом. Используя выражение Alias::Exception , вы гарантируете, что Alias будет
обрабатываться как псевдоним пространства имен и не будет ошибочно связываться с типом.
См. также
Руководство по программированию на C#
Пространства имен
Оператор .
Оператор ::
Псевдоним extern
Практическое руководство. Использование
пространства имен "My" (руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Добавление ссылки
Прежде чем использовать классы MyServices в решении, необходимо добавить ссылку на библиотеку Visual
Basic.
Добавление ссылки на библиотеку Visual Basic
1. В обозревателе решений щелкните правой кнопкой мыши узел Ссылки и выберите команду
Добавить ссылку.
2. В диалоговом окне Ссылки прокрутите список вниз и выберите библиотеку Microsoft.VisualBasic.dll.
Также можно включить следующую строку в раздел using в начале программы.
using Microsoft.VisualBasic.Devices;
Пример
В этом примере вызываются различные статические методы, содержащиеся в пространстве имен
MyServices . Чтобы скомпилировать этот код, необходимо добавить в проект ссылку на библиотеку
Microsoft.VisualBasic.DLL.
using System;
using Microsoft.VisualBasic.Devices;
class TestMyServices
{
static void Main()
{
// Play a sound with the Audio class:
Audio myAudio = new Audio();
Console.WriteLine("Playing sound...");
myAudio.Play(@"c:\WINDOWS\Media\chimes.wav");
if (myComputer.Network.IsAvailable)
{
Console.WriteLine("Computer is connected to network.");
}
else
{
Console.WriteLine("Computer is not connected to network.");
}
}
}
Некоторые классы из пространства имен MyServices нельзя вызывать из приложения C#, как, например,
несовместимый класс FileSystemProxy. Конкретно в этом случае вместо него можно использовать
статические методы из состава FileSystem (также входит в библиотеку VisualBasic.dll). Например, ниже
показано, как дублировать каталог с помощью одного из таких методов:
// Duplicate a directory
Microsoft.VisualBasic.FileIO.FileSystem.CopyDirectory(
@"C:\original_directory",
@"C:\copy_of_original_directory");
См. также
Руководство по программированию на C#
Пространства имен
Использование пространств имен
Руководство по программированию на C#.
Небезопасный код и указатели
23.10.2019 • 2 minutes to read • Edit Online
NOTE
В общеязыковой среде выполнения (CLR) небезопасный код называется непроверяемым. Небезопасный код в C# не
обязательно опасен, просто его безопасность нельзя проверить в CLR. Поэтому CLR будет выполнять небезопасный
код, только если он находится в полностью доверенной сборке. Если вы используете небезопасный код, вы сами
несете ответственность за возможные риски безопасности и ошибки указателей.
Связанные разделы
Дополнительные сведения:
Типы указателей
Буферы фиксированного размера
Спецификация языка C#
Дополнительные сведения см. в разделе Небезопасный код в Спецификации языка C#.
См. также
Руководство по программированию на C#
unsafe
Буферы фиксированного размера (Руководство по
программированию на C#)
25.11.2019 • 4 minutes to read • Edit Online
В языке C# для создания буфера с массивом фиксированного размера в структуре данных можно
использовать оператор fixed. Буферы фиксированного размера полезны при написании методов,
взаимодействующих с источниками данных, созданными на других языках или платформах. Фиксированный
массив может принимать любые атрибуты или модификаторы, допустимые для обычных членов структуры.
Единственным ограничением является то, что массив должен иметь тип bool , byte , char , short , int ,
long , sbyte , ushort , uint , ulong , float или double .
Примечания
В безопасном коде структура C#, содержащая массив, не содержит элементы массива. Вместо этого в ней
присутствуют ссылки на элементы. Вы можете внедрить массив фиксированного размера в структуру, если
он используется в блоке небезопасного кода.
Размер следующего типа struct составляет 8 байт. Массив pathName является ссылкой:
struct может содержать внедренный массив в небезопасном коде. В приведенном ниже примере массив
fixedBuffer имеет фиксированный размер. Для установки указателя на первый элемент используется
оператор fixed . С помощью этого указателя осуществляется доступ к элементам массива. Оператор fixed
закрепляет поле экземпляра fixedBuffer в определенном месте в памяти.
internal unsafe struct MyBuffer
{
public fixed char fixedBuffer[128];
}
unsafe
{
// Pin the buffer to a fixed location in memory.
fixed (char* charPtr = myC.myBuffer.fixedBuffer)
{
*charPtr = 'A';
}
// Access safely through the index:
char c = myC.myBuffer.fixedBuffer[0];
Console.WriteLine(c);
// modify through the index:
myC.myBuffer.fixedBuffer[0] = 'B';
Console.WriteLine(myC.myBuffer.fixedBuffer[0]);
}
Размер массива из 128 элементов char составляет 256 байт. В буферах типа char фиксированного размера
на один символ всегда приходится два байта независимо от кодировки. Это справедливо даже в том случае,
когда буферы char маршалируются в методы API или структуры с CharSet = CharSet.Auto или
CharSet = CharSet.Ansi . Для получения дополнительной информации см. CharSet.
В предыдущем примере демонстрировался доступ к полям fixed без закрепления в памяти, доступный в
C#, начиная с версии 7.3.
Еще одним распространенным массивом фиксированного размера является массив bool. Элементы в
массиве bool всегда имеют размер в один байт. Массивы bool не подходят для создания битовых массивов
или буферов.
NOTE
За исключением памяти, созданной с помощью stackalloc, компилятор C# и среда CLR не выполняют проверку
переполнения буфера безопасности. Как и при работе с любым небезопасным кодом, следует проявлять
осторожность.
См. также
Руководство по программированию на C#
Небезопасный код и указатели
Оператор fixed
Взаимодействие
Типы указателей (Руководство по
программированию на C#)
04.11.2019 • 6 minutes to read • Edit Online
В небезопасном контексте тип может быть типом указателя, типом значения или ссылочным типом.
Объявления типа указателя выполняется одним из следующих способов:
type* identifier;
void* identifier; //allowed but not recommended
Тип, указанный до * в типе указателя, называется ссылочным типом. Ссылочным типом может быть
только неуправляемый тип.
Типы указателей не наследуются от объекта, а типы указателей не преобразуются в object . Кроме того,
упаковка-преобразование и распаковка-преобразование не поддерживают указатели. Однако можно
выполнять преобразования между различными типами указателей, а также между типами указателей и
целочисленными типами.
При объявлении нескольких указателей в одном объявлении знак * указывается только с базовым типом;
он не используется в качестве префикса для каждого имени указателя. Например:
Указатель не может указывать на ссылку или на структуру, содержащую ссылки, поскольку ссылка на
объект может быть подвергнута сбору мусора, даже если на нее указывает указатель. Сборщик мусора не
отслеживает наличие указателей любых типов, указывающих на объекты.
Значением переменной-указателя типа myType* является адрес переменной типа myType . Ниже
приведены примеры объявлений типов указателей.
ПРИМЕР ОПИСАНИЕ
Оператор косвенного обращения указателя * можно использовать для доступа к содержимому, на которое
указывает переменная-указатель. В качестве примера рассмотрим следующее объявление:
int* myVariable;
Выражение *myVariable обозначает переменную int , находящуюся по адресу, содержащемуся в
myVariable .
Console.WriteLine("--------");
Console.WriteLine(a[0]);
/*
Output:
10
20
30
--------
10
11
12
--------
12
*/
Для указателя типа void* использовать оператор косвенного обращения нельзя. Однако можно
использовать приведение для преобразования указателя типа void в любой другой тип и наоборот.
Указатель может иметь значение null . При применении оператора косвенного обращения к указателю со
значением null результат зависит от конкретной реализации.
При передаче указателей между методами может возникнуть неопределенное поведение. Рекомендуется
использовать метод, возвращающий указатель в локальную переменную с помощью параметра in , out
или ref либо в виде результата функции. Если указатель был задан в фиксированном блоке, переменная,
на которую он указывает, больше не может быть фиксированной.
В следующей таблице перечислены операторы, которые можно использовать для указателей в
небезопасном контексте.
ОПЕРАТОР ИСПОЛЬЗОВАТЬ
[] Индексирование указателя.
Спецификация языка C#
Дополнительные сведения см. в разделе Типы указателей в статье Спецификации языка C#.
См. также
Руководство по программированию на C#
Небезопасный код и указатели
Преобразования указателей
Типы
unsafe
Преобразования указателей (Руководство по
программированию на C#)
04.11.2019 • 2 minutes to read • Edit Online
Явное преобразование указателя используется для выполнения преобразований, для которых отсутствует
неявное преобразование, с помощью выражения приведения. Эти преобразования показаны в следующей
таблице.
sbyte, byte, short, ushort, int, uint, long или ulong Любой тип указателя
Любой тип указателя sbyte, byte, short, ushort, int, uint, long или ulong
Пример
В следующем примере указатель на int преобразуется в указатель на byte . Обратите внимание, что
указатель указывает на наименьший адресуемый байт переменной. При последовательном увеличении
результата до размера int (4 байта) можно отобразить оставшиеся байты переменной.
unsafe
{
// Convert to byte:
byte* p = (byte*)&number;
См. также
Руководство по программированию на C#
Типы указателей
Типы
unsafe
Оператор fixed
stackalloc
Практическое руководство. Использование
указателей для копирования массива байтов
(руководство по программированию на C#)
23.10.2019 • 3 minutes to read • Edit Online
В следующем примере указатели используются для копирования байт из одного массива в другой.
В этом примере применяется ключевое слово unsafe, которое позволяет использовать указатели в методе
Copy . Оператор fixed используется для объявления указателей исходного и конечного массивов. Оператор
fixed закрепляет расположение исходного и конечного массивов в памяти, чтобы они не перемещались
при сборке мусора. Закрепление этих блоков памяти отменяется, когда завершается обработка блока fixed .
Поскольку метод Copy в этом примере использует ключевое слово unsafe , он должен быть скомпилирован
с параметром компилятора -unsafe.
В этом примере доступ к элементам обоих массивов выполняется с помощью индексов, а не второго
неуправляемого указателя. Объявление указателей pSource и pTarget закрепляет массивы. Эта возможность
доступна начиная с C# 7.3.
Пример
static unsafe void Copy(byte[] source, int sourceOffset, byte[] target,
int targetOffset, int count)
{
// If either array is not instantiated, you cannot complete the copy.
if ((source == null) || (target == null))
{
throw new System.ArgumentException();
}
// If the number of bytes from the offset to the end of the array is
// less than the number of bytes you want to copy, you cannot complete
// the copy.
if ((source.Length - sourceOffset < count) ||
(target.Length - targetOffset < count))
{
throw new System.ArgumentException();
}
// The following fixed statement pins the location of the source and
// target objects in memory so that they will not be moved by garbage
// collection.
fixed (byte* pSource = source, pTarget = target)
{
// Copy the specified number of bytes from source to target.
for (int i = 0; i < count; i++)
{
pTarget[targetOffset + i] = pSource[sourceOffset + i];
}
}
}
См. также
Руководство по программированию на C#
Небезопасный код и указатели
-unsafe (параметры компилятора C#)
Сборка мусора
Комментарии XML-документации (Руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
В Visual C# можно создавать документацию для кода путем включения XML -элементов в специальные
поля комментариев (начинающиеся с трех символов косой черты) в исходном коде непосредственно перед
блоком кода, к которому относятся комментарии. Например:
/// <summary>
/// This class performs an important function.
/// </summary>
public class MyClass {}
При выполнении компиляции с параметром -doc компилятор осуществляет поиск всех тегов XML в
исходном коде и создает XML -файл документации. Для создания окончательной документации на основе
сгенерированного компилятором файла можно создать пользовательское средство или применить такие
средства, как DocFX или Sandcastle.
Для ссылки на XML -элементы (например, если функция обрабатывает определенные XML -элементы,
которые требуется включить в комментарии XML -документации) можно использовать стандартный
механизм заключения в скобки ( < и > ). Для ссылки на универсальные идентификаторы в элементах ссылок
кода ( cref ) можно использовать escape-символы (например, cref="List<T>" ) или фигурные скобки (
cref="List{T}" ). В особом случае компилятор анализирует фигурные скобки, как угловые, чтобы при ссылке
на универсальные идентификаторы сделать комментарий документации менее громоздким.
NOTE
Комментарии XML-документации не являются метаданными. Они не включаются в скомпилированную сборку, и
поэтому не доступны посредством отражения.
В этом разделе
Рекомендуемые теги для комментариев документации
Обработка XML -файла
Разделители для тегов документации
Практическое руководство. Использование XML -документации
Связанные разделы
Дополнительные сведения можно найти в разделе
-doc (обработка комментариев документации)
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев
документации (Руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
NOTE
Комментарии документации не применяются к пространству имен.
Компилятор обрабатывает любые теги, имеющие допустимый формат XML. С помощью следующих
тегов реализуются часто используемые в пользовательской документации возможности.
Tags
/// <summary>
/// This property always returns a value < 1.
/// </summary>
См. также
Руководство по программированию на C#
-doc (параметры компилятора C#)
Комментарии XML -документации
Обработка XML-файла (руководство по
программированию на C#)
31.10.2019 • 9 minutes to read • Edit Online
Компилятор создает строку идентификатора для каждой конструкции в коде, помеченной для создания
документации. Дополнительные сведения о разметке кода см. в статье Рекомендуемые теги для
комментариев документации. Строка идентификатора однозначно определяет конструкцию. Программы,
обрабатывающие этот XML -файл, могут с помощью строки идентификатора определить, какому элементу
метаданных или отражения .NET Framework соответствует эта документация.
XML -файл не содержит иерархическое представление кода, а только одноуровневый список с
идентификаторами всех элементов.
Компилятор соблюдает следующие правила при формировании строк идентификаторов.
В строке нет пробелов.
Первая часть строки идентификатора определяет тип идентифицируемого члена. Это один символ, за
которым следует двоеточие. Используются следующие типы элементов.
ЗНАК ОПИСАНИЕ
Нет namespace
C поле
E event
! текст ошибки
Вторая часть строки содержит полное имя элемента, начиная от корня пространства имен. Имя
элемента, включающие типы и пространства имен разделяются точками. Если в имени самого
элемента есть точки, они заменяются символами решетки (#). Предполагается, что в именах
элементов не может содержаться символ решетки. Например полное, полное имя конструктора для
объекта String будет иметь вид System.String.#ctor.
Для свойств и методов, имеющих аргументы, список этих аргументов заключается в круглые скобки и
указывается в конце. Если аргументы не используются, скобки отсутствуют. Аргументы разделяются
запятыми. Кодировка каждого аргумента указывается в точности так, как она закодирована в
сигнатуре .NET Framework.
Базовые типы. Регулярные типы (ELEMENT_TYPE_CLASS или ELEMENT_TYPE_VALUETYPE )
представляются полным именам типа.
Внутренние типы (например, ELEMENT_TYPE_I4, ELEMENT_TYPE_OBJECT,
ELEMENT_TYPE_STRING, ELEMENT_TYPE_TYPEDBYREF и ELEMENT_TYPE_VOID )
представляются полным именем соответствующего полного типа. Например, System.Int32 или
System.TypedReference.
ELEMENT_TYPE_PTR представляется как "*" после имени измененного типа.
ELEMENT_TYPE_BYREF представляется как '@' после имени измененного типа.
ELEMENT_TYPE_PINNED представляется как "^" после имени измененного типа. Компилятор
C# никогда не создает такой текст.
ELEMENT_TYPE_CMOD_REQ представляется как "|" с указанием полного имени класса
модификатора после имени измененного типа. Компилятор C# никогда не создает такой текст.
ELEMENT_TYPE_CMOD_OPT представляется как "!" с указанием полного имени класса
модификатора после имени измененного типа.
ELEMENT_TYPE_SZARRAY представляется как "[]" после типа элемента для массива.
ELEMENT_TYPE_GENERICARRAY представляется как "[?]" после типа элемента для массива.
Компилятор C# никогда не создает такой текст.
ELEMENT_TYPE_ARRAY представляется как [нижняя_граница: size ,нижняя_граница: size ] где
число запятых на единицу меньше размерности массива, а нижние границы и размер каждого
измерения, если они известны, представлены в десятичном формате. Если нижняя граница или
размер не указаны, они опускаются. Если нижняя граница и размер для определенной
размерности опущены, опускается и символ ":". Например, двухмерный массив со значением 1
для нижних границ без указания размеров обозначается так: "[1:,1:]".
ELEMENT_TYPE_FNPTR представляется как "=FUNC: type (подпись)", где type обозначает
возвращаемый тип, а подпись представляет аргументы метода. Если аргументы не
используются, скобки опускаются. Компилятор C# никогда не создает такой текст.
Следующие компоненты подписи не представляются, поскольку они никогда не используются
для различения перегруженных методов:
соглашение о вызовах;
тип возвращаемого значения;
ELEMENT_TYPE_SENTINEL.
Только для операторов преобразования (op_Implicit и op_Explicit) возвращаемое значение метода
кодируется как символ "~", за которым следует возвращаемый тип, как описано выше.
Для универсальных типов в конце имени указывается символ обратного апострофа и число, которое
обозначает количество параметров универсального типа. Например:
<member name="T:SampleClass`2"> — это тег для типа, который определен как
public class SampleClass<T, U> .
Для методов, принимающих в качестве параметров универсальные типы, параметры универсального
типа указываются в виде чисел, которым предшествуют символы обратного апострофа (например,
`0,`1). Каждое число представляет нотацию массива с отсчетом индексации для параметров
универсального типа.
Примеры
Следующие примеры демонстрируют, как создаются строки идентификатора для класса и его элементов:
namespace N
{
/// <summary>
/// Enter description here for class X.
/// ID string generated is "T:N.X".
/// </summary>
public unsafe class X
{
/// <summary>
/// Enter description here for the first constructor.
/// ID string generated is "M:N.X.#ctor".
/// </summary>
public X() { }
/// <summary>
/// Enter description here for the second constructor.
/// ID string generated is "M:N.X.#ctor(System.Int32)".
/// </summary>
/// <param name="i">Describe parameter.</param>
public X(int i) { }
/// <summary>
/// Enter description here for field q.
/// ID string generated is "F:N.X.q".
/// </summary>
public string q;
/// <summary>
/// Enter description for constant PI.
/// ID string generated is "F:N.X.PI".
/// </summary>
public const double PI = 3.14;
/// <summary>
/// Enter description for method f.
/// ID string generated is "M:N.X.f".
/// </summary>
/// <returns>Describe return value.</returns>
public int f() { return 1; }
/// <summary>
/// Enter description for method bb.
/// ID string generated is "M:N.X.bb(System.String,System.Int32@,System.Void*)".
/// </summary>
/// <param name="s">Describe parameter.</param>
/// <param name="y">Describe parameter.</param>
/// <param name="z">Describe parameter.</param>
/// <returns>Describe return value.</returns>
public int bb(string s, ref int y, void* z) { return 1; }
/// <summary>
/// <summary>
/// Enter description for method gg.
/// ID string generated is "M:N.X.gg(System.Int16[],System.Int32[0:,0:])".
/// </summary>
/// <param name="array1">Describe parameter.</param>
/// <param name="array">Describe parameter.</param>
/// <returns>Describe return value.</returns>
public int gg(short[] array1, int[,] array) { return 0; }
/// <summary>
/// Enter description for operator.
/// ID string generated is "M:N.X.op_Addition(N.X,N.X)".
/// </summary>
/// <param name="x">Describe parameter.</param>
/// <param name="xx">Describe parameter.</param>
/// <returns>Describe return value.</returns>
public static X operator +(X x, X xx) { return x; }
/// <summary>
/// Enter description for property.
/// ID string generated is "P:N.X.prop".
/// </summary>
public int prop { get { return 1; } set { } }
/// <summary>
/// Enter description for event.
/// ID string generated is "E:N.X.d".
/// </summary>
public event D d;
/// <summary>
/// Enter description for property.
/// ID string generated is "P:N.X.Item(System.String)".
/// </summary>
/// <param name="s">Describe parameter.</param>
/// <returns></returns>
public int this[string s] { get { return 1; } }
/// <summary>
/// Enter description for class Nested.
/// ID string generated is "T:N.X.Nested".
/// </summary>
public class Nested { }
/// <summary>
/// Enter description for delegate.
/// ID string generated is "T:N.X.D".
/// </summary>
/// <param name="i">Describe parameter.</param>
public delegate void D(int i);
/// <summary>
/// Enter description for operator.
/// ID string generated is "M:N.X.op_Explicit(N.X)~System.Int32".
/// </summary>
/// <param name="x">Describe parameter.</param>
/// <returns>Describe return value.</returns>
public static explicit operator int(X x) { return 1; }
}
}
См. также
Руководство по программированию на C#
-doc (параметры компилятора C#)
Комментарии XML -документации
Разделители для тегов в документации
(Руководство по программированию на C#)
23.10.2019 • 4 minutes to read • Edit Online
NOTE
В интегрированной среде разработки Visual Studio имеется функция автоматического редактирования комментариев
Smart Comment Editing, которая автоматически вставляет теги <summary> и </summary> и перемещает курсор в этих
тегах после ввода разделителя /// в редакторе кода. Вы можете включить или отключить эту функцию в диалоговом
окне "Параметры".
/** */
Многострочные разделители.
При использовании разделителей /** */ нужно следовать нескольким правилам форматирования.
Строка, содержащая разделитель /** , не обрабатывается как комментарий, если оставшаяся ее часть
представляет собой пробелы. Если первый знак после разделителя /** является пробелом, то этот
пробел игнорируется, а оставшаяся часть строки обрабатывается. В противном случае весь текст,
расположенный в строке после разделителя /** , обрабатывается как часть комментария.
Строка, содержащая разделитель */ , игнорируется, если перед разделителем */ стоят только
пробелы. В противном случае текст строки, расположенный перед разделителем */ , обрабатывается
как часть комментария, и к нему применяются правила сопоставления шаблонов, описанные в
следующем разделе.
В начале каждой из строк, расположенных после строки, которая начинается с разделителя /** ,
компилятор выполняет поиск общего шаблона. Шаблон может включать необязательный пробел и
знак звездочки ( * ), после которого следуют необязательные пробелы. Если компилятор находит
общий шаблон в начале каждой строки, которая не начинается с разделителя /** или */ , он
игнорирует этот шаблон для каждой строки.
Эти правила показаны в следующем примере.
В следующем комментарии будет обработана только строка, которая начинается с <summary> . Формат
с тремя тегами позволяет создать точно такие же комментарии.
/** <summary>text</summary> */
/**
<summary>text</summary>
*/
/**
* <summary>text</summary>
*/
Компилятор обнаруживает в начале второй и третьей строки общий шаблон " * ". Шаблон не
включается в выходные данные.
/**
* <summary>
* text </summary>*/
Компилятор не обнаруживает общий шаблон в следующем комментарии, так как второй знак в
третьей строке не является звездочкой. Следовательно, весь текст во второй и третьей строках
обрабатывается как часть примечания.
/**
* <summary>
text </summary>
*/
/**
* <summary>
* text
* text2
* </summary>
*/
См. также
Руководство по программированию на C#
Комментарии XML -документации
-doc (параметры компилятора C#)
Комментарии XML -документации
Практическое руководство. Использование XML-
документации
23.10.2019 • 5 minutes to read • Edit Online
Пример
// If compiling from the command line, compile with: -doc:YourFileName.xml
/// <summary>
/// Class level summary documentation goes here.
/// </summary>
/// <remarks>
/// Longer comments can be associated with a type or member through
/// the remarks tag.
/// </remarks>
public class TestClass : TestInterface
{
/// <summary>
/// Store for the Name property.
/// </summary>
private string _name = null;
/// <summary>
/// The class constructor.
/// </summary>
public TestClass()
{
// TODO: Add Constructor Logic here.
}
/// <summary>
/// Name property.
/// </summary>
/// <value>
/// A value tag is used to describe the property value.
/// </value>
public string Name
{
get
{
if (_name == null)
{
throw new System.Exception("Name is null");
}
return _name;
}
}
/// <summary>
/// Description for SomeMethod.
/// </summary>
/// <param name="s"> Parameter description for s goes here.</param>
/// <seealso cref="System.String">
/// You can use the cref attribute on any tag to reference a type or member
/// and the compiler will check that the reference exists.
/// </seealso>
public void SomeMethod(string s)
{
{
}
/// <summary>
/// Some other method.
/// </summary>
/// <returns>
/// Return values are described through the returns tag.
/// </returns>
/// <seealso cref="SomeMethod(string)">
/// Notice the use of the cref attribute to reference a specific method.
/// </seealso>
public int SomeOtherMethod()
{
return 0;
}
/// <summary>
/// The entry point for the application.
/// </summary>
/// <param name="args"> A list of command line arguments.</param>
static int Main(System.String[] args)
{
// TODO: Add code to start application here.
return 0;
}
}
/// <summary>
/// Documentation that describes the interface goes here.
/// </summary>
/// <remarks>
/// Details about the interface go here.
/// </remarks>
interface TestInterface
{
/// <summary>
/// Documentation that describes the method goes here.
/// </summary>
/// <param name="n">
/// Parameter n requires an integer argument.
/// </param>
/// <returns>
/// The method returns an integer.
/// </returns>
int InterfaceMethod(int n);
}
<?xml version="1.0"?>
<doc>
<assembly>
<name>xmlsample</name>
</assembly>
<members>
<member name="T:TestClass">
<summary>
Class level summary documentation goes here.
</summary>
<remarks>
Longer comments can be associated with a type or member through
the remarks tag.
</remarks>
</member>
<member name="F:TestClass._name">
<summary>
Store for the Name property.
</summary>
</member>
<member name="M:TestClass.#ctor">
<summary>
The class constructor.
</summary>
</member>
<member name="P:TestClass.Name">
<summary>
Name property.
</summary>
<value>
A value tag is used to describe the property value.
</value>
</member>
<member name="M:TestClass.SomeMethod(System.String)">
<summary>
Description for SomeMethod.
</summary>
<param name="s"> Parameter description for s goes here.</param>
<seealso cref="T:System.String">
You can use the cref attribute on any tag to reference a type or member
and the compiler will check that the reference exists.
</seealso>
</member>
<member name="M:TestClass.SomeOtherMethod">
<summary>
Some other method.
</summary>
<returns>
Return values are described through the returns tag.
</returns>
<seealso cref="M:TestClass.SomeMethod(System.String)">
Notice the use of the cref attribute to reference a specific method.
</seealso>
</member>
<member name="M:TestClass.Main(System.String[])">
<summary>
The entry point for the application.
</summary>
<param name="args"> A list of command line arguments.</param>
</member>
<member name="T:TestInterface">
<summary>
Documentation that describes the interface goes here.
</summary>
<remarks>
Details about the interface go here.
</remarks>
</member>
<member name="M:TestInterface.InterfaceMethod(System.Int32)">
<summary>
Documentation that describes the method goes here.
</summary>
<param name="n">
Parameter n requires an integer argument.
</param>
<returns>
The method returns an integer.
</returns>
</member>
</members>
</doc>
Компиляция кода
Чтобы скомпилировать этот пример, введите в командной строке:
csc XMLsample.cs /doc:XMLsample.xml
Эта команда создает XML -файл XMLsample.xml, который можно просмотреть в браузере или с помощью
команды TYPE.
Отказоустойчивость
Документация XML начинается с ///. При создании нового проекта мастер добавляет несколько строк ///.
Обработка этих комментариев имеет некоторые ограничения.
Документация должна представлять собой XML с правильным форматом. Если формат XML неверен,
то выдается предупреждение и файл документации содержит комментарий о том, что произошла
ошибка.
Разработчики могут создавать собственные наборы тегов. Существует рекомендуемый набор тегов
(см. статью о рекомендуемых тегах для комментариев документации). Некоторые рекомендуемые
теги имеют особые значения.
Тег <param> используется для описания параметров. При использовании этого тега
компилятор проверяет, что параметр существует и все параметры описаны в документации.
При сбое проверки компилятор выдает предупреждение.
Атрибут cref может быть присоединен к любому тегу для предоставления ссылки на элемент
кода. Компилятор проверяет наличие этого элемента кода. При сбое проверки компилятор
выдает предупреждение. Компилятор учитывает любые операторы using при поиске типа,
описанного в атрибуте cref .
Тег <summary> используется технологией IntelliSense в Visual Studio для отображения
дополнительных сведений о типе или члене.
NOTE
XML-файл не предоставляет полную информацию о типе и членах (например, он не содержит никаких
сведений о типе). Чтобы получить полную информацию о типе или члене, необходимо использовать
файл документации вместе с отражением на текущий тип или член.
См. также
Руководство по программированию на C#
-doc (параметры компилятора C#)
Комментарии XML -документации
Обработчик документации DocFX
Обработчик документации Sandcastle
<c> (руководство по программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<c>text</c>
Параметры
text
Текст, который нужно указать в качестве кода.
Примечания
С помощью тега <c> можно указать, что текст в описании необходимо пометить как код. Чтобы определить
несколько строк в качестве кода, используйте тег <code>.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// compile with: -doc:DocFileName.xml
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<code> (руководство по программированию на
C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<code>content</code>
Параметры
content
Текст, который необходимо пометить как код.
Примечания
С помощью тега <code> можно определить несколько строк как код. С помощью тега <c> можно указать,
что текст в описании необходимо пометить как код.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
В разделе <example> можно найти пример использования тега <code>.
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
Атрибут cref (Руководство по программированию
на C#)
23.10.2019 • 3 minutes to read • Edit Online
Атрибут cref в теге XML -документации означает "кодовая ссылка". Он указывает, что текст внутри тега
представляет собой элемент кода, например тип, метод или свойство. Средства создания документации,
такие как DocFX и Sandcastle, используют атрибуты cref для автоматического создания гиперссылок на
страницу, где документирован тип или член.
Пример
В следующем примере показаны атрибуты cref , используемые в тегах <see>.
namespace TestNamespace
{
/// <summary>
/// TestClass contains several cref examples.
/// </summary>
public class TestClass
{
/// <summary>
/// This sample shows how to specify the <see cref="TestClass"/> constructor as a cref attribute.
/// </summary>
public TestClass()
{ }
/// <summary>
/// This sample shows how to specify the <see cref="TestClass(int)"/> constructor as a cref attribute.
/// </summary>
public TestClass(int value)
{ }
/// <summary>
/// The GetZero method.
/// </summary>
/// <example>
/// This sample shows how to call the <see cref="GetZero"/> method.
/// <code>
/// class TestClass
/// {
/// static int Main()
/// {
/// return GetZero();
/// }
/// }
/// </code>
/// </example>
public static int GetZero()
{
return 0;
}
/// <summary>
/// The GetGenericValue method.
/// </summary>
/// <remarks>
/// <remarks>
/// This sample shows how to specify the <see cref="GetGenericValue"/> method as a cref attribute.
/// </remarks>
/// <summary>
/// GenericClass.
/// </summary>
/// <remarks>
/// This example shows how to specify the <see cref="GenericClass{T}"/> type as a cref attribute.
/// </remarks>
class GenericClass<T>
{
// Fields and members.
}
class Program
{
static int Main()
{
return TestClass.GetZero();
}
}
}
В результате компиляции программа создает следующий XML -файл. Обратите внимание, что атрибут cref
метода GetZero , в частности, был преобразован компилятором в "M:TestNamespace.TestClass.GetZero" .
Префикс "M:" означает "метод" и является условным обозначением, распознаваемым средствами
документации, такими как DocFX и Sandcastle. Полный список префиксов см. в разделе Обработка XML -
файла.
<?xml version="1.0"?>
<doc>
<assembly>
<name>CRefTest</name>
</assembly>
<members>
<member name="T:TestNamespace.TestClass">
<summary>
TestClass contains cref examples.
</summary>
</member>
<member name="M:TestNamespace.TestClass.#ctor">
<summary>
This sample shows how to specify the <see cref="T:TestNamespace.TestClass"/> constructor as a cref
attribute.
</summary>
</member>
<member name="M:TestNamespace.TestClass.#ctor(System.Int32)">
<summary>
This sample shows how to specify the <see cref="M:TestNamespace.TestClass.#ctor(System.Int32)"/>
constructor as a cref attribute.
</summary>
</member>
<member name="M:TestNamespace.TestClass.GetZero">
<summary>
The GetZero method.
</summary>
<example>
This sample shows how to call the <see cref="M:TestNamespace.TestClass.GetZero"/> method.
<code>
class TestClass
class TestClass
{
static int Main()
{
return GetZero();
}
}
</code>
</example>
</member>
<member name="M:TestNamespace.TestClass.GetGenericValue``1(``0)">
<summary>
The GetGenericValue method.
</summary>
<remarks>
This sample shows how to specify the <see
cref="M:TestNamespace.TestClass.GetGenericValue``1(``0)"/> method as a cref attribute.
</remarks>
</member>
<member name="T:TestNamespace.GenericClass`1">
<summary>
GenericClass.
</summary>
<remarks>
This example shows how to specify the <see cref="T:TestNamespace.GenericClass`1"/> type as a cref
attribute.
</remarks>
</member>
</members> <members>
<member name="T:TestNamespace.TestClass">
<summary>
TestClass contains two cref examples.
</summary>
</member>
<member name="M:TestNamespace.TestClass.GetZero">
<summary>
The GetZero method.
</summary>
<example> This sample shows how to call the <see cref="M:TestNamespace.TestClass.GetZero"/>
method.
<code>
class TestClass
{
static int Main()
{
return GetZero();
}
}
</code>
</example>
</member>
<member name="M:TestNamespace.TestClass.GetGenericValue``1(``0)">
<summary>
The GetGenericValue method.
</summary>
<remarks>
This sample shows how to specify the <see
cref="M:TestNamespace.TestClass.GetGenericValue``1(``0)"/> method as a cref attribute.
</remarks>
</member>
<member name="T:TestNamespace.GenericClass`1">
<summary>
GenericClass.
</summary>
<remarks>
This example shows how to specify the <see cref="T:TestNamespace.GenericClass`1"/> type as a cref
attribute.
</remarks>
</member>
</members>
</members>
</doc>
См. также
Комментарии XML -документации
Рекомендуемые теги для комментариев документации
<example> (руководство по программированию
на C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<example>description</example>
Параметры
description
Описание примера кода.
Примечания
Тег <example> позволяет указать пример использования метода или другого элемента библиотеки. Вместе с
ним часто применяется тег <code>.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// Save this file as CRefTest.cs
// Compile with: csc CRefTest.cs -doc:Results.xml
namespace TestNamespace
{
/// <summary>
/// TestClass contains several cref examples.
/// </summary>
public class TestClass
{
/// <summary>
/// This sample shows how to specify the <see cref="TestClass"/> constructor as a cref attribute.
/// </summary>
public TestClass()
{ }
/// <summary>
/// This sample shows how to specify the <see cref="TestClass(int)"/> constructor as a cref attribute.
/// </summary>
public TestClass(int value)
{ }
/// <summary>
/// The GetZero method.
/// </summary>
/// <example>
/// This sample shows how to call the <see cref="GetZero"/> method.
/// <code>
/// class TestClass
/// {
/// static int Main()
/// {
/// {
/// return GetZero();
/// }
/// }
/// </code>
/// </example>
public static int GetZero()
{
return 0;
}
/// <summary>
/// The GetGenericValue method.
/// </summary>
/// <remarks>
/// This sample shows how to specify the <see cref="GetGenericValue"/> method as a cref attribute.
/// </remarks>
/// <summary>
/// GenericClass.
/// </summary>
/// <remarks>
/// This example shows how to specify the <see cref="GenericClass{T}"/> type as a cref attribute.
/// </remarks>
class GenericClass<T>
{
// Fields and members.
}
class Program
{
static int Main()
{
return TestClass.GetZero();
}
}
}
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<exception> (руководство по программированию
на C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<exception cref="member">description</exception>
Параметры
cref = " member "
Ссылка на исключение, которое доступно из текущей среды компиляции. Компилятор проверяет, существует
ли исключение, и приводит member к каноническому имени элемента в выходных XML -данных. member
необходимо заключать в двойные кавычки (" ").
См. подробнее о том, как форматировать member , чтобы создать ссылку на универсальный тип, в
руководстве по обработке XML -файла.
description
Описание исключения.
Примечания
Тег <exception> служит для указания возможных исключений. Этот тег может применяться к определениям
методов, свойств, событий и индексаторов.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Дополнительные сведения об обработке исключений см. в разделе Исключения и обработка исключений.
Пример
// compile with: -doc:DocFileName.xml
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<include> (руководство по программированию на
C#)
23.10.2019 • 3 minutes to read • Edit Online
Синтаксис
<include file='filename' path='tagpath[@name="id"]' />
Параметры
filename
Имя XML -файла, содержащего документацию. Имя файла может быть квалифицировано с помощью
относительного пути к файлу исходного кода. filename необходимо заключать в одинарные кавычки (' ').
tagpath
Путь тегов в filename , который ведет к тегу name . Путь необходимо заключать в одинарные кавычки (' ').
name
Спецификатор имени в теге, предшествующий комментариям. name будет иметь идентификатор id .
id
Идентификатор тега, который предшествует комментариям. Идентификатор заключается в двойные
кавычки (" ").
Примечания
Тег <include> позволяет задать ссылку на комментарии в другом файле, которые описывают типы и члены
вашего исходного кода. Этот способ является альтернативой размещению комментариев документации
непосредственно в файле исходного кода. Помещая комментарии документации в отдельный файл, вы
можете реализовать управление их версиями отдельно от версий исходного кода. В этом случае файл
исходного кода может быть извлечен для изменения одним пользователем, а файл документации — другим.
Тег <include> использует XML -синтаксис XPath. Сведения об использовании тега <include> см. в
документации по XPath.
Пример
В этом примере представлено несколько файлов. Ниже показан первый файл, в котором используется тег
<include>:
// compile with: -doc:DocFileName.xml
<MyDocs>
<MyMembers name="test">
<summary>
The summary for this type.
</summary>
</MyMembers>
<MyMembers name="test2">
<summary>
The summary for this other type.
</summary>
</MyMembers>
</MyDocs>
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<list> (руководство по программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<list type="bullet" | "number" | "table">
<listheader>
<term>term</term>
<description>description</description>
</listheader>
<item>
<term>term</term>
<description>description</description>
</item>
</list>
Параметры
term
Термин, который будет определен в description .
description
Либо элемент маркированного или нумерованного списка, либо определение term .
Примечания
Блок <listheader> используется для определения строки заголовка в таблице или списке определений. При
определении таблицы необходимо ввести данные для термина в заголовке.
Каждый элемент в списке указывается в блоке <item>. При создании списка определений необходимо
указать одновременно term и description . Тем не менее для таблицы, маркированного или нумерованного
списка достаточно ввести только description .
Число блоков <item> в списке или таблице не ограничено.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// compile with: -doc:DocFileName.xml
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<para> (руководство по программированию на
C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<para>content</para>
Параметры
content
Текст абзаца.
Примечания
Тег <para> используется внутри другого тега, например <summary>, <remarks> или <returns>, и позволяет
добавить к тексту структуру.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
В разделе <summary> можно найти пример использования тега <para>.
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<param> (руководство по программированию на
C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<param name="name">description</param>
Параметры
name
Имя параметра метода. Имя заключается в двойные кавычки (" ").
description
Описание параметра.
Примечания
Тег <param> следует использовать в комментариях к объявлению метода для описания одного из
параметров такого метода. Чтобы задокументировать несколько параметров, используйте несколько тегов
<param>.
Текст тега <param> будет отображаться в IntelliSense, в окне обозревателя объектов и в веб-отчете по
комментариям к коду.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// compile with: -doc:DocFileName.xml
// Multiple parameters.
/// <param name="Int1">Used to indicate status.</param>
/// <param name="Float1">Used to specify context.</param>
public static void DoWork(int Int1, float Float1)
{
}
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<paramref> (руководство по программированию
на C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<paramref name="name"/>
Параметры
name
Имя параметра, на который указывается ссылка. Имя заключается в двойные кавычки (" ").
Примечания
Тег <paramref> позволяет указать, что слово в комментариях к коду, например в блоке <summary> или
<remarks>, ссылается на параметр. В XML -файл такое слово может выделяться особым образом, например
курсивом или полужирным шрифтом.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// compile with: -doc:DocFileName.xml
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<permission> (руководство по программированию
на C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<permission cref="member">description</permission>
Параметры
cref = " member "
Ссылка на член или поле, которые доступны для вызова из текущей среды компиляции. Компилятор
проверяет, существует ли элемент кода, и приводит member к каноническому имени элемента в выходных
XML -данных. member необходимо заключать в двойные кавычки (" ").
Дополнительные сведения о создании ссылки cref на универсальный тип см. в разделе <see>.
description
Описание уровня доступа для члена.
Примечания
Тег <permission> tag позволяет документировать уровень доступа для члена. Задать уровень доступа для
члена можно с помощью класса PermissionSet.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// compile with: -doc:DocFileName.xml
class TestClass
{
/// <permission cref="System.Security.PermissionSet">Everyone can access this method.</permission>
public static void Test()
{
}
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<remarks> (руководство по программированию на
C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<remarks>description</remarks>
Параметры
Description
Описание элемента.
Примечания
Тег <remarks> позволяет добавить сведения о типе, дополняющие информацию, указанную с помощью
тега <summary>. Эти сведения отображаются в окне "Обозреватель объектов".
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// compile with: -doc:DocFileName.xml
/// <summary>
/// You may have some primary information about this class.
/// </summary>
/// <remarks>
/// You may have some additional information about this class.
/// </remarks>
public class TestClass
{
/// text for Main
static void Main()
{
}
}
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<returns> (Руководство по программированию на
C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<returns>description</returns>
Параметры
description
Описание возвращаемого значения.
Примечания
Тег <returns> следует использовать в комментариях к объявлению метода для описания возвращаемого
значения.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// compile with: -doc:DocFileName.xml
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<see> (руководство по программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<see cref="member"/>
Параметры
cref = " member "
Ссылка на член или поле, которые доступны для вызова из текущей среды компиляции. Компилятор
проверяет, существует ли элемент кода, и передает member в имя элемента в выходных XML -данных.
member необходимо заключать в двойные кавычки (" ").
Примечания
Тег <see> позволяет задать ссылку из текста. С помощью тега <seealso> можно указать, что текст должен
быть размещен в разделе "См. также". Используйте cref Attribute для создания внутренних гиперссылок на
страницы документации для элементов кода.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
В следующем примере показан тег <see> в разделе сводки.
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<seealso> (руководство по программированию на
C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<seealso cref="member"/>
Параметры
cref = " member "
Ссылка на член или поле, которые доступны для вызова из текущей среды компиляции. Компилятор
проверяет, существует ли элемент кода, и передает member в имя элемента в выходных XML -данных. member
необходимо заключать в двойные кавычки (" ").
Дополнительные сведения о создании ссылки cref на универсальный тип см. в разделе <see>.
Примечания
Кроме того, с помощью тега <seealso> можно указать текст, который должен отображаться в разделе "См.
также". Тег <see> позволяет задать ссылку из текста.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
В разделе <summary> можно найти пример использования тега <seealso>.
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<summary> (руководство по программированию
на C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<summary>description</summary>
Параметры
description
Сводка объекта.
Примечания
Тег <summary> следует использовать для описания типа или члена типа. Чтобы добавить
дополнительную информацию в описание типа, используйте <remarks>. Чтобы включить средства
документации, такие как DocFX и Sandcastle, для создания внутренних гиперссылок на страницы
документации для элементов кода, используйте атрибут cref.
Текст в теге <summary> является единственным источником сведений о типе для технологии IntelliSense и
также отображается в окне обозревателя объектов.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc. Для создания окончательной документации на основе сгенерированного компилятором файла можно
создать пользовательское средство или применить такие средства, как DocFX или Sandcastle.
Пример
// compile with: -doc:DocFileName.xml
Пример
В следующем примере показано, как создать ссылку cref на универсальный тип.
// the following cref shows how to specify the reference, such that,
// the compiler will resolve the reference
/// <summary cref="C{T}">
/// </summary>
class A { }
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<typeparam> (руководство по программированию
на C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<typeparam name="name">description</typeparam>
Параметры
name
Имя параметра типа. Имя заключается в двойные кавычки (" ").
description
Описание параметра типа.
Примечания
Тег <typeparam> следует использовать в комментариях к объявлению универсального типа или метода для
описания параметра типа. Добавьте такой тег для каждого параметра типа универсального типа или метода.
Дополнительные сведения см. в статье Универсальные шаблоны.
Текст тега <typeparam> будет отображаться в IntelliSense, (веб-отчет по комментариям к коду в окне
обозревателя объектов).
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// compile with: -doc:DocFileName.xml
См. также
Справочник по C#
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<typeparamref> (руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<typeparamref name="name"/>
Параметры
name
Имя параметра типа. Имя заключается в двойные кавычки (" ").
Примечания
Дополнительные сведения о параметрах типа в универсальных типах и методах см. в разделе Универсальные
шаблоны.
Используйте этот тег, чтобы разрешить получателям файла документации форматировать слово
определенным образом, например курсивным шрифтом.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// compile with: -doc:DocFileName.xml
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
<value> (руководство по программированию на
C#)
23.10.2019 • 2 minutes to read • Edit Online
Синтаксис
<value>property-description</value>
Параметры
property-description
Описание свойства.
Примечания
Тег <value> позволяет описывать значение, которое представляется свойством. Обратите внимание, что при
добавлении свойства с помощью мастера создания кода из среды разработки Visual Studio .NET для нового
свойства будет добавлен тег <summary>. После этого следует вручную добавить тег <value> для описания
значения, которое представляется свойством.
Чтобы обработать комментарии документации и сохранить их в файл, выполняйте сборку с параметром -
doc.
Пример
// compile with: -doc:DocFileName.xml
См. также
Руководство по программированию на C#
Рекомендуемые теги для комментариев документации
Исключения и обработка исключений
(Руководство по программированию в C#)
04.11.2019 • 5 minutes to read • Edit Online
class ExceptionTest
{
static double SafeDivision(double x, double y)
{
if (y == 0)
throw new System.DivideByZeroException();
return x / y;
}
static void Main()
{
// Input for test purposes. Change the values to see
// exception handling behavior.
double a = 98, b = 0;
double result = 0;
try
{
result = SafeDivision(a, b);
Console.WriteLine("{0} divided by {1} = {2}", a, b, result);
}
catch (DivideByZeroException e)
{
Console.WriteLine("Attempted divide by zero.");
}
}
}
Связанные разделы
Дополнительные сведения об исключениях и обработке исключений вы найдете в следующих статьях:
Использование исключений
Обработка исключений
Создание и порождение исключений
Исключения, создаваемые компилятором
Практическое руководство. Руководство по программированию на C#. Обработка исключений с
помощью блока try-catch
Практическое руководство. Выполнение кода очистки с использованием блока finally
Практическое руководство. Перехват несовместимого с CLS исключения
Спецификация языка C#
Дополнительные сведения см. в разделе Исключения в Спецификации языка C#. Спецификация языка
является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
SystemException
Руководство по программированию на C#
Ключевые слова в C#
throw
try-catch
try-finally
try-catch-finally
Исключения
Использование исключений (Руководство по
программированию на C#)
23.10.2019 • 5 minutes to read • Edit Online
}
private static void TestThrow()
{
CustomException ex =
new CustomException("Custom exception in TestThrow()");
throw ex;
}
После выдачи исключения среда выполнения проверяет, входит ли текущий оператор в блок try . Если да,
она проверяет, может ли какой-либо из блоков catch , связанных с блоком try , перехватить исключение.
Блоки Catch обычно задают типы исключений; если тип блока catch совпадает с типом или базовым
классом исключения, блок catch может обработать этот метод. Например:
Если оператор, который вызывает исключение, не находится в блоке try или блок try , в который он
входит, не имеет соответствующего блока catch , среда выполнения проверяет вызывающий метод на
наличие оператора try и блоков catch . Среда выполнения продолжает перебирать стек вызовов в поиска
подходящего блока catch . После того как блок catch будет найден и выполнен, управление передается
оператору, следующему после блока catch .
Оператор try может содержать не один блок catch . Выполняется первый оператор catch , который может
обработать исключение; все последующие операторы catch игнорируются, даже если они совместимы. В
связи с этим блоки catch следует располагать в порядке от наиболее конкретных (наиболее производных) до
наименее конкретных. Например:
using System;
using System.IO;
Console.WriteLine("Done");
}
}
Прежде чем выполнять блок catch , среда выполнения проверяет наличие блоков finally . Блоки Finally
позволяют программисту удалить любое неоднозначное состояние, которое может остаться после
прерванного блока try , а также освободить любые внешние ресурсы (включая обработчики графики,
соединители баз данных или файловые потоки), не дожидаясь, пока сборщик мусора в среде выполнения
завершит объекты. Например:
static void TestFinally()
{
System.IO.FileStream file = null;
//Change the path to something that works on your machine.
System.IO.FileInfo fileInfo = new System.IO.FileInfo(@"C:\file.txt");
try
{
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
finally
{
// Closing the file allows you to reopen it immediately - otherwise IOException is thrown.
if (file != null)
{
file.Close();
}
}
try
{
file = fileInfo.OpenWrite();
System.Console.WriteLine("OpenWrite() succeeded");
}
catch (System.IO.IOException)
{
System.Console.WriteLine("OpenWrite() failed");
}
}
Если WriteByte() выдает исключение, код во втором блоке try , который пытается открыть файл повторно,
завершается ошибкой, если file.Close() не вызывается, а файл остается заблокированным. Поскольку блоки
finally выполняются, даже если выдается исключение, блок finally в предыдущем примере обеспечивает
правильное закрытие файла и помогает избежать ошибки.
Если после выдачи исключения обнаружить совместимый блок catch в стеке вызовов не удается,
происходит одно из трех:
Если исключение возникает в методе завершения, он прерывается, и вызывается базовый метод
завершения (если есть).
Если стек вызовов содержит статический конструктор или инициализатор статического поля, выдается
исключение TypeInitializationException, а исходное исключение назначается свойству InnerException
нового исключения.
Как только достигается начало потока, он прерывается.
См. также
Руководство по программированию на C#
Исключения и обработка исключений
Обработка исключений (Руководство по
программированию на C#)
04.11.2019 • 6 minutes to read • Edit Online
Блок try используется программистами C# для разбиения на разделы кода, который может затрагиваться
исключением. Связанные с ним блоки catch используются для обработки возможных исключений. Блок
finally содержит код, выполняемый вне зависимости от того, вызывается ли исключение в блоке try ,
например для освобождения ресурсов, выделенных в блоке try . Блоку try требуется один или несколько
связанных блоков catch или блок finally (либо и то, и другое).
В приведенных ниже примерах показаны операторы try-catch , try-finally и try-catch-finally .
try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
// Only catch exceptions that you know how to handle.
// Never catch base class System.Exception without
// rethrowing it at the end of the catch block.
}
try
{
// Code to try goes here.
}
finally
{
// Code to execute after the try block goes here.
}
try
{
// Code to try goes here.
}
catch (SomeSpecificException ex)
{
// Code to handle the exception goes here.
}
finally
{
// Code to execute after the try (and possibly catch) blocks
// goes here.
}
Блок try без блока catch или блока finally вызовет ошибку компилятора.
Блоки catch
Блок catch может определять тип перехватываемого исключения. Спецификация типа называется
фильтром исключений. Тип исключения должен быть производным от Exception. Как правило, не следует
задавать Exception в качестве фильтра исключений, кроме случаев, когда известно, как обрабатывать все
исключения, которые могут быть вызваны в блоке try , или когда оператор throw добавлен в конце блока
catch .
Несколько блоков catch с различными фильтрами исключений могут быть соединены друг с другом. Блоки
catch проверяются сверху вниз в коде, однако для каждого вызванного исключения выполняется только
один блок catch . Выполняется первый блок catch , в котором указан точный тип или базовый класс
вызванного исключения. Если нет блока catch , в котором определен соответствующий фильтр исключений,
выбирается блок catch , в котором не выбран фильтр, если таковой имеется в операторе. Важно, чтобы
первыми были размещены блоки catch с самыми конкретными (т. е. самыми производными) типами
исключений.
Исключения следует перехватывать при выполнении указанных ниже условий.
Вы ясно понимаете возможные причины вызова исключения и можете выполнить восстановление,
например, предложив пользователю ввести новое имя файла при перехвате объекта
FileNotFoundException.
Вы можете создать и вызвать новое, более конкретное исключение.
try
{
// Try to access a resource.
}
catch (System.UnauthorizedAccessException e)
{
// Call a custom error logging procedure.
LogError(e);
// Re-throw the error.
throw;
}
Блоки "Finally"
Блок finally позволяет удалить действия, выполненные в блоке try . При наличии блока finally он
выполняется последним, после блока try и всех выполняемых блоков catch . Блок finally выполняется
всегда вне зависимости от возникновения исключения или обнаружения блока catch , соответствующего
типу исключения.
Блок finally можно использовать для высвобождения ресурсов, например потоков данных, подключений к
базам данных, графических дескрипторов, не ожидая финализации объектов сборщиком мусора во время
выполнения. Дополнительные сведения см. в разделе Оператор using.
В приведенном ниже примере с помощью блока finally закрывается файл, открытый в блоке try .
Обратите внимание, что состояние дескриптора файла проверяется до закрытия файла. Если блок try не
может открыть этот файл, дескриптор файла по-прежнему имеет значение null , и блок finally не
пытается закрыть его. Кроме того, если файл успешно открыт в блоке try , блок finally закрывает
открытый файл.
Спецификация языка C#
Дополнительные сведения см. в разделах Исключения и Оператор try в Спецификации языка C#.
Спецификация языка является предписывающим источником информации о синтаксисе и использовании
языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Исключения и обработка исключений
try-catch
try-finally
try-catch-finally
Оператор using
Создание и генерация исключений (Руководство
по программированию C#)
04.11.2019 • 6 minutes to read • Edit Online
Исключения позволяют обозначить, что во время выполнения программы произошла ошибка. Объекты
исключений, описывающие ошибку, создаются и затем вызываются с помощью ключевого слова throw.
Далее среда выполнения ищет наиболее совместимый обработчик исключений.
Программисты должны вызывать исключения при выполнении одного или нескольких из перечисленных
ниже условий.
Метод не способен выполнить свои функции.
Например, если значение параметра метода является недопустимым:
class ProgramLog
{
System.IO.FileStream logFile = null;
void OpenLog(System.IO.FileInfo fileName, System.IO.FileMode mode) {}
void WriteLog()
{
if (!this.logFile.CanWrite)
{
throw new System.InvalidOperationException("Logfile cannot be read-only");
}
// Else write data to the log and return.
}
}
Исключения содержат свойство с именем StackTrace. Строка содержит имена методов в текущем стеке
вызовов вместе с именем файла и номером строки, в которой было вызвано исключение для каждого
метода. Объект StackTrace создается автоматически средой CLR из точки оператора throw , так что
исключения должны вызываться из той точки, где должна начинаться трассировка стека.
Все исключения содержат свойство с именем Message. Эта строка должна содержать сообщение с
объяснением причины исключения. Обратите внимание, что важные с точки зрения безопасности сведения
не следует помещать в текст сообщения. Помимо Message, ArgumentException содержит свойство с именем
ParamName, которому должно присваиваться имя аргумента, ставшего причиной возникновения
исключения. Для метода задания свойства ParamName должно получать значение value .
Открытые и защищенные методы должны вызывать исключения каждый раз, когда не удается выполнить
назначенные им функции. Вызываемый класс исключения должен быть самым конкретным доступным
исключением, удовлетворяющим условиям ошибки. Эти исключения должны документироваться в составе
функций класса, а производные классы или обновления исходного класса должны сохранять то же поведение
для обеспечения обратной совместимости.
Новые свойства следует добавлять к классу исключений только в том случае, если данные в них могут
помочь в разрешении исключения. При добавлении новых свойств в производный класс исключений метод
ToString() необходимо переопределить так, чтобы он возвращал добавленные сведения.
Спецификация языка C#
Дополнительные сведения см. в разделах Исключения и Оператор throw в Спецификации языка C#.
Спецификация языка является предписывающим источником информации о синтаксисе и использовании
языка C#.
См. также
Руководство по программированию на C#
Исключения и обработка исключений
Иерархия исключений
Обработка исключений
Исключения, создаваемые компилятором
(Руководство по программированию в C#)
23.10.2019 • 2 minutes to read • Edit Online
Некоторые исключения создаются средой .NET Framework CLR (CLR ) автоматически, когда происходит сбой
основной операции. В следующей таблице перечислены эти исключения и условия возникновения ошибок.
См. также
Руководство по программированию на C#
Исключения и обработка исключений
Обработка исключений
try-catch
try-finally
try-catch-finally
Практическое руководство. Обработка
исключений с помощью блока try-catch
(руководство по программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Блок try-catch- предназначен для перехвата и обработки исключений, происходящих в исполняемом коде.
Некоторые исключения могут обрабатываться в блоке catch , и проблема решается без повторного
создания исключения. Но в большинстве случаев на этом этапе можно только проверить, что создано
подходящее исключение.
Пример
В этом примере IndexOutOfRangeException не является наиболее подходящим исключением. Для данного
метода больше подходит исключение ArgumentOutOfRangeException, поскольку ошибка вызвана
переданным методу аргументом index .
class TestTryCatch
{
static int GetInt(int[] array, int index)
{
try
{
return array[index];
}
catch (System.IndexOutOfRangeException e) // CS0168
{
System.Console.WriteLine(e.Message);
// Set IndexOutOfRangeException to the new exception's InnerException.
throw new System.ArgumentOutOfRangeException("index parameter is out of range.", e);
}
}
}
Комментарии
Код, вызывающий исключение, находится в блоке try . Оператор catch добавляется сразу после него,
чтобы обрабатывать исключение IndexOutOfRangeException , если оно происходит. Блок catch обрабатывает
исключение IndexOutOfRangeException и вместо него вызывает более подходящее исключение
ArgumentOutOfRangeException . Чтобы вызывающий объект получил максимально подробную информацию,
рекомендуется указать исходное исключение в качестве значения InnerException нового исключения.
Поскольку свойство InnerException доступно только для чтения, его значение необходимо присваивать
только в конструкторе нового исключения.
См. также
Руководство по программированию на C#
Исключения и обработка исключений
Обработка исключений
Практическое руководство. Выполнение кода
очистки с использованием блока finally
(руководство по программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Оператор finally позволяет гарантировать, что необходимая очистка объектов, как правило, объектов,
занимающих внешние ресурсы, возникает немедленно, даже при создании исключения. Примером
подобной очистки является вызов Close для FileStream сразу после использования вместо ожидания сборки
мусора, выполняемой для объекта средой CLR, следующим образом:
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
file.Close();
}
Пример
Чтобы преобразовать предыдущий код в оператор try-catch-finally , код очистки отделяется от рабочего
кода следующим образом:
try
{
fileInfo = new System.IO.FileInfo("C:\\file.txt");
file = fileInfo.OpenWrite();
file.WriteByte(0xF);
}
catch(System.UnauthorizedAccessException e)
{
System.Console.WriteLine(e.Message);
}
finally
{
if (file != null)
{
file.Close();
}
}
}
Так как исключение может возникнуть в любой момент в блоке try до вызова OpenWrite() , или может
произойти сбой самого вызова OpenWrite() , мы не можем гарантировать, что файл будет открыт при
попытке закрыть его. Блок finally добавляет проверку того, что объект FileStream имеет отличное от null
значение, прежде чем будет вызван метод Close. Без проверки null блок finally может создавать
собственное исключение NullReferenceException, однако создания исключений в блоках finally следует по
возможности избегать.
Подключение к базе данных — еще один подходящий кандидат для заключения в блок finally . Так как
количество разрешенных подключений к серверу базы данных иногда ограничено, закрывать подключения
к базе данных рекомендуется как можно быстрее. Если перед закрытием подключения возникает
исключение, это еще один случай, когда использование блока finally предпочтительнее, чем ожидание
сборки мусора.
См. также
Руководство по программированию на C#
Исключения и обработка исключений
Обработка исключений
Оператор using
try-catch
try-finally
try-catch-finally
Практическое руководство. Перехват
несовместимого с CLS исключения
23.10.2019 • 2 minutes to read • Edit Online
Некоторые языки .NET, включая C++/CLI, позволяют объектам вызывать исключения, которые не являются
производными от Exception. Такие исключения называются несовместимыми с CLS исключениями или
необработанными исключениями. В C# невозможно вызвать несовместимые с CLS исключения, однако
можно перехватить их следующими двумя способами.
В блоке catch (RuntimeWrappedException e) .
По умолчанию сборка Visual C# перехватывает несовместимые с CLS исключения как заключенные в
оболочку. Этот метод следует использовать, если требуется доступ к исходному исключению, который
можно получить с помощью свойства RuntimeWrappedException.WrappedException. Далее в этом
разделе описывается процедура перехвата исключений таким способом.
В общем блоке перехвата (блоке перехвата, для которого не указан тип исключения), который
помещается после остальных блоков catch .
Используйте этот способ, если требуется выполнить какое-либо действие (например, запись в файл
журнала) в ответ на несовместимые с CLS исключения, и вам не нужен доступ к сведениям об
исключении. По умолчанию среда CLR создает оболочку для всех исключений. Чтобы отключить этот
режим, добавьте этот атрибут уровня сборки в код, как правило, в файле AssemblyInfo.cs:
[assembly: RuntimeCompatibilityAttribute(WrapNonExceptionThrows = false)] .
Пример
В следующем примере показано, как перехватить несовместимое с CLS исключение, которое было вызвано
из библиотеки классов, написанной на C++/CLI. Обратите внимание, что в этом примере клиентскому коду
C# заранее известно, что тип вызываемого исключения — System.String. Можно привести свойство
RuntimeWrappedException.WrappedException к его исходному типу, если этот тип доступен из кода.
// Class library written in C++/CLI.
var myClass = new ThrowNonCLS.Class1();
try
{
// throws gcnew System::String(
// "I do not derive from System.Exception!");
myClass.TestThrow();
}
catch (RuntimeWrappedException e)
{
String s = e.WrappedException as String;
if (s != null)
{
Console.WriteLine(s);
}
}
См. также
RuntimeWrappedException
Исключения и обработка исключений
Файловая система и реестр (Руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Перечисленные здесь статьи демонстрируют, как использовать C# и .NET Framework для выполнения
различных базовых операций над файлами, папками и реестром.
В этом разделе
ЗАГОЛОВОК ОПИСАНИЕ
Практическое руководство. Итерация дерева каталогов Демонстрирует ручной перебор дерева каталогов.
Практическое руководство. Получение сведений о файлах, Демонстрирует, как получить сведения о файлах, папках и
папках и дисках дисках, например время создания и размер.
Практическое руководство. Создание файла или папки Демонстрирует создание нового файла и новой папки.
Практическое руководство. Отображение диалогового Объясняет, как отобразить стандартное диалоговое окно
окна "Ход выполнения" для операций с файлами Windows для хода выполнения определенных файловых
операций.
Практическое руководство. Построчное чтение текстового Демонстрирует извлечение текста из файла по одной
файла строке.
Практическое руководство. Создание раздела в реестре Демонстрирует запись раздела в системный реестр.
Связанные разделы
Файловый и потоковый ввод-вывод
Практическое руководство. Руководство по программированию в C#. Копирование, удаление и
перемещение файлов и папок
Руководство по программированию на C#
Файлы, папки и диски
System.IO
Практическое руководство. Обход дерева
каталогов (руководство по программированию на
C#)
23.10.2019 • 10 minutes to read • Edit Online
Под фразой "обход дерева каталогов" подразумевается доступ к каждому файлу в каждом вложенном
подкаталоге в заданной корневой папке на любую глубину. Необязательно открывать каждый файл. Можно
просто извлечь имя файла или подкаталога в виде значения string или получить дополнительную
информацию в форме объекта System.IO.FileInfo или System.IO.DirectoryInfo.
NOTE
В Windows термины "каталог" и "папка" являются взаимозаменяемыми. В большей части документации и текста
пользовательского интерфейса используется термин "папка", но библиотека классов .NET Framework использует
термин "каталог".
В простейшем случае, когда точно известно, что имеются права доступа ко всем каталогам в указанном
корне, можно использовать флаг System.IO.SearchOption.AllDirectories . Этот флаг возвращает все вложенные
подкаталоги, соответствующие заданному шаблону. В приведенном ниже примере показано, как
использовать этот флаг.
root.GetDirectories("*.*", System.IO.SearchOption.AllDirectories);
Слабая сторона такого подхода заключается в том, что если какой-либо из подкаталогов в указанном корне
вызовет DirectoryNotFoundException или UnauthorizedAccessException, то весь метод не будет выполнен и
каталоги не будут возвращены. Это же относится и к использованию метода GetFiles. Если необходимо
обработать эти исключения в определенных вложенных папках, необходимо вручную пройти по дереву
каталога, как показано в приведенных ниже примерах.
При проходе вручную по дереву каталога можно обработать сначала подкаталоги (обход в прямом порядке)
или файлы (обход в обратном порядке). При выполнении обхода в прямом порядке проходится все дерево
в текущей папке перед итерацией файлов, которые находятся в самой папке. В приведенных ниже примерах
выполняется обход в обратном порядке, но его можно легко изменить на обход в прямом порядке.
Еще одним параметром является использование рекурсии или обхода на основе стека. В приведенных ниже
примерах показаны оба подхода.
Если необходимо выполнить различные операции с файлами и папками, то можно смоделировать эти
примеры путем рефакторинга операции на отдельные функции, которые можно вызвать с помощью одного
делегата.
NOTE
Файловые системы NTFS могут содержать точки повторного анализа в форме точек соединения, символических
ссылок и жестких связей. Методы платформы .NET Framework, такие как GetFiles и GetDirectories, не возвращают
подкаталоги ниже точки повторного анализа. Такое поведение предотвращает риск входа в бесконечный цикл, когда
две точки повторного анализа ссылаются друг на друга. Как правило, следует быть предельно осторожными при
работе с точками повторного анализа, чтобы избежать случайного изменения или удаления файлов. Если требуется
строгий контроль над точками повторного анализа, используйте вызов неуправляемого кода или машинный код для
прямого вызова подходящих методов файловой системы Win32.
Пример
В приведенном ниже примере показан проход по дереву каталога с помощью рекурсии. Рекурсивный
подход является элегантным, но он потенциально может вызвать исключение переполнения стека, если
дерево каталога большое и имеет большой уровень вложения.
Обрабатываемые исключения и действия, выполняемые с каждым файлом или папкой, предоставляются
только в качестве примеров. Этот код следует изменить в соответствии с конкретными требованиями.
Дополнительные сведения см. в комментариях в коде.
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
}
if (files != null)
{
foreach (System.IO.FileInfo fi in files)
{
// In this example, we only access the existing FileInfo object. If we
// want to open, delete or modify the file, then
// a try-catch block is required here to handle the case
// where the file has been deleted since the call to TraverseTree().
Console.WriteLine(fi.FullName);
}
Пример
В приведенном ниже примере показан обход файлов и папок в дереве папок без использования рекурсии.
Этот метод использует универсальный тип коллекции Stack<T>, который представляет стек типа "последним
пришел — первым вышел" (LIFO ).
Обрабатываемые исключения и действия, выполняемые с каждым файлом или папкой, предоставляются
только в качестве примеров. Этот код следует изменить в соответствии с конкретными требованиями.
Дополнительные сведения см. в комментариях в коде.
if (!System.IO.Directory.Exists(root))
{
throw new ArgumentException();
}
dirs.Push(root);
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
continue;
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
// Perform the required action on each file here.
// Modify this block to perform your required task.
foreach (string file in files)
{
try
{
// Perform whatever action is required in your scenario.
System.IO.FileInfo fi = new System.IO.FileInfo(file);
Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
}
catch (System.IO.FileNotFoundException e)
{
{
// If file was deleted by a separate application
// or thread since the call to TraverseTree()
// then just continue.
Console.WriteLine(e.Message);
continue;
}
}
Обычно проверка каждой папки на наличие у приложения разрешений на ее открытие занимает слишком
много времени. Поэтому в примере кода эта часть операции просто включена в блок try/catch . Блок catch
можно изменить так, чтобы при отказе в доступе к папке предпринималась попытка повысить права, а затем
повторная попытка получить доступ. Как правило, следует перехватывать только те исключения, которые
можно обработать, не оставляя приложение в неопределенном состоянии.
Если необходимо сохранить содержимое дерева каталога либо в памяти, либо на диске, наилучшим
вариантом будет сохранение только свойства FullName (типа string ) для каждого файла. Затем можно
использовать эту строку для создания объекта FileInfo или DirectoryInfo по мере необходимости или открыть
любой файл, для которого требуется дополнительная обработка.
Отказоустойчивость
Надежный код обхода файлов должен учитывать сложности файловой системы. Дополнительные сведения о
файловой системе Windows см. в статье Обзор NTFS.
См. также
System.IO
LINQ и каталоги файлов
Файловая система и реестр (руководство по программированию на C#)
Практическое руководство. Руководство по
программированию на C#. Получение сведений о
файлах, папках и дисках
23.10.2019 • 3 minutes to read • Edit Online
В платформе .NET Framework доступ к сведениям о файловой системе можно получить, используя
следующие классы:
System.IO.FileInfo
System.IO.DirectoryInfo
System.IO.DriveInfo
System.IO.Directory
System.IO.File
Классы FileInfo и DirectoryInfo представляют файл или каталог и содержат свойства, представляющие многие
атрибуты файла, поддерживаемые файловой системой NTFS. Они также содержат методы для открытия,
закрытия, перемещения и удаления файлов и папок. Экземпляры этих классов можно создать, передав в
конструктор строку, представляющую имя файла, папки или диска.
Имена файлов, папок или дисков можно также получить с помощью вызова DirectoryInfo.GetDirectories,
DirectoryInfo.GetFiles и DriveInfo.RootDirectory.
Классы System.IO.Directory и System.IO.File предоставляют статические методы для получения сведений о
каталогах и файлах.
Пример
В следующем примере показаны различные способы доступа к сведениям о файлах и папках.
class FileSysInfo
{
static void Main()
{
// You can also use System.Environment.GetLogicalDrives to
// obtain names of all logical drives on the computer.
System.IO.DriveInfo di = new System.IO.DriveInfo(@"C:\");
Console.WriteLine(di.TotalFreeSpace);
Console.WriteLine(di.VolumeLabel);
// Get the root directory and print out some information about it.
System.IO.DirectoryInfo dirInfo = di.RootDirectory;
Console.WriteLine(dirInfo.Attributes.ToString());
// Get the files in the directory and print out some information about them.
System.IO.FileInfo[] fileNames = dirInfo.GetFiles("*.*");
System.IO.Directory.SetCurrentDirectory(@"C:\Users\Public\TestFolder\");
currentDirName = System.IO.Directory.GetCurrentDirectory();
Console.WriteLine(currentDirName);
См. также
System.IO
Руководство по программированию на C#
Файловая система и реестр (руководство по программированию на C#)
Практическое руководство. Руководство по
программированию на C#. Создание файла или
папки
23.10.2019 • 4 minutes to read • Edit Online
Вы можете программно создать на компьютере папку, вложенную папку и файл во вложенной папке, а
затем записать данные в этот файл.
Пример
public class CreateFileOrFolder
{
static void Main()
{
// Specify a name for your top-level folder.
string folderName = @"c:\Top-Level Folder";
// You can write out the path name directly instead of using the Combine
// method. Combine just makes the process easier.
string pathString2 = @"c:\Top-Level Folder\SubFolder2";
// You can extend the depth of your path if you want to.
//pathString = System.IO.Path.Combine(pathString, "SubSubFolder");
// Create the subfolder. You can verify in File Explorer that you have this
// structure in the C: drive.
// Local Disk (C:)
// Top-Level Folder
// SubFolder
System.IO.Directory.CreateDirectory(pathString);
// This example uses a random string for the name, but you also can specify
// a particular name.
//string fileName = "MyNewFile.txt";
// Check that the file doesn't already exist. If it doesn't exist, create
// the file and write integers 0 - 99 to it.
// DANGER: System.IO.File.Create will overwrite the file if it already exists.
// This could happen even with random file names, although it is unlikely.
if (!System.IO.File.Exists(pathString))
{
using (System.IO.FileStream fs = System.IO.File.Create(pathString))
{
for (byte i = 0; i < 100; i++)
{
fs.WriteByte(i);
fs.WriteByte(i);
}
}
}
else
{
Console.WriteLine("File \"{0}\" already exists.", fileName);
return;
}
//0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
//30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
// 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 8
//3 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99
}
Если папка уже существует, CreateDirectory не выполняет никаких действий и исключение не возникает. Но
File.Create заменяет существующий файл новым. Для того чтобы этого избежать, в примере используется
оператор if - else .
Изменив пример указанным ниже образом, вы можете задать различные результаты в зависимости от того,
существует ли файл с определенным именем. Если файл не существует, он создается. Если файл существует,
код добавляет в него данные.
Укажите конкретное имя файла.
Выполните код в примере несколько раз, чтобы убедиться, что каждый раз данные добавляются в файл.
Другие значения FileMode , которые можно использовать для проверки, см. в разделе FileMode.
При следующих условиях возможно возникновение исключения:
Имя папки недопустимо, Например, оно содержит недопустимые символы или состоит из одних
пробелов (класс ArgumentException). Используйте класс Path, чтобы создавать допустимые пути.
Родительская папка создаваемой папки доступна только для чтения (класс IOException).
Имя папки — null (класс ArgumentNullException).
Имя папки слишком длинное (класс PathTooLongException).
Имя папки состоит из одного двоеточия ":" (класс PathTooLongException).
См. также
System.IO
Руководство по программированию на C#
Файловая система и реестр (руководство по программированию на C#)
Практическое руководство. Копирование,
удаление и перемещение файлов и папок
(руководство по программированию на C#)
23.10.2019 • 4 minutes to read • Edit Online
В следующих примерах показано, как синхронно копировать, перемещать и удалять файлы и папки с
помощью классов System.IO.File, System.IO.Directory, System.IO.FileInfo, и System.IO.DirectoryInfo из
пространства имен System.IO. В этих примерах не используется индикатор хода выполнения или какой-либо
иной пользовательский интерфейс. Сведения о том, как предоставить стандартное диалоговое окно "Ход
выполнения операций", см. в этом практическом руководстве.
Используйте System.IO.FileSystemWatcher для предоставления событий, которые позволяют вычислять ход
выполнения при работе с несколькими файлами. Другим подходом является использование вызова
неуправляемого кода для вызова в Windows Shell методов, относящихся к обработке файлов. Сведения о
способах асинхронного выполнения таких операций над файлами см. в разделе Асинхронный файловый
ввод-вывод.
Пример
В следующем примере показано, как копировать файлы и каталоги.
// Simple synchronous file copy operations with no user interface.
// To run this sample, first create the following directories and files:
// C:\Users\Public\TestFolder
// C:\Users\Public\TestFolder\test.txt
// C:\Users\Public\TestFolder\SubDir\test.txt
public class SimpleFileCopy
{
static void Main()
{
string fileName = "test.txt";
string sourcePath = @"C:\Users\Public\TestFolder";
string targetPath = @"C:\Users\Public\TestFolder\SubDir";
// Copy the files and overwrite destination files if they already exist.
foreach (string s in files)
{
// Use static Path methods to extract only the file name from the path.
fileName = System.IO.Path.GetFileName(s);
destFile = System.IO.Path.Combine(targetPath, fileName);
System.IO.File.Copy(s, destFile, true);
}
}
else
{
Console.WriteLine("Source path does not exist!");
}
Пример
В следующем примере показано, как перемещать файлы и каталоги.
// Simple synchronous file move operations with no user interface.
public class SimpleFileMove
{
static void Main()
{
string sourceFile = @"C:\Users\Public\public\test.txt";
string destinationFile = @"C:\Users\Public\private\test.txt";
Пример
В следующем примере показано, как удалять файлы и каталоги.
catch (System.IO.IOException e)
{
Console.WriteLine(e.Message);
}
}
}
}
См. также
System.IO
Руководство по программированию на C#
Файловая система и реестр (руководство по программированию на C#)
Практическое руководство. Отображение диалогового окна "Ход выполнения" для операций с файлами
Файловый и потоковый ввод-вывод
Распространенные задачи ввода-вывода
Практическое руководство. Отображение
диалогового окна хода выполнения для операций
с файлами (руководство по программированию на
C#)
23.10.2019 • 2 minutes to read • Edit Online
Вы можете предоставить стандартное диалоговое окно, в котором будет отображаться ход выполнения
операций с файлами в Windows, при использовании метода CopyFile(String, String, UIOption) из
пространства имен Microsoft.VisualBasic.
NOTE
Отображаемые на компьютере имена или расположения некоторых элементов пользовательского интерфейса Visual
Studio могут отличаться от указанных в следующих инструкциях. Это зависит от имеющегося выпуска Visual Studio и
используемых параметров. Дополнительные сведения см. в разделе Персонализация среды IDE.
Пример
Следующий код копирует каталог, указанный sourcePath , в каталог, указанный destinationPath . Этот код
также предоставляет стандартное диалоговое окно, в котором отображается примерное время, оставшееся
до завершения операции.
class FileProgress
{
static void Main()
{
// Specify the path to a folder that you want to copy. If the folder is small,
// you won't have time to see the progress dialog box.
string sourcePath = @"C:\Windows\symbols\";
// Choose a destination for the copied files.
string destinationPath = @"C:\TestFolder";
FileSystem.CopyDirectory(sourcePath, destinationPath,
UIOption.AllDialogs);
}
}
См. также
Файловая система и реестр (руководство по программированию на C#)
Практическое руководство. Руководство по
программированию на C#. Запись в текстовый
файл
23.10.2019 • 3 minutes to read • Edit Online
В этих примерах показаны различные способы записи текста в файл. В первых двух примерах используются
удобные статические методы класса System.IO.File для записи каждого элемента любого объекта
IEnumerable<string> и строки в текстовый файл. В примере 3 показано, как добавить текст в файл, если
необходимо обрабатывать отдельно каждую строку по мере ее записи в файл. В примерах 1–3
перезаписывается все существующее содержимое файла, а в примере 4 показано, как добавить текст в
существующий файл.
Все эти примеры записывают строковые литералы в файлы. Чтобы отформатировать записываемый в файл
текст, воспользуйтесь методом Format или функцией интерполяции строк C#.
Пример
class WriteTextFile
{
static void Main()
{
Отказоустойчивость
При следующих условиях возможно возникновение исключения:
Файл существует и является файлом только для чтения.
Имя пути имеет слишком большую длину.
Диск может быть переполнен.
См. также
Руководство по программированию на C#
Файловая система и реестр (руководство по программированию на C#)
Пример. Сохранение коллекции настраиваемых объектов в локальном хранилище
Практическое руководство. Руководство по
программированию на C#. Чтение из текстового
файла
23.10.2019 • 2 minutes to read • Edit Online
В этом примере считывается содержимое текстового файла с помощью статических методов ReadAllText и
ReadAllLines из класса System.IO.File.
Пример использования StreamReader см. в практическом руководстве по построчному чтению текстового
файла.
NOTE
Файлы, которые используются в этом примере, созданы при работе с практическим руководством по записи в
текстовый файл.
Пример
class ReadFromFile
{
static void Main()
{
// The files used in this example are created in the topic
// How to: Write to a Text File. You can change the path and
// file name to substitute text files of your own.
// Example #1
// Read the file as one string.
string text = System.IO.File.ReadAllText(@"C:\Users\Public\TestFolder\WriteText.txt");
// Example #2
// Read each line of the file into a string array. Each element
// of the array is one line of the file.
string[] lines = System.IO.File.ReadAllLines(@"C:\Users\Public\TestFolder\WriteLines2.txt");
Компиляция кода
Скопируйте код и вставьте его в консольное приложение C#.
Если вы не используете текстовые файлы, созданные при работе с практическим руководством по записи в
текстовый файл, замените аргумент ReadAllText и ReadAllLines соответствующим путем и именем файла на
компьютере.
Отказоустойчивость
При следующих условиях возможно возникновение исключения:
Файл не существует или не существует в указанном месте. Проверьте правильность написания имени
файла и путь к нему.
См. также
System.IO
Руководство по программированию на C#
Файловая система и реестр (руководство по программированию на C#)
Практическое руководство. Построчное чтение
текстового файла (Visual C#)
23.10.2019 • 2 minutes to read • Edit Online
В этом примере производится построчное чтение содержимого текстового файла в строку с помощью
метода ReadLine класса StreamReader . Каждая строка текста сохраняется в строке line и отображается на
экране.
Пример
int counter = 0;
string line;
file.Close();
System.Console.WriteLine("There were {0} lines.", counter);
// Suspend the screen.
System.Console.ReadLine();
Компиляция кода
Скопируйте код и вставьте его в метод Main консольного приложения.
Замените "c:\test.txt" фактическим именем файла.
Отказоустойчивость
При следующих условиях возможно возникновение исключения:
Возможно, файл не существует.
См. также
System.IO
Руководство по программированию на C#
Файловая система и реестр (руководство по программированию на C#)
Практическое руководство. Создание раздела в
реестре (Visual C#)
23.10.2019 • 2 minutes to read • Edit Online
Код в этом примере добавляет в раздел Names реестра текущего пользователя пару значений — Name и
Isabella.
Пример
Microsoft.Win32.RegistryKey key;
key = Microsoft.Win32.Registry.CurrentUser.CreateSubKey("Names");
key.SetValue("Name", "Isabella");
key.Close();
Компиляция кода
Скопируйте код и вставьте его в метод Main консольного приложения.
Замените параметр Names на имя ключа, который находится прямо в узле HKEY_CURRENT_USER
реестра.
Замените параметр Name на имя значения, которое находится прямо в узле Names.
Отказоустойчивость
Проверьте структуру реестра и найдите подходящее место для ключа. Для этого можно, например, открыть
ключ программного обеспечения текущего пользователя и создать ключ с названием вашей компании.
Затем добавьте в ключ компании значения реестра.
При следующих условиях может возникнуть исключение:
Пустое имя ключа.
У пользователя нет разрешения на создание разделов реестра.
Имя ключа превышает ограничение в 255 символов.
Раздел является закрытым.
Раздел реестра доступен только для чтения.
См. также
System.IO
Руководство по программированию на C#
Файловая система и реестр (руководство по программированию на C#)
Чтение, запись и удаление данных реестра с помощью C#
Взаимодействие (Руководство по
программированию в C#)
04.11.2019 • 2 minutes to read • Edit Online
В этом разделе
Общие сведения о взаимодействии
Описывает способы взаимодействия между управляемым кодом C# и неуправляемым кодом.
Практическое руководство. Доступ к объектам взаимодействия Office с помощью функций языка Visual C#
Описывает возможности, представленные в Visual C#, которые упрощают программирование для Office.
Практическое руководство. Использование индексированных свойств в программировании COM -
взаимодействия
Описывает использование индексированных свойств для доступа к свойствам COM, которые имеют
параметры.
Практическое руководство. Использование вызова неуправляемого кода для воспроизведения звукового
файла
Описывает, как использовать платформу вызова служб, чтобы воспроизвести звуковой WAV -файл в
операционной системе Windows.
Пошаговое руководство. Программирование для Office
Описывает процесс создания книги Excel и документа Word со ссылкой на эту книгу.
Пример COM -класса
Предлагает пример предоставления класса C# в качестве COM -объекта.
Спецификация языка C#
Дополнительные сведения см. в разделе Основные понятия в Спецификации языка C#. Спецификация
языка является предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Marshal.ReleaseComObject
Руководство по программированию на C#
Взаимодействие с неуправляемым кодом
Пошаговое руководство. Программирование для Office
Общие сведения о взаимодействии. (Руководство
по программированию в C#)
23.10.2019 • 5 minutes to read • Edit Online
NOTE
Среда CLR управляет доступом к системным ресурсам. Вызов неуправляемого кода, который находится вне среды
CLR, обходит этот механизм защиты и таким образом представляет угрозу безопасности. Например, неуправляемый
код может обращаться к ресурсам в неуправляемом коде напрямую, минуя механизмы обеспечения безопасности
среды CLR. Дополнительные сведения см. в разделе Безопасность в .NET.
взаимодействие C++
Взаимодействие C++, также известное как IJW, можно использовать для создания оболочки для
собственного класса C++ для использования в коде, созданном на C# или другом языке .NET Framework.
Для этого необходимо написать код C++, создающий оболочку для собственного DLL - или COM -
компонента. В отличие от других языков .NET Framework, Visual C++ включает поддержку взаимодействия,
что позволяет размещать управляемый и неуправляемый код в одном приложении и даже в одном файле.
Затем вы выполняете сборку кода C++ с помощью параметра компилятора /clr для создания управляемой
сборки. И, наконец, необходимо добавить ссылку на сборку в проекте C# и использовать объекты с
оболочкой так же, как другие управляемые классы.
См. также
Улучшение производительности взаимодействия
Общие сведения о взаимодействии между COM и .NET
Общие сведения о взаимодействии между COM и Visual Basic
Marshaling between Managed and Unmanaged Code (Маршалинг между управляемым и неуправляемым
кодом)
Взаимодействие с неуправляемым кодом
Руководство по программированию на C#
Практическое руководство. Доступ к объектам
взаимодействия Office с помощью возможностей
Visual C# (Руководство по программированию на
C#)
04.11.2019 • 18 minutes to read • Edit Online
В Visual C# предусмотрены функции, упрощающие доступ к объектам API Office. К новым функциям
относятся именованные и необязательные аргументы, новый тип dynamic , а также возможность передавать
аргументы ссылочным параметрам в методах COM, как если бы они были параметрами значений.
В этом разделе с использованием новых функций будет написан код, который создает и отображает лист
Microsoft Office Excel. После этого будет написан код для добавления документа Office Word, который
содержит значок, ссылающийся на лист Excel.
Для выполнения данного пошагового руководства на компьютере должны быть установлены Microsoft
Office Excel 2007 и Microsoft Office Word 2007 или более поздние версии продуктов.
NOTE
Отображаемые на компьютере имена или расположения некоторых элементов пользовательского интерфейса Visual
Studio могут отличаться от указанных в следующих инструкциях. Это зависит от имеющегося выпуска Visual Studio и
используемых параметров. Дополнительные сведения см. в разделе Персонализация среды IDE.
Добавление ссылок
1. В обозревателе решений щелкните имя проекта правой кнопкой мыши и выберите пункт
Добавить ссылку. Откроется диалоговое окно Добавление ссылки.
2. На странице Сборки в списке Имя компонента выберите Microsoft.Office.Interop.Word, а затем,
удерживая нажатой клавишу CTRL, выберите Microsoft.Office.Interop.Excel. Если сборки
отсутствуют, может потребоваться проверить, что они установлены и отображаются. См.
практическое руководство по установке основных сборок взаимодействия Microsoft Office.
3. Нажмите кнопку ОК.
2. Чтобы создать список bankAccounts , содержащий два счета, добавьте в метод Main следующий код.
2. Добавьте в конец метода DisplayInExcel следующий код. Этот код вставляет значения в первые два
столбца первой строки листа.
3. Добавьте в конец метода DisplayInExcel следующий код. Цикл foreach помещает сведения из
списка счетов в первые два столбца последовательных строк листа.
var row = 1;
foreach (var acct in accounts)
{
row++;
workSheet.Cells[row, "A"] = acct.ID;
workSheet.Cells[row, "B"] = acct.Balance;
}
4. Добавьте в конец метода DisplayInExcel следующий код, чтобы ширина столбца изменялась в
соответствии с содержимым.
workSheet.Columns[1].AutoFit();
workSheet.Columns[2].AutoFit();
В более ранних версиях C# требовалось явное приведение типов для этих операций, поскольку
ExcelApp.Columns[1] возвращает тип Object , а метод AutoFit является методом Excel Range.
Приведение показано в следующих строках.
((Excel.Range)workSheet.Columns[1]).AutoFit();
((Excel.Range)workSheet.Columns[2]).AutoFit();
Запуск проекта
1. Добавьте в конец метода Main следующую строку.
// The Add method has four reference parameters, all of which are
// optional. Visual C# allows you to omit arguments for them if
// the default values are what you want.
wordApp.Documents.Add();
В C# 3.0 и более ранних версиях приходилось использовать следующий более сложный синтаксис.
// The Add method has four parameters, all of which are optional.
// In Visual C# 2008 and earlier versions, an argument has to be sent
// for every parameter. Because the parameters are reference
// parameters of type object, you have to create an object variable
// for the arguments that represents 'no value'.
3. Добавьте в конец метода DisplayInExcel следующую инструкцию. Метод Copy добавляет лист в
буфер обмена.
// Put the spreadsheet contents on the clipboard. The Copy method has one
// optional parameter for specifying a destination. Because no argument
// is sent, the destination is the Clipboard.
workSheet.Range["A1:B3"].Copy();
((Excel.Range)workSheet.Columns[1]).AutoFit();
((Excel.Range)workSheet.Columns[2]).AutoFit();
2. Чтобы изменить поведение по умолчанию и использовать сборки PIA вместо внедрения сведений о
типе, разверните узел Ссылки в обозревателе решений и выберите
Microsoft.Office.Interop.Excel или Microsoft.Office.Interop.Word.
3. Если окно Свойства не отображается, нажмите клавишу F4.
4. В списке свойств найдите свойство Внедрить типы взаимодействия и измените его значение на
False. Также можно выполнить компиляцию с помощью командной строки с использованием
параметра компилятора -reference вместо -link.
У метода AutoFormat имеется семь параметров значений, и все они являются необязательными.
Именованные и необязательные аргументы позволяют задать аргументы для всех параметров, их
части или ни для одного параметра. В приведенной выше инструкции аргумент задается только для
одного из параметров, Format . Поскольку Format является первым параметром в списке, имя
параметра указывать не требуется. Однако инструкция может быть проще для понимания, если
указать имя параметра, как показано в следующем фрагменте кода.
2. Нажмите сочетание клавиш CTRL + F5, чтобы увидеть результат. Другие форматы представлены в
перечислении XlRangeAutoFormat.
3. Сравните инструкцию в шаге 1 со следующим кодом, в котором показаны аргументы, необходимые
в C# 3.0 или более ранних версиях.
Пример
Ниже приведен полный пример кода.
using System;
using System.Collections.Generic;
using System.Linq;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;
namespace OfficeProgramminWalkthruComplete
{
class Walkthrough
{
static void Main(string[] args)
{
// Create a list of accounts.
var bankAccounts = new List<Account>
{
new Account {
ID = 345678,
Balance = 541.27
},
new Account {
ID = 1230221,
Balance = -127.44
}
};
var row = 1;
foreach (var acct in accounts)
{
row++;
workSheet.Cells[row, "A"] = acct.ID;
workSheet.Cells[row, "B"] = acct.Balance;
}
workSheet.Columns[1].AutoFit();
workSheet.Columns[2].AutoFit();
// Put the spreadsheet contents on the clipboard. The Copy method has one
// optional parameter for specifying a destination. Because no argument
// is sent, the destination is the Clipboard.
workSheet.Range["A1:B3"].Copy();
}
// The Add method has four reference parameters, all of which are
// optional. Visual C# allows you to omit arguments for them if
// the default values are what you want.
wordApp.Documents.Add();
См. также
Type.Missing
dynamic
Использование типа dynamic
Именованные и необязательные аргументы
Практическое руководство. Использование именованных и необязательных аргументов в
программировании приложений Office
Практическое руководство. Использование
индексированных свойств в программировании
COM-взаимодействия (Руководство по
программированию на C#)
04.11.2019 • 3 minutes to read • Edit Online
// Visual C# 2010.
var excelApp = new Excel.Application();
// . . .
Excel.Range targetRange = excelApp.Range["A1"];
NOTE
В предыдущем примере также используются необязательные аргументы, которые позволяют опустить
Type.Missing .
Аналогичным образом, чтобы задать значение свойства Value объекта Range в C# 3.0 и более ранних
версиях, требуются два аргумента. Один предоставляет аргумент для необязательного параметра,
указывающего тип значения диапазона. Другой предоставляет значение для свойства Value . Эти методы
показаны в следующих примерах. В примерах ячейке A1 задается значение Name .
// Visual C# 2008.
targetRange.set_Value(Type.Missing, "Name");
// Or
targetRange.Value2 = "Name";
Пример
Ниже приведен полный пример кода. Дополнительные сведения о создании проекта, который обращается к
Office API, см. в практическом руководстве по получению доступа к объектам взаимодействия Office с
помощью функций Visual C#.
namespace IndexedProperties
{
class Program
{
static void Main(string[] args)
{
CSharp2010();
//CSharp2008();
}
}
}
}
См. также
Именованные и необязательные аргументы
dynamic
Использование типа dynamic
Практическое руководство. Использование именованных и необязательных аргументов в
программировании приложений Office
Практическое руководство. Доступ к объектам взаимодействия Office с помощью функций языка Visual
C#
Пошаговое руководство: Программирование для Office
Практическое руководство. Использование вызова
неуправляемого кода для воспроизведения
звукового файла (Руководство по
программированию на C#)
25.11.2019 • 3 minutes to read • Edit Online
В следующем примере кода C# показано, как использовать службы вызова неуправляемого кода для
воспроизведения звуковых WAV -файлов в операционной системе Windows.
Пример
В этом примере DllImportAttribute используется для импорта точки входа метода PlaySound файла
winmm.dll как Form1 PlaySound() . Пример включает простую форму Windows с кнопкой. При нажатии
кнопки открывается стандартное диалоговое окно Windows OpenFileDialog, позволяющее выбрать файл для
воспроизведения. Выбранный WAV -файл воспроизводится с помощью метода PlaySound() из метода
библиотеки winmm.dll. Дополнительные сведения об этом методе см. в разделе Использование функции
PlaySound со звуковыми WAV -файлами. Найдите и выберите файл с расширением WAV и нажмите кнопку
Открыть, чтобы воспроизвести этот WAV -файл, используя вызов неуправляемого кода. В текстовом поле
отображается полный путь к выбранному файлу.
За счет указанных ниже настроек в диалоговом окне Открыть файлы отображаются только файлы с
расширением WAV:
namespace WinSound
{
public partial class Form1 : Form
{
private TextBox textBox1;
private Button button1;
[System.Flags]
public enum PlaySoundFlags : int
{
SND_SYNC = 0x0000,
SND_ASYNC = 0x0001,
SND_NODEFAULT = 0x0002,
SND_LOOP = 0x0008,
SND_NOSTOP = 0x0010,
SND_NOWAIT = 0x00002000,
SND_FILENAME = 0x00020000,
SND_RESOURCE = 0x00040004
}
if (dialog1.ShowDialog() == DialogResult.OK)
{
textBox1.Text = dialog1.FileName;
PlaySound(dialog1.FileName, new System.IntPtr(), PlaySoundFlags.SND_SYNC);
}
}
Компиляция кода
1. Создайте проект приложения Windows Forms на C# в Visual Studio и назовите его WinSound.
2. Скопируйте указанный выше код и вставьте его поверх содержимого файла Form1.cs.
3. Скопируйте следующий код и вставьте его в файл Form1.Designer.cs в метод InitializeComponent()
после существующего кода.
См. также
Руководство по программированию на C#
Общие сведения о взаимодействии
Подробный обзор вызова неуправляемого кода
Маршалинг данных при вызове неуправляемого кода
Пошаговое руководство. Программирование для
Office (C# и Visual Basic)
04.11.2019 • 19 minutes to read • Edit Online
Visual Studio предлагает новые функции C# и Visual Basic, позволяющие улучшить программирование для
Microsoft Office. В число полезных функций C# входят именованные и необязательные аргументы и
возвращаемые значения типа dynamic . В программировании COM можно опустить ключевое слово ref и
получить доступ к индексированным свойствам. Список функций Visual Basic включает автоматически
реализуемые свойства, инструкции в лямбда-выражениях и инициализаторы коллекций.
Оба языка поддерживают внедрение сведений о типах, что позволяет развертывать сборки,
взаимодействующие с компонентами COM, без предварительного развертывания на компьютере основных
сборок взаимодействия (PIA). Дополнительные сведения см. в разделе Пошаговое руководство: внедрению
типов из управляемых сборок.
В данном пошаговом руководстве эти возможности показаны в контексте программирования для Microsoft
Office, но многие из них могут оказаться полезными и в других ситуациях. В этом пошаговом руководстве
вы создадите книгу Excel с помощью надстройки Excel, а затем документ Word со ссылкой на эту книгу.
Наконец, вы узнаете, как включать и отключать зависимость PIA.
Предварительные требования
Для выполнения данного пошагового руководства на компьютере должны быть установлены Microsoft
Office Excel и Microsoft Office Word.
NOTE
Отображаемые на компьютере имена или расположения некоторых элементов пользовательского интерфейса Visual
Studio могут отличаться от указанных в следующих инструкциях. Это зависит от имеющегося выпуска Visual Studio и
используемых параметров. Дополнительные сведения см. в разделе Персонализация среды IDE.
using System.Collections.Generic;
using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;
Imports Microsoft.Office.Interop
class Account
{
public int ID { get; set; }
public double Balance { get; set; }
}
3. Чтобы создать список bankAccounts , содержащий два счета, добавьте в метод ThisAddIn_Startup в
файле ThisAddIn.vb или ThisAddIn.cs следующий код. В объявлениях списков используются
инициализаторы коллекций. Дополнительные сведения см. в разделе Инициализаторы коллекций.
var bankAccounts = new List<Account>
{
new Account
{
ID = 345,
Balance = 541.27
},
new Account
{
ID = 123,
Balance = -127.44
}
};
With Me.Application
' Add a new Excel workbook.
.Workbooks.Add()
.Visible = True
.Range("A1").Value = "ID"
.Range("B1").Value = "Balance"
.Range("A2").Select()
В этом методе используются две новые возможности C#. Оба эти возможности уже существуют в
Visual Basic.
У метода Add есть необязательный параметр для указания конкретного шаблона. Для
необязательных параметров, впервые появившихся в C# 4, можно опускать аргумент, если
нужно использовать значение параметра по умолчанию. Поскольку в предыдущем примере
никакой аргумент не передается, в методе Add используется шаблон по умолчанию и
создается новая книга. В эквивалентном операторе в более ранних версиях C# необходимо
было использовать аргумент-местозаполнитель excelApp.Workbooks.Add(Type.Missing) .
Дополнительные сведения см. в разделе Именованные и необязательные аргументы.
Свойства Range и Offset объекта Range используют возможность индексированных свойств.
Она позволяет использовать свойства типов COM с помощью стандартного синтаксиса C#.
Кроме того, индексированные свойства позволяют использовать свойство Value объекта
Range , устраняя необходимость в использовании свойства Value2 . Свойство Value является
индексированным, но индекс — необязательным. Совместная работа необязательных
аргументов и индексированных свойств показана в следующем примере.
// In Visual C# 2008, you cannot access the Range, Offset, and Value
// properties directly.
excelApp.get_Range("A1").Value2 = "ID";
excelApp.ActiveCell.get_Offset(1, 0).Select();
excelApp.Columns[1].AutoFit();
excelApp.Columns[2].AutoFit();
' Add the following two lines at the end of the With statement.
.Columns(1).AutoFit()
.Columns(2).AutoFit()
Эти дополнения демонстрируют еще одну возможность C#: значения Object , возвращаемые
главными приложениями COM, например приложениями Office, и обрабатываются так, как если бы
они имели тип dynamic. Это происходит автоматически, если параметр Внедрить типы
взаимодействия имеет значение по умолчанию ( True ), или, что эквивалентно, если ссылка на
сборку задается с помощью параметра компилятора -link. Тип dynamic делает возможным позднее
связывание, уже доступное в Visual Basic, и не допускает явного приведения, которое требовалось бы
в C# 3.0 и более ранних версиях языка.
Например, excelApp.Columns[1] возвращает Object , а AutoFit является методом Excel Range. Без
типа dynamic необходимо выполнять приведение объекта, возвращаемого excelApp.Columns[1] , к
экземпляру Range перед вызовом метода AutoFit .
Дополнительные сведения о внедрении типов взаимодействия см. в подразделах "Поиск ссылки PIA"
и "Восстановление зависимости PIA" далее в этом разделе. Дополнительные сведения о dynamic см. в
разделе dynamic или Использование типа dynamic.
Вызов метода DisplayInExcel
1. Добавьте следующий код в конец метода ThisAddIn_StartUp . Вызов метода DisplayInExcel содержит
два аргумента. Первый аргумент представляет собой имя списка счетов, которые требуется
обработать. Второй аргумент — это состоящее из нескольких строк лямбда-выражение, которое
определяет, каким образом следует обрабатывать данные. Значения ID и balance для каждого из
счетов отображаются в соседних ячейках, а если баланс имеет отрицательное значение, строка
отображается красным. Дополнительные сведения см. в разделе Лямбда-выражения.
2. Чтобы запустить программу, нажмите клавишу F5. Появится книга Excel, содержащая данные о
счетах.
Добавление документа Word
1. Добавьте в конец метода ThisAddIn_StartUp следующий код, чтобы создать документ Word,
содержащий ссылку на книгу Excel.
В этом коде демонстрируются некоторые новые функции C#: возможность опускать ключевое слово
ref при программировании в модели COM, именованные аргументы и необязательные аргументы.
Эти возможности уже существовали в Visual Basic. Метод PasteSpecial имеет семь параметров,
которые определены как необязательные ссылочные параметры. Именованные и необязательные
аргументы позволяют определять параметры, к которым требуется обращаться по имени, и
передавать аргументы только для этих параметров. В этом примере аргументы передаются, чтобы
показать, что необходимо создать ссылку на книгу в буфере (параметр Link ) и что эта ссылка
должна отображаться в документе Word в виде значка (параметр DisplayAsIcon ). Кроме того, Visual
C# позволяет опускать ключевое слово ref для таких аргументов.
Запуск приложения
1. Нажмите клавишу F5 для запуска приложения. Будет запущено приложение Excel, в котором будет
открыта таблица, содержащая сведения о двух счетах из списка bankAccounts . Затем будет открыт
документ Word, содержащий ссылку на таблицу Excel.
Очистка готового проекта
1. В Visual Studio в меню Построение выберите пункт Очистить решение. В противном случае
надстройка будет запускаться при каждом открытии Excel на компьютере разработчика.
Поиск ссылки PIA
1. Запустите приложение снова, но не выбирайте пункт Очистить решение.
2. Выберите кнопку Пуск. Найдите Microsoft Visual Studio <версия > и откройте командную строку
разработчика.
3. В окне командной строки разработчика для Visual Studio введите команду ildasm , а затем нажмите
клавишу ВВОД. Появится окно программы IL DASM.
4. В меню Файл в окне IL DASM выберите пункт Файл > Открыть. Дважды щелкните Visual Studio
<версия> , а затем дважды щелкните Проекты. Откройте папку проекта и найдите в папке
bin/Debug файл имя_проекта.dll. Дважды щелкните файл имя_проекта.dll. В новом окне будут
показаны атрибуты проекта, а также ссылки на другие модули и сборки. Обратите внимание, что в
сборку включены пространства имен Microsoft.Office.Interop.Excel и Microsoft.Office.Interop.Word .
По умолчанию в Visual Studio компилятор импортирует в сборку необходимые типы из сборки PIA,
на которую указывает ссылка.
Дополнительные сведения см. в разделе Практическое руководство. просмотреть одержимое сборки.
5. Дважды щелкните значок МАНИФЕСТ. Откроется окно со списком сборок, содержащих элементы,
на которые имеются ссылки в проекте. Сборки Microsoft.Office.Interop.Excel и
Microsoft.Office.Interop.Word не будут указаны в этом списке. Поскольку необходимые для проекта
типы были импортированы в сборку проекта, ссылки на сборки PIA не требуется. Это упрощает
развертывание. Сборки PIA не обязательно должны присутствовать на компьютере пользователя, а
поскольку приложение не требует развертывания конкретной версии сборки PIA, можно
разрабатывать приложения, которые работают с различными версиями Office, если в этих версиях
имеются все необходимые интерфейсы API.
Поскольку развертывать сборки PIA больше не требуется, можно создавать приложения для
применения в сложных сценариях, чтобы эти приложения работали с несколькими версиями Office,
включая и более ранние версии. Тем не менее это возможно только в том случае, если в коде не
используются интерфейсы API, которые недоступны в используемой версии Office. Разработчик не
всегда знает, был ли доступен тот или иной интерфейс API в более ранней версии, поэтому работать с
более ранними версиями Office не рекомендуется.
NOTE
До Office 2003 сборки PIA не публиковались. Поэтому единственными способом создания сборки
взаимодействия в Office 2002 или более ранних версиях является импорт ссылки COM.
См. также
Автоматически реализуемые свойства (Visual Basic)
Автоматически реализуемые свойства (C#)
Инициализаторы коллекций
Инициализаторы объектов и коллекций
Необязательные параметры
Передача аргументов по позиции и по имени
Именованные и необязательные аргументы
Раннее и позднее связывание
dynamic
Использование типа dynamic
Лямбда-выражения (Visual Basic)
Лямбда-выражения (C#)
Практическое руководство. Использование индексированных свойств в программировании COM -
взаимодействия
Пошаговое руководство: Внедрение данных о типах из сборок Microsoft Office в Visual Studio (C#)
Пошаговое руководство: внедрение типов из управляемых сборок
Пошаговое руководство: создание первой надстройки VSTO для Excel
COM -взаимодействие
Взаимодействие
Пример COM-класса (Руководство по
программированию на C#)
23.10.2019 • 2 minutes to read • Edit Online
Далее приведен пример класса, который можно предоставить в качестве COM -объекта. После помещения
этого кода в CS -файл и добавления в проект свойства Регистрация для COM -взаимодействия
необходимо присвоить значение Истина. Дополнительные сведения см. в разделе Практическое
руководство. Register a Component for COM Interop (Практическое руководство. Регистрация компонента
для COM -взаимодействия).
Предоставление объектов Visual C# для COM требует объявления интерфейса класса, интерфейса событий
(если необходимо) и самого класса. Члены класса должны соответствовать указанным ниже правилам,
чтобы стать доступными для COM.
Класс должен быть открытым.
Свойства, методы и события должны быть открытыми.
Свойства и методы должны быть объявлены в интерфейсе класса.
События должны быть объявлены в интерфейсе событий.
Другие открытые члены в классе, которые не объявлены в этих интерфейсах, не будут доступны для COM, но
будут доступны другим объектам .NET Framework.
Чтобы предоставить свойства и методы COM, их необходимо объявить в интерфейсе класса, пометить
атрибутом DispId и реализовать в классе. Порядок объявления членов в интерфейсе — это порядок,
используемый для виртуальной таблицы COM.
Чтобы предоставить события из класса, их необходимо объявить в интерфейсе событий и пометить
атрибутом DispId . Класс не должен реализовывать этот интерфейс.
Класс реализует интерфейс класса; он может реализовывать несколько интерфейсов, но первой реализацией
будет интерфейс класса по умолчанию. Реализуйте методы и свойства, доступные модели COM здесь. Они
должны быть помечены как открытые и соответствовать объявлениям в интерфейсе класса. Кроме того,
объявите здесь события, инициируемые классом. Они должны быть помечены как открытые и
соответствовать объявлениям в интерфейсе событий.
Пример
using System.Runtime.InteropServices;
namespace project_name
{
[Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
public interface ComClass1Interface
{
}
[Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ComClass1Events
{
}
[Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
ClassInterface(ClassInterfaceType.None),
ComSourceInterfaces(typeof(ComClass1Events))]
public class ComClass1 : ComClass1Interface
{
}
}
См. также
Руководство по программированию на C#
Взаимодействие
Страница "Сборка" в конструкторе проектов (C#)
Справочник по C#
24.10.2019 • 5 minutes to read • Edit Online
Содержание раздела
Ключевые слова в C#
Ссылки на сведения о ключевых словах и синтаксисе языка C#.
Операторы в C#
Ссылки на сведения об операторах и синтаксисе языка C#.
Специальные символы в C#
Предоставляет ссылки на сведения о специальных контекстные символов в C# и их
использовании.
Директивы препроцессора C#
Ссылки на сведения о командах компилятора для внедрения в исходном коде C#.
Параметры компилятора C#
Сведения о параметрах компилятора и их использовании.
Ошибки компилятора C#
Фрагменты кода, демонстрирующие причины и способы исправления ошибок и
предупреждений компилятора C#.
Спецификация языка C#
Спецификация языка C# версии 6.0 Это черновой вариант для языка C# версии 6.0.
Этот документ будет пересмотрен в рамках работы с комитетом по стандартам C#
ECMA. Версия 5.0 была выпущена в декабре 2017 г. как стандартный 5-й выпуск ECMA-
334.
Возможности, которые были реализованы в языке C# версий более поздних чем 6.0,
представлены в предложениях по спецификации языка. В этих документах описываются
изменения спецификации языка, связанные с добавлением новых функций. Это
черновой вариант для формы. Эти спецификации будут улучшены и отправлены в
комитет по стандартам ECMA для официального анализа и внедрения в будущую
версию стандарта C#.
Предложения по спецификации C# 7.0
В версии C# 7.0 реализован ряд новых возможностей, включая сопоставления
шаблонов, локальные функции, объявления выходной переменной, выражения throw,
двоичные литералы и разделители между цифрами. Эта папка содержит спецификации
для каждой из этих функций.
Предложения по спецификации C# 7.1
В версию C# 7.1 добавлено несколько новых возможностей. Можно написать метод
Main , возвращающий Task или Task<int> . Это позволяет добавлять модификатор
async в метод Main . Выражение default можно использовать без типа в тех
расположениях, где возможен вывод типа. Кроме того, появилось еще одно
дополнительное усовершенствование: вывод имен элементов кортежа. И, наконец,
сопоставление шаблонов можно использовать с универсальными шаблонами.
Предложения по спецификации C# 7.2
В версию C#7.2 добавлен ряд простых функций. С помощью ключевого слова in
можно передавать аргументы по ссылке только для чтения. Внесен ряд незначительных
изменений для поддержки безопасности во время компиляции для Span и связанных
типов. В некоторых ситуациях можно использовать именованные аргументы, если
следующие за ними аргументы являются позиционными. Модификатор доступа
private protected позволяет указывать, что вызывающие объекты ограничены
производными типами, реализованными в той же сборке. Оператор ?: можно
использовать для разрешения в ссылку на переменную. С помощью разделителя
начальных цифр можно форматировать шестнадцатеричные и двоичные числа.
Предложения по спецификации C# 7.3
Версия C# 7.3 является очередным промежуточным выпуском, содержащим несколько
небольших обновлений. К параметрам универсальных типов можно применять новые
ограничения. Другие изменения упрощают работу с полями fixed , включая
использование выделений stackalloc . Локальные переменные, объявленные с
ключевым словом ref , можно переназначать для указания на новое хранилище.
Можно применять атрибуты к автоматически реализуемым свойствам,
предназначенным для созданного компилятором резервного поля. Переменные
выражений можно использовать в инициализаторах. Кортежи можно проверять на
равенство (или неравенство). Кроме того, были внесены некоторые улучшения в
разрешение перегрузки.
Предложения по спецификации C# 8.0
Версия C# 8.0 доступна для .NET Core 3.0. В число возможностей входят использование
ссылочных типов, допускающих значения NULL, рекурсивное сопоставление шаблонов,
методы интерфейса по умолчанию, асинхронные потоки, диапазоны и индексы,
использование шаблонов и объявлений using, назначение объединения со значением
NULL и члены экземпляров с доступом только на чтение.
Связанные разделы
Руководство по языку C#
Портал для документации по Visual C#.
Использование среды разработки Visual Studio для C#
Ссылки на концептуальные разделы и разделы задач, описывающие интегрированную
среду разработки и редактор.
Руководство по программированию на C#
Сведения об использовании языка программирования C#.
Управление версиями языка C#
23.10.2019 • 5 minutes to read • Edit Online
расписания
Компилятор определяет значение по умолчанию на основе следующих правил:
Значение preview использует последнюю предварительную версию языка C#, которую поддерживает
компилятор.
Настройка нескольких проектов
Можно создать файл Directory.Build.props, содержащий элемент <LangVersion> , чтобы настроить
несколько каталогов. Обычно это делается в каталоге решения. Добавьте следующий код в файл
Directory.Build.props в каталоге решения:
<Project>
<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>
</Project>
Теперь сборки в каждом подкаталоге каталога, который содержит этот файл, будут использовать
предварительную версию C#. Дополнительные сведения см. в статье о настройке сборки.
ЗНАЧЕНИЕ ЗНАЧЕНИЕ
volatile while
async await by
let nameof on
yield
См. также
справочник по C#
Типы значений (справочник по C#)
25.11.2019 • 5 minutes to read • Edit Online
Простые типы
Простые типы — это набор предопределенных типов структур, предоставленных C#, который
содержит следующие типы:
Целочисленные типы: типы целого числа и тип char.
Типы с плавающей запятой
bool
Простые типы определяются с помощью ключевых слов, но эти ключевые слова являются просто
псевдонимами для предопределенных типов структур в пространстве имен System. Например, int
является псевдонимом типа System.Int32. Полный список псевдонимов см. в разделе Таблица
встроенных типов (Справочник по C#).
Простые типы отличаются от других типов структур тем, что они разрешают некоторые
дополнительные операции:
Простые типы можно инициализировать с помощью литералов. Например, 'A' — это литерал
типа char , а 2001 — литерал типа int .
Константы простых типов можно объявить с помощью ключевого слова const. Невозможно
иметь константы других типов структур.
Константные выражения, операнды которых являются константами простого типа, вычисляются
во время компиляции.
Дополнительные сведения см. в разделе Простые типы статьи Предварительная спецификация C# 6.0.
int myInt;
При необходимости объявление и инициализация могут быть выполнены в одной инструкции, как
показано в следующем примере:
– или –
int myInt = 0;
Оператор new вызывает конструктор без параметров конкретного типа и присваивает переменной
значение по умолчанию. В приведенном выше примере конструктору без параметров присвоено
значение 0 для myInt . См. подробнее о значениях, присваиваемых путем вызова конструктора без
параметров в таблице значений по умолчанию.
С определяемыми пользователем типами используйте new для вызова конструктора без параметров.
Например, следующая инструкция вызывает конструктор без параметров структуры Point :
После этого вызова структура считается определенно присвоенной, то есть все ее элементы будут
инициализированы и получат значения по умолчанию.
Дополнительные сведения об операторе new см. в разделе new (справочник по C#).
Дополнительные сведения о форматировании выходных данных числовых типов см. в разделе
Таблица форматирования числовых результатов (справочник по C#).
См. также
справочник по C#
Ключевые слова C#
Ссылочные типы
Типы значений, допускающие значение NULL
Целочисленные типы (справочник
по C#)
25.11.2019 • 5 minutes to read • Edit Online
К ЛЮЧЕВОЕ СЛОВО
ИЛИ ТИП C# ДИАПАЗОН РАЗМЕР ТИП .NET
int a = 123;
System.Int32 b = 123;
Целочисленные литералы
Целочисленные литералы могут быть:
десятичным числом: без префикса;
шестнадцатеричным числом: с префиксом 0x или 0X ;
двоичными: с префиксом 0b или 0B (доступно в C# 7.0 и более поздних
версиях).
В приведенном ниже коде показан пример каждого из них.
NOTE
Строчную букву l можно использовать в качестве суффикса. Однако при
этом выдается предупреждение компилятора, так как букву l можно
перепутать с цифрой 1 . Для ясности используйте L .
Если у литерала есть суффикс UL , Ul , uL , ul , LU , Lu , lU или lu ,
его тип — ulong .
Если значение, представленное целочисленным литералом, превышает
UInt64.MaxValue, происходит ошибка компиляции CS1021.
Если определенный тип целочисленного литерала — int , а значение,
представленное литералом, находится в диапазоне целевого типа, значение
можно неявно преобразовать в sbyte , byte , short , ushort , uint или ulong :
byte a = 17;
byte b = 300; // CS0031: Constant value '300' cannot be converted to a 'byte'
Преобразования
Любой целочисленный тип можно преобразовать в любой другой
целочисленный тип. Если целевой тип может хранить все значения исходного
типа, преобразование является неявным. В противном случае используйте
оператор приведения () для вызова явного преобразования. Для получения
дополнительной информации см. статью Встроенные числовые
преобразования.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация
языка C#:
Целочисленные типы
Целочисленные литералы
См. также
справочник по C#
Таблица встроенных типов
Типы с плавающей запятой
Таблица форматирования числовых результатов
Числовые значения в .NET
Числовые типы с плавающей запятой
(справочник по C#)
31.10.2019 • 5 minutes to read • Edit Online
ПРИБЛИЗИТЕЛЬНЫ
К ЛЮЧЕВОЕ СЛОВО Й ДИАПАЗОН
ИЛИ ТИП C# ЗНАЧЕНИЙ ТОЧНОСТЬ РАЗМЕР ТИП .NET
В приведенной выше таблице каждый тип ключевого слова C# из крайнего левого столбца является
псевдонимом для соответствующего типа .NET. Они взаимозаменяемые. Например, следующие
объявления объявляют переменные одного типа:
double a = 12.3;
System.Double b = 12.3;
По умолчанию все типы с плавающей запятой имеют значение 0 . Все типы с плавающей запятой
имеют константы MinValue и MaxValue с минимальным и максимальными итоговыми значениями
этого типа. Типы float и double также предоставляют константы, обозначающие бесконечные и
нечисловые значения. Например, тип double предоставляет следующие константы: Double.NaN,
Double.NegativeInfinity и Double.PositiveInfinity.
Так как тип decimal характеризуется более высокой точностью и меньшим диапазоном, чем float
и double , он подходит для финансовых расчетов.
В одном и том же выражении можно сочетать и целочисленные типы, и типы с плавающей запятой.
В этом случае целочисленные типы преобразуются в типы с плавающей запятой. Выражение
вычисляется по следующим правилам:
Если одним из типов с плавающей запятой является double , выражение оценивается как double
или bool в реляционных сравнениях или сравнениях на равенство.
Если в выражении нет типа double , оно оценивается как float или bool в реляционных
сравнениях или сравнениях на равенство.
Выражение с плавающей запятой может содержать следующие наборы значений:
Положительный и отрицательный ноль
Положительная и отрицательная бесконечность
Нечисловое значение (NaN )
Конечный набор ненулевых значений
Дополнительные сведения об этих значениях см. в документе "Стандарт IEEE для двоичной
арифметики с плавающей запятой" на веб-сайте IEEE.
Можно использовать строки стандартных числовых форматов или строки пользовательских
числовых форматов для форматирования значения с плавающей запятой.
Вещественные литералы
Тип реального литерала определяется его суффиксом следующим образом:
Литерал без суффикса или с суффиксом d или D имеет тип double .
Литерал с суффиксом f или F имеет тип float .
Литерал с суффиксом m или M имеет тип decimal .
double d = 3D;
d = 4d;
d = 3.934_001;
float f = 3_000.5F;
f = 5.4f;
double d = 0.42e2;
Console.WriteLine(d); // output 42;
float f = 134.45E-2f;
Console.WriteLine(f); // output: 1.3445
decimal m = 1.5E6m;
Console.WriteLine(m); // output: 1500000
Преобразования
Существует только одно неявное преобразование между числовыми типами с плавающей запятой:
из float в double . Однако можно преобразовать любой тип с плавающей запятой в любой другой
тип с плавающей запятой с помощьюявного приведения. Для получения дополнительной
информации см. статью Встроенные числовые преобразования.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:
Типы с плавающей запятой
Тип decimal
Вещественные литералы
См. также
справочник по C#
Таблица встроенных типов
Целочисленные типы
Таблица форматирования числовых результатов
Строки стандартных числовых форматов
Числовые значения в .NET
System.Numerics.Complex
Встроенные числовые преобразования
(справочник по C#)
31.10.2019 • 7 minutes to read • Edit Online
float double
NOTE
Неявные преобразования из int , uint , long или ulong в float и из long или ulong в double могут
приводить к потере точности, но не к потере порядка величин. Другие неявные числовые преобразования никогда
не теряют никаких сведений.
byte a = 13;
byte b = 300; // CS0031: Constant value '300' cannot be converted to a 'byte'
Как показано в предыдущем примере, если значение константы выходит за пределы диапазона
целевого типа, возникает ошибка компилятора CS0031.
byte sbyte
NOTE
Явное числовое преобразование может привести к утрате данных или созданию исключения, обычно
OverflowException.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:
Неявные числовые преобразования
Явные числовые преобразования
См. также
справочник по C#
Приведение и преобразование типов
bool (Справочник по C#)
28.10.2019 • 3 minutes to read • Edit Online
Ключевое слово bool является псевдонимом для System.Boolean. Оно используется для объявления
переменных для хранения логических значений true и false.
NOTE
Используйте тип bool? , если вам нужно использовать трехзначную логику, например при работе с базами
данных, которые поддерживают трехзначный логический тип. Для операндов bool? предопределенные
операторы & и | поддерживают троичную логику. См. подробнее о логических операторах,
поддерживающих значение NULL в описании логических операторов.
Литералы
Логическое значение можно назначить переменной bool . Переменной bool также может
назначить выражение, результатом которого является bool .
Преобразования
В C++ значение типа bool можно преобразовать в значение типа int ; другими словами, false
эквивалентно нулю, а true эквивалентно ненулевым значениям. В C# не предусмотрено
преобразование между типом bool и другими типами. Например, следующий оператор if
недопустим в C#:
int x = 123;
Чтобы проверить переменную типа int , нужно явно сравнить ее со значением, например с нулем,
следующим образом:
if (x != 0) // The C# way
{
Console.Write("The value of x is nonzero.");
}
Пример
В этом примере символ вводится с клавиатуры, и программа проверяет, является ли введенный
символ буквой. Если это буква, проверяется ее регистр. Эти проверки выполняются с использованием
IsLetter и IsLower, которые возвращают значение типа bool :
Enter a character: x
The character is lowercase.
Enter a character: 2
The character is not an alphabetic character.
*/
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Целочисленные типы
Таблица встроенных типов
2 minutes to read
enum (Справочник по C#)
25.11.2019 • 6 minutes to read • Edit Online
Ключевое слово enum используется для объявления перечисления — отдельного типа, который
состоит из набора именованных констант, называемого списком перечислителей.
Обычно лучше всего определять перечисление непосредственно в пространстве имен, чтобы всем
классам в пространстве имен было одинаково удобно обращаться к нему. Однако перечисление
также может быть вложенным в классе или структуре.
По умолчанию первый перечислитель имеет значение 0, и значение каждого последующего
перечислителя увеличивается на 1. Например, в следующем перечислении Sat — 0 , Sun — 1 ,
Mon — 2 и т. д.
enum Day : byte {Sat=1, Sun, Mon, Tue, Wed, Thu, Fri};
Переменной типа "перечисление" может быть присвоено любое значение в диапазоне базового
типа. Эти значения не ограничиваются именованными константами.
Значение по умолчанию enum E является значением, полученным с помощью выражения (E)0 .
NOTE
Перечислитель не может содержать пробелы в имени.
Базовый тип указывает, какой объем хранилища выделяется для каждого перечислителя. Тем не
менее необходимо явное приведение, чтобы преобразовывать из типа enum в целочисленный тип.
Например, следующий оператор назначает перечислитель Sun для переменной типа int с
помощью приведения, чтобы преобразовать enum в int .
int x = (int)Day.Sun;
Отказоустойчивость
Как и в случае с любой другой константой, все ссылки на отдельные значения перечисления
преобразуются в числовые литералы во время компиляции. Это может создать потенциальные
проблемы с управлением версиями, как описано в разделе Константы.
Назначение дополнительных значений для новых версий перечислений или изменение значений
элементов перечислений в новой версии может вызвать проблемы в зависимом исходном коде.
Значения перечислений часто используются в инструкциях switсh . Если были добавлены
дополнительные элементы для типа enum , раздел по умолчанию инструкции switch может быть
выбран неожиданным образом.
Если другие разработчики используют ваш код, необходимо предоставить рекомендации о том,
как их код должен реагировать при добавлении новых элементов к любому типу enum .
Пример
В следующем примере объявлено перечисление Day . Два перечислителя явно преобразуются в
целое число и назначаются для целочисленных переменных.
Пример
В следующем примере используется параметр базового типа для объявления enum , члены
которого имеют тип long . Обратите внимание, что, даже если базовый тип перечисления является
long , члены перечисления по-прежнему должны быть явно преобразованы в тип long с
помощью приведения.
public class EnumTest2
{
enum Range : long { Max = 2147483648L, Min = 255L };
static void Main()
{
long x = (long)Range.Max;
long y = (long)Range.Min;
Console.WriteLine("Max = {0}", x);
Console.WriteLine("Min = {0}", y);
}
}
/* Output:
Max = 2147483648
Min = 255
*/
Пример
В следующем примере кода показано использование и влияние атрибута System.FlagsAttribute на
объявление enum .
class FlagTest
{
static void Main()
{
// The bitwise OR of 0001 and 0100 is 0101.
CarOptions options = CarOptions.SunRoof | CarOptions.FogLights;
Комментарии
Если удалить Flags , в примере будут показаны следующие значения.
5
5
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Типы перечисления
Ключевые слова в C#
Целочисленные типы
Таблица встроенных типов
Соглашения об именовании перечислений
struct (справочник по C#)
04.11.2019 • 2 minutes to read • Edit Online
Тип struct представляет собой тип значения, который обычно используется для
инкапсуляции небольших групп связанных переменных, например координат
прямоугольника или характеристик элемента в инвентаризации. В следующем примере
показано простое объявление структуры:
Примечания
Структуры также могут содержать конструкторы, константы, поля, методы, свойства,
индексаторы, операторы, события и вложенные типы, хотя, если требуется несколько таких
членов, тип необходимо заменить классом.
Примеры см. в разделе Использование структур.
Структуры могут реализовать интерфейс, но не могут наследоваться из другой структуры.
По этой причине члены структуры не могут объявляться как protected .
Дополнительные сведения см. в разделе Структуры.
Примеры
Примеры и дополнительные сведения см. в разделе Использование структур.
Спецификация языка C#
Примеры см. в разделе Использование структур.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Таблица значений по умолчанию
Таблица встроенных типов
Типы
Типы значений
class
interface
Классы и структуры
Справочник по C#. Типы значений, допускающие
значение NULL
25.11.2019 • 12 minutes to read • Edit Online
Тип значений, допускающий значение NULL, T? может представлять все значения своего базового типа
T , а также дополнительное значение NULL. Например, можно присвоить переменной bool? любое из
следующих трех значений: true , false или null . Базовый тип значения T не может соответствовать
типу значения, допускающему значение NULL.
NOTE
В C# 8.0 появилась возможность использования ссылочных типов, допускающих значение NULL. Дополнительные
сведения см. в статье Ссылочные типы, допускающие значение NULL. Типы значений, допускающие значение
NULL, доступны начиная с C# 2.
Назначение и объявление
Так как тип значения можно неявно преобразовать в соответствующий тип значения, допускающий
значение NULL, вы назначаете значение переменной такого типа значения так же, как для базового типа
значения. Вы также можете присвоить значение null . Например:
double? pi = 3.14;
char? letter = 'a';
int m2 = 10;
int? m = m2;
Значение по умолчанию для типа значения, допускающего значение NULL, представляет null , то есть
это экземпляр, свойство Nullable<T>.HasValue которого возвращает false .
int? a = 42;
if (a is int valueOfA)
{
Console.WriteLine($"a is {valueOfA}");
}
else
{
Console.WriteLine("a does not have a value");
}
// Output:
// a is 42
Вы всегда можете использовать следующие свойства только для чтения, чтобы проверить и получить
значение переменной типа, допускающего значение NULL:
Nullable<T>.HasValue указывает, имеет ли экземпляр типа, допускающего значение NULL, значение
своего базового типа.
Nullable<T>.Value возвращает значение базового типа, если HasValue имеет значение true . Если
HasValue имеет значение false , свойство Value выдает исключение InvalidOperationException.
В следующем примере используется свойство HasValue , чтобы проверить, содержит ли переменная
значение, перед его отображением:
int? b = 10;
if (b.HasValue)
{
Console.WriteLine($"b is {b.Value}");
}
else
{
Console.WriteLine("b does not have a value");
}
// Output:
// b is 10
Вы также можете сравнить переменную типа значения, допускающего значение NULL, с null вместо
использования свойства HasValue , как показано в следующем примере:
int? c = 7;
if (c != null)
{
Console.WriteLine($"c is {c.Value}");
}
else
{
Console.WriteLine("c does not have a value");
}
// Output:
// c is 7
int? a = 28;
int b = a ?? -1;
Console.WriteLine($"b is {b}"); // output: b is 28
int? c = null;
int d = c ?? -1;
Console.WriteLine($"d is {d}"); // output: d is -1
Если вы хотите использовать значение по умолчанию базового типа значения вместо null ,
воспользуйтесь методом Nullable<T>.GetValueOrDefault().
Вы можете явно привести тип значения, допускающий значение NULL, к типу, не допускающему значение
NULL, как показано в примере ниже.
int? n = null;
Во время выполнения, если значение типа значения, допускающего значение NULL, равно null , явное
приведение вызывает исключение InvalidOperationException.
Тип T , не допускающий значение NULL, неявно преобразуется в соответствующий тип, допускающий
значение NULL, T? .
Операторы с нулификацией
Предопределенные унарные и бинарные операторы или любые перегруженные операторы,
поддерживаемые типом значения T , также поддерживаются соответствующим типом значения,
допускающим значение NULL, T? . Эти операторы, также называемые операторами с нулификацией,
возвращают значение null , если один или оба операнда имеют значение null . В противном случае
оператор использует содержащиеся значения операндов для вычисления результата. Например:
int? a = 10;
int? b = null;
int? c = 10;
a++; // a is 11
a = a * c; // a is 110
a = a + b; // a is null
NOTE
Для типа bool? предопределенные операторы & и | не следуют правилам, описанным в этом разделе:
результат вычисления оператора может быть отличным от NULL, даже если один из операндов имеет значение
null . См. подробнее о логических операторах, поддерживающих значение NULL в описании логических
операторов.
Для операторов сравнения < , > , <= и >= , если один или оба операнда равны null , результат будет
равен false . В противном случае сравниваются содержащиеся значения операндов. Тут важно не
полагать, что если какая-то операция сравнения (например, <= ) возвращает false , то противоположное
сравнение ( > ) обязательно вернет true . В следующем примере показано, что 10
не больше и не равно значению null ,
не меньше чем null .
int? a = 10;
Console.WriteLine($"{a} >= null is {a >= null}");
Console.WriteLine($"{a} < null is {a < null}");
Console.WriteLine($"{a} == null is {a == null}");
// Output:
// 10 >= null is False
// 10 < null is False
// 10 == null is False
int? b = null;
int? c = null;
Console.WriteLine($"null >= null is {b >= c}");
Console.WriteLine($"null == null is {b == c}");
// Output:
// null >= null is False
// null == null is True
В приведенном выше примере видно, что проверка на равенство двух экземпляров типа значения,
допускающего значение NULL, которые оба равны null , всегда даст true .
Если между двумя типами данных определено пользовательское преобразование типов, то это же
преобразование можно также использовать между соответствующими типами, допускающими значение
NULL.
Упаковка-преобразование и распаковка-преобразование
Экземпляр типа значения, допускающего значение NULL, T? упакован следующим образом:
Если HasValue возвращает false , создается пустая ссылка.
Если HasValue возвращает true , упаковывается соответствующее значение базового типа T , а не
экземпляр Nullable<T>.
Можно распаковать упакованный тип значения T в соответствующий тип, допускающий значение NULL,
T? , как показано в следующем примере:
int a = 41;
object aBoxed = a;
int? aNullable = (int?)aBoxed;
Console.WriteLine($"Value of aNullable: {aNullable}");
// Output:
// int? is nullable type
// int is non-nullable type
Как показано в примере, при помощи оператора typeof можно создать экземпляр System.Type.
Если вы хотите определить, принадлежит ли экземпляр к типу значений, допускающему значение NULL,
не используйте метод Object.GetType для получения экземпляра Type для тестирования с помощью
приведенного выше кода. При вызове метода Object.GetType в экземпляре типа значений, допускающего
значение NULL, экземпляр упаковывается в Object. Так как упаковка экземпляра типа значений,
допускающего значение NULL, значение которого отлично от NULL, эквивалентна упаковке значения
базового типа, GetType возвращается экземпляр Type, представляющий базовый тип типа значений,
допускающего значение NULL:
int? a = 17;
Type typeOfA = a.GetType();
Console.WriteLine(typeOfA.FullName);
// Output:
// System.Int32
Кроме того, не используйте оператор is, чтобы определить, принадлежит ли экземпляр к типу значений,
допускающему значение NULL. Как показано в следующем примере, вы не можете отличить типы
экземпляра типа значений, допускающего значение NULL, и его экземпляра базового типа с помощью
оператора is :
int? a = 14;
if (a is int)
{
Console.WriteLine("int? instance is compatible with int");
}
int b = 17;
if (b is int?)
{
Console.WriteLine("int instance is compatible with int?");
}
// Output:
// int? instance is compatible with int
// int instance is compatible with int?
Чтобы определить, принадлежит ли экземпляр типу значений, допускающему значение NULL, можно
использовать код, представленный в следующем примере:
int? a = 14;
Console.WriteLine(IsOfNullableType(a)); // output: True
int b = 17;
Console.WriteLine(IsOfNullableType(b)); // output: False
bool IsOfNullableType<T>(T o)
{
var type = typeof(T);
return Nullable.GetUnderlyingType(type) != null;
}
NOTE
Методы, описанные в этом разделе, неприменимы в случае ссылочных типов, допускающих значения NULL.
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:
Типы, допускающие значения NULL
Операторы с нулификацией
Неявные преобразования, допускающие значения NULL
Явные преобразования, допускающие значения NULL
Операторы преобразования с нулификацией
См. также
справочник по C#
What exactly does 'lifted' mean? (Что означает термин "расширенные"?)
System.Nullable<T>
System.Nullable
Nullable.GetUnderlyingType
Ссылочные типы, допускающие значение NULL
Ссылочные типы (справочник по C#)
04.11.2019 • 2 minutes to read • Edit Online
В C# существуют две разновидности типов: ссылочные типы и типы значений. В переменных ссылочных
типов хранятся ссылки на их данные (объекты), а переменные типа значений содержат свои данные
непосредственно. Две переменные ссылочного типа могут ссылаться на один и тот же объект, поэтому
операции над одной переменной могут затрагивать объект, на который ссылается другая переменная.
Каждая переменная типа значения имеет собственную копию данных, и операции над одной
переменной не могут затрагивать другую (за исключением переменных параметров in, ref и out; см.
описание модификатора параметров in, ref и out).
Для объявления ссылочных типов используются следующие ключевые слова:
class
interface
delegate
В C# также предусмотрены следующие встроенные ссылочные типы:
dynamic
object
string
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Типы
Типы значений
Встроенные ссылочные типы (справочник
по C#)
25.11.2019 • 11 minutes to read • Edit Online
В C# есть ряд встроенных ссылочных типов. У них есть ключевые слова или операторы,
которые являются синонимами типа в библиотеке .NET.
Тип object
Тип object является псевдонимом System.Object в .NET. В унифицированной системе
типов C# все типы, стандартные и определяемые пользователем, ссылочные типы и типы
значений напрямую или косвенно наследуются из System.Object. Переменным типа
object можно назначать значения любого типа. Любой переменной object можно
назначить значение по умолчанию с помощью литерала null . Если переменная типа
значения преобразуется в объект, она считается упакованной. Если переменная типа
значения преобразуется в тип значения, она считается распакованной. Дополнительные
сведения см. в разделе Упаковка-преобразование и распаковка-преобразование.
Тип string
Тип string представляет последовательность, состоящую из нуля или более символов в
кодировке Юникод. string является псевдонимом для System.String в .NET.
Несмотря на то что string представляет собой ссылочный тип, операторы равенства ==
и != по определению сравнивают не ссылки, а значения объектов string . Это делает
проверку равенства строк более интуитивно понятной. Например:
string a = "hello";
string b = "h";
// Append to contents of 'b'
b += "ello";
Console.WriteLine(a == b);
Console.WriteLine(object.ReferenceEquals(a, b));
Строковые литералы имеют тип string и могут быть записаны в двух формах: в кавычках
(") или в кавычках с префиксом @ . Строковые литералы в кавычках заключаются в
двойные кавычки ("):
NOTE
Escape-код \udddd (где dddd состоит из четырех цифр) представляет символ Юникода U+
dddd . Также распознаются восьмизначные escape-коды Юникода: \Udddddddd .
Тип delegate
Объявление типа делегата аналогично сигнатуре метода. Оно имеет возвращаемое
значение и любое число параметров любого типа:
Тип dynamic
Тип dynamic указывает, что использование переменной и ссылок на ее члены обходит
проверку типа во время компиляции. Такие операции разрешаются во время выполнения.
Тип dynamic упрощает доступ к API COM, таким как API автоматизации Office, к
динамическим API, таким как библиотеки IronPython, и к HTML -модели DOM.
Тип dynamic в большинстве случаев ведет себя как тип object . В частности, можно
преобразовать любое выражение, отличное от NULL, в тип dynamic . Тип dynamic
отличается от object тем, что операции, которые содержат выражения типа dynamic , не
разрешаются или тип проверяется компилятором. Компилятор объединяет сведения об
операции, которые впоследствии будут использоваться для оценки этой операции во
время выполнения. В рамках этого процесса переменные типа dynamic компилируются в
переменные типа object . Таким образом, тип dynamic существует только во время
компиляции, но не во время выполнения.
В следующем примере переменной типа dynamic противопоставляется переменная типа
object . Чтобы проверить тип каждой переменной во время компиляции, наведите
указатель мыши на dyn или obj в операторах WriteLine . Скопируйте следующий код в
редактор, где доступен IntelliSense. IntelliSense отображает dynamic для dyn и object для
obj .
class Program
{
static void Main(string[] args)
{
dynamic dyn = 1;
object obj = 1;
// Rest the mouse pointer over dyn and obj to see their
// types at compile time.
System.Console.WriteLine(dyn.GetType());
System.Console.WriteLine(obj.GetType());
}
}
Операторы WriteLine отображают типы времени выполнения dyn и obj . На этом этапе
оба имеют один и тот же тип — целое число. Выводятся следующие результаты.
System.Int32
System.Int32
Чтобы увидеть разницу между dyn и obj во время компиляции, добавьте между
объявлениями и операторами WriteLine в предыдущем примере следующие две строки:
dyn = dyn + 3;
obj = obj + 3;
При попытке добавления целого числа и объекта в выражение obj + 3 выдается ошибка
компилятора. При этом для dyn + 3 ошибка не возникает. Выражение, которое содержит
dyn , не проверяется во время компиляции, поскольку тип dyn имеет значение dynamic .
namespace DynamicExamples
{
class Program
{
static void Main(string[] args)
{
ExampleClass ec = new ExampleClass();
Console.WriteLine(ec.exampleMethod(10));
Console.WriteLine(ec.exampleMethod("value"));
class ExampleClass
{
static dynamic field;
dynamic prop { get; set; }
if (d is int)
{
return local;
}
else
{
return two;
}
}
}
}
// Results:
// Local variable
// 2
// Local variable
См. также
Справочник по C#
Ключевые слова в C#
События
Использование типа dynamic
Рекомендации по использованию строк
Базовые операции со строками в .NET Framework
Создание строк
Операторы приведения и тестирования типов
Практическое руководство. Безопасное приведение с помощью сопоставления
шаблонов, а также операторов is и as
Пошаговое руководство. Создание и использование динамических объектов (C# и
Visual Basic)
System.Object
System.String
System.Dynamic.DynamicObject
класс (Справочник по C#)
23.10.2019 • 4 minutes to read • Edit Online
class TestClass
{
// Methods, properties, fields, events, delegates
// and nested classes go here.
}
Примечания
В C# допускается только одиночное наследование. Другими словами, класс может
наследовать реализацию только от одного базового класса. Однако класс может
реализовывать несколько интерфейсов. В таблице ниже приведены примеры
наследования класса и реализации интерфейса.
НАСЛЕДОВАНИЕ ПРИМЕР
Члены класса, включая вложенные классы, могут объявляться с типом доступа public,
protected internal, protected, internal, private или private protected. По умолчанию члены
имеют тип доступа private .
Дополнительные сведения см. в статье Модификаторы доступа.
Можно объявить универсальные классы, имеющие параметры типа. Дополнительные
сведения см. в разделе Универсальные классы.
Класс может содержать объявления следующих членов:
Конструкторы
Константы
Поля
Методы завершения
Методы
Свойства
Индексаторы
Инструкции
События
Делегаты
Классы
Интерфейсы
Структуры
Перечисления
Пример
В приведенном ниже примере показано объявление полей, конструкторов и методов
класса. В нем также демонстрируется создание экземпляра объекта и печать данных
экземпляра. В этом примере объявляются два класса. Первый класс, Child , содержит
два частных поля ( name и age ), два общих конструктора и один общий метод. Второй
класс, StringTest , используется для хранения Main .
class Child
{
private int age;
private string name;
// Default constructor:
public Child()
{
name = "N/A";
}
// Constructor:
public Child(string name, int age)
{
this.name = name;
this.age = age;
}
// Printing method:
public void PrintChild()
{
Console.WriteLine("{0}, {1} years old.", name, age);
}
}
class StringTest
{
static void Main()
{
// Create objects by using the new operator:
Child child1 = new Child("Craig", 11);
Child child2 = new Child("Sally", 10);
// Display results:
Console.Write("Child #1: ");
child1.PrintChild();
Console.Write("Child #2: ");
child2.PrintChild();
Console.Write("Child #3: ");
child3.PrintChild();
}
}
/* Output:
Child #1: Craig, 11 years old.
Child #2: Sally, 10 years old.
Child #3: N/A, 0 years old.
*/
Комментарии
Обратите внимание, что в предыдущем примере доступ к частным полям ( name и age )
возможен только с помощью общих методов класса Child . Например, имя ребенка
нельзя напечатать из метода Main с помощью следующего оператора:
Console.Write(child1.name); // Error
Получить доступ к закрытым членам класса Child из метода Main можно было бы
лишь в том случае, если бы Main был членом класса.
Типы, объявленные в классе без модификатора доступа, по умолчанию являются
private , поэтому члены данных в этом примере останутся private , если ключевые
слова будут удалены.
Наконец, обратите внимание, что для объекта, созданного с помощью конструктора без
параметров ( child3 ), поле age по умолчанию инициализировано с нулевым
значением.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Ссылочные типы
interface (Справочник по C#)
23.10.2019 • 3 minutes to read • Edit Online
Интерфейс содержит только сигнатуры методов, свойств, событий или индексаторов. Класс или
структура, реализующие интерфейс, должны реализовать члены интерфейса, заданные в
определении интерфейса. В следующем примере класс ImplementationClass должен реализовать
метод с именем SampleMethod , не имеющий параметров и возвращающий значение void .
Дополнительные сведения и примеры см. в разделе Интерфейсы.
Пример
interface ISampleInterface
{
void SampleMethod();
}
Интерфейс может быть членом пространства имен или класса и содержать сигнатуры следующих
членов:
Методы
Свойства
Индексаторы
События
Интерфейс может наследовать от одного или нескольких базовых интерфейсов.
Если список базовых типов содержит базовый класс и интерфейсы, базовый класс должен стоять
первым в списке.
Класс, реализующий интерфейс, может явно реализовывать члены этого интерфейса. При явной
реализации члена к нему можно получить доступ только через экземпляр интерфейса, но не через
экземпляр класса.
Дополнительные сведения и примеры кода с явной реализацией интерфейса см. в разделе Явная
реализация интерфейса.
Пример
В следующем примере показана реализация интерфейса. В этом примере интерфейс содержит
объявление свойства, а класс содержит реализацию. Любой экземпляр класса, который реализует
IPoint , имеет целочисленные свойства x и y .
interface IPoint
{
// Property signatures:
int x
{
get;
set;
}
int y
{
get;
set;
}
}
// Constructor:
public Point(int x, int y)
{
_x = x;
_y = y;
}
// Property implementation:
public int x
{
get
{
return _x;
}
set
{
_x = value;
}
}
public int y
{
get
{
return _y;
}
set
{
_y = value;
}
}
}
class MainClass
{
static void PrintPoint(IPoint p)
{
Console.WriteLine("x={0}, y={1}", p.x, p.y);
Console.WriteLine("x={0}, y={1}", p.x, p.y);
}
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Ссылочные типы
Интерфейсы
Использование свойств
Использование индексаторов
class
struct
Интерфейсы
void (справочник по C#)
23.10.2019 • 2 minutes to read • Edit Online
При использовании в качестве типа возвращаемого значения для метода ключевое слово void указывает,
что метод не возвращает значение.
Ключевое слово void не допускается в списке параметров метода. Не принимающий параметров и не
возвращающий значений метод объявляется следующим образом:
Ключевое слово void также используется в небезопасном контексте для объявления указателя на
неизвестный тип. Дополнительные сведения см. в разделе Типы указателей.
void — это псевдоним для типа System.Void в .NET Framework.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Ссылочные типы
Типы значений
Методы
Небезопасный код и указатели
var (справочник по C#)
23.10.2019 • 2 minutes to read • Edit Online
Начиная с Visual C# 3.0, переменные, объявленные в области действия метода, получают неявный
"тип" var . Неявно типизированные локальные переменные является строго типизированными,
как если бы вы объявили тип самостоятельно, однако его определяет компилятор. Следующие два
объявления i функционально эквивалентны:
Пример
В следующем примере показаны два выражения запросов. В первом выражении использование
var разрешено, но не является обязательным, поскольку тип результата запроса может быть
задан явно как IEnumerable<string> . Однако во втором выражении благодаря var результат
может быть коллекцией анонимных типов, и имя этого типа доступно только для компилятора.
Использование var делает создание нового класса для результата необязательным. Обратите
внимание на то, что во втором примере переменная итерации foreach item также типизирована
неявно.
См. также
Справочник по C#
Руководство по программированию на C#
Неявно типизированные локальные переменные
Справочник по C#. Неуправляемые типы
23.10.2019 • 2 minutes to read • Edit Online
using System;
Универсальная структура может быть источником как для неуправляемых сконструированных типов, так
и для сконструированных типов, отличных от неуправляемых. В предыдущем примере определяется
универсальная структура Coords<T> , а также представлены примеры неуправляемых сконструированных
типов. Примером типа, отличного от неуправляемого, является Coords<object> . Он не является
неуправляемым, так как содержит поля типа object , которые являются отличными от неуправляемых.
Если необходимо, чтобы все сконструированные типы были неуправляемыми, используйте ограничение
unmanaged в определении универсальной структуры:
public struct Coords<T> where T : unmanaged
{
public T X;
public T Y;
}
Спецификация языка C#
Дополнительные сведения см. в разделе Типы указателей в статье Спецификации языка C#.
См. также
справочник по C#
Типы указателей
Типы, связанные с памятью и диапазонами
Оператор sizeof
Оператор stackalloc
Таблица встроенных типов (Справочник по C#)
25.11.2019 • 2 minutes to read • Edit Online
В следующей таблице показаны ключевые слова для встроенных типов C#, которые являются
псевдонимами предопределенных типов в пространстве имен System:
bool System.Boolean
byte System.Byte
sbyte System.SByte
char System.Char
decimal System.Decimal
double System.Double
float System.Single
int System.Int32
uint System.UInt32
long System.Int64
ulong System.UInt64
object System.Object
short System.Int16
ushort System.UInt16
string System.String
Примечания
Все типы в таблице, за исключением object и string , считаются простыми.
Типы .NET и псевдонимы ключевых слов типов C# являются взаимозаменяемыми. Например, можно
объявить целочисленную переменную с помощью любого из следующих объявлений:
int x = 123;
System.Int32 y = 123;
Используйте оператор typeof, чтобы получить экземпляр System.Type, представляющий указанный тип:
Type stringType = typeof(string);
Console.WriteLine(stringType.FullName);
// Output:
// System.String
// System.Double
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Типы значений
Ссылочные типы
Таблица значений по умолчанию
dynamic
Таблица типов значений (cправочник по C#)
25.11.2019 • 2 minutes to read • Edit Online
bool логический
enum Перечисление
См. также
справочник по C#
Таблица значений по умолчанию
Типы значений
Таблица форматирования числовых результатов
Таблица значений по умолчанию (справочник
по C#)
25.11.2019 • 2 minutes to read • Edit Online
bool false
Любой тип значения, допускающий значение NULL Экземпляр, свойство false которого имеет значение
HasValue, а свойство Value не определено. Это
значение по умолчанию также называется значением
NULL типа значения, допускающего значение NULL.
Используйте оператор по умолчанию, чтобы получить значение типа по умолчанию, как показано в
следующем примере:
int a = default(int);
int a = default;
Для типа значения неявный конструктор без параметров также создает значение по умолчанию для
типа, как показано в следующем примере:
Спецификация языка C#
Дополнительные сведения см. в следующих разделах статьи Спецификация языка C#:
Значения по умолчанию
Конструкторы по умолчанию
См. также
справочник по C#
Ключевые слова C#
Таблица встроенных типов
Конструкторы
Таблица форматирования числовых результатов
(справочник по C#)
04.11.2019 • 2 minutes to read • Edit Online
string s = $"{2.5:F0}"; 3
Примечания
Для создания строки формата используйте описатель формата. Строка формата имеет вид Axx :
где A — это описатель формата, который управляет типом форматирования, применяемым к числовому
значению;
а xx — указатель точности, который влияет на количество цифр в форматированном выводе. Значение
описателя точности находится в диапазоне от 0 до 99.
Описатели десятичного ("D" или "d") и шестнадцатеричного форматов ("X" или "x") поддерживаются только
для целочисленных типов. Описатель формата обратного преобразования ("R" или "r") поддерживается
только для типов Single, Double и BigInteger.
Строки стандартных числовых форматов поддерживаются в следующих сценариях.
Некоторые перегрузки метода ToString для всех числовых типов. Например, можно задать строку
числового формата для методов Int32.ToString(String) и Int32.ToString(String, IFormatProvider).
Функция составного форматирования .NET, которая поддерживается методом String.Format,
например.
Интерполированные строки.
Дополнительные сведения см. в статье Строки стандартных числовых форматов.
См. также
Справочник по C#
Руководство по программированию на C#
Типы форматирования
Составное форматирование
Интерполяция строк
string
Модификаторы доступа (Справочник по C#)
04.11.2019 • 2 minutes to read • Edit Online
Модификаторы доступа — это ключевые слова, которые задают объявленный уровень доступности
члена или типа. В этом разделе описываются четыре модификатора доступа:
public
protected
internal
private
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы доступа
Ключевые слова доступа
Модификаторы
Уровни доступности (Справочник по C#)
23.10.2019 • 3 minutes to read • Edit Online
Модификаторы доступа public , protected , internal или private используются для указания одного из
следующих объявленных уровней доступности к членам.
Вы можете указать для члена или типа только один модификатор доступа, за исключением случаев
использования сочетаний protected internal или private protected .
Модификаторы доступа не могут быть указаны для пространств имен. Пространства имен не имеют
ограничений доступа.
В зависимости от контекста, в котором производится объявление члена, допускаются только некоторые
объявленные уровни доступности. Если модификатор доступа не указывается в объявлении члена,
используется доступность по умолчанию.
Типы верхнего уровня, не вложенные в другие типы, могут иметь только уровень доступности internal
или public . Для этих типов уровнем доступности по умолчанию является internal .
Вложенные типы, которые являются членами других типов, могут иметь объявленные уровни
доступности, как указано в следующей таблице.
protected
internal
private
protected internal
private protected
internal
private
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы доступа
Домен доступности
Ограничения на использование уровней доступности
Модификаторы доступа
public
private
protected
internal
Область доступности (Справочник по C#)
23.10.2019 • 3 minutes to read • Edit Online
Область доступности члена определяет, в каких разделах программы может присутствовать ссылка на этот
член. Если член вложен в другой тип, его область доступности определяется как уровнем доступности
самого члена, так и областью доступности типа, непосредственно содержащего вложенный тип.
Область доступности типа верхнего уровня — это по крайней мере текст программы проекта, в котором он
объявлен. То есть область включает в себя все исходные файлы данного проекта. Область доступности
вложенного типа — это по крайней мере текст программы типа, в котором он объявлен. Таким образом,
областью является тело типа, включающее все вложенные типы. Область доступности вложенного типа
никогда не выходит за пределы области доступности содержащего его типа. Эти принципы
продемонстрированы в приведенном ниже примере.
Пример
Этот пример содержит тип верхнего уровня T1 и два вложенных класса: M1 и M2 . Классы содержат поля,
имеющие различную объявленную доступность. В методе Main после каждого оператора следует
комментарий, указывающий область доступности для каждого члена. Обратите внимание, что операторы,
ссылающиеся на недоступные члены, закомментированы. Если необходимо просмотреть сообщения об
ошибках, выдаваемые компилятором при попытке обращения к недоступному члену, удаляйте
комментарии по одному.
public class T1
{
public static int publicInt;
internal static int internalInt;
private static int privateInt = 0;
static T1()
{
// T1 can access public or internal members
// in a public or private (or internal) nested class.
M1.publicInt = 1;
M1.internalInt = 2;
M2.publicInt = 3;
M2.internalInt = 4;
public class M1
{
public static int publicInt;
internal static int internalInt;
private static int privateInt = 0;
}
private class M2
{
public static int publicInt = 0;
internal static int internalInt = 0;
private static int privateInt = 0;
}
}
class MainClass
class MainClass
{
static void Main()
{
// Access is unlimited.
T1.publicInt = 1;
// Access is unlimited.
T1.M1.publicInt = 1;
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы доступа
Уровни доступности
Ограничения на использование уровней доступности
Модификаторы доступа
public
private
protected
internal
Ограничения на использование уровней
доступности (справочник по C#)
23.10.2019 • 4 minutes to read • Edit Online
При задании типа в объявлении необходимо проверить, зависит ли уровень доступности типа от уровня
доступности члена или другого типа. Например, прямой базовый класс должен иметь по крайней мере
такой же уровень доступности, как и производный класс. Следующие объявления вызывают ошибку
компиляции, так как базовый класс BaseClass менее доступен, чем MyClass :
КОНТЕКСТ ПРИМЕЧАНИЯ
Пример
В приведенном ниже примере содержатся ошибочные объявления различных типов. В комментарии после
каждого объявления указывается предполагаемая ошибка компиляции.
// Restrictions on Using Accessibility Levels
// CS0052 expected as well as CS0053, CS0056, and CS0057
// To make the program work, change access level of both class B
// and MyPrivateMethod() to public.
using System;
// A delegate:
delegate int MyDelegate();
class B
{
// A private method:
static int MyPrivateMethod()
{
return 0;
}
}
public class A
{
// Error: The type B is less accessible than the field A.myField.
public B myField = new B();
public B MyMethod()
{
// Error: The type B is less accessible
// than the method A.MyMethod.
return new B();
}
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы доступа
Домен доступности
Уровни доступности
Модификаторы доступа
public
private
protected
internal
internal (Справочник по C#)
04.11.2019 • 3 minutes to read • Edit Online
Ключевое слово internal является модификатором доступа для типов и членов типов.
Эта страница содержит доступ internal . Ключевое слово internal также является частью
модификатора доступа protected internal .
Внутренние типы или члены доступны только внутри файлов в той же сборке, как в следующем
примере:
Пример
Этот пример содержит два файла, Assembly1.cs и Assembly1_a.cs . Первый файл содержит
внутренний базовый класс BaseClass . Во втором файле попытка создать экземпляр BaseClass
приведет к ошибке.
// Assembly1.cs
// Compile with: /target:library
internal class BaseClass
{
public static int intM = 0;
}
// Assembly1_a.cs
// Compile with: /reference:Assembly1.dll
class TestAccess
{
static void Main()
{
var myBase = new BaseClass(); // CS0122
}
}
Пример
В этом примере используйте те же файлы, которые использовались в примере 1, однако измените
уровень доступности BaseClass на public . Кроме того, измените уровень доступности члена intM
на internal . В этом случае можно создать экземпляр класса, но нельзя получить доступ к
внутреннему члену.
// Assembly2.cs
// Compile with: /target:library
public class BaseClass
{
internal static int intM = 0;
}
// Assembly2_a.cs
// Compile with: /reference:Assembly2.dll
public class TestAccess
{
static void Main()
{
var myBase = new BaseClass(); // Ok.
BaseClass.intM = 444; // CS0117
}
}
Спецификация языка C#
Дополнительные сведения см. в разделе Объявленная доступность в Спецификации языка C#.
Спецификация языка является предписывающим источником информации о синтаксисе и
использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы доступа
Уровни доступности
Модификаторы
public
private
protected
private (Справочник по C#)
04.11.2019 • 2 minutes to read • Edit Online
Эта страница содержит доступ private . Ключевое слово private также является частью
модификатора доступа private protected .
class Employee
{
private int i;
double d; // private access by default
}
Вложенные типы в том же теле могут также обращаться к таким закрытым членам.
Ошибка времени компиляции возникнет в том случае, если создать ссылку на закрытый член за
пределами класса или структуры, в которой он объявлен.
Сравнение модификатора private с другими модификаторами доступа см. в разделах Уровни
доступности и Модификаторы доступа.
Пример
В этом примере класс Employee содержит два закрытых элемента данных — name и salary . Как
к закрытым членам, к ним нельзя получить доступ, кроме как через методы членов. Для
получения управляемого доступа к закрытым членам можно использовать открытые методы
GetName и Salary . Доступ к члену name можно поучить через открытый метод, а к члену salary
— через открытое свойство только для чтения. (Дополнительные сведения см. в разделе
Свойства.)
class Employee2
{
private string name = "FirstName, LastName";
private double salary = 100.0;
class PrivateTest
{
static void Main()
{
var e = new Employee2();
Спецификация языка C#
Дополнительные сведения см. в разделе Объявленная доступность в Спецификации языка C#.
Спецификация языка является предписывающим источником информации о синтаксисе и
использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы доступа
Уровни доступности
Модификаторы
public
protected
internal
protected (справочник по C#)
04.11.2019 • 2 minutes to read • Edit Online
Эта страница содержит доступ protected . Ключевое слово protected также является частью
модификаторов доступа protected internal и private protected .
Пример
Доступ к защищенному элементу базового класса может быть получен в производном классе,
только если доступ осуществляется через тип производного класса. Для примера рассмотрим
следующий сегмент кода:
class A
{
protected int x = 123;
}
class B : A
{
static void Main()
{
var a = new A();
var b = new B();
Пример
В этом примере класс DerivedPoint является производным от класса Point . В связи с этим доступ к
защищенным элементам базового класса можно получить напрямую из производного класса.
class Point
{
protected int x;
protected int y;
}
Спецификация языка C#
Дополнительные сведения см. в разделе Объявленная доступность в Спецификации языка C#.
Спецификация языка является предписывающим источником информации о синтаксисе и
использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы доступа
Уровни доступности
Модификаторы
public
private
internal
Вопросы безопасности, связанные с использованием ключевых слов internal virtual
public (справочник по C#)
04.11.2019 • 2 minutes to read • Edit Online
Ключевое слово public является модификатором доступа для типов и членов типов. Общий
доступ является уровнем доступа с максимальными правами. Ограничений доступа к общим
членам не существует, как показано в следующем примере:
class SampleClass
{
public int x; // No access restrictions.
}
Пример
В следующем примере объявляются два класса: PointTest и MainClass . Доступ к открытым
членам x и y класса PointTest осуществляется непосредственно из класса MainClass .
class PointTest
{
public int x;
public int y;
}
class MainClass4
{
static void Main()
{
var p = new PointTest();
// Direct access to public members.
p.x = 10;
p.y = 15;
Console.WriteLine($"x = {p.x}, y = {p,y}");
}
}
// Output: x = 10, y = 15
Если уровень доступа public изменить на private или protected, будет выводится следующее
сообщение об ошибке:
"PointTest.y" недоступен из-за его уровня защиты.
Спецификация языка C#
Дополнительные сведения см. в разделе Объявленная доступность в Спецификации языка C#.
Спецификация языка является предписывающим источником информации о синтаксисе и
использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Модификаторы доступа
Ключевые слова в C#
Модификаторы доступа
Уровни доступности
Модификаторы
private
protected
internal
protected internal (справочник по C#)
04.11.2019 • 2 minutes to read • Edit Online
Комбинация ключевых слов protected internal является модификатором доступа к члену. Доступ к
членам с модификатором доступа protected internal может осуществляться из текущей сборки или
типов, которые являются производными от содержащего класса. Сравнение модификатора
protected internal с другими модификаторами доступа см. в разделе Уровни доступности.
Пример
Член базового класса с модификатором доступа protected internal доступен из любого типа в пределах
содержащей сборки. Он также доступен в производном классе, расположенном в другой сборке,
только в том случае, если доступ осуществляется через переменную типа производного класса. Для
примера рассмотрим следующий сегмент кода:
// Assembly1.cs
// Compile with: /target:library
public class BaseClass
{
protected internal int myValue = 0;
}
class TestAccess
{
void Access()
{
var baseObject = new BaseClass();
baseObject.myValue = 5;
}
}
// Assembly2.cs
// Compile with: /reference:Assembly1.dll
class DerivedClass : BaseClass
{
static void Main()
{
var baseObject = new BaseClass();
var derivedObject = new DerivedClass();
Этот пример содержит два файла, Assembly1.cs и Assembly2.cs . Первый файл содержит открытый
базовый класс, BaseClass , и еще один класс, TestAccess . BaseClass владеет членом protected internal,
myValue , доступ к которому осуществляется типом TestAccess . Во втором файле попытка получить
доступ к myValue через экземпляр BaseClass приведет к ошибке во время доступа к этому члену через
экземпляр производного класса. DerivedClass гарантирует успешное выполнение.
Элементы структуры не могут иметь модификатор protected internal , поскольку структура не может
наследоваться.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы доступа
Уровни доступности
Модификаторы
public
private
internal
Вопросы безопасности, связанные с использованием ключевых слов internal virtual
private protected (справочник по C#)
04.11.2019 • 2 minutes to read • Edit Online
Комбинация ключевых слов private protected является модификатором доступа к члену. К члену
private protected имеют доступ типы, производные от содержащего класса, но только в пределах
содержащей сборки. Сравнение модификатора private protected с другими модификаторами доступа
см. в разделе Уровни доступности.
NOTE
Модификатор доступа private protected допустим в C# 7.2 и более поздних версий.
Пример
Член базового класса private protected доступен из производных типов в содержащей сборке только в
том случае, если статический тип переменной является типом производного класса. Для примера
рассмотрим следующий сегмент кода:
// Assembly1.cs
// Compile with: /target:library
public class BaseClass
{
private protected int myValue = 0;
}
// Assembly2.cs
// Compile with: /reference:Assembly1.dll
class DerivedClass2 : BaseClass
{
void Access()
{
// Error CS0122, because myValue can only be
// accessed by types in Assembly1
// myValue = 10;
}
}
Этот пример содержит два файла, Assembly1.cs и Assembly2.cs . Первый файл содержит открытый
базовый класс, BaseClass , и производный от него тип, DerivedClass1 . BaseClass владеет членом private
protected, myValue , к которому DerivedClass1 пытается получить доступ двумя способами. Первая
попытка доступа к myValue через экземпляр BaseClass приведет к ошибке. Однако попытка
использовать его в качестве наследуемого члена в DerivedClass1 завершится успешно. Во втором
файле попытка получить доступ к myValue в качестве наследуемого члена DerivedClass2 приведет к
ошибке, поскольку он доступен только для производных типов в Assembly1.
Элементы структуры не могут иметь модификатор private protected , поскольку структура не может
наследоваться.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы доступа
Уровни доступности
Модификаторы
public
private
internal
Вопросы безопасности, связанные с использованием ключевых слов internal virtual
abstract (Справочник по C#)
04.11.2019 • 5 minutes to read • Edit Online
Модификатор abstract указывает, что изменяемый элемент имеет отсутствующую или неполную
реализацию. Модификатор abstract можно использовать с классами, методами, свойствами,
индексаторами и событиями. Используйте модификатор abstract в объявлении класса, чтобы
указать, что класс предназначен только для использования в качестве базового класса для других
классов и не должен быть создан сам по себе. Элементы с пометкой abstract должны быть
реализованы не абстрактными классами, производными от абстрактного класса.
Пример
В этом примере класс Square должен обеспечивать реализацию GetArea , поскольку является
производным от класса Shape :
interface I
{
void M();
}
abstract class C : I
{
public abstract void M();
}
Пример
В следующем примере класс DerivedClass является производным от абстрактного класса
BaseClass . Абстрактный класс содержит абстрактный метод, AbstractMethod , и два абстрактных
свойства, X и Y .
abstract class BaseClass // Abstract class
{
protected int _x = 100;
protected int _y = 150;
public abstract void AbstractMethod(); // Abstract method
public abstract int X { get; }
public abstract int Y { get; }
}
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Модификаторы
virtual
override
Ключевые слова в C#
async (справочник по C#)
23.10.2019 • 7 minutes to read • Edit Online
Модификатор async позволяет указать, что метод, лямбда-выражение или анонимный метод
является асинхронным. Если этот модификатор используется в методе или выражении, они
называются асинхронными методами. Ниже приводится пример асинхронного метода с именем
ExampleMethodAsync :
Асинхронный метод выполняется синхронным образом до тех пор, пока не будет достигнуто
первое выражение await , после чего метод приостанавливается, пока не будет завершена
ожидаемая задача. В то же время управление возвращается в вызывающий объект метода, как
показано в примере в следующем разделе.
Если метод, который изменяется ключевым словом async , не содержит выражения или оператора
await , метод выполняется синхронно. Компилятор выводит предупреждения обо всех
асинхронных методах, не содержащих операторы await , поскольку такая ситуация может
указывать на ошибку. См. раздел Предупреждение компилятора (уровень 1) CS4014.
Ключевое слово async — это контекстно-зависимо ключевое слово, которое является ключевым
словом, когда оно изменяет метод, лямбда-выражение или анонимный метод. Во всех других
контекстах он интерпретируется как идентификатор.
Пример
В следующем примере показана структура и поток управления между обработчиком асинхронных
событий StartButton_Click и асинхронным методом ExampleMethodAsync . В результате выполнения
этого асинхронного метода возвращается число символов на веб-странице. Код подходит для
приложения Windows Presentation Foundation (WPF ) или приложения для Магазина Windows
Presentation Foundation (WPF ), которые создаются в Visual Studio; см. комментарии к коду для
настройки приложения.
Этот код можно выполнить в Visual Studio как приложение Windows Presentation Foundation (WPF )
или приложение Магазина Windows. Вам понадобятся элементы управления типа "Кнопка" (
StartButton ) и "Текстовое поле" ( ResultsTextBox ). Не забудьте задать имена и обработчик, чтобы
получить код следующего вида:
<Button Content="Button" HorizontalAlignment="Left" Margin="88,77,0,0" VerticalAlignment="Top"
Width="75"
Click="StartButton_Click" Name="StartButton"/>
<TextBox HorizontalAlignment="Left" Height="137" Margin="88,140,0,0" TextWrapping="Wrap"
Text="<Enter a URL>" VerticalAlignment="Top" Width="310" Name="ResultsTextBox"/>
try
{
int length = await ExampleMethodAsync();
// Note that you could put "await ExampleMethodAsync()" in the next line where
// "length" is, but due to when '+=' fetches the value of ResultsTextBox, you
// would not see the global side effect of ExampleMethodAsync setting the text.
ResultsTextBox.Text += String.Format("Length: {0:N0}\n", length);
}
catch (Exception)
{
// Process the exception if one occurs.
}
}
IMPORTANT
Дополнительные сведения о задачах и коде, который выполняется во время ожидания задачи, см. в
разделе Асинхронное программирование с использованием ключевых слов Async и Await. Полный пример
WPF, в котором используются аналогичные элементы, см. в статье Пошаговое руководство. Получение
доступа к Интернету с помощью модификатора Async и оператора Await (C#).
Типы возвращаемых значений
Асинхронные методы могут иметь следующие типы возвращаемых значений:
Task
Task<TResult>
void. Методы async void обычно рекомендуются для кода, отличного от обработчиков
событий, поскольку вызывающие объекты не могут await эти методы и должны реализовать
другой механизм уведомления об успешном завершении или ошибках.
Начиная с версии 7.0 в языке C# поддерживаются любые типы с доступным методом
GetAwaiter . Одной из таких реализаций является тип System.Threading.Tasks.ValueTask<TResult> .
Доступ к нему осуществляется посредством пакета NuGet System.Threading.Tasks.Extensions .
Асинхронный метод не может объявлять параметры in, ref или out, а также иметь ссылочное
возвращаемое значение, но он может вызывать методы с такими параметрами.
Task<TResult> указывается в качестве возвращаемого типа асинхронного метода, если оператор
return метода задает операнд типа TResult . Класс Task используется при отсутствии
содержательного значения, возвращаемого методом при его завершении. То есть вызов метода
возвращает Task , однако когда Task завершен, любое выражение await , которое ожидает Task ,
возвращает значение void .
Возвращаемый тип void используется в основном для определения обработчиков событий,
которые требуют этого возвращаемого типа. Вызывающий объект асинхронного метода,
возвращающего void , не может ожидать его и перехватывать создаваемые методом исключения.
Начиная с версии C# 7.0, возвращается другой тип, обычно тип значения, с методом GetAwaiter ,
что позволяет свести к минимуму операции выделения памяти в разделах кода с критическими
требованиями к производительности.
Дополнительные сведения и примеры см. в разделе Асинхронные типы возвращаемых значений.
См. также
AsyncStateMachineAttribute
await
Пошаговое руководство: Получение доступа к Интернету с помощью модификатора Async и
оператора Await (C#)
Асинхронное программирование с использованием ключевых слов async и await
const (Справочник по C#)
04.11.2019 • 3 minutes to read • Edit Online
Для объявления константного поля или константной локальной используется ключевое слово const .
Константные поля и локальные не являются переменными и не могут быть изменены. Константы могут
быть числами, логическими значениями, строками или нулевыми ссылками. Не создавайте константу для
предоставления сведений, которые могут измениться в любое время. Например, не используйте
константное поле для хранения цены услуги, номера версии продукта или торгового названия компании.
Эти значения могут со временем измениться, а поскольку константы распространяются компиляторами,
для отражения изменений потребуется повторная компиляция остальных кодов, скомпилированных с
использованием ваших библиотек. См. также описание ключевого слова readonly. Например:
const int X = 0;
public const double GravitationalConstant = 6.673e-11;
private const string ProductName = "Visual C#";
Примечания
Тип объявления константы указывает на тип членов, которые вводятся объявлением. Инициализатор
локальной константы или константного поля должен быть выражением константы, поддерживающим
неявное преобразование в конечный тип.
Выражение константы — это выражение, которое можно полностью вычислить во время компиляции.
Таким образом, единственно возможными значениями для констант типов ссылок являются string и
нулевые ссылки.
Объявление константы может объявлять несколько констант, например:
NOTE
Ключевое слово readonly отличается от ключевого слова const . Поле const может быть инициализировано
только при объявлении поля. Поле readonly может быть инициализировано при объявлении или в конструкторе.
Таким образом, поля readonly могут иметь разные значения в зависимости от использованного конструктора.
Также, несмотря на то, что поле const является константой во время компиляции, поле readonly можно
использовать для констант во время выполнения, как в следующей строке:
public static readonly uint l1 = (uint)DateTime.Now.Ticks;
Пример
public class ConstTest
{
class SampleClass
{
public int x;
public int y;
public const int C1 = 5;
public const int C2 = C1 + 5;
Пример
В этом примере показан способ использования констант в качестве локальных переменных.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы
readonly
event (Справочник по C#)
04.11.2019 • 4 minutes to read • Edit Online
Пример
Следующий пример демонстрирует объявление и вызов события, которое использует EventHandler как
базовый тип делегата. Полный пример кода, демонстрирующий использование универсального типа
делегата EventHandler<TEventArgs>, создание подписки на событие и создание метода обработчика
событий, см. в практическом руководстве по публикации событий в соответствии с рекомендациями по
.NET Framework.
События представляют собой специальный вид многоадресного делегата, который можно вызвать только
из класса или структуры, в которых он объявлен (класс Publisher). Если другие классы или структуры
подписываются на событие, их методы обработчиков событий будут вызываться, когда класс Publisher
будет вызывать событие. Дополнительные сведения и примеры кода см. в разделах События и Делегаты.
События могут иметь пометку public, private, protected, internal, protected internal или private protected. Эти
модификаторы доступа определяют, каким образом пользователи класса смогут получать доступ к
событию. Дополнительные сведения см. в статье Модификаторы доступа.
Событие может быть объявлено как статическое событие с помощью ключевого слова static. Это делает
событие доступным для вызывающих объектов в любое время, даже если экземпляр класса не существует.
Дополнительные сведения см. в статье Статические классы и члены статических классов.
Событие может быть помечено как виртуальное событие с помощью ключевого слова virtual. Это
позволяет производным классам переопределять поведение события с помощью ключевого слова
override. Дополнительные сведения см. в разделе Наследование. Событие, переопределяющее
виртуальное событие, также может быть запечатанным (sealed), что указывает, что для производных
классов оно больше не является виртуальным. И наконец, можно объявить событие абстрактным
(abstract), что означает, что компилятор не будет создавать блоки доступа к событиям add и remove .
Поэтому производные классы должны предоставлять собственную реализацию.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
add
remove
Модификаторы
Практическое руководство. Объединение делегатов (многоадресные делегаты)
extern (справочник по C#)
04.11.2019 • 3 minutes to read • Edit Online
Модификатор extern используется для объявления метода с внешней реализацией. При применении служб
взаимодействия для вызова неуправляемого кода модификатор extern обычно используется с атрибутом
DllImport . В этом случае также необходимо объявить метод как static в соответствии со следующим
примером:
[DllImport("avifil32.dll")]
private static extern void AVIFileInit();
Ключевое слово extern может также определять внешний псевдоним сборки, который позволяет
ссылаться на разные версии одного компонента из одной сборки. Дополнительные сведения см. в разделе
Псевдоним extern.
Совместное использование модификаторов abstract и extern для изменения одного члена недопустимо.
Использование модификатора extern означает, что метод реализуется вне кода C#, а применение
модификатора abstract указывает на то, что в данном классе реализация метода не обеспечивается.
В C# ключевое слово extern имеет более ограниченное применение, чем в C++. Сравнительные
характеристики использования этого ключевого слова в C# и в C++ см. в разделе "Использование extern для
указания компоновки" Справочника по языку C++.
Пример 1
В этом примере программа получает от пользователя строку и отображает ее в окне сообщения. В этой
программе используется метод MessageBox , импортированный из библиотеки User32.dll.
//using System.Runtime.InteropServices;
class ExternTest
{
[DllImport("User32.dll", CharSet=CharSet.Unicode)]
public static extern int MessageBox(IntPtr h, string m, string c, int type);
Пример 2
В этом примере показана программа C#, в которой вызывается библиотека C (собственная библиотека
DLL ).
1. Создайте следующий файл C и назовите его cmdll.c .
// cmdll.c
// Compile with: -LD
int __declspec(dllexport) SampleMethod(int i)
{
return i*10;
}
2. Откройте из каталога установки Visual Studio окно командной строки Visual Studio x64 (или x32)
Native Tools и скомпилируйте файл cmdll.c , введя в командной строке cl -LD cmdll.c.
3. В том же каталоге создайте следующий файл C# и назовите его cm.cs .
// cm.cs
using System;
using System.Runtime.InteropServices;
public class MainClass
{
[DllImport("Cmdll.dll")]
public static extern int SampleMethod(int x);
4. Откройте из каталога установки Visual Studio окно командной строки Visual Studio x64 (или x32)
Native Tools и скомпилируйте файл cm.cs , введя:
csc cm.cs (для командной строки x64) — или — csc -platform:x86 cm.cs (для командной строки
x32)
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
System.Runtime.InteropServices.DllImportAttribute
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы
in (универсальный модификатор) (Справочник по
C#)
04.11.2019 • 3 minutes to read • Edit Online
Для параметров универсального типа ключевое слово in указывает, что параметр типа является
контравариантным. Ключевое слово in может применяться в универсальных интерфейсах и делегатах.
Контравариантность позволяет использовать производные типы со степенью наследования меньше, чем у
типа, заданного универсальным параметром. Благодаря этому можно осуществлять неявное преобразование
классов, реализующих контравариантные интерфейсы, и неявное преобразование типов делегатов.
Ковариантность и контравариантность поддерживаются для ссылочных типов, но не для типов значений.
Тип может быть объявлен контравариантным в универсальном интерфейсе или делегате, только если он
определяет тип параметров метода, но не тип значения, возвращаемого методом. Параметры In , ref и
out должны быть инвариантными, то есть не являться ковариантными или контравариантными.
// Contravariant interface.
interface IContravariant<in A> { }
class Program
{
static void Test()
{
IContravariant<Object> iobj = new Sample<Object>();
IContravariant<String> istr = new Sample<String>();
// Contravariant delegate.
public delegate void DContravariant<in A>(A argument);
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
out
Ковариация и контрвариантность
Модификаторы
Модификатор new (справочник по C#)
04.11.2019 • 5 minutes to read • Edit Online
При использовании в качестве модификатора объявления ключевое слово new явным образом
скрывает члены, унаследованные от базового класса. При скрытии унаследованного члена его
производная версия заменяет версию базового класса. Хотя члены можно скрывать без использования
модификатора new , в этом случае появляется предупреждение компилятора. При использовании new
для явного скрытия члена, предупреждение не появляется.
Ключевое слово new можно также использовать для создания экземпляра типа или как ограничение
универсального типа.
Чтобы скрыть унаследованный член, объявите его в производном классе с использованием такого же
имени члена и измените с помощью ключевого слова new . Например:
Пример
В этом примере вложенный класс скрывает класс, имеющий такое же имя в базовом классе. Здесь
показано использование модификатора new для исключения предупреждений, а также обращение к
членам скрытого класса с помощью их полных имен.
public class BaseC
{
public class NestedC
{
public int x = 200;
public int y;
}
}
Console.WriteLine(c1.x);
Console.WriteLine(c2.x);
}
}
/*
Output:
100
200
*/
The keyword new is required on 'MyDerivedC.x' because it hides inherited member 'MyBaseC.x'.
Спецификация языка C#
Дополнительные сведения см. в разделе Модификатор new в спецификации языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы
Управление версиями с помощью ключевых слов Override и New
Использование ключевых слов Override и New
out (универсальный модификатор) (Справочник по
C#)
04.11.2019 • 4 minutes to read • Edit Online
Для параметров универсального типа ключевое слово out указывает, что параметр типа является
ковариантным. Ключевое слово out может применяться в универсальных интерфейсах и делегатах.
Ковариация позволяет использовать производные типы со степенью наследования больше, нежели у типа,
заданного универсальным параметром. Благодаря этому можно осуществлять неявное преобразование
классов, реализующих ковариантные интерфейсы, и неявное преобразование типов делегатов. Ковариация и
контравариантность поддерживаются для ссылочных типов, но не для типов значений.
Интерфейс с параметром ковариантного типа позволяет своим методам возвращать аргументы
производных типов, степень наследования у которых больше, чем у параметра типа. Например, так как в
.NET Framework 4 в IEnumerable<T> тип T является ковариантным, можно назначить объект типа
IEnumerable(Of String) объекту типа IEnumerable(Of Object) без применения каких-либо специальных
методов преобразования.
Ковариантный делегат может быть назначен другому делегату того же типа, но с производным параметром
универсального типа большей степени.
Дополнительные сведения см. в разделе Ковариация и контравариантность.
// Covariant interface.
interface ICovariant<out R> { }
class Program
{
static void Test()
{
ICovariant<Object> iobj = new Sample<Object>();
ICovariant<String> istr = new Sample<String>();
В универсальном интерфейсе параметр типа может быть объявлен ковариантным, если он удовлетворяет
следующим условиям:
Параметр типа используется только в качестве типа значения, возвращаемого методами интерфейса,
и не используется в качестве типа аргументов метода.
NOTE
Существует одно исключение из данного правила. Если в ковариантном интерфейсе в качестве параметра
метода используется контравариантный универсальный делегат, ковариантный тип можно использовать в
качестве параметра универсального типа для этого делегата. Дополнительные сведения о ковариантных и
контравариантных универсальных методах-делегатах см. в разделе Вариативность в делегатах и Использование
вариативности в универсальных методах-делегатах Func и Action.
// Covariant delegate.
public delegate R DCovariant<out R>();
В универсальном методе-делегате тип может быть объявлен ковариантным, если он используется только как
тип значения, возвращаемого методом, и не используется для аргументов метода.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Расхождение в универсальных интерфейсах
in
Модификаторы
override (Справочник по C#)
04.11.2019 • 3 minutes to read • Edit Online
Модификатор override требуется для расширения или изменения абстрактной или виртуальной
реализации унаследованного метода, свойства, индексатора или события.
Пример
В этом примере класс Square должен предоставить переопределенную реализацию GetArea , так
как GetArea является унаследованным от абстрактного класса Shape :
Модификаторы new , static и virtual нельзя использовать для изменения метода override .
Переопределяющее объявление свойства должно задавать такие же модификатор уровня доступа,
тип и имя, которые имеются у унаследованного свойства, а переопределенное свойство должно
иметь тип virtual , abstract или override .
Дополнительные сведения об использовании ключевого слова override см. в разделах
Управление версиями с помощью ключевых слов Override и New и Использование ключевых слов
Override и New.
Пример
В этом примере определяется базовый класс с именем Employee и производный класс с именем
SalesEmployee . Класс SalesEmployee включает дополнительное поле salesbonus , для
использования которого переопределяется метод CalculatePay .
class TestOverride
{
public class Employee
{
public string name;
См. также
Справочник по C#
Руководство по программированию на C#
Наследование
Ключевые слова в C#
Модификаторы
abstract
virtual
new (модификатор)
Полиморфизм
readonly (Справочник по C#)
04.11.2019 • 10 minutes to read • Edit Online
Ключевое слово readonly — это модификатор, который может использоваться в четырех контекстах:
В объявлении поля readonly указывает на то, что присвоение значения полю может происходить
только при объявлении или в конструкторе этого класса. Полю только для чтения можно несколько
раз назначить значения в объявлении поля и в конструкторе.
Поле readonly нельзя изменять после выхода из конструктора. Это правило влечет за собой
разные последствия для типов значений и ссылочных типов:
Поскольку типы значений содержат данные, поле readonly с типом значения является
неизменяемым.
Ссылочные типы содержат только ссылку на соответствующие данные, а значит поле readonly
ссылочного типа будет всегда ссылаться на один объект. Но сам этот объект не является
неизменяемым. Модификатор readonly запрещает замену поля другим экземпляром
ссылочного типа. Но этот модификатор не препятствует изменению данных экземпляра, на
которое ссылается поле только для чтения, в том числе через это поле.
WARNING
Видимый извне тип, который содержит видимое извне и доступное только для чтения поле с изменяемым
ссылочным типом, может представлять уязвимость и приводить к предупреждению CA2104: Не объявляйте
изменяющиеся ссылочные типы только для чтения.
В определении readonly struct readonly указывает на то, что struct является неизменяемым.
В определения элемента readonly readonly указывает, что элемент struct не изменяет
внутреннее состояние структуры.
В возврате метода ref readonly модификатор readonly указывает, что метод возвращает ссылку, и
записи для этой ссылки не допускаются.
Контексты readonly struct и ref readonly были добавлены в C# 7.2. Члены структуры readonly
добавлены в C# 8.0
NOTE
Ключевое слово readonly отличается от ключевого слова const. Поле const может быть инициализировано
только при объявлении поля. Поле readonly может быть назначено несколько раз в объявлении поля и в любом
конструкторе. Таким образом, поля readonly могут иметь разные значения в зависимости от использованного
конструктора. К тому же, в то время как поле const является константой во время компиляции, поле readonly
можно использовать для констант во время выполнения, как в следующем примере:
public SampleClass()
{
// Initialize a readonly instance field
z = 24;
}
В предыдущем примере используются автоматические свойства только для чтения для объявления
хранилища. Это указывает компилятору на необходимость создания резервных полей readonly для этих
свойств. Можно также объявить поля readonly напрямую:
Добавление поля без отметки readonly вызывает ошибку компилятора CS8340 с информацией о том, что
поля экземпляров в структурах только для чтения должны быть доступными только для чтения.
Можно также добавить модификатор readonly к отдельным методам доступа get или set свойства или
индексатора:
Нельзя добавить модификатор readonly одновременно к свойству и к одному или нескольким методам
доступа этого свойства. Это же ограничение применяется к индексаторам.
Компилятор неявно применяет модификатор readonly к автоматически реализуемым свойствам, в
которых код, реализованный компилятором, не изменяет состояние. Это эквивалентно следующим
объявлениям:
Вы можете добавить модификатор readonly в эти места, но это не будет иметь никакого эффекта. Нельзя
добавить модификатор readonly к методу задания автоматически реализуемого свойства или к
автоматически реализуемому свойству, доступному для чтения и записи.
Необязательно должен возвращаться тип readonly struct . Любой тип, возвращаемый из ref , может
возвращаться из ref readonly .
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
Вы также можете ознакомиться с предложениями языковых спецификаций:
Ссылка и структура readonly
Члены структуры readonly
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы
const
Поля
sealed (Справочник по C#)
04.11.2019 • 3 minutes to read • Edit Online
При применении к классу модификатор sealed запрещает другим классам наследовать от этого
класса. В следующем примере класс B наследует от класса A , но никакие классы не могут
наследовать от класса B .
class A {}
sealed class B : A {}
Модификатор sealed можно использовать для метода или свойства, которое переопределяет
виртуальный метод или свойство в базовом классе. Это позволяет классам наследовать от вашего
класса, запрещая им при этом переопределять определенные виртуальные методы или свойства.
Пример
В следующем примере класс Z наследует от класса Y , но Z не может переопределить
виртуальную функцию F , которая объявлена в классе X и запечатана в классе Y .
class X
{
protected virtual void F() { Console.WriteLine("X.F"); }
protected virtual void F2() { Console.WriteLine("X.F2"); }
}
class Y : X
{
sealed protected override void F() { Console.WriteLine("Y.F"); }
protected override void F2() { Console.WriteLine("Y.F2"); }
}
class Z : Y
{
// Attempting to override F causes compiler error CS0239.
// protected override void F() { Console.WriteLine("Z.F"); }
// Overriding F2 is allowed.
protected override void F2() { Console.WriteLine("Z.F2"); }
}
Чтобы предотвратить переопределение производных классов при определении новых методов или
свойств, не назначайте их в качестве виртуальных (virtual).
Нельзя использовать модификатор abstract с запечатанным классом, поскольку абстрактный класс
должен наследоваться классом, реализующим абстрактные методы или свойства.
При применении к методу или свойству модификатор sealed всегда следует использовать с
override.
Поскольку структуры неявно запечатаны, их нельзя наследовать.
Дополнительные сведения см. в разделе Наследование.
Дополнительные примеры см. в разделе Абстрактные и запечатанные классы и члены классов.
Пример
sealed class SealedClass
{
public int x;
public int y;
}
class SealedTest2
{
static void Main()
{
var sc = new SealedClass();
sc.x = 110;
sc.y = 150;
Console.WriteLine($"x = {sc.x}, y = {sc.y}");
}
}
// Output: x = 110, y = 150
Примечания
Чтобы определить, нужно ли запечатывать класс, метод или свойство, имейте в виду следующее:
потенциальные преимущества, которые производные классы могут получить от
возможности настраивать ваш класс;
вероятность того, что производные классы могут корректировать ваши классы, препятствуя
их нормальной работе.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Статические классы и члены статических классов
Абстрактные и запечатанные классы и члены классов
Модификаторы доступа
Модификаторы
override
virtual
static (Справочник по C#)
04.11.2019 • 5 minutes to read • Edit Online
Пример
Следующий класс объявляется как static и содержит только методы static :
Console.WriteLine(MyBaseC.MyStruct.x);
Так как экземпляр класса содержит отдельную копию всех полей экземпляра класса, каждому
статическому полю соответствует только одна копия.
Невозможно использовать this для ссылки на статические методы или методы доступа к
свойствам.
Если к классу применяется ключевое слово static , все члены этого класса должны быть
статическими.
Классы и статические классы могут иметь статические конструкторы. Статические конструкторы
вызываются на определенном этапе между запуском программы и созданием экземпляра класса.
NOTE
Ключевое слово static имеет более ограниченное применение по сравнению с C++. Сведения о
сравнении с ключевым словом С++ см. в статье Классы хранения (C++).
Пример
В этом примере выполняется чтение имени и идентификатора нового сотрудника, увеличение
счетчика сотрудников на единицу, а также отображение сведений о новом сотруднике и новом
числе сотрудников. Для простоты эта программа считывает текущее число сотрудников с
клавиатуры. В реальном приложении эта информация должна считываться из файла.
public class Employee4
{
public string id;
public string name;
public Employee4()
{
}
Пример
Этот пример показывает, что несмотря на то, что вы можете инициализировать статическое поле
посредством другого, еще не объявленного статического поля, результаты не будут определены
до тех пор, пока статическому полю не будет явно присвоено значение.
class Test
{
static int x = y;
static int y = 5;
Test.x = 99;
Console.WriteLine(Test.x);
}
}
/*
Output:
0
5
99
*/
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы
Статические классы и члены статических классов
unsafe (Справочник по C#)
04.11.2019 • 2 minutes to read • Edit Online
Ключевое слово unsafe обозначает небезопасный контекст, необходимый для выполнения любых
операций с применением указателей. Дополнительные сведения см. в разделе Небезопасный код и
указатели.
В объявлении типа или члена типа можно использовать модификатор unsafe . Все текстовое
пространство типа или члена типа считается небезопасным контекстом. Например, следующий метод
объявлен с модификатором unsafe :
unsafe static void FastCopy ( byte* ps, byte* pd, int count ) {...}
Кроме того, небезопасный блок позволяет добавлять небезопасный код в блок. Например:
unsafe
{
// Unsafe context: can use pointers here.
}
Пример
// compile with: -unsafe
class UnsafeTest
{
// Unsafe method: takes pointer to int.
unsafe static void SquarePtrParam(int* p)
{
*p *= *p;
}
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Оператор fixed
Небезопасный код и указатели
Буферы фиксированного размера
virtual (Справочник по C#)
24.10.2019 • 4 minutes to read • Edit Online
Ключевое слово virtual используется для изменения объявлений методов, свойств, индексаторов
и событий и разрешения их переопределения в производном классе. Например, этот метод может
быть переопределен любым наследующим его классом:
Примечания
При вызове виртуального метода тип времени выполнения объекта проверяется на
переопределение члена. Вызывается переопределение члена в самом дальнем классе. Это может
быть исходный член, если никакой производный класс не выполнял переопределение этого члена.
По умолчанию методы не являются виртуальными. Такой метод переопределить невозможно.
Нельзя использовать модификатор virtual с модификаторами static , abstract , private , или
override . В следующем примере показано виртуальное свойство.
class MyBaseClass
{
// virtual auto-implemented property. Overrides can only
// provide specialized behavior if they implement get and set accessors.
public virtual string Name { get; set; }
Пример
В этом примере класс Shape содержит две координаты x , y и виртуальный метод Area() .
Различные классы фигур, такие как Circle , Cylinder и Sphere , наследуют класс Shape , и для
каждой фигуры вычисляется площадь поверхности. Каждый производный класс обладает
собственной реализацией переопределения метода Area() .
Обратите внимание, что наследуемые классы Circle , Sphere и Cylinder используют
конструкторы, которые инициализируют базовый класс, как показано в следующем объявлении.
class TestClass
{
public class Shape
{
public const double PI = Math.PI;
protected double x, y;
public Shape()
{
}
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Полиморфизм
abstract
override
new (модификатор)
volatile (Справочник по C#)
04.11.2019 • 4 minutes to read • Edit Online
Ключевое слово volatile означает, что поле может изменить несколько потоков, выполняемых
одновременно. Компилятор, среда выполнения или даже аппаратное обеспечение могут изменять порядок
операций чтения и записи в расположения в памяти для повышения производительности. К полям, которые
объявлены как volatile , такие оптимизации не применяются. Добавление модификатора volatile
гарантирует, что все потоки будут видеть временные записи, выполняемые другим потоком, в порядке их
выполнения. Нет никакой гарантии единого общего прядка временных записей во всех потоках выполнения.
Ключевое слово volatile может применяться к полям следующих типов:
Ссылочные типы.
Типы указателей (в небезопасном контексте). Несмотря на то, что сам указатель может быть изменяемым,
объект, на который он указывает, должен быть постоянным. Другими словами, объявить указатель на
изменяемый объект невозможно.
Простые типы, например sbyte , byte , short , ushort , int , uint , char , float и bool .
Тип enum с одним из следующих базовых типов: byte , sbyte , short , ushort , int или uint .
Параметры универсального типа называются ссылочными типами.
IntPtr и UIntPtr.
Другие типы, включая double и long , нельзя снабдить модификатором volatile , потому что для них не
гарантируется атомарность операций чтения и записи. Чтобы защитить многопотоковый доступ к полям
таких типов, используйте члены класса Interlocked или защиту доступа с помощью инструкции lock .
Ключевое слово volatile можно применять только к полям class или struct . Локальные переменные не
могут объявляться как volatile .
Пример
В следующем примере показано, как объявить переменную поля открытого типа volatile .
class VolatileTest
{
public volatile int sharedStorage;
Следующий пример демонстрирует создание вспомогательного или рабочего потока и его применение для
выполнения обработки параллельно с обработкой основного потока. Дополнительные сведения о
многопоточности см. в разделе Управляемая поточность.
public class Worker
{
// This method is called when the thread is started.
public void DoWork()
{
bool work = false;
while (!_shouldStop)
{
work = !work; // simulate some work
}
Console.WriteLine("Worker thread: terminating gracefully.");
}
public void RequestStop()
{
_shouldStop = true;
}
// Keyword volatile is used as a hint to the compiler that this data
// member is accessed by multiple threads.
private volatile bool _shouldStop;
}
Добавив модификатор volatile к объявлению _shouldStop , вы всегда получите одинаковые результаты (как
показано в приведенном выше фрагменте кода). Но если член _shouldStop не имеет этого модификатора,
поведение будет непредсказуемым. Метод DoWork может оптимизировать доступ к членам, что приведет к
чтению устаревших данных. В условиях многопоточного программирования невозможно прогнозировать
число операций чтения устаревших данных. При каждом запуске программы результаты могут отличаться.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является предписывающим
источником информации о синтаксисе и использовании языка C#.
См. также
Спецификация языка C#: ключевое слово volatile
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
Модификаторы
Оператор lock
Interlocked
Ключевые слова операторов (Справочник по C#)
23.10.2019 • 2 minutes to read • Edit Online
Операторы представляют собой инструкции для программы. За исключением случаев, которые описаны в
разделах, приведенных в следующей таблице, операторы выполняются последовательно. В следующей
таблице приводятся ключевые слова операторов в C#. Дополнительные сведения об операторах, которые не
выражаются ключевыми словами, см. в разделе Операторы.
См. также
Справочник по C#
Операторы
Ключевые слова в C#
if-else (Справочник по C#)
23.10.2019 • 7 minutes to read • Edit Online
Оператор if определяет, какой оператор будет выполняться при выполнения условия, заданного
логическим выражением. В приведенном ниже примере переменной bool типа condition
присваивается значение true , а затем она проверяется оператором if . В результате получается
The variable is set to true. .
if (condition)
{
Console.WriteLine("The variable is set to true.");
}
else
{
Console.WriteLine("The variable is set to false.");
}
Примеры в этом разделе можно выполнить, разместив их в методе Main консольного приложения.
Оператор if в С# может иметь две формы, как показано в приведенном ниже примере.
// if-else statement
if (condition)
{
then-statement;
}
else
{
else-statement;
}
// Next statement in the program.
В операторе if-else , если condition имеет значение true, выполняется then-statement . Если condition
имеет значение false, выполняется else-statement . Так как condition не может одновременно иметь
значения true и false, then-statement и else-statement оператора if-else не могут выполняться оба.
После выполнения then-statement или else-statement управление передается следующему оператору
после оператора if .
В операторе if, не включающем оператор else , если condition имеет значение true, выполняется
then-statement . Если condition имеет значение false, то управление передается следующему оператору
после оператора if .
then-statementи else-statement могут состоять из одного или нескольких операторов, заключенных в
фигурные скобки ( {} ). Для одного оператора скобки необязательны, но рекомендуются.
Оператор или операторы в then-statement и else-statement могут быть любого типа, включая другой
оператор if , вложенный в исходный оператор if . Во вложенных операторах if каждое
предложение else относится к последнему оператору if , у которого нет соответствующего объекта
else . В приведенном ниже примере Result1 получается, если оба выражения m > 10 и n > 20 имеют
значение true. Если m > 10 имеет значение true, но n > 20 — значение false, то получается Result2 .
if (m > 10)
if (n > 20)
{
Console.WriteLine("Result1");
}
else
{
Console.WriteLine("Result2");
}
Если вместо этого нужно, чтобы Result2 получался, когда значение (m > 10) равно false, можно указать
такую связь с помощью фигурных скобок для задания начала и конца вложенного оператора if , как
показано в приведенном ниже примере.
Пример
В приведенном ниже примере вы вводите символ с помощью клавиатуры, а программа использует
вложенный оператор if для определения того, является ли введенный символ буквой. Если введенный
символ является буквой, программа определяет его регистр. Для каждого случая предусмотрено
отдельное сообщение.
Console.Write("Enter a character: ");
char c = (char)Console.Read();
if (Char.IsLetter(c))
{
if (Char.IsLower(c))
{
Console.WriteLine("The character is lowercase.");
}
else
{
Console.WriteLine("The character is uppercase.");
}
}
else
{
Console.WriteLine("The character isn't an alphabetic character.");
}
//Sample Output:
//Enter a character: 2
//The character isn't an alphabetic character.
//Enter a character: A
//The character is uppercase.
//Enter a character: h
//The character is lowercase.
Пример
Также можно поместить оператор if в блок else, как показано в части кода, приведенной ниже. В
примере операторы if помещаются в два блока else и один блок then. Комментарии определяют какие
условия выполняются в каждом из блоков.
// Change the values of these variables to test the results.
bool Condition1 = true;
bool Condition2 = true;
bool Condition3 = true;
bool Condition4 = true;
if (Condition1)
{
// Condition1 is true.
}
else if (Condition2)
{
// Condition1 is false and Condition2 is true.
}
else if (Condition3)
{
if (Condition4)
{
// Condition1 and Condition2 are false. Condition3 and Condition4 are true.
}
else
{
// Condition1, Condition2, and Condition4 are false. Condition3 is true.
}
}
else
{
// Condition1, Condition2, and Condition3 are false.
}
Пример
В приведенном ниже примере определяется, является ли введенный символ строчной буквой, прописной
буквой или цифрой. Если все три условия имеют значение false, то символ не является алфавитно-
цифровым. Для каждого случая выводится сообщение.
Console.Write("Enter a character: ");
char ch = (char)Console.Read();
if (Char.IsUpper(ch))
{
Console.WriteLine("The character is an uppercase letter.");
}
else if (Char.IsLower(ch))
{
Console.WriteLine("The character is a lowercase letter.");
}
else if (Char.IsDigit(ch))
{
Console.WriteLine("The character is a number.");
}
else
{
Console.WriteLine("The character is not alphanumeric.");
}
//Enter a character: e
//The character is a lowercase letter.
//Enter a character: 4
//The character is a number.
//Enter a character: =
//The character is not alphanumeric.
Точно так же как оператор в блоке else или блоке then может быть любым допустимым оператором, в
качестве условия можно использовать любое допустимое логическое выражение. Вы можете
использовать логические операторы, такие как ! , && , || , & , | и ^ , для формирования составных
условий. В коде ниже приведены примеры.
// NOT
bool result = true;
if (!result)
{
Console.WriteLine("The condition is true (result is false).");
}
else
{
Console.WriteLine("The condition is false (result is true).");
}
// Short-circuit AND
int m = 9;
int n = 7;
int p = 5;
if (m >= n && m >= p)
{
Console.WriteLine("Nothing is larger than m.");
}
// Short-circuit OR
if (m > n || m > p)
{
Console.WriteLine("m isn't the smallest.");
}
// NOT and OR
m = 4;
if (!(m >= n || m >= p))
{
Console.WriteLine("Now m is the smallest.");
}
// Output:
// The condition is false (result is true).
// Nothing is larger than m.
// Nothing is larger than m.
// m isn't the smallest.
// Now m is the smallest.
Спецификация языка C#
Дополнительные сведения см. в спецификации языка C#. Спецификация языка является
предписывающим источником информации о синтаксисе и использовании языка C#.
См. также
Справочник по C#
Руководство по программированию на C#
Ключевые слова в C#
?: Оператор
Оператор if-else (C++)
switch
switch (справочник по C#)
04.11.2019 • 23 minutes to read • Edit Online
switch — это оператор выбора, который выбирает для выполнения один раздел switch из списка
кандидатов, сравнивая их с выражением соответствия.
using System;
switch (caseSwitch)
{
case 1:
Console.WriteLine("Case 1");
break;
case 2:
Console.WriteLine("Case 2");
break;
default:
Console.WriteLine("Default case");
break;
}
}
}
// The example displays the following output:
// Case 1
Оператор switch часто используется вместо конструкции if-else, если одно выражение
проверяется на соответствие трем или больше условиям. Например, следующий оператор switch
определяет, имеет ли переменная типа Color одно из трех значений:
using System;
using System;
Выражение соответствия
Выражение соответствия предоставляет значение для сравнения в шаблонами в метках case . Он
имеет следующий синтаксис:
switch (expr)
Раздел switch
Оператор switch включает один или несколько разделов switch. Каждый раздел switch содержит
одну или несколько меток case (меток case или меток default), за которыми следует один или
несколько операторов. Оператор switch может включать не более одной метки default в каждом
разделе switch. В следующем примере показан простой оператор switch с тремя разделами switch,
каждый из которых содержит два оператора. Второй раздел switch содержит метки case 2: и
case 3: .
Оператор switch может содержать любое количество разделов switch, а каждый раздел может
иметь одну или несколько меток case (как показано в следующем примере). Однако две метки case
не могут содержать одно и то же выражение.
using System;
switch (caseSwitch)
{
case 1:
Console.WriteLine("Case 1");
break;
case 2:
case 3:
Console.WriteLine($"Case {caseSwitch}");
break;
default:
Console.WriteLine($"An unexpected value ({caseSwitch})");
break;
}
}
}
// The example displays output like the following:
// Case 1
Обычно для соблюдения этого требования выполняется явный выход из раздела switch с
использованием оператора break, goto или return. При этом допустим также приведенный ниже
код, так как он гарантирует, что управление программой не будет передано дальше, в раздел switch
default .
switch (caseSwitch)
{
case 1:
Console.WriteLine("Case 1...");
break;
case 2:
case 3:
Console.WriteLine("... and/or Case 2");
break;
case 4:
while (true)
Console.WriteLine("Endless looping. . . .");
default:
Console.WriteLine("Default value...");
break;
}
Метки case
Каждая метка case указывает на шаблон для сравнения с выражением сопоставления (переменная
caseSwitch в предыдущем примере). Если они совпадают, управление передается разделу switch,
который содержит первую соответствующую метку case. Если с выражением соответствия не
совпадает ни один шаблон метки case, управление передается разделу с меткой case default при
условии, что такой раздел существует. Если метки case default нет, никакие операторы ни в одном
из разделов switch не выполняются, а оператор switch теряет управление.
Дополнительные сведения об операторе switch и сопоставлении шаблонов см. в разделе
Сопоставление шаблонов с оператором switch .
Так как C# 6 поддерживает только шаблон констант и не допускает повтор постоянных значений,
метки case определяют взаимоисключающие значения. При этом выражению сопоставления
может соответствовать только один шаблон. В связи с этим порядок отображения операторов
case не имеет значения.
Тем не менее, поскольку в C# 7.0 поддерживаются другие шаблоны, метки case не нужно
определять как взаимоисключающие значения, и выражению соответствия могут соответствовать
сразу несколько шаблонов. Поскольку в первом разделе switch выполняются только те операторы,
которые содержат совпадающий шаблон, порядок операторов case становится важным.
Обнаружив раздел switch, оператор или операторы которого эквивалентны предыдущим
операторам или являются их подмножествами, C# выдает ошибку компилятора CS8120, "Метка
case оператора switch уже обработана предыдущей меткой case".
В следующем примере демонстрируется оператор switch с использованием различных не
взаимоисключающих шаблонов. Если раздел switch case 0: перемещается и перестает быть
первым разделом в операторе switch , C# выдает ошибку компилятора, так как целое число с
нулевым значением — это подмножество всех целых чисел. Такой шаблон определен оператором
case int val .
using System;
using System.Collections.Generic;
using System.Linq;
здесь constant — это значение для проверки. Значением constant может быть любое из следующих
константных выражений:
литерал bool, true или false ;
любой целочисленный тип, такой как int, long или byte;
имя объявленной переменной const ;
константа перечисления;
литерал char;
литерал string.
Константное выражение вычисляется следующим образом.
Если expr и constant являются целочисленными типами, оператор равенства C# определяет,
возвращает ли выражение true (то есть выполняется ли условие expr == constant ).
В противном случае значение выражения определяется с помощью вызова статического
метода Object.Equals (expr, constant).
В следующем примере шаблон константы используется для того, чтобы определить, выпадает ли
определенная дата на выходные, первый или последний рабочий день недели либо середину
недели. Он сравнивает свойство DateTime.DayOfWeek текущего дня с членами перечисления
DayOfWeek.
using System;
class Program
{
static void Main()
{
switch (DateTime.Now.DayOfWeek)
{
case DayOfWeek.Sunday:
case DayOfWeek.Saturday:
Console.WriteLine("The weekend");
break;
case DayOfWeek.Monday:
Console.WriteLine("The first day of the work week.");
break;
case DayOfWeek.Friday:
Console.WriteLine("The last day of the work week.");
break;
default:
Console.WriteLine("The middle of the work week.");
break;
}
}
}
// The example displays output like the following:
// The middle of the work week.
class Example
{
static void Main()
{
Console.WriteLine("Coffee sizes: 1=small 2=medium 3=large");
Console.Write("Please enter your selection: ");
string str = Console.ReadLine();
int cost = 0;
Шаблон типа
Шаблон типа обеспечивает точное определение и преобразование типов. При использовании
оператора switch для сопоставления шаблонов он проверяет, можно ли преобразовать
выражение в указанный тип и, если это возможно, приводит его к переменной этого типа. Он
имеет следующий синтаксис:
здесь type — это имя типа, в который должен быть преобразован результат expr, varname — это
объект, в который преобразуется результат expr, если сопоставление завершается успешно.
Начиная с C# 7.1 тип времени компиляции у expr может быть параметром универсального типа.
Выражение case имеет значение true , если выполняется одно из следующих условий:
expr представляет собой экземпляр того же типа, что и type;
expr является экземпляром типа, производного от type. Другими словами, результат expr
может быть приведен с повышением к экземпляру type.
expr имеет тип времени компиляции, который является базовым классом для type, и expr
имеет тип среды выполнения, который является type или производным от type. Тип времени
компиляции переменной представляет собой тип переменной, заданный в ее определении
типа. Тип среды выполнения переменной представляет собой тип экземпляра, назначенный
этой переменной.
expr является экземпляром типа, который реализует интерфейс type.
Если case имеет значение true, varname присваивается определенным образом и получает
локальную область в пределах раздела switch.
Обратите внимание на то, что null не соответствует типу. Для сопоставления null используйте
следующую метку case :
case null:
В следующем примере шаблон типа используется для предоставления сведений о различные видах
коллекций типов.
using System;
using System.Collections;