Возможные нововведения C#

b6dbc05c05bc440a85b998db0d5be001.jpg

Вы должно быть знаете, что в последнее время 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 или пишите комментарии здесь.

Комментарии (25)

  • 5 декабря 2016 в 15:45

    +1

    Разработка новой версии языка определенно не идет за закрытыми дверями…

    Так в чем вопрос, надо просто открыть двери
  • 5 декабря 2016 в 15:48

    –5

    Лично я бы проголосовал за

    1 Множественное наследование.
    2 virtual static function

    • 5 декабря 2016 в 15:59

      0

      Эти фичи требуют поддержки не только компилятора, но и самого рантайма.

      P.S. Я лично против множественного наследования в том виде, как это сделано в C++. Если делать что-то подобное, то лучше реализовать систему mixin’ов.

    • 5 декабря 2016 в 15:59

      0

      Нет. Множественное наследование с успехом заменяется интерфейсами, но при этом не привносит связанных с множественным наследованием проблем. А понятия виртуальной статической функции не существует в природе.
      • 5 декабря 2016 в 16:10

        0

        >А понятия виртуальной статической функции не существует в природе.

        Как же не существует, если мы ее обсуждаем?

        И не вижу причин, по которым невозможно, это реализовать в C#. (это конечно не значит, что таких причин не существует)

        • 5 декабря 2016 в 16:32

          +2

          Можно ссылку на описание понятия виртуальной статической функции?
          • 5 декабря 2016 в 20:06

            –1

            Понятие «виртуальная статическая функция», по моему интуитивно понятно, это статическая функция которую можно наследовать.
            • 5 декабря 2016 в 20:18

              +3

              Поясните на примере, мне очень интересно, что вы имеете в виду. Поначалу я подумал, что вы имели в виду статический полиморфизм (шаблоны C++), но слово «static» однозначно даёт понять, что вы имеете в виду что-то непонятное.

              Слово «виртуальная» означает, что:
               — функция может быть переопределена в производных классах.
               — конкретная реализация функции определяется в момент исполнения программы в зависимости от объекта, у которого она вызывается.

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

            • 6 декабря 2016 в 02:19

              0

              А как можно наследовать функцию, подскажите?
  • 5 декабря 2016 в 16:01 (комментарий был изменён)

    0

    Кстати удобно для ViewModel удобно использовать PropertyChanged.Fody Сокращаем объем кода с помощью Fody
  • 5 декабря 2016 в 16:02

    0

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

    Почему я в этом случае могу определить метод-расширение Add, не не могу — operator +? Почему нельзя сделать так, чтобы при ненайденном операторе + автоматически искался бы метод-расширение Add?

    • 5 декабря 2016 в 18:40

      0

      Скорее всего, это вопрос затрат и удобочитаемости. Тем более, что это можно уже сделать с помощью интерфейсов.

      А вот generic constraint для числовых типов был бы очень кстати. Так можно было бы писать обобщенные арифметические функции без извращений типа dynamic:

      public T Add(T a, T b)
      where T: numeric // например, так
      {
          return a + b;
      }
      
      • 5 декабря 2016 в 18:42

        0

        Для этого нужен не T: numeric, а T: has (T + T). И в F#, кстати, такое есть.

        • 5 декабря 2016 в 19:06

          0

          С точки зрения рантайма обобщенной операции сложения не существует, поэтому F# использует какой-то хитрый ход. Не уверен, но скорее всего типы разрешаются в момент компиляции, т.е. создается не честная generic-функция, а по конкретной реализации на каждый использованный при вызове этой функции тип T. Либо под капотом dynamic, хотя очень сомневаюсь.

          После работы с Typescript вообще начинает не хватать структурной типизации. Но сомневаюсь, что ее приделают в C# в каком-либо обозримом будущем.

          • 6 декабря 2016 в 02:24

            0

            Обобщенного numeric в рантайме тоже нет. А уж если мечтать, то ограничения по методам намного удобнее.

      • 5 декабря 2016 в 19:01

        0

        Скорее всего, это вопрос затрат и удобочитаемости. Тем более, что это можно уже сделать с помощью интерфейсов.

        И чем же a.Add (b.Multiply©) лучше a + b * c?
        • 5 декабря 2016 в 19:10

          0

          Написал идею:
          https://visualstudio.uservoice.com/forums/121579-visual-studio-ide/suggestions/17335231-extend-operator-overloading
        • 5 декабря 2016 в 19:16

          0

          Например тем, что можно перейти к реализации метода с помощью ctrl+click и сразу узнать, что именно он делает. А к телу перегруженного оператора, насколько я знаю, так просто не перейдешь.

          Вообще дизайн стандартной библиотеки C# склоняется к методам и интерфейсам. Если хотите примеров того, насколько неудобочитаемым становится код при активном использовании (кастомных) операторов — вот, например, когда-то мы писали парсер на F#…

          • 5 декабря 2016 в 19:58

            0

            Точно что там происходит я не скажу, но в общем понятно, такие API для создания парсеров довольно распространенная штука.
          • 5 декабря 2016 в 20:20

            0

            Как это? Подвёл курсор к плюсику, нажал на F12 и перешёл к оператору.
  • 5 декабря 2016 в 18:08

    0

    CodeInjector выглядит круто — Никогда не понимал этот гемморой с INotifyPropertyChanged, а тут вполне карасивое решение.

    Ещё мне очень нехватает строковых Enum-ов. Простая вещь, а сделает код более красивым и удобным в написании.

  • 5 декабря 2016 в 18:55

    0

    Голосуйте за свои предпочтения в развитии C# (там же ссылки на другие проекты от MS)

    https://visualstudio.uservoice.com/forums/121579-visual-studio-ide/category/30931-languages-c

  • 5 декабря 2016 в 20:24 (комментарий был изменён)

    –1

    Асинхронный Main

    В C# очень неудачная реализация async/await, идея хорошая, но перемудрили конкретно. Поэтому в Main всё это не работает, хотя и должно по задумке. Весь интернет пестрит непонятками.

    Что касается генерации кода это прекрасно, однако как я могу судить — снова перемудрили и теперь нужно будет при отладке большого проекта ещё и искать «кто же включает очередную функцию в этот класс». Самая верная реализация генерации это у Хаскеля.

    • 5 декабря 2016 в 20:43

      0

      А мне, наоборот, реализация асинхронности в C# очень нравится, а конкретно генерация state machine из асинхронных функций — руками их писать очень и очень тяжело. Альтернативой ей является использование более сложных в отладке fibers (например, coroutines в C++).

      А неудачной я считаю реализацию планировщика. Главная прелесть асинхронности — выполнение всех задач в одном потоке, что существенно снижает расходы на межпотоковую синхронизацию и переключение между ними.
      Но в C# используется либо ThreadPool (по умолчанию), либо циклы сообщений WinForms/WPF. Всё это не даёт большого выигрыша в производительности от использования асинхронности, а при низкой гранулярности задач производительность ещё и падает.

      Как итог — в своих проектах я использую собственный планировщик и собственные обёртки над сокетами. Заодно получаю полноценную кроссплатформенность — при запуске под Windows используется IOCP, под Linux — epoll.

  • 6 декабря 2016 в 00:08 (комментарий был изменён)

    0

    Не совсем понятна идея добавления таких вещей, как возможность писать код в виде строк и докомпилировать его или вклиниваться в вызов метода и делать что-то с ним такое, что уже сделать нельзя используя полиморфизм, переопределение в языке каким изначально задумывался C#(строготипизированным и со статической типизацией).
    Сейчас там же можно вызвать наследуемый PropertyChanged метод без дубликтов логики или применить логирование через action filter в крайнем случае dynamic proxy, благо для AOP в дотнете инструментария уже очень много.
    С трудом вериться с учетом сопровождение этого кода, тестируемости, переносимость, использование АПИ привязанного к конкретному комплиятору, оно реально удобней — кроме что разве красивого и на первый взгял гибкого функционала.

© Habrahabr.ru