Можливі нововведення C#



Ви, мабуть, знаєте, що останнім часом 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 або пишіть коментарі тут.
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.