Поиск по сайту:

8 замечательных возможностей C#, о которых вы должны знать


C# и .NET последовательно активно разрабатывались в течение последних двух десятилетий; каждый год язык получает набор новых полезных функций. Мы обсудим некоторые из наших фаворитов, о которых, по нашему мнению, должны знать новички.

Обнуляемые ссылочные типы

В C# некоторое время существовали типы value, допускающие значение NULL, например  int? , который может содержать либо int , либо значение . null, в отличие от традиционного int, значение которого по умолчанию всегда равно нулю. Они полезны для многих вещей, включая классы, предназначенные для десериализации JSON, где могут присутствовать не все поля.

Однако ссылочным типам всегда можно было присвоить значение null , так в чем смысл этой новой функции из C# 8.0?

Ссылочные типы, допускающие значение NULL, в основном обеспечивают различие между ссылочными переменными, которые могут стать нулевыми, и ссылочными переменными, которые не могут. Это критическая функция, которая, скорее всего, оставит в вашей кодовой базе множество предупреждений, поэтому вам придется включать ее вручную. Как только он включен, компилятор начинает различать:

  • строка?, которая может быть нулевой и сохраняет поведение «по умолчанию» из более ранних версий, и
  • строка, которая не может быть нулевой. Он никогда не может быть нулевым, потому что ему должно быть присвоено значение по умолчанию, и его нельзя установить равным нулю.

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

Хотя это может показаться ограничивающим, как и статическая типизация, в конечном итоге это приводит к созданию более качественного и удобного кода. Мы настоятельно рекомендуем включить его для большинства проектов, и вы можете прочитать наше полное руководство по ним, чтобы узнать больше.

Чтобы включить его, вам нужно отредактировать файл проекта. В Visual Studio щелкните правой кнопкой мыши свой проект и выберите «Редактировать файл проекта». Затем включите его с помощью следующей директивы:

<Nullable>enable</Nullable>

Если вы используете устаревший формат проекта, вам может потребоваться вручную переопределить это с помощью директивы в начале каждого файла:

#nullable enable

Нулевые условные и нулевые операторы объединения

Вместо того, чтобы проверять if(something == null), в C# есть отличное сокращение с операторами доступа к членам с условием null. По сути, вместо того, чтобы использовать точку для доступа к чему-то, что может быть нулевым, вы можете использовать вопросительный знак и точку, что автоматически выполнит проверку на нулевое значение.

Вы также можете использовать их для вызова методов нулевых объектов или доступа к индексам нулевых массивов. Если объект оказывается нулевым, он просто ничего не делает и возвращает нулевое значение.

reference?.field
reference?.method();
reference?[N]

Обратите внимание, что последний не предотвращает исключение IndexOutOfRangeException — он просто обращается к N-му элементу возможно нулевого списка.

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

string value = GetValue() ?? "Backup"

Существует также оператор ??= , который действует подобно || в том смысле, что он не будет оценивать резервное значение, если первое значение возвращает правильный результат.

Кортежи

Вы когда-нибудь хотели вернуть несколько значений из метода? С кортежами это возможно, и современный C# имеет отличную языковую поддержку для них, начиная с C# 7.0. Просто верните два значения, заключенные в круглые скобки и разделенные запятыми, и вы сможете получить доступ к отдельным элементам внутри них.

Хотя это и не требуется, общепринятой практикой является присвоение этих имен, таких как (float X, float Y, float Z), а не доступ к ним по номерам элементов.

Вы также можете использовать деконструкцию кортежа, чтобы разбить кортеж на несколько переменных-компонентов.

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

Конструктор перегружается с помощью: this()

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

Быстрое и грязное исправление состоит в том, чтобы совместно использовать метод «инициализации класса», который вызывается из всех перегруженных методов конструктора, но если у вас включены ссылочные типы, допускающие значение NULL, вы получите предупреждения об отсутствии значений для полей, не допускающих значения NULL, которые фактически устанавливаются, поскольку компилятор недостаточно умен, чтобы понять инициализацию в нечистых вызовах функций.

Но для этого есть исправление, и это немного странно. Это связано с наследованием конструктора — еще одной замечательной функцией, позволяющей расширять возможности конструктора базового класса. Он использует тот же синтаксис наследования, двоеточие, за которым следует base (параметры). Это автоматически вызовет базовый конструктор (перед новым). Обратите внимание, что вам все еще нужно поместить параметры базового конструктора в определение метода.

Самое интересное, что вам не нужно использовать base; на самом деле вы можете сделать то же самое с : this(), который вызовет конструктор внутри самого класса. Вы можете использовать это для указания дополнительных параметров без копирования кода инициализации.

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

Статические конструкторы

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

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

Однако они точно не запускаются при запуске. Хотя приведенный выше пример выглядит правильно, установка startupTime в статическом конструкторе не гарантируется во время выполнения, поскольку C# и MSIL, на котором он выполняется, являются языками компиляции Just-In-Time.

Только JIT-компиляция происходит, ну, как раз вовремя, именно тогда, когда класс нужен. Это означает, что класс будет сидеть в своем углу сборки, собирая пыль до тех пор, пока не понадобится одно из его полей или методов. Как только он понадобится, среда выполнения .NET сотрет с него пыль, скомпилирует и только потом вызовет статический конструктор.

Однако статический конструктор по-прежнему запускается перед чем-либо, даже до настройки статических полей и до того, как на что-либо можно будет сослаться. Они по-прежнему весьма полезны, когда они вам нужны. Кроме того, вы можете вызвать метод инициализации из процедуры запуска вашего приложения, если вам нужно запустить что-то в хронологическом порядке.

Общие параметры типа

Вы определенно сталкивались с ними раньше, хотя, возможно, вы не писали их сами. Параметры универсального типа позволяют вам писать функции, которые не зависят от типа и не заботятся о том, какой тип им передается. Основным примером этого являются коллекции; List и List используют один и тот же код, но получают разные параметры универсального типа.

Дженерики довольно легко использовать самостоятельно. Просто добавьте имя для переменной типа в скобках в определении класса или метода. Общепринятой практикой является использование T или, по крайней мере, имен, начинающихся с T. Например, словарь может иметь TKey и TValue, два разных типа.

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

Универсальные типы создают несколько разных «типов» универсального класса. Это означает, что статические поля будут разделены в зависимости от типа класса, поэтому List не имеет общих данных с List.

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

Делегаты

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

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

Затем вы можете назначить функцию этой переменной, а затем использовать переменную для вызова функции. Вы можете сделать это напрямую, как показано, или использовать myDelegate.Invoke(), который делает то же самое, но более подробно.

Вы можете узнать больше о делегатах в нашем руководстве по их использованию.

Индексаторы

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

Вы можете сделать то же самое для индексации, например. класс[индекс]. Это можно использовать для создания пользовательского поведения индексации. Например, вы можете создать двумерный список из одномерного List, создав собственный индексатор, который возвращает значение на основе входных аргументов.