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



    Вы должно быть знаете, что в последнее время Microsoft старается общаться с сообществом, получать как можно больше отзывов и внедрять их в жизнь. Не обошла эта мода и команду разработчиков C#. Разработка новой версии языка определенно не идет за закрытыми дверями…

    Статья Mads Torgersen под названием What’s New in C# 7.0 уже разобрана вдоль и поперек. Но есть что-то, что в ней не было упомянуто.

    Предлагаю вам пройтись обзором по нескольким предложениям из репозитория Roslyn. Название темы C# 7 Work List of Features. Информация обновлялась пару месяцев назад (то есть она не сильно актуальная), но это то, что определенно было не обделено вниманием Mads Torgersen.

    Даже дважды, в рубриках Strong interest и Non-language упомянуты следующие предложения:

    1. Enable code generating extensions to the compiler #5561
    2. Add supersede modifier to enable more tool generated code scenarios #5292

    Фактически, это предложение добавления в C# некоторого функционала аспектно-ориентированного программирования. Я постараюсь передать в сокращенном виде суть.

    Добавление генерирующего код расширения для компилятора


    Хотелось бы заметить, что на github это предложение помечено как находящееся в стадии разработки. Оно предлагает избавится от повторяющегося кода с помощью Code Injectors. При компиляции определенный код будет добавлен компилятором автоматически. Что важно: исходный код не будет изменен, а также внедрение кода будет происходить не после компиляции в бинарный файл.

    В описании указано, что процесс написания инжектора будет похож на написание diagnostic analyzer. Я же надеюсь, что все будет гораздо проще (в таком случае классы можно будет создавать и редактировать вручную). Но, возможно, что это будет удобно делать только с помощью инструментов, которые генерируют код автоматически.

    В качестве примера приведен класс, который в свою очередь внедряет в каждый класс проекта новое поле, — константу типа string с именем ClassName и значением, содержащим имя этого класса.

    [CodeInjector(LanguageNames.CSharp)]
    public class MyInjector : CodeInjector
    {
        public override void Initialize(InitializationContext context)
        {
            context.RegisterSymbolAction(InjectCodeForSymbol);
        }
    
        public void InjectCodeForSymbol(SymbolInjectionContext context)
        {
            if (context.Symbol.TypeKind == TypeKind.Class)
            {
                context.AddCompilationUnit($@"partial class {context.Symbol.Name} 
                          {{ public const string ClassName = ""{context.Symbol.Name}""; }}");
            }
        }
    }
    

    Инжекторы кода изначально будут иметь возможность только добавить какой-нибудь код, но не изменить существующий. С их помощью можно будет избавиться от такой шаблонной логики, как, например, реализация INotifyPropertyChanged.

    А как можно ускорить реализацию INotifyPropertyChanged сейчас? АОП фреймворк PostSharp фактически сделает это самое внедрение Walkthrough: Automatically Implementing INotifyPropertyChanged
    В этом случае код реализации будет скрыт. Не ошибусь, если напишу, что некоторые изменения PostSharp совершает уже в после компиляции в коде IL (How Does PostSharp Work). Доподлинно известно, что эта утилита довольно продвинутая в плане изменения IL кода.

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

    Вот так выглядит простой класс, реализующий интерфейс INotifyPropertyChanged. Фактически в нем только одно свойство Name, но очень много лишнего кода реализации:

    class Employee: INotifyPropertyChanged
    {
     private string _name;
     
     public string Name
     {
        get { return _name; }
        set 
        {
           _name = value;
           RaisePropertyChanged();
        }
     }
     
     private void RaisePropertyChanged([CallerMemberName] string caller="")
     {
        if( PropertyChanged != null )
        {
           PropertyChanged(this, new PropertyChangedEventArgs(caller));
        }
     }
    }
    

    Все это можно будет заменить таким небольшим классом:

    [INoifyPropertyChanged]
    public class Employee
    {
       public string Name { get; set; } 
    }
    

    Или, может быть, можно будет придумать свою конвенцию наименований и все классы в названии которых будет INPC будут реализовывать INoifyPropertyChanged. Получилось бы еще короче:

    public class EmployeeINPC
    {
       public string Name { get; set; } 
    }
    

    Но это я уже немного дал волю фантазии.

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


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

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

    // написанный пользователем код
    public partial class MyClass
    {
          public void Method1()
          {
              // что-то чудесное здесь происходит
          }
    }
    
    // код сгенерированный инструментом
    public partial class MyClass
    {
          public supersede void Method1()
          {
               Console.WriteLine("entered Method1");
               superseded();
               Consolel.WriteLine("exited Method1");
          }
    }
    

    В данном случае Method1, который был сгенерирован инструментом замещает Method1, который был написан пользователем. Все вызовы Method1 будут вызывать код созданный инструментом. Код, который создан инструментом может вызывать пользовательский код с помощью ключевого слова superseded. То есть, выходит, что в данном примере мы имеем автоматическое добавление логов в Method1.

    Используя эту технику, реализация INotifyPropertyChanged может получиться такой вот:

    // написанный пользователем код
    public partial class MyClass
    {
         public int Property { get; set; }
    }
    
    // код сгенерированный инструментом
    public partial class MyClass  : INotifyPropertyChanged
    {
         public event PropertyChangedEventHandler PropertyChanged;
    
         public supersede int Property
         {
              get { return superseded; }
              set
              {
                  if (superseded != value)
                  {
                        superseded = value;
                        var ev = this.PropertyChanged;
                        if (ev != null) ev(this, new PropertyChangedEventArgs("Property"));
                  }
              }
         } 
    }
    

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

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


    Это предложение также помечено на github, как находящееся в стадии разработки. Внутри метода Main будет разрешено использование await. Делается это по причине того, что есть множество программ, внутри которых содержится подобная конструкция:

    static async Task DoWork() {
        await ...
        await ...
    }
    
    static void Main() {
        DoWork().GetAwaiter().GetResult();
    }
    

    В данном случае возможно прерывание работы программы по причине возникновения исключения. В случае же использования DoWork().Wait() любая возникшая ошибка будет преобразована в AggregateException.

    Так как CLR не поддерживает асинхронные методы в качестве точек входа, то компилятор сгенерирует искусственный main метод, который будет вызывать пользовательский async Main.

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



    Не факт, что эти фичи появятся в C# 7.0, и даже не факт, что они появятся в 8.0, но они определенно рассматривались командой разработчиков языка как потенциально интересные.

    В анонсе Visual Studio 2017 упоминается еще одно предложение Task-like return types for async methods, которое точно будет в 7-ой версии языка.

    Присоединяйтесь к обсуждению на GitHub или пишите комментарии здесь.
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 159
    • +1
      Разработка новой версии языка определенно не идет за закрытыми дверями…

      Так в чем вопрос, надо просто открыть двери
      • –13
        Лично я бы проголосовал за

        1 Множественное наследование.
        2 virtual static function
        • +1
          Эти фичи требуют поддержки не только компилятора, но и самого рантайма.

          P.S. Я лично против множественного наследования в том виде, как это сделано в C++. Если делать что-то подобное, то лучше реализовать систему mixin'ов.
          • +4
            Нет. Множественное наследование с успехом заменяется интерфейсами, но при этом не привносит связанных с множественным наследованием проблем. А понятия виртуальной статической функции не существует в природе.
            • 0
              >А понятия виртуальной статической функции не существует в природе.

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

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

              • +4
                Можно ссылку на описание понятия виртуальной статической функции?
                • –6
                  Понятие «виртуальная статическая функция», по моему интуитивно понятно, это статическая функция которую можно наследовать.
                  • +8
                    Поясните на примере, мне очень интересно, что вы имеете в виду. Поначалу я подумал, что вы имели в виду статический полиморфизм (шаблоны C++), но слово «static» однозначно даёт понять, что вы имеете в виду что-то непонятное.

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

                    Вы можете наследовать статические классы и вызывать в них функции базовых классов — никто этого не запрещает. Вы можете сокрыть функцию с той же сигнатурой. Но вы не можете сделать статическую функцию виртуальной, потому что нет объекта, который бы отвечал за выбор конкретной реализации функции.
                    • –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
                        В C# класс это не объект, у классов есть представление в виде объекта, но это не они сами.
                        • +1

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

                          • 0

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

                            • 0

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

                              • 0

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

                                • 0

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

                                  • 0

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


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

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

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

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

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

                          • 0

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

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

                            interface IDeserealizable
                            {
                            static IDeserealizable deserialize(IDeserializer deserializer)
                            }

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

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

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

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

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

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

                              class A2:A
                              {
                              public virtual static string info() {return «a2»;}
                              }
                              • +2
                                А зачем?
                                Вне зависимости от того, что именно вы хотите сделать — это можно сделать через сокрытие (new на функции)

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


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

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

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

                                    Она не должна знать ничего о «нестатических» полях/методах/св-вах.
                                    • 0
                                      Я чуть о другом, вот такой код не работает:

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

                                      A a = new A();
                                      string s = a.info(); < — тут баг
                                      • 0
                                        Здесь вы просто ходите, чтобы в таблице виртуальных функций объекта существовали функции, которые не принимают this в качестве первого аргумента, например, из соображений вычислительных затрат. Но при этом эти функции можно вызывать и как статические — при явном указании типа компилятор просто подставит нужную реализацию.

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

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

                                            A.info();
                                            и
                                            a.info();
                                            • +1
                                              Это почему же?

                                              A a = new B();

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

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

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

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

                                        Но зачем?

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

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

                                          Это хорошо проявляется в задачах типа фабрика объектов.
                                          • 0
                                            Как минимум, наличие возможности лучшее ее отсутствия.

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


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

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

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

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

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

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

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


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

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


                                                yourObject -> getType -> someMethod
                                                • –2
                                                  Ну а я за предоставление максимальных возможностей. Только не надо писать про выстрелы в ногу, это не тот случай.

                                                  >yourObject -> getType -> someMethod

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

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

                                                  Тогда да, тогда я понимаю вашу принципиальность.
                                                  • +1
                                                    Но зачем «getType ->» если его нужно писать всегда?

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


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

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


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

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

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

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

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

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

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

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

                                                          :) Расскажите мистер паладин, чем именно ужасен вызов статика из объекта?
                                                          • –2
                                                            Например тем, что объект может оказаться null. Ловить NullReferenceException на статических методах — не очень большая радость. И вообще тогда какие отличия от экземплярных методов, кроме потери доступа к this в теле метода?
                                                            • –4
                                                              :) Вызов любой функции может дать NullReferenceException.
                                                              А самое забавное, что вызов статической функции НИКАК не может вызвать NullReferenceException.

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

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

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

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

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

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

                                                                      Откуда null, в статик функции? Даже у виртуальной.
                                                                      • +1
                                                                        >> А если (B(null)).info(); то от B; Что вас смущает?
                                                                        Здесь — ничего. А если так:
                                                                        A a = new B();
                                                                        a.info();
                                                                        

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

                                                                        >> Откуда null, в статик функции? Даже у виртуальной.
                                                                        А как CLR должен в рантайме определить, виртуальную статическую функцию какого класса вызывать? Видимо, по экземпляру класса. Если он null, все плохо. Если не по экземпляру, то как еще?
                                                                        • –1
                                                                          >А если так: A a = new B(); a.info();

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

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

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

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

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

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

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

                                                                                    • 0
                                                                                      В этом и есть смысл — единообразие вызовов статик методов и экземпляр методов. С возможностью вызова base метода.
                                                                                      • +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()

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

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

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

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

                                                                                          Например классу А реализовать функцию
                                                                                          static int Perimetr() {return (Width() + Height())*2;}
                                                                                          Которая нормально работала бы для все наследников.
                                                                                          • 0
                                                                                            у них общий набор типовых характеристик например
                                                                                            static Name, Type, Width, Height, у каждого класса свои значения. И возможно они зависит от расчета в родителе.

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


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

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

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

                                                                                              class A2: A
                                                                                              {
                                                                                              }

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

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

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

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


                                                                                                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);
                                                                                                    }
                                                                                                }

                                                                                                Может, стоило бы вообще убрать такого рода информацию из статических полей и организовать доступ к ней как-то еще. Например, importantInfoProvider.Get().Width. А внутри по типу определять, что вернуть. Не думаю, что решение через статику — единственное или лучшее возможное.

                                                                                            • 0
                                                                                              Пожалуйста:

                                                                                              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 генерировать отдельный для каждого типа, а не вызывать интерфейс + нет оверхеда по памяти.

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

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

                                                                                              Если над идеей статических виртуальных функции хорошо подумать, то может, что-то годное и выйдет. Одной из возможных реализаций этого механизма была бы следующая: 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();
                                                                                              }


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

                                                                                            Практически же передача одного параметра (ещё и через регистр) — ничто по сравнению с виртуальным вызовом (переходом по адресу), а язык и CLR усложнит.
                                                              • +1
                                                                Что функция вернет то и будет. Я имел в виду неоднозначностей для компилятора.

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


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

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

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

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

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

                                        • 0
                                          А как можно наследовать функцию, подскажите?
                                          • 0
                                            Виртуальная статическая функция не лучшее название, хотя в какой-то мере отражает суть явления.
                                            DistortNeo в чем то ApeCoder прав, вызовы виртуальных статических членов должны разрешаться от типа, а не от объекта. (Можно даже сказать от инстанса тайпкласса :^) )
                                            Набросал такой примерчик (1) реализации (2), прошу извинить если накосячил с синтаксисом, я скорее джавист чем шарпист.
                                            1: http://pastebin.com/dpPEPzc8
                                            2: https://wiki.haskell.org/Monoid
                                            • 0
                                              Спасибо! Вместе с примером с 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(...);
                                              • 0
                                                > Единственная область применения этого механизма — вызов статических методов для generic типов.
                                                Generic-типов и интерфейсов

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

                                                И тогда статические методы становятся мощным инструментом для написания фабрик с простым синтаксисом из без магии рантайма внутри.
                                          • +1
                                            В Delphi например есть метаклассы
                                             class function  ObjectName(AObject: TObject): String; virtual
                                            
                                            ;

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


                                            метаданные объектов
                                            • 0
                                              Те кто работал с 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;
                                              
                                              
                                    • 0
                                      Кстати удобно для ViewModel удобно использовать PropertyChanged.Fody Сокращаем объем кода с помощью Fody
                                      • +2
                                        Лично я бы хотел видеть возможность более широкой перегрузки операторов.
                                        Например, есть дженерик класс, но оператор хочу определить только для конкретных типов.

                                        Почему я в этом случае могу определить метод-расширение Add, не не могу — operator +? Почему нельзя сделать так, чтобы при ненайденном операторе + автоматически искался бы метод-расширение Add?
                                        • +1
                                          Скорее всего, это вопрос затрат и удобочитаемости. Тем более, что это можно уже сделать с помощью интерфейсов.

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

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

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

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

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

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

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

                                              И чем же a.Add(b.Multiply©) лучше a + b * c?
                                              • 0
                                                Написал идею:
                                                https://visualstudio.uservoice.com/forums/121579-visual-studio-ide/suggestions/17335231-extend-operator-overloading
                                                • 0
                                                  Например тем, что можно перейти к реализации метода с помощью ctrl+click и сразу узнать, что именно он делает. А к телу перегруженного оператора, насколько я знаю, так просто не перейдешь.

                                                  Вообще дизайн стандартной библиотеки C# склоняется к методам и интерфейсам. Если хотите примеров того, насколько неудобочитаемым становится код при активном использовании (кастомных) операторов — вот, например, когда-то мы писали парсер на F#
                                                  • 0
                                                    Точно что там происходит я не скажу, но в общем понятно, такие API для создания парсеров довольно распространенная штука.
                                                    • 0
                                                      Как это? Подвёл курсор к плюсику, нажал на F12 и перешёл к оператору.
                                                      • +1
                                                        В идеале не нужно ходить к телу перегруженного оператора, он должен быть семантически очевиден. Если это не так, то перегружать оператор для этого типа не стоит.
                                                • +1
                                                  CodeInjector выглядит круто — Никогда не понимал этот гемморой с INotifyPropertyChanged, а тут вполне карасивое решение.

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

                                                    А можно пример?
                                                    • 0
                                                      Пожалуйста

                                                      Вместо:
                                                      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-а (т.е. создавать перечисления любого типа) — не думаю что это будет часто-используемая фича, но вполне полезная.
                                                      • 0
                                                        Строка — не перечисляемый тип.
                                                        В каких случаях второй пункт может быть использован? Почему нужно присваивать именно строку, а не значение enum, которое позже преобразуется в строку?
                                                        • 0
                                                          Преимущества:
                                                          — одинаковый подход к перечисляемым типам
                                                          — точно знаешь что присваивать в поле (т.к. в текущей реализации — это просто строка и можно присвоить что угодно, а в предлагаемой — только значения из списка)


                                                          А почему бы просто не пользоваться Енумом в том виде, который сейчас есть? Ну вот допустим
                                                          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#?
                                                          • 0
                                                            А почему бы просто не пользоваться Енумом в том виде, который сейчас есть?

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

                                                              Привычный — объявить enum в классе и рядом положить статический Dictionary<enum, string > с нужными строками(некрасиво)
                                                              Красивый — использовать атрибуты.
                                                              • 0
                                                                Атрибуты терпимы, когда сериализатор один. Зоопарк из атрибутов сложно назвать красотой.
                                                                • 0
                                                                  В данном случае тип атрибута — один, например можно для этого использовать 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
                                                                          }
                                                                  

                                                                  не всех стилистически радует.
                                                                  • 0
                                                                    Вы уверены, что все сериализаторы ищут DescriptionAttribute на каждом enum и с радостью будут преобразовывать enum <--> string? Или мне самому их обучать?
                                                                    • 0
                                                                      Всё, наконец-то я понял, что вы имеете ввиду, у вас есть некие стандартные сериализаторы в некие строковые файлы, и сериализация в Enum-овское нестроковое value или "." вас не устроит, нужно чтобы value было строковым.
                                                                      Тогда да, перечислимые строки были бы решением, если просто строковые константы не устраивают.
                                                                  • 0
                                                                    А так у вас будет зоопарк энумов, если сериализаторы называют поля по разному.
                                                                    • 0
                                                                      Сериализаторам без разницы, как называются поля. Допустим, что это 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);
                                                                      
                                                                      • 0
                                                                        Ну во-первых, всё таки
                                                                        var json = JsonConverter.Serialize<MyStrings>(enVal);
                                                                        


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

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

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

                                                                          И нет таких стандартных атрибутов, которые я бы мог использовать везде. Поэтому будут вешать свой кастомный для каждого сериализатора. И в этом соль.
                                                      • 0
                                                        Голосуйте за свои предпочтения в развитии C# (там же ссылки на другие проекты от MS)

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

                                                        • –5
                                                          Асинхронный Main

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

                                                          Что касается генерации кода это прекрасно, однако как я могу судить — снова перемудрили и теперь нужно будет при отладке большого проекта ещё и искать «кто же включает очередную функцию в этот класс». Самая верная реализация генерации это у Хаскеля.
                                                          • +5
                                                            А мне, наоборот, реализация асинхронности в C# очень нравится, а конкретно генерация state machine из асинхронных функций — руками их писать очень и очень тяжело. Альтернативой ей является использование более сложных в отладке fibers (например, coroutines в C++).

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

                                                            Как итог — в своих проектах я использую собственный планировщик и собственные обёртки над сокетами. Заодно получаю полноценную кроссплатформенность — при запуске под Windows используется IOCP, под Linux — epoll.
                                                            • 0
                                                              Если вы напишете 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.
                                                              • 0
                                                                Если вы напишете await asyncFunction() то не будет выделено нового потока, если внутри asyncFunction он нигде явно не выделяется

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

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

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

                                                                    Следующий код будет выдавать разный результат в зависимости от контекста:
                                                                    Console.WriteLine(Thread.CurrendThread.ManagedThreadId);
                                                                    await Task.Yield();
                                                                    Console.WriteLine(Thread.CurrendThread.ManagedThreadId);


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

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

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


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

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

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

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

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


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

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

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


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

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

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

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


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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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


                                                                                                картинка

                                                                                                Imgur


                                                                                                Отсюда прекрасно видно, что продолжения были вызваны синхронно. Если у вас меняется номер потока — значит, что-то мешает синхронному вызову. Какой-нибудь extension-метод, который перекрыл системный, или класс. Или просто установленный контекст синхронизации. Или хитрый аспект, который что-то нарушил.


                                                                                                Ну, или же просто какой-то необычный рантайм.

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

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

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

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

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


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

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

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

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

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

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


                                                                                                        Получается, что в 1 случае из 100 программа ведёт себя не так, как должна себя вести. Это типичный пример неверного предположения о работе программы в многопоточной среде, которое может привести к очень неприятным и трудноуловимым ошибкам.
                                                                                      • 0
                                                                                        Здравствуйте, а можно где-то посмотреть на код вашей реализации планировщика?
                                                                                      • +2

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