Вы должно быть знаете, что в последнее время Microsoft старается общаться с сообществом, получать как можно больше отзывов и внедрять их в жизнь. Не обошла эта мода и команду разработчиков C#. Разработка новой версии языка определенно не идет за закрытыми дверями…
Статья Mads Torgersen под названием What’s New in C# 7.0 уже разобрана вдоль и поперек. Но есть что-то, что в ней не было упомянуто.
Предлагаю вам пройтись обзором по нескольким предложениям из репозитория Roslyn. Название темы C# 7 Work List of Features. Информация обновлялась пару месяцев назад (то есть она не сильно актуальная), но это то, что определенно было не обделено вниманием Mads Torgersen.
Даже дважды, в рубриках Strong interest и Non-language упомянуты следующие предложения:
1. Enable code generating extensions to the compiler #5561
2. Add supersede modifier to enable more tool generated code scenarios #5292
Фактически, это предложение добавления в C# некоторого функционала аспектно-ориентированного программирования. Я постараюсь передать в сокращенном виде суть.
Добавление генерирующего код расширения для компилятора
Хотелось бы заметить, что на github это предложение помечено как находящееся в стадии разработки. Оно предлагает избавится от повторяющегося кода с помощью Code Injectors. При компиляции определенный код будет добавлен компилятором автоматически. Что важно: исходный код не будет изменен, а также внедрение кода будет происходить не после компиляции в бинарный файл.
В описании указано, что процесс написания инжектора будет похож на написание diagnostic analyzer. Я же надеюсь, что все будет гораздо проще (в таком случае классы можно будет создавать и редактировать вручную). Но, возможно, что это будет удобно делать только с помощью инструментов, которые генерируют код автоматически.
В качестве примера приведен класс, который в свою очередь внедряет в каждый класс проекта новое поле, — константу типа string с именем ClassName и значением, содержащим имя этого класса.
[CodeInjector(LanguageNames.CSharp)]
public class MyInjector : CodeInjector
{
public override void Initialize(InitializationContext context)
{
context.RegisterSymbolAction(InjectCodeForSymbol);
}
public void InjectCodeForSymbol(SymbolInjectionContext context)
{
if (context.Symbol.TypeKind == TypeKind.Class)
{
context.AddCompilationUnit($@"partial class {context.Symbol.Name}
{{ public const string ClassName = ""{context.Symbol.Name}""; }}");
}
}
}
Инжекторы кода изначально будут иметь возможность только добавить какой-нибудь код, но не изменить существующий. С их помощью можно будет избавиться от такой шаблонной логики, как, например, реализация INotifyPropertyChanged.
А как можно ускорить реализацию INotifyPropertyChanged сейчас? АОП фреймворк PostSharp фактически сделает это самое внедрение Walkthrough: Automatically Implementing INotifyPropertyChanged
В этом случае код реализации будет скрыт. Не ошибусь, если напишу, что некоторые изменения PostSharp совершает уже в после компиляции в коде IL (How Does PostSharp Work). Доподлинно известно, что эта утилита довольно продвинутая в плане изменения IL кода.
Но если компилятор будет иметь возможность распознать атрибут и добавить код самостоятельно до или во время компиляции, то это должно лучше сказаться на производительности и добавит возможность кастомизации.
Вот так выглядит простой класс, реализующий интерфейс INotifyPropertyChanged. Фактически в нем только одно свойство Name, но очень много лишнего кода реализации:
class Employee: INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
RaisePropertyChanged();
}
}
private void RaisePropertyChanged([CallerMemberName] string caller="")
{
if( PropertyChanged != null )
{
PropertyChanged(this, new PropertyChangedEventArgs(caller));
}
}
}
Все это можно будет заменить таким небольшим классом:
[INoifyPropertyChanged]
public class Employee
{
public string Name { get; set; }
}
Или, может быть, можно будет придумать свою конвенцию наименований и все классы в названии которых будет INPC будут реализовывать INoifyPropertyChanged. Получилось бы еще короче:
public class EmployeeINPC
{
public string Name { get; set; }
}
Но это я уже немного дал волю фантазии.
Superseding members — замещение модификатора для того, чтобы дать больше возможностей инструментам генерации кода
Эта фича разрешает членам класса переопределять члены этого же класса, замещая декларацию класса новой декларацией, используя модификатор supersede.
На примере должно быть понятнее, чем на словах:
// написанный пользователем код
public partial class MyClass
{
public void Method1()
{
// что-то чудесное здесь происходит
}
}
// код сгенерированный инструментом
public partial class MyClass
{
public supersede void Method1()
{
Console.WriteLine("entered Method1");
superseded();
Consolel.WriteLine("exited Method1");
}
}
В данном случае Method1, который был сгенерирован инструментом замещает Method1, который был написан пользователем. Все вызовы Method1 будут вызывать код созданный инструментом. Код, который создан инструментом может вызывать пользовательский код с помощью ключевого слова superseded. То есть, выходит, что в данном примере мы имеем автоматическое добавление логов в Method1.
Используя эту технику, реализация INotifyPropertyChanged может получиться такой вот:
// написанный пользователем код
public partial class MyClass
{
public int Property { get; set; }
}
// код сгенерированный инструментом
public partial class MyClass : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public supersede int Property
{
get { return superseded; }
set
{
if (superseded != value)
{
superseded = value;
var ev = this.PropertyChanged;
if (ev != null) ev(this, new PropertyChangedEventArgs("Property"));
}
}
}
}
Под ключевым словом superseded здесь уже фигурирует не метод, а свойство.
Асинхронный Main
Это предложение также помечено на github, как находящееся в стадии разработки. Внутри метода Main будет разрешено использование await. Делается это по причине того, что есть множество программ, внутри которых содержится подобная конструкция:
static async Task DoWork() {
await ...
await ...
}
static void Main() {
DoWork().GetAwaiter().GetResult();
}
В данном случае возможно прерывание работы программы по причине возникновения исключения. В случае же использования DoWork().Wait() любая возникшая ошибка будет преобразована в AggregateException.
Так как CLR не поддерживает асинхронные методы в качестве точек входа, то компилятор сгенерирует искусственный main метод, который будет вызывать пользовательский async Main.
Разработчикам зачастую требуется этот функционал и при реализации вручную они могут совершать ошибки. С этой точки зрения добавление этой фичи очевидный плюс. Минусом является то, что неопытные разработчики могут использовать асинхронность и там где нужно и там где ненужно.
Не факт, что эти фичи появятся в C# 7.0, и даже не факт, что они появятся в 8.0, но они определенно рассматривались командой разработчиков языка как потенциально интересные.
В анонсе Visual Studio 2017 упоминается еще одно предложение Task-like return types for async methods, которое точно будет в 7-ой версии языка.
Присоединяйтесь к обсуждению на GitHub или пишите комментарии здесь.
Комментарии (159)
Durimar123
05.12.2016 15:48-13Лично я бы проголосовал за
1 Множественное наследование.
2 virtual static functionimpwx
05.12.2016 15:59+1Эти фичи требуют поддержки не только компилятора, но и самого рантайма.
P.S. Я лично против множественного наследования в том виде, как это сделано в C++. Если делать что-то подобное, то лучше реализовать систему mixin'ов.
DistortNeo
05.12.2016 15:59+4Нет. Множественное наследование с успехом заменяется интерфейсами, но при этом не привносит связанных с множественным наследованием проблем. А понятия виртуальной статической функции не существует в природе.
Durimar123
05.12.2016 16:10>А понятия виртуальной статической функции не существует в природе.
Как же не существует, если мы ее обсуждаем?
И не вижу причин, по которым невозможно, это реализовать в C#. (это конечно не значит, что таких причин не существует)
Sirikid
05.12.2016 16:32+4Можно ссылку на описание понятия виртуальной статической функции?
Durimar123
05.12.2016 20:06-6Понятие «виртуальная статическая функция», по моему интуитивно понятно, это статическая функция которую можно наследовать.
DistortNeo
05.12.2016 20:18+8Поясните на примере, мне очень интересно, что вы имеете в виду. Поначалу я подумал, что вы имели в виду статический полиморфизм (шаблоны C++), но слово «static» однозначно даёт понять, что вы имеете в виду что-то непонятное.
Слово «виртуальная» означает, что:
— функция может быть переопределена в производных классах.
— конкретная реализация функции определяется в момент исполнения программы в зависимости от объекта, у которого она вызывается.
Вы можете наследовать статические классы и вызывать в них функции базовых классов — никто этого не запрещает. Вы можете сокрыть функцию с той же сигнатурой. Но вы не можете сделать статическую функцию виртуальной, потому что нет объекта, который бы отвечал за выбор конкретной реализации функции.ApeCoder
06.12.2016 08:31-1Наверное, как объектно-ориентированных языках, класс, это тоже такой объект.
типа
abstract class Animal{ abstract virtual static string GetDescription(); } class Dog{ static string GetDescription() { return "собака"; } } var animalClasses = new []{Dog, Cat, Squirell}; Animal CreateByDescription(string description) { return animalClasses .Where(x => x.GetDescription() == descriuption) .First() .CreateNewInstance(); }
Sirikid
06.12.2016 11:39-1В C# класс это не объект, у классов есть представление в виде объекта, но это не они сами.
ApeCoder
06.12.2016 13:25+1Речь идет об изменении языка => можно ввести такое
lair
06.12.2016 13:39Для этого вам придется не только язык поменять, но и солидный кусок CLR.
ApeCoder
06.12.2016 13:54Не факт. Можно какой-нибудь трюк типа автобоксинга сделать. При приведении к типу MetaClass генерировать какой-нибудь класс с членами статическими методами.
lair
06.12.2016 13:55… которые хранить где?
ApeCoder
06.12.2016 15:14Не знаю. Может быть в сборке с классом. Тогда, наверное, надо его генерить по атрибуту а не при приведении. Вы лучше меня знаете дотнет, придумайте сами :)
lair
06.12.2016 15:23Я просто не могу придумать никакого сценария, при котором я обращаюсь именно к статическому методу в терминах CLR (т.е.
<typename>.<method>
) и при этом у меня работает subclass polymorphism.
Есть дженерики, конечно, но там на момент развертывания известен точный тип.
mayorovp
06.12.2016 16:59SuperType.classType t = SubType.class; t.StaticVirtualMethod();
Мне такая идея тоже не нравится — но это не значит что она нереализуема.
Deosis
07.12.2016 07:55
Статические виртуальные методы можно имитировать с помощью фабрик.AnimalFactory[] factories = new[]{ new CatFactory(), new DogFactory(), new SquirellFactory(), }; Animal CreateByDescription(string description) { return factories .Where(x => x.Description == description) .First() .Create(); }
Как бонус мы получаем состояние, (почти) не используя статических полей. Также возможность иметь несколько настраиваемых фабрик и передавать их параметром. И отдельную фабрику для тестов.
lair
07.12.2016 11:24Ну оно больше и не выглядит как статический метода, а выглядит как инстанс. Я об этом и говорю.
AnarchyMob
07.12.2016 20:13Тот код который вы привели можно спокойно реализовать при помощи рефлексии, то бишь атрибутов, и не нужно всякого там статического полиморфизма...
Krypt
07.12.2016 23:04Не соглашусь. Знаю пару случаев использования C# как скриптового языка (с помощью Roslyn). И рефлексия там запрещена из соображений безопасности.
Krypt
06.12.2016 10:02Ну, кстати, статические методы в интерфейсах были бы крайне полезны. Как пример использования — десериализация.
interface IDeserealizable
{
static IDeserealizable deserialize(IDeserializer deserializer)
}
А ещё:
— указание нескольких интерфейсов в качестве типа поля
— реализация интерфейсов через расширение
Собственно, это фичи Objective-C.Krypt
06.12.2016 10:16Как, собственно, и «виртуальные статические функции». В том плане, что статические функции в ObjC перегружать можно.
В случае с C# — не знаю, никогда не пытался :) Но сама затея в C# на данный момент выглядит безнадёжной по той причине, что для статических методов почти нет фич, где бы их можно было использовать.
Durimar123
06.12.2016 11:40>Но вы не можете сделать статическую функцию виртуальной, потому что нет объекта, который бы отвечал за выбор конкретной реализации функции.
Для статической функции объект совсем не обязателен, вполне хватит и самого класса.
Похоже, что действительно возникло недопонимание в определении, что такое virtual static, даю пример
class A
{
public virtual static string info() {return «a»;}
}
class A2:A
{
public virtual static string info() {return «a2»;}
}nightwolf_du
06.12.2016 13:46+2А зачем?
Вне зависимости от того, что именно вы хотите сделать — это можно сделать через сокрытие (new на функции)
class A { public static string info() {return "a";} } class A2:A { public new static string info() {return "a2";} }
С точки зрения языка, виртуальная статическая функция — бессмысленна, виртуальные методы нужны для маршрутизации с вызовом типа, который на самом деле лежит под базовым типом/интерфесом.
При вызове статической функции — тип строго определен, маршрутизация типов не нужна.Durimar123
06.12.2016 16:50Понял, почему народ так удивлялся — C# просто не позволяет вызвать через экземпляра класса статическую функцию этого класса.
Тогда естественно виртуальность статик функции теряет смысл.
Так что появилось еще одно пожелание:
Возможность вызова статик функции через экземпляра класса.nightwolf_du
06.12.2016 17:06Тогда чем статическая функция будет отличаться от обычной?
Сейчас модификатор static — принадлежность функции типу целиком, а не экземпляру.
Она не должна знать ничего о «нестатических» полях/методах/св-вах.Durimar123
06.12.2016 17:12Я чуть о другом, вот такой код не работает:
class A {
public static string info() {return «a»;}
}
A a = new A();
string s = a.info(); < — тут багDistortNeo
06.12.2016 17:27Здесь вы просто ходите, чтобы в таблице виртуальных функций объекта существовали функции, которые не принимают this в качестве первого аргумента, например, из соображений вычислительных затрат. Но при этом эти функции можно вызывать и как статические — при явном указании типа компилятор просто подставит нужную реализацию.
Идея выглядит разумной. Но при этом не рассматривается вопрос вызова статической функции не по объекту, а по типу (generic).Sirikid
06.12.2016 17:37Не обязательно в VMT, в Java вызовы статических методов разрешаються во время компиляции, поэтому можно вызывать статические методы даже через null приведенный к необходимому типу (да, статические методы можно вызывать через объекты, но это бесполезно и порицается), возможно отчасти поэтому у нас и нету нормальных дженериков, но в C# дженерики другие и к ним прикрутить «виртуальные статические» функции скорее всего будет проще.
Durimar123
06.12.2016 17:56>Но при этом не рассматривается вопрос вызова статической функции не по объекту, а по типу (generic).
Почему же?
Для A a; одинаково должно вызываться и
A.info();
и
a.info();DistortNeo
06.12.2016 18:27+1Это почему же?
A a = new B();
A.info() — вызов статического метода класса A
a.info() — вполне может быть вызовом метода info класса BDurimar123
06.12.2016 18:37+1Конечно! В этом весь смысл.
В прочем я нашел способ решить эту проблему с помощью reflection, но решение мне не нравится.
Хочется возможностей самого языка, тем более, что новый синтаксис для этого не нужен.
А явных проблем, почему это нельзя сделать, лично я не вижу.qw1
08.12.2016 21:18Можно же просто добавить обычный виртуальный метод объекту, и из него вызвать статический метод.
Будет два метода: статический, который можно вызвать по имени класса, и аналогичный ему виртуальный, который можно вызвать через экземпляр. Оверхед на передачу ненужного параметра this в любом случае ниже, чем затраты на рефлексию.
lair
07.12.2016 11:25+1Возможность вызова статик функции через экземпляра класса.
Но зачем?
Durimar123
07.12.2016 11:45Как минимум, наличие возможности лучшее ее отсутствия. Тем более возможности которая никак не изменяет ни синтаксис, не идеологию.
А так это дает более объектно ориентированный доступ к параметрам и функциям которые меняются при наследовании, но не нуждаются для расчетов в самом объекте.
Это хорошо проявляется в задачах типа фабрика объектов.lair
07.12.2016 11:47Как минимум, наличие возможности лучшее ее отсутствия.
Нет. Слишком много возможностей нарушает целостность.
А так это дает более объектно ориентированный доступ к параметрам и функциям которые меняются при наследовании, но не нуждаются для расчетов в самом объекте.
Если они не нуждаются для расчетов в объекте — это не функции этого объекта. И именно поэтому синтаксис
объект.метод
по отношению к ним будет вводить в заблуждение.Durimar123
07.12.2016 12:07-5>Нет. Слишком много возможностей нарушает целостность.
Я так понимаю вы поклонник асемблера и противник библиотек и объектного программирования?
> Если они не нуждаются для расчетов в объекте — это не функции этого объекта.
Конечно — это функция класса. Но для того, что бы она вызвалась у объекта именно того класса которым он является, ее нужно вызывать непосредственно у этого экземпляра класса. Пояснить примером?lair
07.12.2016 12:19+1Я так понимаю вы поклонник асемблера и противник библиотек и объектного программирования?
Нет, я поклонник консистентных систем и противник избыточности.
Но для того, что бы она вызвалась у объекта именно того класса которым он является, ее нужно вызывать непосредственно у этого экземпляра класса.
Нет такого "нужно". Если вам нужна функция класса, вам нужно вызывать ее на классе. Типичный пример такого (на псевдоязычке) выглядел бы так:
yourObject -> getType -> someMethod
Durimar123
07.12.2016 12:42-2Ну а я за предоставление максимальных возможностей. Только не надо писать про выстрелы в ногу, это не тот случай.
>yourObject -> getType -> someMethod
Именно! Но зачем «getType ->» если его нужно писать всегда? Можно и пропустить двузначностей это не принесет.
Тем более «нормального» getType к сожалению нет :( и вроде делать его не собираются.
Или вас возмущает, что вызов функций у объекта и через ссылку на объект идет через "."?
Тогда да, тогда я понимаю вашу принципиальность.lair
07.12.2016 12:49+1Но зачем «getType ->» если его нужно писать всегда?
Не всегда, а только тогда, когда обращение идет к методам класса, а не объекта. И именно за этим и нужно — чтобы четко видеть, к какому объекту мы обращаемся.
Можно и пропустить двузначностей это не принесет.
Да ну?
yourObject -> Name
— имя чего я сейчас получу?yourObject -> setMaxCount(15)
— у чего ограничился пул?
Тем более «нормального» getType к сожалению нет
Так вот лучше сделать нормальный
getType
, который не нарушает парадигму, чем вводить неявный вызов статиков, который парадигму нарушает.Durimar123
07.12.2016 12:58>Да ну? yourObject -> Name — имя чего я сейчас получу?
Что функция вернет то и будет. Я имел в виду неоднозначностей для компилятора.
>Так вот лучше сделать нормальный getType
Это бесспорно «нормальный getType» без рефлектинга по мне тоже куда важнее. Хотя и проблем вызовом статиков я не вижу.
Похоже я больше склонен к универсальности и общему синтаксису, а вы за строгость и явные разграничения.DistortNeo
07.12.2016 13:03+2вы за строгость и явные разграничения.
И это правильно. Чтобы стрелять в ноги, у нас уже есть C++, оставьте C# в покое.Durimar123
07.12.2016 13:19>И это правильно. Чтобы стрелять в ноги, у нас уже есть C++, оставьте C# в покое.
:) Расскажите мистер паладин, чем именно ужасен вызов статика из объекта?INC_R
07.12.2016 13:33-2Например тем, что объект может оказаться null. Ловить NullReferenceException на статических методах — не очень большая радость. И вообще тогда какие отличия от экземплярных методов, кроме потери доступа к this в теле метода?
Durimar123
07.12.2016 13:48-4:) Вызов любой функции может дать NullReferenceException.
А самое забавное, что вызов статической функции НИКАК не может вызвать NullReferenceException.
>И вообще тогда какие отличия от экземплярных методов, кроме потери доступа к this в теле метода?
В том что их можно вызвать и без наличия экземпляра класса.INC_R
07.12.2016 13:55>> Вызов любой функции может дать NullReferenceException.
>> А самое забавное, что вызов статической функции НИКАК не может вызвать NullReferenceException.
Вы противоречите сами себе.
class A { public static virtual info() {...}; }
class B: A { public static override info() {...}; }
A a = null;
a.info(); // что произойдет по-вашему?Durimar123
07.12.2016 14:14Отработает функция, она же обращаться к полям не будет.
INC_R
07.12.2016 14:21Какая? Класса A или класса B?
Если речь о том, чтобы сделать две одинаково работающих возможности вызова статик метода: A.info() и a.info(), то действительно работать будет. Только непонятно, зачем.
Но о виртуальных статических методах можно забыть из-за возможного null.Durimar123
07.12.2016 15:36>Какая? Класса A или класса B?
Конечно класса А.
А если (B(null)).info(); то от B; Что вас смущает?
>Но о виртуальных статических методах можно забыть из-за возможного null.
Откуда null, в статик функции? Даже у виртуальной.INC_R
07.12.2016 15:45+1>> А если (B(null)).info(); то от B; Что вас смущает?
Здесь — ничего. А если так:
A a = new B(); a.info();
Должен вызваться метод A.info() или B.info()?
>> Откуда null, в статик функции? Даже у виртуальной.
А как CLR должен в рантайме определить, виртуальную статическую функцию какого класса вызывать? Видимо, по экземпляру класса. Если он null, все плохо. Если не по экземпляру, то как еще?Durimar123
07.12.2016 15:51-1>А если так: A a = new B(); a.info();
B.info() или есть сомнения?
>Если он null, все плохо
Все хорошо — тип объекта то есть. Вот по типу и вызовет.
INC_R
07.12.2016 15:59+1>> Все хорошо — тип объекта то есть. Вот по типу и вызовет.
Вот в этом и проблема. Типа объекта нет.
1. Во время компиляции конкретный тип объекта неизвестен.
2. Во время исполнения у вас, грубо говоря, в памяти 0 (т.е. null). Метаданные связываются с конкретным экземпляром класса, а не со ссылкой на него. Соответственно, по null ссылке вы никак не узнаете, на экземпляр какого типа она указывает.Durimar123
07.12.2016 16:09-5Скорее всего у вас нет специфических знаний по вызовам виртуальных функций в рантайме.
Поэтому давайте решим так, если бы вы реализовывали вызовы виртуальных функций вы бы не смогли сделать вызов функций у null объектов.INC_R
07.12.2016 16:14+3Скорее всего у вас нет специфических знаний по вызовам виртуальных функций в рантайме.
Так просветите, интересно же.
Мне вот кажется, что это вы не знаете, как CLR хранит ссылки на объекты и сами объекты. Если бы со ссылкой связывалась информация о типе объекта, тогда да, проблем нет. Но информации о типе у ссылки нет.Durimar123
07.12.2016 16:51Значит будет NullReferenceException, как и у обычных функций.
INC_R
07.12.2016 16:59О чем и речь. Мы получили экземплярный виртуальный метод. Зачем тогда делать статический метод виртуальным, чтобы он вел себя ровно так же, как экземплярный? Причем это полное нарушение концепции статических методов.
Durimar123
07.12.2016 17:12В этом и есть смысл — единообразие вызовов статик методов и экземпляр методов. С возможностью вызова base метода.
INC_R
07.12.2016 17:22+1О каком единообразии может идти речь, когда это фундаментально разные вещи? Такое единообразие мозг сломает в момент. И еще раз, нельзя называть метод статическим, если он требует наличия созданного экземпляра класса для своего вызова.
Можно вести речь о том, чтобы заиметь в языке что-то вроде "класса как объекта", и писать, например:
class A { static virtual void foo() {...}} class B : A { static override void foo() {...}} Class<A> a = A; a.foo(); // вызывает A.foo() a = B; a.foo(); // вызывает B.foo() a = (new B())->class; a.foo(); // вызывает B.foo()
Но вызывать статику на экземплярах — увольте.
Durimar123
07.12.2016 17:39Хорошо, например есть набор классов A1 A2 А3 и т.д. порожденные от А.
у них общий набор типовых характеристик например
static Name, Type, Width, Height, у каждого класса свои значения. И возможно они зависит от расчета в родителе.
Эти данные используются как без объектов, так и внутри объектов.
Как удобней писать? в месте каждого вызова A1.Width() или просто Width()?
И сколько будет ошибок при copy/paste.
А если бы у этих статик функций было наследование, то можно было бы общую часть их обработки вообще отдать родителю:
Например классу А реализовать функцию
static int Perimetr() {return (Width() + Height())*2;}
Которая нормально работала бы для все наследников.INC_R
07.12.2016 17:57у них общий набор типовых характеристик например
static Name, Type, Width, Height, у каждого класса свои значения. И возможно они зависит от расчета в родителе.Почему бы не сделать какое-нибудь статическое свойство Meta, куда в статическом конструкторе положить экземпляр класса характеристик с нужными значениями, привязкой к базовым характеристикам и т.п.? Будет у вас A1.Meta.Width, например. Реализация чуть сложнее, но снаружи разницы особо нет. Можно добавить еще экземплярное свойство Meta, которое просто возвращает значение статического. Тогда и на экземплярах удобно будет звать статику.
Как удобней писать? в месте каждого вызова A1.Width() или просто Width()?
По мне, удобнее писать одни и те же вещи одинаково, и разные вещи по-разному. Именно поэтому для статики писать везде A1.Width лучше, чем где-то A1.Width, а где-то a.Width (который статический, но пишется почему-то как экземплярный).
Durimar123
07.12.2016 19:35Как-то сомнительно звучит, думаю не получится.
class A
{
public static string inf;
}
class A2: A
{
}
A.inf = «a»;
A2.inf = «a2»;
после такого в A.inf будет «a2».
Или я не так понял идею?INC_R
08.12.2016 04:26Немного не так:
class A { public static string inf; } class A2 : A { public static new string inf; } A.inf = "a"; A2.inf = "a2";
Хотя решение не очень красивое, конечно.
Если в более общем виде, то такusing System; class Meta { string _meta; public Meta(string meta) { _meta = meta; } public override string ToString() { return _meta; } } class A { public static Meta meta; } class A1 : A { public static new Meta meta; } class A2 : A { public static new Meta meta; } public class Program { public static void Main() { A1.meta = new Meta("a1"); A2.meta = new Meta("a2"); Console.WriteLine(A.meta); Console.WriteLine(A1.meta); Console.WriteLine(A2.meta); } }
DistortNeo
07.12.2016 18:43Пожалуйста:
interface IDerivedStatic { int Width(); int Height(); } class A<T> where T : struct, IDerivedStatic { static T data = new T(); public static int Width() => data.Width(); public static int Height() => data.Height(); public static int Perimetr() => Width() * Height(); } class A1 : A<A1.Static> { public struct Static : IDerivedStatic { public int Height() => A1.Height(); public int Width() => A1.Width(); } static new int Width() => 12; static new int Height() => 14; }
Использование struct заставляет JIT генерировать отдельный для каждого типа, а не вызывать интерфейс + нет оверхеда по памяти.
Делал замеры — всё инлайнится, производительность идентична прямому вызовы.Durimar123
07.12.2016 20:00Биг сенскс!
Хоть и выглядит куда более сложно чем через virtual / override. Но похоже это решает проблему переопределения статик значений.
DistortNeo
07.12.2016 17:57Да-да, именно так. При взгляде на код должно быть чётко понятно, где вызывается метод объекта, а где — статическая функция. Именно поэтому нельзя смешивать этот синтаксис.
Если над идеей статических виртуальных функции хорошо подумать, то может, что-то годное и выйдет. Одной из возможных реализаций этого механизма была бы следующая: GetType() возвращает не Type, а унаследованный от Type объект, содержащий вызовы статических методов. А для упрощения синтаксиса можно добавить ключевое слово какое-нибудь (a.class — см.выше).
То есть
class A { static virtual void foo() { ... } }
разворачивается в
class A.Static: Type { virtual void foo() { A.foo(); } } } class A { static void foo() { ... } A.Static GetType(); }
И используется так, например:
void Generic<T>(T a) where T: A { a.GetType().foo(); } void Generic<T>() where T: A { // typeof(T) возвращает объект T.Static, приведённый к A.Static // это можно сделать точно так же, как реализован механизм new T() typeof(T).foo(); }
В таком виде я был бы даже не против увидеть это в языке.Durimar123
08.12.2016 16:32-2Еще раз спасибо за пример, натолкнул на некоторые идеи.
Но чисто умозрительно, как бы выглядело все с моим подходом:
class A
{
public virtual static int Width() => 10;
public virtual static int Height() => 11;
public static int Square() => Width() * Height();
}
class A1: A
{
pubilc override static int Width() => 12;
pubilc override static int Height() => 14;
}
По моему проще.
И вызовы были единообразные
A1.Square();
и
A a1 = new A1();
a1.Square();
вообще красота (по мне) :)|
DistortNeo
07.12.2016 17:25+1Теоретически какой-то смысл в этом есть: вызывать виртуальный метод, не передавая указатель на объект — меньше на 1 параметр и можно вызвать метод у пустого объекта, передавая голый vfptr, типа A.class.init().
Практически же передача одного параметра (ещё и через регистр) — ничто по сравнению с виртуальным вызовом (переходом по адресу), а язык и CLR усложнит.
lair
07.12.2016 14:45+1Что функция вернет то и будет. Я имел в виду неоднозначностей для компилятора.
Меня мало волнует компилятор, меня волнует, как я это читать буду. И вот мне это читать очень неудобно.
Это бесспорно «нормальный getType» без рефлектинга по мне тоже куда важнее.
Вообще-то
GetType
и сейчас не использует Reflection.Durimar123
07.12.2016 15:30На вкус и цвет фломастеры разные.
Но для того что бы использовать Type для вызова функций нужен Reflection.lair
07.12.2016 15:32Но для того что бы использовать Type для вызова функций нужен Reflection.
Не так. Использование метаданных для (например) позднего вызова функций — это и есть reflection, как подсистема CLR.
Sirikid
06.12.2016 12:03Виртуальная статическая функция не лучшее название, хотя в какой-то мере отражает суть явления.
DistortNeo в чем то ApeCoder прав, вызовы виртуальных статических членов должны разрешаться от типа, а не от объекта. (Можно даже сказать от инстанса тайпкласса :^) )
Набросал такой примерчик (1) реализации (2), прошу извинить если накосячил с синтаксисом, я скорее джавист чем шарпист.
1: http://pastebin.com/dpPEPzc8
2: https://wiki.haskell.org/MonoidDistortNeo
06.12.2016 15:20Спасибо! Вместе с примером с IDeserealizable теперь действительно понятно, что это такое и как может применяться. Единственная область применения этого механизма — вызов статических методов для generic типов. Сейчас же такая возможность предусмотрена только для конструкторов (new()).
Реализация статических интерфейсов потребует довольно серьёзное изменение CLR, на что MS пойти не сможет точно.
Прямо сейчас этот функционал можно сэмулировать путём создания дженерик класса со статическим полем — делегатом на статический метод, который создаётся единожды через рефлексию в статическом конструкторе, что-то вроде:
struct Deserializer<T> where T: IDeserializable { public static readonly Func<T> deserialize(IDeserializer deserializer); static Deserializer() { deserialize = что-то типа typeof(T).GetMethod("deserialize", BindingFlags.Static | BindingFlags.Public).CreateDelegate(typeof(Func<T>)) as Func<T>; } }
и вызывать как
Deserializer<T>.Deserialize(...);
Krypt
06.12.2016 20:18> Единственная область применения этого механизма — вызов статических методов для generic типов.
Generic-типов и интерфейсов
Для полного счастья нужна возможность реализовывать интерфейс используя расширения и указывать несколько интерфейсов для поля (без указания класса).
И тогда статические методы становятся мощным инструментом для написания фабрик с простым синтаксисом из без магии рантайма внутри.
Serginio1
06.12.2016 15:05+1В Delphi например есть метаклассы
;class function ObjectName(AObject: TObject): String; virtual
Нужны ли метаклассы????
метаданные объектовSerginio1
06.12.2016 15:09Те кто работал с Delphi знают и применяют так или иначе виртуальные конструкторы, статические виртуальные методы (class virtual) и конструкции языка типа
TVirtClass = class of TBaseVirtClass.
В Delphi метакласс это ссылка на VMT и все виртуальные методы класса (не экземпляра класса) располагаются с отрицательным смещением
И все метаклассы наследуются от
TClass= class of TObject. { Virtual method table entries } vmtSelfPtr = -76; vmtIntfTable = -72; vmtAutoTable = -68; vmtInitTable = -64; vmtTypeInfo = -60; vmtFieldTable = -56; vmtMethodTable = -52; vmtDynamicTable = -48; vmtClassName = -44; vmtInstanceSize = -40; vmtParent = -36; vmtSafeCallException = -32 deprecated; // don't use these constants. vmtAfterConstruction = -28 deprecated; // use VMTOFFSET in asm code instead vmtBeforeDestruction = -24 deprecated; vmtDispatch = -20 deprecated; vmtDefaultHandler = -16 deprecated; vmtNewInstance = -12 deprecated; vmtFreeInstance = -8 deprecated; vmtDestroy = -4 deprecated;
Serginio1
05.12.2016 16:01Кстати удобно для ViewModel удобно использовать PropertyChanged.Fody Сокращаем объем кода с помощью Fody
DistortNeo
05.12.2016 16:02+2Лично я бы хотел видеть возможность более широкой перегрузки операторов.
Например, есть дженерик класс, но оператор хочу определить только для конкретных типов.
Почему я в этом случае могу определить метод-расширение Add, не не могу — operator +? Почему нельзя сделать так, чтобы при ненайденном операторе + автоматически искался бы метод-расширение Add?impwx
05.12.2016 18:40+1Скорее всего, это вопрос затрат и удобочитаемости. Тем более, что это можно уже сделать с помощью интерфейсов.
А вот generic constraint для числовых типов был бы очень кстати. Так можно было бы писать обобщенные арифметические функции без извращений типаdynamic
:
public T Add<T>(T a, T b) where T: numeric // например, так { return a + b; }
lair
05.12.2016 18:42+3Для этого нужен не
T: numeric
, аT: has (T + T)
. И в F#, кстати, такое есть.impwx
05.12.2016 19:06С точки зрения рантайма обобщенной операции сложения не существует, поэтому F# использует какой-то хитрый ход. Не уверен, но скорее всего типы разрешаются в момент компиляции, т.е. создается не честная generic-функция, а по конкретной реализации на каждый использованный при вызове этой функции тип
T
. Либо под капотомdynamic
, хотя очень сомневаюсь.
После работы с Typescript вообще начинает не хватать структурной типизации. Но сомневаюсь, что ее приделают в C# в каком-либо обозримом будущем.lair
06.12.2016 02:24+2Обобщенного
numeric
в рантайме тоже нет. А уж если мечтать, то ограничения по методам намного удобнее.
DistortNeo
05.12.2016 19:01Скорее всего, это вопрос затрат и удобочитаемости. Тем более, что это можно уже сделать с помощью интерфейсов.
И чем же a.Add(b.Multiply©) лучше a + b * c?DistortNeo
05.12.2016 19:10Написал идею:
https://visualstudio.uservoice.com/forums/121579-visual-studio-ide/suggestions/17335231-extend-operator-overloading
impwx
05.12.2016 19:16Например тем, что можно перейти к реализации метода с помощью ctrl+click и сразу узнать, что именно он делает. А к телу перегруженного оператора, насколько я знаю, так просто не перейдешь.
Вообще дизайн стандартной библиотеки C# склоняется к методам и интерфейсам. Если хотите примеров того, насколько неудобочитаемым становится код при активном использовании (кастомных) операторов — вот, например, когда-то мы писали парсер на F#…Sirikid
05.12.2016 19:58Точно что там происходит я не скажу, но в общем понятно, такие API для создания парсеров довольно распространенная штука.
Free_ze
07.12.2016 10:43+1В идеале не нужно ходить к телу перегруженного оператора, он должен быть семантически очевиден. Если это не так, то перегружать оператор для этого типа не стоит.
zenkz
05.12.2016 18:08+1CodeInjector выглядит круто — Никогда не понимал этот гемморой с INotifyPropertyChanged, а тут вполне карасивое решение.
Ещё мне очень нехватает строковых Enum-ов. Простая вещь, а сделает код более красивым и удобным в написании.TheShock
06.12.2016 09:40Ещё мне очень нехватает строковых Enum-ов
А можно пример?zenkz
06.12.2016 17:34Пожалуйста
Вместо:
public static class StringEnum
{
public const string ValueA = «A»;
public const string ValueB = «B»;
}
public string Field {get; set;} = StringEnum.ValueA;
Сделать так:
public enum StringEnum
{
ValueA = «A»,
ValueB = «B»
};
public StringEnum Field {get; set;} = StringEnum.ValueA;
Преимущества:
— одинаковый подход к перечисляемым типам
— точно знаешь что присваивать в поле (т.к. в текущей реализации — это просто строка и можно присвоить что угодно, а в предлагаемой — только значения из списка)
Может быть вообще можно расширить до объектного enum-а (т.е. создавать перечисления любого типа) — не думаю что это будет часто-используемая фича, но вполне полезная.Krypt
06.12.2016 20:24Строка — не перечисляемый тип.
В каких случаях второй пункт может быть использован? Почему нужно присваивать именно строку, а не значение enum, которое позже преобразуется в строку?
TheShock
07.12.2016 02:52Преимущества:
— одинаковый подход к перечисляемым типам
— точно знаешь что присваивать в поле (т.к. в текущей реализации — это просто строка и можно присвоить что угодно, а в предлагаемой — только значения из списка)
А почему бы просто не пользоваться Енумом в том виде, который сейчас есть? Ну вот допустим
enum VehicleType { Light, Medium, Heavy } class Vehicle { private readonly VehicleType type; public Vehicle (VehicleType type) { this.type = type; } public string GetImageUrl () { return "/images/" + type + ".png"; } } new Vehicle(VehicleType.Heavy).GetImageUrl(); // "/images/Heavy.png"
Меня больше огорчает, что енум не наследуется. Например у меня есть аддон, который расширяет оригинал и я хотел бы добавить типы:
enum ExtendedVehicleType : VehicleType { Spg, Spatg } new Vehicle(VehicleType.Spg);
Приходится для такого делать костыль вместо енума, а это имеет ряд своих недостатков:
class VehicleType { public static readonly Light = new VehicleType("Light"); public static readonly Medium = new VehicleType("Medium"); public static readonly Heavy = new VehicleType("Heavy"); // .. }
Может есть какой-либо вменяемый способ это использовать в c#?Free_ze
07.12.2016 09:37А почему бы просто не пользоваться Енумом в том виде, который сейчас есть?
Как бы сделать (де)сериализацию того, что нельзя выразить идентификатором C# (пробелы, пунктуация и прочее)? Конечно, сейчас можно навесить кучу аттрибутов под каждый используемый сериализатор, но это смотрится плохо.nightwolf_du
07.12.2016 11:45Я видел два варианта, кроме идентификаторов
Привычный — объявить enum в классе и рядом положить статический Dictionary<enum, string > с нужными строками(некрасиво)
Красивый — использовать атрибуты.Free_ze
07.12.2016 11:54Атрибуты терпимы, когда сериализатор один. Зоопарк из атрибутов сложно назвать красотой.
nightwolf_du
07.12.2016 15:33В данном случае тип атрибута — один, например можно для этого использовать DescriptionAttribute(он стандартный).
Другое дело, что
public static string GetDescription(this Enum value) { DescriptionAttribute[] descriptionAttributeArray = (DescriptionAttribute[]) value.GetType().GetField(value.ToString()).GetCustomAttributes(typeof (DescriptionAttribute), false); ...
работает медленно, и enum вида
private enum Verdict : byte { [Description(" ")] Space, [Description(",")] Comma }
не всех стилистически радует.Free_ze
07.12.2016 15:50Вы уверены, что все сериализаторы ищут DescriptionAttribute на каждом enum и с радостью будут преобразовывать enum <--> string? Или мне самому их обучать?
nightwolf_du
07.12.2016 16:00Всё, наконец-то я понял, что вы имеете ввиду, у вас есть некие стандартные сериализаторы в некие строковые файлы, и сериализация в Enum-овское нестроковое value или "." вас не устроит, нужно чтобы value было строковым.
Тогда да, перечислимые строки были бы решением, если просто строковые константы не устраивают.
Krypt
07.12.2016 15:34А так у вас будет зоопарк энумов, если сериализаторы называют поля по разному.
Free_ze
07.12.2016 16:00Сериализаторам без разницы, как называются поля. Допустим, что это JSON, они знают, что тип — enum, что у него строковый базовый тип => его нужно сериализовать в соответствующую строку. Выглядело это бы так:
enum MyStrings : string { One = "One with whitespaces", Two = "Two with whitespaces", Three = "Three with whitespaces" } MyStrings enVal = MyStrings::One; var json = JsonConverter.Serialize<string>(enVal);
Krypt
07.12.2016 16:13Ну во-первых, всё таки
var json = JsonConverter.Serialize<MyStrings>(enVal);
А во вторых — с аттрибутами он может выглядеть точно так же.
upd:
На самом деле вот так:
var json = JsonConverter.Serialize(enVal);
Тип шаблона может резолвиться автоматом.Free_ze
07.12.2016 16:19Да, верно. С атрибутами он может выглядеть подобно. Только будет с атрибутами)
И нет таких стандартных атрибутов, которые я бы мог использовать везде. Поэтому будут вешать свой кастомный для каждого сериализатора. И в этом соль.
eugenebb
05.12.2016 18:55Голосуйте за свои предпочтения в развитии C# (там же ссылки на другие проекты от MS)
https://visualstudio.uservoice.com/forums/121579-visual-studio-ide/category/30931-languages-c
rikert
05.12.2016 20:24-5Асинхронный Main
В C# очень неудачная реализация async/await, идея хорошая но перемудрили конкретно. Поэтому в Main всё это не работает, хотя и должно по задумке. Весь интернет пестрит непонятками.
Что касается генерации кода это прекрасно, однако как я могу судить — снова перемудрили и теперь нужно будет при отладке большого проекта ещё и искать «кто же включает очередную функцию в этот класс». Самая верная реализация генерации это у Хаскеля.DistortNeo
05.12.2016 20:43+5А мне, наоборот, реализация асинхронности в C# очень нравится, а конкретно генерация state machine из асинхронных функций — руками их писать очень и очень тяжело. Альтернативой ей является использование более сложных в отладке fibers (например, coroutines в C++).
А неудачной я считаю реализацию планировщика. Главная прелесть асинхронности — выполнение всех задач в одном потоке, что существенно снижает расходы на межпотоковую синхронизацию и переключение между ними.
Но в C# используется либо ThreadPool (по умолчанию), либо циклы сообщений WinForms/WPF. Всё это не даёт большого выигрыша в производительности от использования асинхронности, а при низкой гранулярности задач производительность ещё и падает.
Как итог — в своих проектах я использую собственный планировщик и собственные обёртки над сокетами. Заодно получаю полноценную кроссплатформенность — при запуске под Windows используется IOCP, под Linux — epoll.smaugfm
06.12.2016 12:22Если вы напишете await asyncFunction() то не будет выделено нового потока, если внутри asyncFunction он нигде явно не выделяется (не выполняется Task.Run и т.д.). Сдесь показано как будет выполняться код в async методе: потоков не создается (если внутри GetStringAsync их не создается). А далее приводится пояснение:
Async methods are intended to be non-blocking operations. An await expression in an async method doesn’t block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method.
The async and await keywords don't cause additional threads to be created. Async methods don't require multithreading because an async method doesn't run on its own thread. The method runs on the current synchronization context and uses time on the thread only when the method is active. You can use Task.Run to move CPU-bound work to a background thread, but a background thread doesn't help with a process that's just waiting for results to become available.DistortNeo
06.12.2016 15:45Если вы напишете await asyncFunction() то не будет выделено нового потока, если внутри asyncFunction он нигде явно не выделяется
Это зависит от текущего SynchronizationContext. Обратите внимание, что по ссылке приведён конкретный пример работы асинхронных вызовов для Windows Forms.lair
06.12.2016 16:32Это зависит от текущего SynchronizationContext
Это зависит в первую очередь от того, что вернула
asyncFunction
, и если она вернула уже выполненный таск, тоSynchronizationContext
вообще не будет использован.DistortNeo
06.12.2016 16:40А если вернула незавершённую задачу (что является гораздо более частным случаем), то продолжение её выполнения таки зависит от SynchronizationContext.
Следующий код будет выдавать разный результат в зависимости от контекста:
Console.WriteLine(Thread.CurrendThread.ManagedThreadId); await Task.Yield(); Console.WriteLine(Thread.CurrendThread.ManagedThreadId);
Если оформить его как асинхронную функцию и вызвать напрямую из Main (Task.Run.Wait()), то вывод на экран будет разный.
Замечание 1: ManagedThreadId разный для разных потоков.
Замечание 2: вместо Task.Yield может быть любая задача, не являющаяся автоматически завершённой.mayorovp
06.12.2016 17:08Не совсем так.
Task.Yield
— это все-таки не задача, а требование уступить поток. Там отдельный Awaiter написан для него.
Если смотреть задачи — то они как раз работают довольно адекватно. При установленном контексте синхронизации метод в него возвращается. А без контекста синхронизации — метод продолжает исполняться в том же потоке, который задачу выполнил (исключение — если синхронное продолжение было явно запрещено в свойствах задачи).
DistortNeo
06.12.2016 17:51«Ожидать» с помощью await можно любой объект (не обязательно Task), имеющий метод GetAwaiter, возвращающий объект, имеющий три метода (OnCompleted, IsCompleted, GetResult). Один из методов — OnCompleted(Action continuation), вызывается по завершении задачи и приводит к продолжению выполнения кода, вызвавшего await.
Вот здесь можно посмотреть реализацию этого метода: если контекст задан, то вызывается функция помещения задачи в очередь из контекста (Post), а если контекта нет, то продолжение задачи планируется к выполнению в пуле потоков.
Аналогично можно посмотреть на Task.GetAwaiter, но там чёрт ногу сломит. В конечном итоге вызывается TaskContinuation.Run, который вызывает всё тот же Post или лезет в ThreadPool.mayorovp
06.12.2016 18:02Для YieldAwaiter приведенное поведение — ожидаемое!
Что же до TaskAwaiter, то тут все тоже просто, если разобраться. Если не было захвачено ни контекста синхронизации, ни планировщика задач — то используется класс AwaitTaskContinuation. Этот класс по возможности вызывает продолжение синхронно.
DistortNeo
06.12.2016 18:37Видимо, он это делает слишком редко, например, когда задача в момент выполнения await уже завершена. Просто вставьте в Main следующий код и посмотрите на результат:
Task.Run(async () => { Console.WriteLine(Thread.CurrentThread.ManagedThreadId); await Task.Delay(10); Console.WriteLine(Thread.CurrentThread.ManagedThreadId); }).Wait();
Но это не беда. Простой планировщик решает эту проблему.mayorovp
06.12.2016 19:23А как, по-вашему, этот код вообще может работать? После истечения 10ти миллисекунд выполнение обязано вернуться в пул потоков. Почему для вас так важно, чтобы это был тот же самый поток? Все потоки пула одинаковы.
Лучше посмотрите на вот этот код:
public static void Main() { A().Wait(); } static async Task A() { await B(); Console.WriteLine(Thread.CurrentThread.ManagedThreadId); } static async Task B() { await C(); Console.WriteLine(Thread.CurrentThread.ManagedThreadId); } static async Task C() { await Task.Yield(); Console.WriteLine(Thread.CurrentThread.ManagedThreadId); }
В данном случае, все три оператора Console.WriteLine выполнятся в одном и том же потоке, что показывает, что продолжения и правда вызываются синхронно.
DistortNeo
06.12.2016 20:02В данном случае, все три оператора Console.WriteLine выполнятся в одном и том же потоке, что показывает, что продолжения и правда вызываются синхронно.
Ага, только у меня периодически получается так:4 5 5
Почему для вас так важно, чтобы это был тот же самый поток?
Потому что я хочу, чтобы все задачи работали последовательно в одном потоке:
1. Нет затрат на синхронизацию между потоками — никаких блокировок и ожиданий, мьютексы и эвенты не нужны.
2. Нет затрат на переключение контекста между потоками в планировщике. Делегировать выполнение задачи в другой поток — это очень долго: за то время, пока поток-воркер проснётся, пока сообщит другому потоку о готовности результата, можно выполнить с сотню задач в основном потоке.
Когда я написал свой планировщик, пиковое количество выполняемых сетевых операций в секунду выросло больше, чем на порядок.mayorovp
06.12.2016 20:33"периодически" — это как?
DistortNeo
06.12.2016 20:45Это значит, что результат выполнения случайный. А другого эффекта в многопоточном приложении сложно ожидать.
mayorovp
07.12.2016 15:57Запустил код на трех разных компьютерах. Каждый раз все три числа совпадали… Вы что-то делаете не так.
DistortNeo
07.12.2016 16:25Многопоточность — она такая неожиданная. Вот вроде всё работает, а потом опа — race condition.
А код уже написан и оттестирован — тесты проходят, а в продакшене почему-то всё падает.
Если вы вызовете 3 раза подряд ThreadPool.QueueЧтоТоТам, то эти 3 задачи могут быть запущены как на одном потоке, так и на нескольких, никакой гарантии здесь нет.mayorovp
07.12.2016 16:29Не надо объяснять мне что такое race condition, я это знаю.
В коде, который я привел, окончания всех трех задач всегда выполняются в одном и том же потоке. Если они оказались в разных потоках — вы что-то сделали не так.
DistortNeo
07.12.2016 17:00В коде, который я привел, окончания всех трех задач всегда выполняются в одном и том же потоке
Если они всегда выполняются конкретно на вашем компьютере в одном и том же потоке, это ещё ничего не значит:
http://funkyimg.com/i/2kCET.png
Запустите 10, 20, 30 раз, попробуйте debug/release поменять, запускать без отладчика. Это именно race condition.mayorovp
07.12.2016 17:20Что такое Utilities.Asynchronius? Если убрать этот using и все лишние референсы — ошибка останется?
DistortNeo
07.12.2016 18:47Это мой планировщик от библиотечки. Да, на голом проекте эффект сохраняется. На разных системах, даже под Mono.
mayorovp
07.12.2016 19:00Вот как раз Mono — совсем не показатель, там запросто может быть более слабая реализация.
DistortNeo
07.12.2016 19:14Ну вот ещё что, зависеть от конкретной реализации конкретного фреймворка. Спасибо, не надо, мне это код ещё и поддерживать надо. Если в документации явно не указано конкретное поведение, то не стоит верить наблюдениям — они могут быть обманчивы.
mayorovp
07.12.2016 17:32Вот мой скриншот:
картинкаDistortNeo
07.12.2016 18:51-1Ага, 1, 3, 5, 7 — простые, значит, и 9 — тоже простое.
mayorovp
07.12.2016 19:01Выполнение программы детерминировано.
DistortNeo
07.12.2016 19:32Работа планировщика в многопоточной среде не является детерминированной.
Объясните же, а?
http://funkyimg.com/i/2kCUY.pngmayorovp
07.12.2016 20:32+1Оно не должно попадать в планировщик.
PS не буду вам больше отвечать, потому что вы спорите для того чтобы спорить, а не чтобы разобраться в чем дело.
DistortNeo
07.12.2016 21:42Конечно, не должен: по завершении задачи управление сразу передаётся в код, который вызывает continuation. Но почему в некоторых случаях continuation вызывается не сразу, а попадает в ThreadPool, мне не понятно, и я хочу в этом разобраться. Ваши же ответы выглядят как «ничего не знаю, у меня всё работает». Я же жду объяснений.
Скорее всего, причина в разных фреймворках, но зависеть от поведения в конкретном фреймворке, к тому же недокументированного, я считаю неправильным.
На самом деле, ответ значения не имеет. Для меня важно то, что в стандартном планировщике (не Forms/WPF) после любых IO операций, которые не могут быть завершены прямо сейчас, продолжение задачи осуществляется не в вызвавшем потоке, а когда таких задач несколько — в разных потоках.
DistortNeo
07.12.2016 22:34Чтобы окончательно разрешить спор, выкладываю проект.
Мои результаты:
Debugger: not attached .NET version: 4.0.30319.42000 Platform: 64 bit Same ThreadId: 98,94%
Получается, что в 1 случае из 100 программа ведёт себя не так, как должна себя вести. Это типичный пример неверного предположения о работе программы в многопоточной среде, которое может привести к очень неприятным и трудноуловимым ошибкам.
forcewake
07.12.2016 00:51Здравствуйте, а можно где-то посмотреть на код вашей реализации планировщика?
lair
07.12.2016 11:31+2По-моему, вы только что продемонстрировали, что модель TPL в .net сделана удобно: для вашей конкретной задачи стандартный планировщик не подошел (это не значит, что он не подойдет другим, у меня он прекрасно работает) — и вы легко заменили его на свой.
dotnetdonik
06.12.2016 00:08-1Не совсем понятна идея добавления таких вещей, как возможность писать код в виде строк и докомпилировать его или вклиниваться в вызов метода и делать что-то с ним такое, что уже сделать нельзя используя полиморфизм, переопределение в языке каким изначально задумывался C#(строготипизированным и со статической типизацией).
Сейчас там же можно вызвать наследуемый PropertyChanged метод без дубликтов логики или применить логирование через action filter в крайнем случае dynamic proxy, благо для AOP в дотнете инструментария уже очень много.
С трудом вериться с учетом сопровождение этого кода, тестируемости, переносимость, использование АПИ привязанного к конкретному комплиятору, оно реально удобней — кроме что разве красивого и на первый взгял гибкого функционала.mayorovp
06.12.2016 17:10А что не так с "конкретным компилятором" если сам компилятор — переносимый и открытый?
dotnetdonik
06.12.2016 22:11Дело не в компиляторе самом, а стандартах. В дотнете/с# уже написано масса хороших библиотек и кода, что используют для аналогичных целей reflection.emit, DLR, Dymamic Proxies, Expressions. мс продолжают добавлять новые инструменты и менять подходы раз в несколько лет, для весьма странных вещей без всякой на то нужды. Количество задач где целесообразно это променять скоро станет меньше количества инструментов, которыми их можно решить — и все это надо будет саппортить в будущем для обратной совместимости? Как считаете это способствующий развитию и гибкости языка момент?
dotnetdonik
06.12.2016 22:17Если на проекте уже надо совсем прямо надо докмпилировать какой-то код из строки, это даже сейчас аналогичным образом можно сделать применив T4 шаблоны или написав автоматизацию вызова csc.exe, зачем это добавлять нативно в компилятор?
AxisPod
06.12.2016 08:52-1>> Добавление генерирующего код расширения для компилятора
Ааа, вот же лютый костыль, почему бы не сделать множественное наследование.
>> Superseding members — замещение модификатора для того, чтобы дать больше возможностей инструментам генерации кода
опять же приведенный механизм использования нормально решается множественным наследованием
И вместо адекватных трейтов для того же INotifyPropertyChanged, будет просто костыль, который непонятно как работает, непонятно где цепляется, с кодом не содержащем подсветку синтаксиса, с неработающими фичами навигации в среде разработки, без работающего Intellisense. Без возможности покрытия тестами. Как говорится: «Счастливой отладки».
Free_ze
06.12.2016 09:55Почему бы не сделать статические generics, на манер шаблонов C++? Нередко наблюдал, как люди из-за отсутствия оных используют dynamic (со всеми вытекающими проблемами в рантайме).
netpilgrim
06.12.2016 22:36Можно пример? Статические классы и методы могут быть generics.
DistortNeo
06.12.2016 23:49Видимо, здесь речь идёт не о статических методах, а о подстановке типов и кодогенерации на этапе компиляции, что, по идее, должно дать выигрыш в производительности по сравнению с дженериками, но за счёт сильного раздутия кода. Ну и об отсутствии интерфейсов типа IAddable для примитивных типов.
Free_ze
07.12.2016 10:35Суть в разнице между дженериками и шаблонами. Первые полагаются на иерархию наследования/реализации интерфейсов, а вторые — буквально генерируют код на этапе компиляции и тут же подвергают его статическому анализу.
Вкупе с variadic templates и развитым выводом типов это дает возможность писать крайне гибкий код. И это уже есть в C++/CLI.mayorovp
07.12.2016 10:41Вот только пользоваться этим в C++ зачастую неудобно. Расширения компилятора выглядят лучше.
Free_ze
07.12.2016 10:54Сложно представить современный C++ без шаблонов.
Расширения компилятора выглядят лучше.
Очень спорно. Семантически это больше на миксины похоже, чем на шаблоны.mayorovp
07.12.2016 11:20Так ведь это зависит от целей использования. Расширение компилятора позволяет сделать и миксин, и шаблон.
Free_ze
07.12.2016 11:34Судя по примерам в статье это всегда будет миксин. Шаблон в C++ всегда создает новую сущность (функция, класс или синоним типа), а эта штука, как я понял из статьи, инжектит новые поля/методы в уже существующие типы.
Расскажите, пожалуйста, чем же это удобнее пресловутых шаблонов?mayorovp
07.12.2016 11:56Логика работы расширений описывается на том же самом языке, для которого эти расширения пишутся.
Шаблоны в c++ — это отдельный функциональный (!) язык (мета)программирования, при том что основной язык — императивный...
- В расширении можно произвольно управлять наличием и именами методов — в то же время шаблоны C++ ограничены заданным при их написании набором методов, а управлять их наличием нужно через местами контринтуитивный механизм SFINAE.
Free_ze
07.12.2016 14:35В C++ с помощью SFINAE затыкают полное отсутствия механизма, вроде where-констрейнтов из C# + Reflection в некоторой степени. Можно же найти золотую середину, имея поддержку обеих технологий на платформе. Допустим, ввести специальный синтаксический assert-блок или дать возможность как-то формализовать требования к типу в отдельный метатип, в котором можно наделать дополнительных запросов к типу, посредством compile-time аналога рефлекшна.
И да, описывать требования удобнее на декларативном языке, как это реализовано для дженериков в C#.
Шаблоны в c++ — это отдельный функциональный (!) язык (мета)программирования, при том что основной язык — императивный...
C++ мультипарадигменный, в том числе и функциональный.
Free_ze
07.12.2016 15:45В расширении можно произвольно управлять наличием и именами методов
А в чем отличие от шаблонов? Наличие методов определяется и без каких-то особых трюков с SFINAE. Причем, мне кажется, расширения компилятора здесь работают так же. А переименовывать вы можете так же свободно.
Viacheslav01
06.12.2016 20:03-2Надавать по рукам за такие «новые» возможности. Код превратиться в кучу гуано!
Deosis
08.12.2016 07:43-1Можно добавить контекстное ключевое слово field в методах доступа свойства.
А также пустой nameof(), который возвращает имя текущего свойства/метода:
public string Property { get { return field; } set { if (field == value) return; field == value; OnPropertyChanged(nameof()); } }
Krypt
08.12.2016 12:20-1Я сейчас еретическую вещь напишу: больше всего в C# мне не хватает препроцессора. Проблема с INotifyPropertyChanged решалась бы одной строчкой.
qw1
08.12.2016 21:29+1А также пустой nameof(), который возвращает имя текущего свойства/метода:
А это уже есть, но немного по-другому записывается:
void OnPropertyChanged([CallerMemberName] string propertyName = "") { .. } ... public string Property1 { set { OnPropertyChanged(); } } public string Property2 { set { OnPropertyChanged(); } }
https://msdn.microsoft.com/en-us/library/system.runtime.compilerservices.callermembernameattribute(v=vs.110).aspx
fishca
Так в чем вопрос, надо просто открыть двери