Одновременно с очередным обновлением Visual Studio 2017 (версия 15.7) стала доступна новая версия C# под номером 7.3. Посмотрим какие новые возможности для написания управляемого кода появились в ней. |
Оптимизация выбора перегруженных методов
В очередной раз улучшен механизм выбора подходящего метода из группы методов с перегрузками. При подготовке списка кандидатов для выбора конкретного метода используются следующие правила:
- Если вызов метода осуществляется при помощи экземпляра объекта или результат возвращается в экземпляр, то статические методы исключаются из списка кандидатов.
- и наоборот, исключаются методы экземпляра при отсутствии экземпляра объекта для вызова метода или в качестве получателя результата.
- При отсутствии получателя возвращаемого значения
- при вызове в статическом контексте включаются только статические методы,
- в противном случае и статические и методы экземпляра.
- Если существует двусмысленность выбора из-за типа, который принимает возвращаемое значение, то включаются и статические методы и методы экземпляра.
- при вызове метода в статическом контексте в список включаются только статические методы.
- в противном случае включаются и статические и методы экземпляра.
- Generic методы, у которых условия вызова не удовлетворяют наложенным в них ограничениям, исключаются из списка кандидатов.
- При преобразовании группы методов (например something.Where(MyOverloadedMethod)), из списка кандидатов удаляются методы, возвращаемый тип которых не совпадает с возвращаемым типом делегата.
Сравнение кортежей
C# 7.3 добавляет возможность сравнивать кортежи с помощью операций == и !=.
Пусть t1 и t2 это два экземпляра кортежа. Тогда:
Если тип кортежи обернуты в Nullable<T>, то добавляется сравнение свойств HasValue. Т.е. тогда t1 == t2 будет эквивалентно записи
t1.HasValue == t2.HasValue ? (t1.HasValue ? … : true) : false
Сравнение двух объектов может вернуть тип, отличный от bool (например если были определены пользовательские operator == и operator !=). В этом случае результат будет приведен к типу bool , а если это не возможно, то будет попытка вызвать operator bool для результата.
Обратите внимание, что сравнение идет с помощью свойств Item{n}. Определенные пользователем имена свойств роли не играют. Поэтому можно сравнивать кортежи с различными пользовательскими именами. Также в аналогичных позициях свойства двух кортежей могут быть различного типа, но для них должна быть определена операция сравнения. Например:
(float a, int b) t1 = (1.0f, 42);
(int c, double d) t2 = (1, 42);
Console.WriteLine(t1 == t2); // выведет "True"
Если сравнить значения не возможно, то это приведет к ошибке на этапе компиляции:
(float a, int b) t1 = (1.0f, 42);
(int c, string d) t2 = (1, "42");
Console.WriteLine(t1 == t2); // Ошибка компиляции: не определено == для string и int
Как следствие, есть опасность использования типа dynamic в сравнениях кортежей. Такое сравнение всегда скомпилируется, но во время исполнения может быть выброшено исключение RuntimeBinderException в зависимости от скрывающегося за dynamic реального типа.
Еще один интересный момент это наличие экземпляров объектов в составе кортежа. Их сравнение в аналогичных позициях Item{n} проходит по правилам:
- Если оба экземпляра равны null, то их сравнение считается успешным, даже если типы самих объектов различные.
- Если экземпляры не null, одного типа и определен operator ==, то для сравнения вызывается operator ==.
- Во всех остальных случаях происходит сравнение по ссылке.
Поддержка Enum, Delegate и unmanaged в качестве ограничений generic параметра
Список ограничителей generic параметров пополнился 3 новыми значениями: Enum, Delegate и unmanaged. Первые два из них разрешают использование в качестве generic только Enum и Delegate соответственно. Например:
public interface IProcessor
{
void Process<T>(T value)
where T : Enum;
}
public enum UserRole { Admin, User, Guest }
var role = UserRole.Admin;
processor.Process(role);
// или
processor.Process(UserRole.User);
processor.Process(new object ()); // ошибка компиляции
Обратите внимание, что необходимо указывать Enum и Delegate как имена типов, а не ключевые слова. Запись "where T : enum" выдаст ошибку на этапе компиляции.
Третьи вариант “unmanaged” накладывает следующие ограничения на тип:
- Это структура (struct), включая системные enum, int и т.д.
- Все ее поля должны соответствовать одному из следующих правил:
- Имеют один из указанных типов: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, IntPtr или UIntPtr.
- Являются перечислением (enum)
- Являются указателем
- Являются структурой, которая также соответствует ограничению unmanaged
Ограничение "unmanaged" не является ключевым словом и не может быть использовано вместе с struct, class или new().
Переменные выражений в инициализаторах и запросах
В C# 7.3 убраны ограничения на использование out переменных и переменных в шаблонах объявлений (например, case int t) в:
- Инициализаторах конструкторов. Переменная будет доступна в теле конструктора.
- Инициализаторах свойств и полей. Переменная будет доступна в самом выражении инициализатора.
- В LINQ запросах, если выражение транслируется в тело лямбды. Переменная будет доступна в теле выражения.
public class RootNode : NodeBase
{
private string _sVal = "3";
public int Value => int.TryParse(this._sVal, out int iVal) ? iVal : 0;
public RootNode(string sValue)
: base(int.TryParse(sValue, out int value))
{
var source = new string[] { "1", "b", "3" };
var query = from s in source
where int.TryParse(s, out var iValue) && iValue != value
select s;
}
}
Конструкция [field] для автоматически реализованных свойств
Теперь для полей, сгенерированных компилятором для автоматически реализованных свойств, можно указать атрибуты с помощью конструкции [field <Attribute>]. Например:
[Serializable]
public class User {
…
[field: NonSerialized]
public string TemoraryComment { get; set; }
}
Данный код будет преобразован в следующий:
[Serializable]
public class User {
[NonSerialized]
private string TemoraryComment_backingField;
…
public string TemoraryComment {
get { return TemoraryComment_backingField; }
set { TemoraryComment_backingField = value; }
}
}