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

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


  1. fishca
    05.12.2016 15:45
    +1

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

    Так в чем вопрос, надо просто открыть двери


  1. Durimar123
    05.12.2016 15:48
    -13

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

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


    1. impwx
      05.12.2016 15:59
      +1

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

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


    1. DistortNeo
      05.12.2016 15:59
      +4

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


      1. Durimar123
        05.12.2016 16:10

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

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

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


        1. Sirikid
          05.12.2016 16:32
          +4

          Можно ссылку на описание понятия виртуальной статической функции?


          1. Durimar123
            05.12.2016 20:06
            -6

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


            1. DistortNeo
              05.12.2016 20:18
              +8

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

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

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


              1. ApeCoder
                06.12.2016 08:31
                -1

                Наверное, как объектно-ориентированных языках, класс, это тоже такой объект.


                типа


                abstract class Animal{
                    abstract virtual static string GetDescription();
                }
                class Dog{
                    static string GetDescription()
                    {
                        return "собака";
                      }
                }
                
                var animalClasses = new []{Dog, Cat, Squirell};
                Animal CreateByDescription(string description)
                {
                     return animalClasses
                          .Where(x => x.GetDescription() == descriuption)
                          .First()
                          .CreateNewInstance();
                }


                1. Sirikid
                  06.12.2016 11:39
                  -1

                  В C# класс это не объект, у классов есть представление в виде объекта, но это не они сами.


                  1. ApeCoder
                    06.12.2016 13:25
                    +1

                    Речь идет об изменении языка => можно ввести такое


                    1. lair
                      06.12.2016 13:39

                      Для этого вам придется не только язык поменять, но и солидный кусок CLR.


                      1. ApeCoder
                        06.12.2016 13:54

                        Не факт. Можно какой-нибудь трюк типа автобоксинга сделать. При приведении к типу MetaClass генерировать какой-нибудь класс с членами статическими методами.


                        1. lair
                          06.12.2016 13:55

                          … которые хранить где?


                          1. ApeCoder
                            06.12.2016 15:14

                            Не знаю. Может быть в сборке с классом. Тогда, наверное, надо его генерить по атрибуту а не при приведении. Вы лучше меня знаете дотнет, придумайте сами :)


                            1. lair
                              06.12.2016 15:23

                              Я просто не могу придумать никакого сценария, при котором я обращаюсь именно к статическому методу в терминах CLR (т.е. <typename>.<method>) и при этом у меня работает subclass polymorphism.


                              Есть дженерики, конечно, но там на момент развертывания известен точный тип.


                              1. mayorovp
                                06.12.2016 16:59

                                SuperType.classType t = SubType.class;
                                t.StaticVirtualMethod();

                                Мне такая идея тоже не нравится — но это не значит что она нереализуема.


                                1. Deosis
                                  07.12.2016 07:55

                                  AnimalFactory[] factories = new[]{ new CatFactory(), new DogFactory(), new SquirellFactory(), };
                                  Animal CreateByDescription(string description)
                                  {
                                       return factories 
                                            .Where(x => x.Description == description)
                                            .First()
                                            .Create();
                                  }
                                  
                                  Статические виртуальные методы можно имитировать с помощью фабрик.
                                  Как бонус мы получаем состояние, (почти) не используя статических полей. Также возможность иметь несколько настраиваемых фабрик и передавать их параметром. И отдельную фабрику для тестов.


                                1. lair
                                  07.12.2016 11:24

                                  Ну оно больше и не выглядит как статический метода, а выглядит как инстанс. Я об этом и говорю.


                1. AnarchyMob
                  07.12.2016 20:13

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


                  1. Krypt
                    07.12.2016 23:04

                    Не соглашусь. Знаю пару случаев использования C# как скриптового языка (с помощью Roslyn). И рефлексия там запрещена из соображений безопасности.


              1. Krypt
                06.12.2016 10:02

                Ну, кстати, статические методы в интерфейсах были бы крайне полезны. Как пример использования — десериализация.

                interface IDeserealizable
                {
                static IDeserealizable deserialize(IDeserializer deserializer)
                }

                А ещё:
                — указание нескольких интерфейсов в качестве типа поля
                — реализация интерфейсов через расширение

                Собственно, это фичи Objective-C.


                1. Krypt
                  06.12.2016 10:16

                  Как, собственно, и «виртуальные статические функции». В том плане, что статические функции в ObjC перегружать можно.

                  В случае с C# — не знаю, никогда не пытался :) Но сама затея в C# на данный момент выглядит безнадёжной по той причине, что для статических методов почти нет фич, где бы их можно было использовать.


              1. Durimar123
                06.12.2016 11:40

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

                Для статической функции объект совсем не обязателен, вполне хватит и самого класса.

                Похоже, что действительно возникло недопонимание в определении, что такое virtual static, даю пример

                class A
                {
                public virtual static string info() {return «a»;}
                }

                class A2:A
                {
                public virtual static string info() {return «a2»;}
                }


                1. nightwolf_du
                  06.12.2016 13:46
                  +2

                  А зачем?
                  Вне зависимости от того, что именно вы хотите сделать — это можно сделать через сокрытие (new на функции)

                  class A {
                     public static string info() {return "a";}
                  }
                  
                  class A2:A {
                      public new static string info() {return "a2";}
                  }
                  


                  С точки зрения языка, виртуальная статическая функция — бессмысленна, виртуальные методы нужны для маршрутизации с вызовом типа, который на самом деле лежит под базовым типом/интерфесом.
                  При вызове статической функции — тип строго определен, маршрутизация типов не нужна.


                  1. Durimar123
                    06.12.2016 16:50

                    Понял, почему народ так удивлялся — C# просто не позволяет вызвать через экземпляра класса статическую функцию этого класса.
                    Тогда естественно виртуальность статик функции теряет смысл.

                    Так что появилось еще одно пожелание:
                    Возможность вызова статик функции через экземпляра класса.


                    1. nightwolf_du
                      06.12.2016 17:06

                      Тогда чем статическая функция будет отличаться от обычной?

                      Сейчас модификатор static — принадлежность функции типу целиком, а не экземпляру.

                      Она не должна знать ничего о «нестатических» полях/методах/св-вах.


                      1. Durimar123
                        06.12.2016 17:12

                        Я чуть о другом, вот такой код не работает:

                        class A {
                        public static string info() {return «a»;}
                        }

                        A a = new A();
                        string s = a.info(); < — тут баг


                        1. DistortNeo
                          06.12.2016 17:27

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

                          Идея выглядит разумной. Но при этом не рассматривается вопрос вызова статической функции не по объекту, а по типу (generic).


                          1. Sirikid
                            06.12.2016 17:37

                            Не обязательно в VMT, в Java вызовы статических методов разрешаються во время компиляции, поэтому можно вызывать статические методы даже через null приведенный к необходимому типу (да, статические методы можно вызывать через объекты, но это бесполезно и порицается), возможно отчасти поэтому у нас и нету нормальных дженериков, но в C# дженерики другие и к ним прикрутить «виртуальные статические» функции скорее всего будет проще.


                          1. Durimar123
                            06.12.2016 17:56

                            >Но при этом не рассматривается вопрос вызова статической функции не по объекту, а по типу (generic).

                            Почему же?
                            Для A a; одинаково должно вызываться и

                            A.info();
                            и
                            a.info();


                            1. DistortNeo
                              06.12.2016 18:27
                              +1

                              Это почему же?

                              A a = new B();

                              A.info() — вызов статического метода класса A
                              a.info() — вполне может быть вызовом метода info класса B


                              1. Durimar123
                                06.12.2016 18:37
                                +1

                                Конечно! В этом весь смысл.

                                В прочем я нашел способ решить эту проблему с помощью reflection, но решение мне не нравится.

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


                                1. qw1
                                  08.12.2016 21:18

                                  Можно же просто добавить обычный виртуальный метод объекту, и из него вызвать статический метод.

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


                    1. lair
                      07.12.2016 11:25
                      +1

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

                      Но зачем?


                      1. Durimar123
                        07.12.2016 11:45

                        Как минимум, наличие возможности лучшее ее отсутствия. Тем более возможности которая никак не изменяет ни синтаксис, не идеологию.

                        А так это дает более объектно ориентированный доступ к параметрам и функциям которые меняются при наследовании, но не нуждаются для расчетов в самом объекте.

                        Это хорошо проявляется в задачах типа фабрика объектов.


                        1. lair
                          07.12.2016 11:47

                          Как минимум, наличие возможности лучшее ее отсутствия.

                          Нет. Слишком много возможностей нарушает целостность.


                          А так это дает более объектно ориентированный доступ к параметрам и функциям которые меняются при наследовании, но не нуждаются для расчетов в самом объекте.

                          Если они не нуждаются для расчетов в объекте — это не функции этого объекта. И именно поэтому синтаксис объект.метод по отношению к ним будет вводить в заблуждение.


                          1. Durimar123
                            07.12.2016 12:07
                            -5

                            >Нет. Слишком много возможностей нарушает целостность.

                            Я так понимаю вы поклонник асемблера и противник библиотек и объектного программирования?

                            > Если они не нуждаются для расчетов в объекте — это не функции этого объекта.

                            Конечно — это функция класса. Но для того, что бы она вызвалась у объекта именно того класса которым он является, ее нужно вызывать непосредственно у этого экземпляра класса. Пояснить примером?


                            1. lair
                              07.12.2016 12:19
                              +1

                              Я так понимаю вы поклонник асемблера и противник библиотек и объектного программирования?

                              Нет, я поклонник консистентных систем и противник избыточности.


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

                              Нет такого "нужно". Если вам нужна функция класса, вам нужно вызывать ее на классе. Типичный пример такого (на псевдоязычке) выглядел бы так:


                              yourObject -> getType -> someMethod


                              1. Durimar123
                                07.12.2016 12:42
                                -2

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

                                >yourObject -> getType -> someMethod

                                Именно! Но зачем «getType ->» если его нужно писать всегда? Можно и пропустить двузначностей это не принесет.
                                Тем более «нормального» getType к сожалению нет :( и вроде делать его не собираются.

                                Или вас возмущает, что вызов функций у объекта и через ссылку на объект идет через "."?

                                Тогда да, тогда я понимаю вашу принципиальность.


                                1. lair
                                  07.12.2016 12:49
                                  +1

                                  Но зачем «getType ->» если его нужно писать всегда?

                                  Не всегда, а только тогда, когда обращение идет к методам класса, а не объекта. И именно за этим и нужно — чтобы четко видеть, к какому объекту мы обращаемся.


                                  Можно и пропустить двузначностей это не принесет.

                                  Да ну? yourObject -> Name — имя чего я сейчас получу? yourObject -> setMaxCount(15) — у чего ограничился пул?


                                  Тем более «нормального» getType к сожалению нет

                                  Так вот лучше сделать нормальный getType, который не нарушает парадигму, чем вводить неявный вызов статиков, который парадигму нарушает.


                                  1. Durimar123
                                    07.12.2016 12:58

                                    >Да ну? yourObject -> Name — имя чего я сейчас получу?

                                    Что функция вернет то и будет. Я имел в виду неоднозначностей для компилятора.

                                    >Так вот лучше сделать нормальный getType

                                    Это бесспорно «нормальный getType» без рефлектинга по мне тоже куда важнее. Хотя и проблем вызовом статиков я не вижу.

                                    Похоже я больше склонен к универсальности и общему синтаксису, а вы за строгость и явные разграничения.


                                    1. DistortNeo
                                      07.12.2016 13:03
                                      +2

                                      вы за строгость и явные разграничения.

                                      И это правильно. Чтобы стрелять в ноги, у нас уже есть C++, оставьте C# в покое.


                                      1. Durimar123
                                        07.12.2016 13:19

                                        >И это правильно. Чтобы стрелять в ноги, у нас уже есть C++, оставьте C# в покое.

                                        :) Расскажите мистер паладин, чем именно ужасен вызов статика из объекта?


                                        1. INC_R
                                          07.12.2016 13:33
                                          -2

                                          Например тем, что объект может оказаться null. Ловить NullReferenceException на статических методах — не очень большая радость. И вообще тогда какие отличия от экземплярных методов, кроме потери доступа к this в теле метода?


                                          1. Durimar123
                                            07.12.2016 13:48
                                            -4

                                            :) Вызов любой функции может дать NullReferenceException.
                                            А самое забавное, что вызов статической функции НИКАК не может вызвать NullReferenceException.

                                            >И вообще тогда какие отличия от экземплярных методов, кроме потери доступа к this в теле метода?

                                            В том что их можно вызвать и без наличия экземпляра класса.


                                            1. INC_R
                                              07.12.2016 13:55

                                              >> Вызов любой функции может дать NullReferenceException.
                                              >> А самое забавное, что вызов статической функции НИКАК не может вызвать NullReferenceException.
                                              Вы противоречите сами себе.

                                              class A { public static virtual info() {...}; }
                                              class B: A { public static override info() {...}; }

                                              A a = null;
                                              a.info(); // что произойдет по-вашему?


                                              1. Durimar123
                                                07.12.2016 14:14

                                                Отработает функция, она же обращаться к полям не будет.


                                                1. INC_R
                                                  07.12.2016 14:21

                                                  Какая? Класса A или класса B?
                                                  Если речь о том, чтобы сделать две одинаково работающих возможности вызова статик метода: A.info() и a.info(), то действительно работать будет. Только непонятно, зачем.
                                                  Но о виртуальных статических методах можно забыть из-за возможного null.


                                                  1. Durimar123
                                                    07.12.2016 15:36

                                                    >Какая? Класса A или класса B?

                                                    Конечно класса А.
                                                    А если (B(null)).info(); то от B; Что вас смущает?

                                                    >Но о виртуальных статических методах можно забыть из-за возможного null.

                                                    Откуда null, в статик функции? Даже у виртуальной.


                                                    1. INC_R
                                                      07.12.2016 15:45
                                                      +1

                                                      >> А если (B(null)).info(); то от B; Что вас смущает?
                                                      Здесь — ничего. А если так:

                                                      A a = new B();
                                                      a.info();
                                                      

                                                      Должен вызваться метод A.info() или B.info()?

                                                      >> Откуда null, в статик функции? Даже у виртуальной.
                                                      А как CLR должен в рантайме определить, виртуальную статическую функцию какого класса вызывать? Видимо, по экземпляру класса. Если он null, все плохо. Если не по экземпляру, то как еще?


                                                      1. Durimar123
                                                        07.12.2016 15:51
                                                        -1

                                                        >А если так: A a = new B(); a.info();

                                                        B.info() или есть сомнения?

                                                        >Если он null, все плохо

                                                        Все хорошо — тип объекта то есть. Вот по типу и вызовет.


                                                        1. INC_R
                                                          07.12.2016 15:59
                                                          +1

                                                          >> Все хорошо — тип объекта то есть. Вот по типу и вызовет.
                                                          Вот в этом и проблема. Типа объекта нет.
                                                          1. Во время компиляции конкретный тип объекта неизвестен.
                                                          2. Во время исполнения у вас, грубо говоря, в памяти 0 (т.е. null). Метаданные связываются с конкретным экземпляром класса, а не со ссылкой на него. Соответственно, по null ссылке вы никак не узнаете, на экземпляр какого типа она указывает.


                                                          1. Durimar123
                                                            07.12.2016 16:09
                                                            -5

                                                            Скорее всего у вас нет специфических знаний по вызовам виртуальных функций в рантайме.
                                                            Поэтому давайте решим так, если бы вы реализовывали вызовы виртуальных функций вы бы не смогли сделать вызов функций у null объектов.


                                                            1. INC_R
                                                              07.12.2016 16:14
                                                              +3

                                                              Скорее всего у вас нет специфических знаний по вызовам виртуальных функций в рантайме.

                                                              Так просветите, интересно же.
                                                              Мне вот кажется, что это вы не знаете, как CLR хранит ссылки на объекты и сами объекты. Если бы со ссылкой связывалась информация о типе объекта, тогда да, проблем нет. Но информации о типе у ссылки нет.


                                                              1. Durimar123
                                                                07.12.2016 16:51

                                                                Значит будет NullReferenceException, как и у обычных функций.


                                                                1. INC_R
                                                                  07.12.2016 16:59

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


                                                                  1. Durimar123
                                                                    07.12.2016 17:12

                                                                    В этом и есть смысл — единообразие вызовов статик методов и экземпляр методов. С возможностью вызова base метода.


                                                                    1. INC_R
                                                                      07.12.2016 17:22
                                                                      +1

                                                                      О каком единообразии может идти речь, когда это фундаментально разные вещи? Такое единообразие мозг сломает в момент. И еще раз, нельзя называть метод статическим, если он требует наличия созданного экземпляра класса для своего вызова.


                                                                      Можно вести речь о том, чтобы заиметь в языке что-то вроде "класса как объекта", и писать, например:


                                                                      class A { static virtual void foo() {...}}
                                                                      class B : A { static override void foo() {...}}
                                                                      
                                                                      Class<A> a = A;
                                                                      a.foo(); // вызывает A.foo()
                                                                      
                                                                      a = B;
                                                                      a.foo(); // вызывает B.foo()
                                                                      
                                                                      a = (new B())->class;
                                                                      a.foo(); // вызывает B.foo()

                                                                      Но вызывать статику на экземплярах — увольте.


                                                                      1. Durimar123
                                                                        07.12.2016 17:39

                                                                        Хорошо, например есть набор классов A1 A2 А3 и т.д. порожденные от А.
                                                                        у них общий набор типовых характеристик например
                                                                        static Name, Type, Width, Height, у каждого класса свои значения. И возможно они зависит от расчета в родителе.

                                                                        Эти данные используются как без объектов, так и внутри объектов.
                                                                        Как удобней писать? в месте каждого вызова A1.Width() или просто Width()?
                                                                        И сколько будет ошибок при copy/paste.

                                                                        А если бы у этих статик функций было наследование, то можно было бы общую часть их обработки вообще отдать родителю:

                                                                        Например классу А реализовать функцию
                                                                        static int Perimetr() {return (Width() + Height())*2;}
                                                                        Которая нормально работала бы для все наследников.


                                                                        1. INC_R
                                                                          07.12.2016 17:57

                                                                          у них общий набор типовых характеристик например
                                                                          static Name, Type, Width, Height, у каждого класса свои значения. И возможно они зависит от расчета в родителе.

                                                                          Почему бы не сделать какое-нибудь статическое свойство Meta, куда в статическом конструкторе положить экземпляр класса характеристик с нужными значениями, привязкой к базовым характеристикам и т.п.? Будет у вас A1.Meta.Width, например. Реализация чуть сложнее, но снаружи разницы особо нет. Можно добавить еще экземплярное свойство Meta, которое просто возвращает значение статического. Тогда и на экземплярах удобно будет звать статику.


                                                                          Как удобней писать? в месте каждого вызова A1.Width() или просто Width()?

                                                                          По мне, удобнее писать одни и те же вещи одинаково, и разные вещи по-разному. Именно поэтому для статики писать везде A1.Width лучше, чем где-то A1.Width, а где-то a.Width (который статический, но пишется почему-то как экземплярный).


                                                                          1. Durimar123
                                                                            07.12.2016 19:35

                                                                            Как-то сомнительно звучит, думаю не получится.
                                                                            class A
                                                                            {
                                                                            public static string inf;
                                                                            }

                                                                            class A2: A
                                                                            {
                                                                            }

                                                                            A.inf = «a»;
                                                                            A2.inf = «a2»;

                                                                            после такого в A.inf будет «a2».

                                                                            Или я не так понял идею?


                                                                            1. INC_R
                                                                              08.12.2016 04:26

                                                                              Немного не так:


                                                                              class A {   public static string inf; }
                                                                              class A2 : A {  public static new string inf; }
                                                                              
                                                                              A.inf = "a";
                                                                              A2.inf = "a2";

                                                                              Хотя решение не очень красивое, конечно.


                                                                              Если в более общем виде, то так
                                                                              using System;
                                                                              
                                                                              class Meta
                                                                              {
                                                                                  string _meta;
                                                                                  public Meta(string meta)
                                                                                  {
                                                                                      _meta = meta;
                                                                                  }
                                                                              
                                                                                  public override string ToString()
                                                                                  {
                                                                                      return _meta;
                                                                                  }
                                                                              }
                                                                              
                                                                              class A
                                                                              {
                                                                                  public static Meta meta;
                                                                              }
                                                                              
                                                                              class A1 : A
                                                                              {
                                                                                  public static new Meta meta;
                                                                              }
                                                                              
                                                                              class A2 : A
                                                                              {
                                                                                  public static new Meta meta;
                                                                              }
                                                                              
                                                                              public class Program
                                                                              {
                                                                                  public static void Main()
                                                                                  {
                                                                                      A1.meta = new Meta("a1");
                                                                                      A2.meta = new Meta("a2");
                                                                                      Console.WriteLine(A.meta);
                                                                                      Console.WriteLine(A1.meta);
                                                                                      Console.WriteLine(A2.meta);
                                                                                  }
                                                                              }


                                                                        1. DistortNeo
                                                                          07.12.2016 18:43

                                                                          Пожалуйста:

                                                                          interface IDerivedStatic
                                                                                      {
                                                                                          int Width();
                                                                                          int Height();
                                                                                      }
                                                                          
                                                                                      class A<T>
                                                                                          where T : struct, IDerivedStatic
                                                                                      {
                                                                                          static T data = new T();
                                                                          
                                                                                          public static int Width() => data.Width();
                                                                                          public static int Height() => data.Height();
                                                                                          public static int Perimetr() => Width() * Height();
                                                                                      }
                                                                          
                                                                                      class A1
                                                                                          : A<A1.Static>
                                                                                      {
                                                                                          public struct Static
                                                                                              : IDerivedStatic
                                                                                          {
                                                                                              public int Height() => A1.Height();
                                                                                              public int Width() => A1.Width();
                                                                                          }
                                                                          
                                                                                          static new int Width() => 12;
                                                                                          static new int Height() => 14;
                                                                                      }


                                                                          Использование struct заставляет JIT генерировать отдельный для каждого типа, а не вызывать интерфейс + нет оверхеда по памяти.

                                                                          Делал замеры — всё инлайнится, производительность идентична прямому вызовы.


                                                                          1. Durimar123
                                                                            07.12.2016 20:00

                                                                            Биг сенскс!

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


                                                                      1. DistortNeo
                                                                        07.12.2016 17:57

                                                                        Да-да, именно так. При взгляде на код должно быть чётко понятно, где вызывается метод объекта, а где — статическая функция. Именно поэтому нельзя смешивать этот синтаксис.

                                                                        Если над идеей статических виртуальных функции хорошо подумать, то может, что-то годное и выйдет. Одной из возможных реализаций этого механизма была бы следующая: GetType() возвращает не Type, а унаследованный от Type объект, содержащий вызовы статических методов. А для упрощения синтаксиса можно добавить ключевое слово какое-нибудь (a.class — см.выше).

                                                                        То есть

                                                                        class A
                                                                        {
                                                                            static virtual void foo() { ... }
                                                                        }


                                                                        разворачивается в

                                                                        class A.Static: Type
                                                                        {
                                                                            virtual void foo() { A.foo(); } }
                                                                        }
                                                                        
                                                                        class A
                                                                        {
                                                                            static void foo() { ... }
                                                                            A.Static GetType();
                                                                        }


                                                                        И используется так, например:

                                                                        void Generic<T>(T a) where T: A
                                                                        {
                                                                            a.GetType().foo();
                                                                        }
                                                                        
                                                                        void Generic<T>() where T: A
                                                                        {
                                                                            // typeof(T) возвращает объект T.Static, приведённый к A.Static
                                                                            // это можно сделать точно так же, как реализован механизм new T()
                                                                            typeof(T).foo();
                                                                        }


                                                                        В таком виде я был бы даже не против увидеть это в языке.


                                                                        1. Durimar123
                                                                          08.12.2016 16:32
                                                                          -2

                                                                          Еще раз спасибо за пример, натолкнул на некоторые идеи.

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

                                                                          class A
                                                                          {
                                                                          public virtual static int Width() => 10;
                                                                          public virtual static int Height() => 11;

                                                                          public static int Square() => Width() * Height();
                                                                          }

                                                                          class A1: A
                                                                          {
                                                                          pubilc override static int Width() => 12;
                                                                          pubilc override static int Height() => 14;
                                                                          }

                                                                          По моему проще.
                                                                          И вызовы были единообразные
                                                                          A1.Square();
                                                                          и
                                                                          A a1 = new A1();
                                                                          a1.Square();

                                                                          вообще красота (по мне) :)|


                                                                  1. DistortNeo
                                                                    07.12.2016 17:25
                                                                    +1

                                                                    Теоретически какой-то смысл в этом есть: вызывать виртуальный метод, не передавая указатель на объект — меньше на 1 параметр и можно вызвать метод у пустого объекта, передавая голый vfptr, типа A.class.init().

                                                                    Практически же передача одного параметра (ещё и через регистр) — ничто по сравнению с виртуальным вызовом (переходом по адресу), а язык и CLR усложнит.


                                    1. lair
                                      07.12.2016 14:45
                                      +1

                                      Что функция вернет то и будет. Я имел в виду неоднозначностей для компилятора.

                                      Меня мало волнует компилятор, меня волнует, как я это читать буду. И вот мне это читать очень неудобно.


                                      Это бесспорно «нормальный getType» без рефлектинга по мне тоже куда важнее.

                                      Вообще-то GetType и сейчас не использует Reflection.


                                      1. Durimar123
                                        07.12.2016 15:30

                                        На вкус и цвет фломастеры разные.

                                        Но для того что бы использовать Type для вызова функций нужен Reflection.


                                        1. lair
                                          07.12.2016 15:32

                                          Но для того что бы использовать Type для вызова функций нужен Reflection.

                                          Не так. Использование метаданных для (например) позднего вызова функций — это и есть reflection, как подсистема CLR.


            1. yarosroman
              06.12.2016 02:19

              А как можно наследовать функцию, подскажите?


            1. Sirikid
              06.12.2016 12:03

              Виртуальная статическая функция не лучшее название, хотя в какой-то мере отражает суть явления.
              DistortNeo в чем то ApeCoder прав, вызовы виртуальных статических членов должны разрешаться от типа, а не от объекта. (Можно даже сказать от инстанса тайпкласса :^) )
              Набросал такой примерчик (1) реализации (2), прошу извинить если накосячил с синтаксисом, я скорее джавист чем шарпист.
              1: http://pastebin.com/dpPEPzc8
              2: https://wiki.haskell.org/Monoid


              1. DistortNeo
                06.12.2016 15:20

                Спасибо! Вместе с примером с IDeserealizable теперь действительно понятно, что это такое и как может применяться. Единственная область применения этого механизма — вызов статических методов для generic типов. Сейчас же такая возможность предусмотрена только для конструкторов (new()).

                Реализация статических интерфейсов потребует довольно серьёзное изменение CLR, на что MS пойти не сможет точно.

                Прямо сейчас этот функционал можно сэмулировать путём создания дженерик класса со статическим полем — делегатом на статический метод, который создаётся единожды через рефлексию в статическом конструкторе, что-то вроде:

                struct Deserializer<T> where T: IDeserializable
                {
                    public static readonly Func<T> deserialize(IDeserializer deserializer);
                
                    static Deserializer()
                    {
                        deserialize = что-то типа typeof(T).GetMethod("deserialize", BindingFlags.Static | BindingFlags.Public).CreateDelegate(typeof(Func<T>)) as Func<T>;
                    }
                }

                и вызывать как
                Deserializer<T>.Deserialize(...);


                1. Krypt
                  06.12.2016 20:18

                  > Единственная область применения этого механизма — вызов статических методов для generic типов.
                  Generic-типов и интерфейсов

                  Для полного счастья нужна возможность реализовывать интерфейс используя расширения и указывать несколько интерфейсов для поля (без указания класса).

                  И тогда статические методы становятся мощным инструментом для написания фабрик с простым синтаксисом из без магии рантайма внутри.


          1. Serginio1
            06.12.2016 15:05
            +1

            В Delphi например есть метаклассы

             class function  ObjectName(AObject: TObject): String; virtual
            
            ;

            Нужны ли метаклассы????


            метаданные объектов


            1. Serginio1
              06.12.2016 15:09

              Те кто работал с Delphi знают и применяют так или иначе виртуальные конструкторы, статические виртуальные методы (class virtual) и конструкции языка типа

              TVirtClass = class of TBaseVirtClass.
              

              В Delphi метакласс это ссылка на VMT и все виртуальные методы класса (не экземпляра класса) располагаются с отрицательным смещением
              И все метаклассы наследуются от
              TClass= class of TObject.
              { Virtual method table entries }
              
              vmtSelfPtr = -76;
              vmtIntfTable = -72;
              vmtAutoTable = -68;
              vmtInitTable = -64;
              vmtTypeInfo = -60;
              vmtFieldTable = -56;
              vmtMethodTable = -52;
              vmtDynamicTable = -48;
              vmtClassName = -44;
              vmtInstanceSize = -40;
              vmtParent = -36;
              vmtSafeCallException = -32 deprecated; // don't use these constants.
              vmtAfterConstruction = -28 deprecated; // use VMTOFFSET in asm code instead
              vmtBeforeDestruction = -24 deprecated;
              vmtDispatch = -20 deprecated;
              vmtDefaultHandler = -16 deprecated;
              vmtNewInstance = -12 deprecated;
              vmtFreeInstance = -8 deprecated;
              vmtDestroy = -4 deprecated;
              
              


  1. Serginio1
    05.12.2016 16:01

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


  1. DistortNeo
    05.12.2016 16:02
    +2

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

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


    1. impwx
      05.12.2016 18:40
      +1

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

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

      public T Add<T>(T a, T b)
      where T: numeric // например, так
      {
          return a + b;
      }
      


      1. lair
        05.12.2016 18:42
        +3

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


        1. impwx
          05.12.2016 19:06

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

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


          1. lair
            06.12.2016 02:24
            +2

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


      1. DistortNeo
        05.12.2016 19:01

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

        И чем же a.Add(b.Multiply©) лучше a + b * c?


        1. DistortNeo
          05.12.2016 19:10

          Написал идею:
          https://visualstudio.uservoice.com/forums/121579-visual-studio-ide/suggestions/17335231-extend-operator-overloading


        1. impwx
          05.12.2016 19:16

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

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


          1. Sirikid
            05.12.2016 19:58

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


          1. DistortNeo
            05.12.2016 20:20

            Как это? Подвёл курсор к плюсику, нажал на F12 и перешёл к оператору.


          1. Free_ze
            07.12.2016 10:43
            +1

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


  1. zenkz
    05.12.2016 18:08
    +1

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

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


    1. TheShock
      06.12.2016 09:40

      Ещё мне очень нехватает строковых Enum-ов

      А можно пример?


      1. zenkz
        06.12.2016 17:34

        Пожалуйста

        Вместо:
        public static class StringEnum
        {
        public const string ValueA = «A»;
        public const string ValueB = «B»;
        }

        public string Field {get; set;} = StringEnum.ValueA;

        Сделать так:
        public enum StringEnum
        {
        ValueA = «A»,
        ValueB = «B»
        };

        public StringEnum Field {get; set;} = StringEnum.ValueA;

        Преимущества:
        — одинаковый подход к перечисляемым типам
        — точно знаешь что присваивать в поле (т.к. в текущей реализации — это просто строка и можно присвоить что угодно, а в предлагаемой — только значения из списка)

        Может быть вообще можно расширить до объектного enum-а (т.е. создавать перечисления любого типа) — не думаю что это будет часто-используемая фича, но вполне полезная.


        1. Krypt
          06.12.2016 20:24

          Строка — не перечисляемый тип.
          В каких случаях второй пункт может быть использован? Почему нужно присваивать именно строку, а не значение enum, которое позже преобразуется в строку?


        1. TheShock
          07.12.2016 02:52

          Преимущества:
          — одинаковый подход к перечисляемым типам
          — точно знаешь что присваивать в поле (т.к. в текущей реализации — это просто строка и можно присвоить что угодно, а в предлагаемой — только значения из списка)


          А почему бы просто не пользоваться Енумом в том виде, который сейчас есть? Ну вот допустим
          enum VehicleType {
            Light,
            Medium,
            Heavy
          }
          
          class Vehicle {
            private readonly VehicleType type;
           
            public Vehicle (VehicleType type) {
              this.type = type;
            }
          
            public string GetImageUrl () {
              return "/images/" + type + ".png";
            }
          }
          
          new Vehicle(VehicleType.Heavy).GetImageUrl(); // "/images/Heavy.png"
          


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

          enum ExtendedVehicleType : VehicleType {
            Spg, Spatg
          }
          
          new Vehicle(VehicleType.Spg);
          


          Приходится для такого делать костыль вместо енума, а это имеет ряд своих недостатков:
          class VehicleType {
            public static readonly Light  = new VehicleType("Light");
            public static readonly Medium = new VehicleType("Medium");
            public static readonly Heavy  = new VehicleType("Heavy");
          
            // ..
          }
          


          Может есть какой-либо вменяемый способ это использовать в c#?


          1. Free_ze
            07.12.2016 09:37

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

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


            1. nightwolf_du
              07.12.2016 11:45

              Я видел два варианта, кроме идентификаторов

              Привычный — объявить enum в классе и рядом положить статический Dictionary<enum, string > с нужными строками(некрасиво)
              Красивый — использовать атрибуты.


              1. Free_ze
                07.12.2016 11:54

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


                1. nightwolf_du
                  07.12.2016 15:33

                  В данном случае тип атрибута — один, например можно для этого использовать DescriptionAttribute(он стандартный).
                  Другое дело, что

                      public static string GetDescription(this Enum value)
                      {
                        DescriptionAttribute[] descriptionAttributeArray = (DescriptionAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (DescriptionAttribute), false);
                  ...
                  

                  работает медленно, и enum вида
                   private enum Verdict : byte {
                              [Description(" ")]
                              Space,
                              [Description(",")]
                              Comma
                          }
                  

                  не всех стилистически радует.


                  1. Free_ze
                    07.12.2016 15:50

                    Вы уверены, что все сериализаторы ищут DescriptionAttribute на каждом enum и с радостью будут преобразовывать enum <--> string? Или мне самому их обучать?


                    1. nightwolf_du
                      07.12.2016 16:00

                      Всё, наконец-то я понял, что вы имеете ввиду, у вас есть некие стандартные сериализаторы в некие строковые файлы, и сериализация в Enum-овское нестроковое value или "." вас не устроит, нужно чтобы value было строковым.
                      Тогда да, перечислимые строки были бы решением, если просто строковые константы не устраивают.


                1. Krypt
                  07.12.2016 15:34

                  А так у вас будет зоопарк энумов, если сериализаторы называют поля по разному.


                  1. Free_ze
                    07.12.2016 16:00

                    Сериализаторам без разницы, как называются поля. Допустим, что это JSON, они знают, что тип — enum, что у него строковый базовый тип => его нужно сериализовать в соответствующую строку. Выглядело это бы так:

                    enum MyStrings : string {
                        One = "One with whitespaces",
                        Two = "Two with whitespaces",
                        Three = "Three with whitespaces"
                    }
                    
                    MyStrings enVal = MyStrings::One;
                    
                    var json = JsonConverter.Serialize<string>(enVal);
                    


                    1. Krypt
                      07.12.2016 16:13

                      Ну во-первых, всё таки

                      var json = JsonConverter.Serialize<MyStrings>(enVal);
                      


                      А во вторых — с аттрибутами он может выглядеть точно так же.

                      upd:
                      На самом деле вот так:
                      var json = JsonConverter.Serialize(enVal);
                      

                      Тип шаблона может резолвиться автоматом.


                      1. Free_ze
                        07.12.2016 16:19

                        Да, верно. С атрибутами он может выглядеть подобно. Только будет с атрибутами)

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


  1. eugenebb
    05.12.2016 18:55

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

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


  1. rikert
    05.12.2016 20:24
    -5

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

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

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


    1. DistortNeo
      05.12.2016 20:43
      +5

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

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

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


      1. smaugfm
        06.12.2016 12:22

        Если вы напишете await asyncFunction() то не будет выделено нового потока, если внутри asyncFunction он нигде явно не выделяется (не выполняется Task.Run и т.д.). Сдесь показано как будет выполняться код в async методе: потоков не создается (если внутри GetStringAsync их не создается). А далее приводится пояснение:

        Async methods are intended to be non-blocking operations. An await expression in an async method doesn’t block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method.
        The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available.


        1. DistortNeo
          06.12.2016 15:45

          Если вы напишете await asyncFunction() то не будет выделено нового потока, если внутри asyncFunction он нигде явно не выделяется

          Это зависит от текущего SynchronizationContext. Обратите внимание, что по ссылке приведён конкретный пример работы асинхронных вызовов для Windows Forms.


          1. lair
            06.12.2016 16:32

            Это зависит от текущего SynchronizationContext

            Это зависит в первую очередь от того, что вернула asyncFunction, и если она вернула уже выполненный таск, то SynchronizationContext вообще не будет использован.


            1. DistortNeo
              06.12.2016 16:40

              А если вернула незавершённую задачу (что является гораздо более частным случаем), то продолжение её выполнения таки зависит от SynchronizationContext.

              Следующий код будет выдавать разный результат в зависимости от контекста:

              Console.WriteLine(Thread.CurrendThread.ManagedThreadId);
              await Task.Yield();
              Console.WriteLine(Thread.CurrendThread.ManagedThreadId);


              Если оформить его как асинхронную функцию и вызвать напрямую из Main (Task.Run.Wait()), то вывод на экран будет разный.

              Замечание 1: ManagedThreadId разный для разных потоков.
              Замечание 2: вместо Task.Yield может быть любая задача, не являющаяся автоматически завершённой.


              1. mayorovp
                06.12.2016 17:08

                Не совсем так. Task.Yield — это все-таки не задача, а требование уступить поток. Там отдельный Awaiter написан для него.


                Если смотреть задачи — то они как раз работают довольно адекватно. При установленном контексте синхронизации метод в него возвращается. А без контекста синхронизации — метод продолжает исполняться в том же потоке, который задачу выполнил (исключение — если синхронное продолжение было явно запрещено в свойствах задачи).


                1. DistortNeo
                  06.12.2016 17:51

                  «Ожидать» с помощью await можно любой объект (не обязательно Task), имеющий метод GetAwaiter, возвращающий объект, имеющий три метода (OnCompleted, IsCompleted, GetResult). Один из методов — OnCompleted(Action continuation), вызывается по завершении задачи и приводит к продолжению выполнения кода, вызвавшего await.

                  Вот здесь можно посмотреть реализацию этого метода: если контекст задан, то вызывается функция помещения задачи в очередь из контекста (Post), а если контекта нет, то продолжение задачи планируется к выполнению в пуле потоков.

                  Аналогично можно посмотреть на Task.GetAwaiter, но там чёрт ногу сломит. В конечном итоге вызывается TaskContinuation.Run, который вызывает всё тот же Post или лезет в ThreadPool.


                  1. mayorovp
                    06.12.2016 18:02

                    Для YieldAwaiter приведенное поведение — ожидаемое!


                    Что же до TaskAwaiter, то тут все тоже просто, если разобраться. Если не было захвачено ни контекста синхронизации, ни планировщика задач — то используется класс AwaitTaskContinuation. Этот класс по возможности вызывает продолжение синхронно.


                    1. DistortNeo
                      06.12.2016 18:37

                      Видимо, он это делает слишком редко, например, когда задача в момент выполнения await уже завершена. Просто вставьте в Main следующий код и посмотрите на результат:

                                  Task.Run(async () =>
                                  {
                                      Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                                      await Task.Delay(10);
                                      Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                                  }).Wait();
                      


                      Но это не беда. Простой планировщик решает эту проблему.


                      1. mayorovp
                        06.12.2016 19:23

                        А как, по-вашему, этот код вообще может работать? После истечения 10ти миллисекунд выполнение обязано вернуться в пул потоков. Почему для вас так важно, чтобы это был тот же самый поток? Все потоки пула одинаковы.


                        Лучше посмотрите на вот этот код:


                        public static void Main()
                        {
                            A().Wait();
                        }
                        
                        static async Task A() {
                            await B();
                            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                        }
                        
                        static async Task B() {
                            await C();
                            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                        }
                        
                        static async Task C() {
                            await Task.Yield();
                            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
                        }

                        В данном случае, все три оператора Console.WriteLine выполнятся в одном и том же потоке, что показывает, что продолжения и правда вызываются синхронно.


                        1. DistortNeo
                          06.12.2016 20:02

                          В данном случае, все три оператора Console.WriteLine выполнятся в одном и том же потоке, что показывает, что продолжения и правда вызываются синхронно.

                          Ага, только у меня периодически получается так:
                          4
                          5
                          5
                          


                          Почему для вас так важно, чтобы это был тот же самый поток?

                          Потому что я хочу, чтобы все задачи работали последовательно в одном потоке:
                          1. Нет затрат на синхронизацию между потоками — никаких блокировок и ожиданий, мьютексы и эвенты не нужны.
                          2. Нет затрат на переключение контекста между потоками в планировщике. Делегировать выполнение задачи в другой поток — это очень долго: за то время, пока поток-воркер проснётся, пока сообщит другому потоку о готовности результата, можно выполнить с сотню задач в основном потоке.

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


                          1. mayorovp
                            06.12.2016 20:33

                            "периодически" — это как?


                            1. DistortNeo
                              06.12.2016 20:45

                              Это значит, что результат выполнения случайный. А другого эффекта в многопоточном приложении сложно ожидать.


                              1. mayorovp
                                07.12.2016 15:57

                                Запустил код на трех разных компьютерах. Каждый раз все три числа совпадали… Вы что-то делаете не так.


                                1. DistortNeo
                                  07.12.2016 16:25

                                  Многопоточность — она такая неожиданная. Вот вроде всё работает, а потом опа — race condition.
                                  А код уже написан и оттестирован — тесты проходят, а в продакшене почему-то всё падает.

                                  Если вы вызовете 3 раза подряд ThreadPool.QueueЧтоТоТам, то эти 3 задачи могут быть запущены как на одном потоке, так и на нескольких, никакой гарантии здесь нет.


                                  1. mayorovp
                                    07.12.2016 16:29

                                    Не надо объяснять мне что такое race condition, я это знаю.


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


                                    1. DistortNeo
                                      07.12.2016 17:00

                                      В коде, который я привел, окончания всех трех задач всегда выполняются в одном и том же потоке

                                      Если они всегда выполняются конкретно на вашем компьютере в одном и том же потоке, это ещё ничего не значит:
                                      http://funkyimg.com/i/2kCET.png

                                      Запустите 10, 20, 30 раз, попробуйте debug/release поменять, запускать без отладчика. Это именно race condition.


                                      1. mayorovp
                                        07.12.2016 17:20

                                        Что такое Utilities.Asynchronius? Если убрать этот using и все лишние референсы — ошибка останется?


                                        1. DistortNeo
                                          07.12.2016 18:47

                                          Это мой планировщик от библиотечки. Да, на голом проекте эффект сохраняется. На разных системах, даже под Mono.


                                          1. mayorovp
                                            07.12.2016 19:00

                                            Вот как раз Mono — совсем не показатель, там запросто может быть более слабая реализация.


                                            1. DistortNeo
                                              07.12.2016 19:14

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


                                      1. mayorovp
                                        07.12.2016 17:32

                                        Вот мой скриншот:


                                        картинка

                                        Imgur


                                        1. DistortNeo
                                          07.12.2016 18:51
                                          -1

                                          Ага, 1, 3, 5, 7 — простые, значит, и 9 — тоже простое.


                                          1. mayorovp
                                            07.12.2016 19:01

                                            Выполнение программы детерминировано.


                                            1. DistortNeo
                                              07.12.2016 19:32

                                              Работа планировщика в многопоточной среде не является детерминированной.

                                              Объясните же, а?
                                              http://funkyimg.com/i/2kCUY.png


                                              1. mayorovp
                                                07.12.2016 20:32
                                                +1

                                                Оно не должно попадать в планировщик.


                                                PS не буду вам больше отвечать, потому что вы спорите для того чтобы спорить, а не чтобы разобраться в чем дело.


                                                1. DistortNeo
                                                  07.12.2016 21:42

                                                  Конечно, не должен: по завершении задачи управление сразу передаётся в код, который вызывает continuation. Но почему в некоторых случаях continuation вызывается не сразу, а попадает в ThreadPool, мне не понятно, и я хочу в этом разобраться. Ваши же ответы выглядят как «ничего не знаю, у меня всё работает». Я же жду объяснений.

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

                                                  На самом деле, ответ значения не имеет. Для меня важно то, что в стандартном планировщике (не Forms/WPF) после любых IO операций, которые не могут быть завершены прямо сейчас, продолжение задачи осуществляется не в вызвавшем потоке, а когда таких задач несколько — в разных потоках.


                                            1. DistortNeo
                                              07.12.2016 22:34

                                              Чтобы окончательно разрешить спор, выкладываю проект.

                                              Мои результаты:

                                              Debugger: not attached
                                              .NET version: 4.0.30319.42000
                                              Platform: 64 bit
                                              
                                              Same ThreadId: 98,94%


                                              Получается, что в 1 случае из 100 программа ведёт себя не так, как должна себя вести. Это типичный пример неверного предположения о работе программы в многопоточной среде, которое может привести к очень неприятным и трудноуловимым ошибкам.


                          1. forcewake
                            07.12.2016 00:51

                            Здравствуйте, а можно где-то посмотреть на код вашей реализации планировщика?


                            1. DistortNeo
                              07.12.2016 01:08
                              +1

                              Можно, в привате ссылка


                          1. lair
                            07.12.2016 11:31
                            +2

                            По-моему, вы только что продемонстрировали, что модель TPL в .net сделана удобно: для вашей конкретной задачи стандартный планировщик не подошел (это не значит, что он не подойдет другим, у меня он прекрасно работает) — и вы легко заменили его на свой.


  1. dotnetdonik
    06.12.2016 00:08
    -1

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


    1. mayorovp
      06.12.2016 17:10

      А что не так с "конкретным компилятором" если сам компилятор — переносимый и открытый?


      1. dotnetdonik
        06.12.2016 22:11

        Дело не в компиляторе самом, а стандартах. В дотнете/с# уже написано масса хороших библиотек и кода, что используют для аналогичных целей reflection.emit, DLR, Dymamic Proxies, Expressions. мс продолжают добавлять новые инструменты и менять подходы раз в несколько лет, для весьма странных вещей без всякой на то нужды. Количество задач где целесообразно это променять скоро станет меньше количества инструментов, которыми их можно решить — и все это надо будет саппортить в будущем для обратной совместимости? Как считаете это способствующий развитию и гибкости языка момент?


        1. dotnetdonik
          06.12.2016 22:17

          Если на проекте уже надо совсем прямо надо докмпилировать какой-то код из строки, это даже сейчас аналогичным образом можно сделать применив T4 шаблоны или написав автоматизацию вызова csc.exe, зачем это добавлять нативно в компилятор?


          1. lair
            07.12.2016 11:28
            +1

            Чтобы перестать писать T4, например.


  1. AxisPod
    06.12.2016 08:52
    -1

    >> Добавление генерирующего код расширения для компилятора
    Ааа, вот же лютый костыль, почему бы не сделать множественное наследование.

    >> Superseding members — замещение модификатора для того, чтобы дать больше возможностей инструментам генерации кода
    опять же приведенный механизм использования нормально решается множественным наследованием

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


  1. Free_ze
    06.12.2016 09:55

    Почему бы не сделать статические generics, на манер шаблонов C++? Нередко наблюдал, как люди из-за отсутствия оных используют dynamic (со всеми вытекающими проблемами в рантайме).


    1. netpilgrim
      06.12.2016 22:36

      Можно пример? Статические классы и методы могут быть generics.


      1. DistortNeo
        06.12.2016 23:49

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


        1. Free_ze
          07.12.2016 10:37

          … но за счёт сильного раздутия кода

          Extern templates же.


      1. Free_ze
        07.12.2016 10:35

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

        Вкупе с variadic templates и развитым выводом типов это дает возможность писать крайне гибкий код. И это уже есть в C++/CLI.


        1. mayorovp
          07.12.2016 10:41

          Вот только пользоваться этим в C++ зачастую неудобно. Расширения компилятора выглядят лучше.


          1. Free_ze
            07.12.2016 10:54

            Сложно представить современный C++ без шаблонов.

            Расширения компилятора выглядят лучше.

            Очень спорно. Семантически это больше на миксины похоже, чем на шаблоны.


            1. mayorovp
              07.12.2016 11:20

              Так ведь это зависит от целей использования. Расширение компилятора позволяет сделать и миксин, и шаблон.


              1. Free_ze
                07.12.2016 11:34

                Судя по примерам в статье это всегда будет миксин. Шаблон в C++ всегда создает новую сущность (функция, класс или синоним типа), а эта штука, как я понял из статьи, инжектит новые поля/методы в уже существующие типы.

                Расскажите, пожалуйста, чем же это удобнее пресловутых шаблонов?


                1. mayorovp
                  07.12.2016 11:56

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


                    Шаблоны в c++ — это отдельный функциональный (!) язык (мета)программирования, при том что основной язык — императивный...


                  2. В расширении можно произвольно управлять наличием и именами методов — в то же время шаблоны C++ ограничены заданным при их написании набором методов, а управлять их наличием нужно через местами контринтуитивный механизм SFINAE.


                  1. Free_ze
                    07.12.2016 14:35

                    В C++ с помощью SFINAE затыкают полное отсутствия механизма, вроде where-констрейнтов из C# + Reflection в некоторой степени. Можно же найти золотую середину, имея поддержку обеих технологий на платформе. Допустим, ввести специальный синтаксический assert-блок или дать возможность как-то формализовать требования к типу в отдельный метатип, в котором можно наделать дополнительных запросов к типу, посредством compile-time аналога рефлекшна.

                    И да, описывать требования удобнее на декларативном языке, как это реализовано для дженериков в C#.

                    Шаблоны в c++ — это отдельный функциональный (!) язык (мета)программирования, при том что основной язык — императивный...

                    C++ мультипарадигменный, в том числе и функциональный.


                  1. Free_ze
                    07.12.2016 15:45

                    В расширении можно произвольно управлять наличием и именами методов

                    А в чем отличие от шаблонов? Наличие методов определяется и без каких-то особых трюков с SFINAE. Причем, мне кажется, расширения компилятора здесь работают так же. А переименовывать вы можете так же свободно.


  1. Viacheslav01
    06.12.2016 20:03
    -2

    Надавать по рукам за такие «новые» возможности. Код превратиться в кучу гуано!


  1. Deosis
    08.12.2016 07:43
    -1

    Можно добавить контекстное ключевое слово field в методах доступа свойства.
    А также пустой nameof(), который возвращает имя текущего свойства/метода:

    public string Property
    {
        get { return field; }
        set
        {
            if (field == value) return;
            field == value;
            OnPropertyChanged(nameof());
        }
    }
    


    1. Krypt
      08.12.2016 12:20
      -1

      Я сейчас еретическую вещь напишу: больше всего в C# мне не хватает препроцессора. Проблема с INotifyPropertyChanged решалась бы одной строчкой.


    1. qw1
      08.12.2016 21:29
      +1

      А также пустой nameof(), который возвращает имя текущего свойства/метода:
      А это уже есть, но немного по-другому записывается:
      void OnPropertyChanged([CallerMemberName] string propertyName = "") { .. }
      ...
      public string Property1
      {
          set
          {
              OnPropertyChanged();
          }
      }
      public string Property2
      {
          set
          {
              OnPropertyChanged();
          }
      }

      https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute(v=vs.110).aspx