Вы должно быть знаете, что в последнее время Microsoft старается общаться с сообществом, получать как можно больше отзывов и внедрять их в жизнь. Не обошла эта мода и команду разработчиков C#. Разработка новой версии языка определенно не идет за закрытыми дверями…
Статья Mads Torgersen под названием What’s New in C# 7.0 уже разобрана вдоль и поперек. Но есть что-то, что в ней не было упомянуто.
Предлагаю вам пройтись обзором по нескольким предложениям из репозитория Roslyn. Название темы C# 7 Work List of Features. Информация обновлялась пару месяцев назад (то есть она не сильно актуальная), но это то, что определенно было не обделено вниманием Mads Torgersen.
Даже дважды, в рубриках Strong interest и Non-language упомянуты следующие предложения:
1. Enable code generating extensions to the compiler #5561
2. Add supersede modifier to enable more tool generated code scenarios #5292
Фактически, это предложение добавления в C# некоторого функционала аспектно-ориентированного программирования. Я постараюсь передать в сокращенном виде суть.
Добавление генерирующего код расширения для компилятора
Хотелось бы заметить, что на github это предложение помечено как находящееся в стадии разработки. Оно предлагает избавится от повторяющегося кода с помощью Code Injectors. При компиляции определенный код будет добавлен компилятором автоматически. Что важно: исходный код не будет изменен, а также внедрение кода будет происходить не после компиляции в бинарный файл.
В описании указано, что процесс написания инжектора будет похож на написание diagnostic analyzer. Я же надеюсь, что все будет гораздо проще (в таком случае классы можно будет создавать и редактировать вручную). Но, возможно, что это будет удобно делать только с помощью инструментов, которые генерируют код автоматически.
В качестве примера приведен класс, который в свою очередь внедряет в каждый класс проекта новое поле, — константу типа string с именем ClassName и значением, содержащим имя этого класса.
[CodeInjector(LanguageNames.CSharp)]
public class MyInjector : CodeInjector
{
public override void Initialize(InitializationContext context)
{
context.RegisterSymbolAction(InjectCodeForSymbol);
}
public void InjectCodeForSymbol(SymbolInjectionContext context)
{
if (context.Symbol.TypeKind == TypeKind.Class)
{
context.AddCompilationUnit($@"partial class {context.Symbol.Name}
{{ public const string ClassName = ""{context.Symbol.Name}""; }}");
}
}
}
Инжекторы кода изначально будут иметь возможность только добавить какой-нибудь код, но не изменить существующий. С их помощью можно будет избавиться от такой шаблонной логики, как, например, реализация INotifyPropertyChanged.
А как можно ускорить реализацию INotifyPropertyChanged сейчас? АОП фреймворк PostSharp фактически сделает это самое внедрение Walkthrough: Automatically Implementing INotifyPropertyChanged
В этом случае код реализации будет скрыт. Не ошибусь, если напишу, что некоторые изменения PostSharp совершает уже в после компиляции в коде IL (How Does PostSharp Work). Доподлинно известно, что эта утилита довольно продвинутая в плане изменения IL кода.
Но если компилятор будет иметь возможность распознать атрибут и добавить код самостоятельно до или во время компиляции, то это должно лучше сказаться на производительности и добавит возможность кастомизации.
Вот так выглядит простой класс, реализующий интерфейс INotifyPropertyChanged. Фактически в нем только одно свойство Name, но очень много лишнего кода реализации:
class Employee: INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged();
}
}
private void RaisePropertyChanged([CallerMemberName] string caller="")
{
if( PropertyChanged != null )
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
}
Все это можно будет заменить таким небольшим классом:
[INoifyPropertyChanged]
public class Employee
{
public string Name { get; set; }
}
Или, может быть, можно будет придумать свою конвенцию наименований и все классы в названии которых будет INPC будут реализовывать INoifyPropertyChanged. Получилось бы еще короче:
public class EmployeeINPC
{
public string Name { get; set; }
}
Но это я уже немного дал волю фантазии.
Superseding members — замещение модификатора для того, чтобы дать больше возможностей инструментам генерации кода
Эта фича разрешает членам класса переопределять члены этого же класса, замещая декларацию класса новой декларацией, используя модификатор supersede.
На примере должно быть понятнее, чем на словах:
// написанный пользователем код
public partial class MyClass
{
public void Method1()
{
// что-то чудесное здесь происходит
}
}
// код сгенерированный инструментом
public partial class MyClass
{
public supersede void Method1()
{
Console.WriteLine("entered Method1");
superseded();
Consolel.WriteLine("exited Method1");
}
}
В данном случае Method1, который был сгенерирован инструментом замещает Method1, который был написан пользователем. Все вызовы Method1 будут вызывать код созданный инструментом. Код, который создан инструментом может вызывать пользовательский код с помощью ключевого слова superseded. То есть, выходит, что в данном примере мы имеем автоматическое добавление логов в Method1.
Используя эту технику, реализация INotifyPropertyChanged может получиться такой вот:
// написанный пользователем код
public partial class MyClass
{
public int Property { get; set; }
}
// код сгенерированный инструментом
public partial class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public supersede int Property
{
get { return superseded; }
set
{
if (superseded != value)
{
superseded = value;
var ev = this.PropertyChanged;
if (ev != null) ev(this, new PropertyChangedEventArgs("Property"));
}
}
}
}
Под ключевым словом superseded здесь уже фигурирует не метод, а свойство.
Асинхронный Main
Это предложение также помечено на github, как находящееся в стадии разработки. Внутри метода Main будет разрешено использование await. Делается это по причине того, что есть множество программ, внутри которых содержится подобная конструкция:
static async Task DoWork() {
await ...
await ...
}
static void Main() {
DoWork().GetAwaiter().GetResult();
}
В данном случае возможно прерывание работы программы по причине возникновения исключения. В случае же использования DoWork().Wait() любая возникшая ошибка будет преобразована в AggregateException.
Так как CLR не поддерживает асинхронные методы в качестве точек входа, то компилятор сгенерирует искусственный main метод, который будет вызывать пользовательский async Main.
Разработчикам зачастую требуется этот функционал и при реализации вручную они могут совершать ошибки. С этой точки зрения добавление этой фичи очевидный плюс. Минусом является то, что неопытные разработчики могут использовать асинхронность и там где нужно и там где ненужно.
Не факт, что эти фичи появятся в C# 7.0, и даже не факт, что они появятся в 8.0, но они определенно рассматривались командой разработчиков языка как потенциально интересные.
В анонсе Visual Studio 2017 упоминается еще одно предложение Task-like return types for async methods, которое точно будет в 7-ой версии языка.
Присоединяйтесь к обсуждению на GitHub или пишите комментарии здесь.
Автор: asommer