Pull to refresh

Comments 70

Не совсем понял, чем Lock принципиально отличается от Monitor

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

В теории новый Lock дает возможность в будущем избавиться от хедера объектов, в которых сейчас приходится записывать thin lock/sync block + уменьшает контешн, если рядом с вашим объектом для лока есть другие горячие поля - будет большой пеналти от false sharing.

Как ответил @DoctorKrolic, по-сути это одно и то же. Думаю, с помощью нового класса Lock будет проще переехать с Monitor на локи (нейминг методов больно схож).

Раньше надо было создавать пустой объект, называть его ЧетоТамЛок, сейчас для этого есть специальный класс. Можно использовать в скоупе юзинг. По сути все.

Если меня не подводит память. Чтобы залочить объект, внутри данных объекта, поле где хеш, заменяется на ссылку в другое место где и хеш и информация о локе. т.е. такой костыль а точнее оптимизация. А тут наверно, будет четкое разделение, и можно будет создателям языка, играться с оптимизациями, хешами и локами, как душе угодно.

Спасибо за обзор. Хотел уточнить, а вот если это фрагмент не выполнится (будет false) , не будет ли выброшено исключение?

finally
{
    if (_lock.IsHeldByCurrentThread)
        _lock.Exit;
}

Наверное, где-то выше можно цикл разместить, в который эта часть будет включена?

В теории свойство должно обеспечить надежность в этом плане. Если мы обратимся из другого потока, то у нас и правда может выпасть исключение "SynchronizationLockException", что неохорошо. Я уверен, что можно круче обработать точку выхода из лока, чем показано в статье.
С другой стороны можно использовать Scope структуру, Dispose() которой будет применен при выходе из области видимости.

"Primary constructors" по-нормальному так и не сделали? :(

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

Я больше жду, когда field добавят для свойств

А что это такое, можно поподробнее, или ссылку? Я что-то раньше никогда не слышал.

Хотят добавить контекстное ключевое слово field для доступа к автосгенерированному полю внутри методов доступа свойства

Что-то вроде такого

До:

private bool _isNecessary
public bool IsNecessary
{
  get => _isNecessary;
  set { _isNecessary = value; OnPropertiesChanged(); }
}

После:

public bool IsNecessary
{
  get => field;
  set { field = value; OnPropertiesChanged(); }
}

После написания для вьюшек n-го количества моделей с доп. логикой в сеттере, использование ключевого слова field для меня является глотком свежего воздуха. Сейчас поля для свойств захламляют класс, делая его менее читабельным (имхо).
Если не ошибаюсь, еще в C# 10 хотели такую фичу добавить. Видимо не в приоритете(

Да, было такое, уже несколько версий проскипала эта фича

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

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

ИМХО, это выглядеть должно как то так: есть возможность объявить 1 раз метод

[RunOnMemberAccess(MemberAccess.PropertySet)]
void MyOnPropertyChanged(MemberAccessInfo info)
  {
  
  }

Или сразу событие

[RunOnMemberAccess(MemberAccess.PropertySet | MemberAccess.PropertyGet)]
public event Action<MemberAccessInfo> MyOnPropertyChange;

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

MemberAccessInfo должно быть ref struct, чтобы не генерировать аллокации и жрать ресурсов по минимуму. Можно даже сделать автоспециализацию. Типа

[RunOnMemberAccess]
void MyOnPropertyChanged(PropertySetInfo info)
  {
  
  }

По типу аргумента PropertySetInfo оно понимает, что это надо вызывать в моменты изменения свойств. Было бы BeforeMethodRunInfo — запускалось бы перед вызовом методов и передавало бы все аргументы метода, что удобно, кстати, для автоматизации проверки аргументов. Для полей это тоже можно сделать, но будет не очень оптимально.

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

Я себе сделал класс ChangeNotifier(object obj), которому можно скормить любой объект, и он будет автоматом засекать изменения свойств и полей, тупо каждые 100 мс сравнивая хэши значений с предыдущими. Но хотелось бы иметь некостыльный, нормально работающий инструмент.

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

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

И, главное, для иерархий навледования всё это делается вполне существующими спосбами: в базовом классе определяется публичный метод, который вызывает специфичную для класса-наследника реализацию, а потом (или до того) производит все остальные, общие для всех реализаций действия. То есть, прием "шаблонный метод" в действии.
Ну да, нажатий на кнопки потребуется чуть больше - зато сразу будет видно по разным именам методов или свойств, что делается ещё что-то дополнительное.

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

А вот с этим должна помогать IDE, помечая/сообщая/выделяя, что вот, изменение свойства обрабатывается таким‑то RunOnMemberAccess‑методом. Или, как вариант, вместо атрибута RunOnMemberAccess постановить, что вызываться будет всегда метод с названием __OnPropertyChangedAggregator__ по аналогии с Deconstruct(out...)у кортежей.

Сейчас тоже при изменении свойства может оказаться, что его 100 500 раз перегрузили, и в этих перегрузках сеттеры и геттеры могут вполне себе генерировать кучу побочных эффектов. Плюс класс вполне может иметь какие‑нибудь фоновые потоки/таски, которые как‑то реагируют на изменение свойств и генерируют побочные эффекты. Выстрелить себе (или не себе) в ногу всегда можно.

Смысл в том, чтобы

public Type1 Property1 { get; set; }
public Type2 Property2 { get; set; }
public Type3 Property3 { get; set; }
public Type4 Property4 { get; set; }
public Type5 Property5 { get; set; }

не превращалось в

private Type1 _property1;
public Type1 Property1
{
    get => _property1;
    set
    {
        _property1 = value;
        OnPropertyChanged();
    }
}

private Type2 _property2;
public Type2 Property2
{
    get => _property2;
    set
    {
        _property2 = value;
        OnPropertyChanged();
    }
}

private Type3 _property3;
public Type3 Property3
{
    get => _property3;
    set
    {
        _property3 = value;
        OnPropertyChanged();
    }
}

private Type4 _property4;
public Type4 Property4
{
    get => _property4;
    set
    {
        _property4 = value;
        OnPropertyChanged();
    }
}

private Type5 _property5;
public Type5 Property5
{
    get => _property5;
    set
    {
        _property5 = value;
        OnPropertyChanged();
    }
}

Ну дичь же, огород приходится городить на ровном месте. Ладно хотя бы CallerMemberName появился. И то, ИМХО — решение ужасно кривое, можно было бы сделать гораздо лаконичнее. Ну или что-типа

public notify Type1 Property1 { get; set; }
public notify Type2 Property2 { get; set; }
public notify Type3 Property3 { get; set; }
public notify Type4 Property4 { get; set; }
public notify Type5 Property5 { get; set; }

Чтобы было виднее, что оно автоматом что‑то там вызовет.

[Notify] public Type1 Property1 { get; set; }
[Notify] public Type2 Property2 { get; set; }
[Notify] public Type3 Property3 { get; set; }
[Notify] public Type4 Property4 { get; set; }
[Notify] public Type5 Property5 { get; set; }

умоляю, не надо новых ключевых слов :)

Вносить логику UI-фреймворка в компилятор, это нелогично.
Сейчас такое делается например через Fody, но плохо что это сторонее решение. Вот бы Microsoft как-то стандартизировала кастомные костыли, вставляемые атрибутами при компиляции кода. Чтобы не завязываться на один лишь INotifyPropertyChanged, а дать механизм делать такое в общем виде.

А если такой бойлерплейт создается автогенерацией?

Это затрудняет чтение кода и внесение изменений. Одно дело, простая ViewModel на 5 строк, другое дело - на 50.

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

А чего не хватает? У нас как-то органично весь код потихоньку праймари конструкторы начинает использовать. Гораздо меньше писанины.

А чего не хватает?

Например:

  1. В него нельзя без костылей прикрутить какую-либо валидацию аргументов (хотя бы банальный null check).

  2. Те "поля" которые он создает невозможно сделать read-only.

  1. Ну нулл чеки, как мне кажется, решены null reference types
    делать дополнительную проверку можно, но мы в проекте этим не маемся

  2. Ну мне кажется праймари конструктор не для того делали, а для таких как мы - кто использует IOC/DI везде где надо и где ненадо...

Ну нулл чеки, как мне кажется, решены null reference types

Не совсем. Если код может быть вызван из кода "nullable context" которого вы не контролируете, то null check нужен. И если включить опцию компилятора EnableNETAnalyzers с достаточным AnalysisLevel, то при отсутствии null check будет появляться warning: "CA1062: Validate arguments of public methods".

Ну мне кажется праймари конструктор не для того делали, а для таких как мы - кто использует IOC/DI везде где надо и где ненадо...

IOC/DI ведь никак не спасет от выстрела в ногу путем изменения ссылки на "вставленный" сервис в каком-либо из методов. readonly (а позже еще и init-only) для того и придуман, чтобы однажды инициализированное (в конструкторе) гарантированно никогда потом не менялось.

Еще не особо нравится нарушение общепринятых правил именования, когда в теле метода сходу непонятно, что перед тобой поле ("_fooBarBaz"), а не локальная переменная или параметр ("fooBarBaz").

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

Ох ёёё... получается теперь в using EnterScope можно будет свободно await вызвать и компилятор слова не скажет. Прям замечательный выстрел в ногу будет для новичков (и не только).

Структура Scope, которую мы получаем, не реализует асинхронную версию метода Dipose(), поэтому мы получим ошибку от компилятора. Но я нашел веселее момент, в рантайме. Можно в теории что-либо асинхронное указать между методами Enter() и Exit(). Компилятор никак на это не отреагирует, но при Runtime мы получим дичь в работе Lock))
Замечу, что при использовании скоупа компилятор отлично реагирует на такие попытки (даже при использовании using без блочных скобок).

Там всё дело всего лишь в том, что, by design, Exit() может только тот же поток, который перед этим делал Enter(). Я наступил когда-то на эти грабли, потому что какая-то старая, тогдашняя версия компилятора (за давностью даже и не помню что за версии компилятора и runtime это были) вполне допускала такую конструкцию:

lock(_lock)
{
   // ....
  await DoSomethingAsync();
  // ....
}

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

Потом это запретили на уровне компилятора ("CS1996: Cannot await in the body of a lock statement").

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

Monitor.Enter(_lock);
try
{
    await FooAsync();
}
finally
{
    Monitor.Exit(_lock);
}

Я тогда выкрутился из этого написав свою собственную реализацию spin lock, а позже наткнулся на то, что в .NET есть уже свой, готовый, который если создавать его с enableThreadOwnerTracking = false соответствующих ограничений не имеет.

Lock обладает теми же ограничениями (по await) что и Monitor - вот тут про это явно написано.

Меня еще вот что интересует. Не так давно мне на собеседовании один чувак доказывал, что Monitor это не полностью "user-space synchronization object", а "гибридный". Я с ним тогда спорить не стал, но до сих пор в его правоте очень и очень сильно сомневаюсь. В документации по API как-то совсем явно про это не сказано, но написано следующее:

The Monitor class is purely managed, fully portable, and might be more efficient in terms of operating-system resource requirements.

Так что, сдается мне что прав был всё-таки я. Но сомнение всё-таки было посеяно и так и осталось.

Засада и правда коварная) Для нового лока такой случай смогли находить, а для мониторов поддержку не завезли.
Что касается второго, то честно говоря не обладаю большой экспертностью в области класса Monitor, поэтому конкретику внести не смогу. Надеюсь, найдутся коллеги, которые более осведомлены в данном вопросе.

гибридный

Вероятно, вы не договорились о терминологии. purely managed - это об аллокациях, а user-space или kernel-space - в какие API оно будет ходить. Очевидно, что даже Thread.Sleep потребует ядерного вызова, чтобы переключить контекст CPU на другой поток.

Если обратиться к исходникам Monitor, увидим что реализация ф-ций в C++. TryEnter → ReliableEnterTimeoutJIT_MonTryEnterObjHeader::TryEnterObjMonitorAwareLock::TryEnterAwareLock::EnterEpilogHelper → CLREvent::Wait → CLREvent::WaitExThread::DoAppropriateWaitDoAppropriateAptStateWait

И тут финиш, вышли на стандартную WinAPI-шную ядерную WaitForMultipleObjectsEx

Да, правда. Сам я в Парижах-Лондонах не бывал, и исходники .NET не читал, но, вот, в порядке освежения мозга открыл снова Рихтера и там:

Hybrid Thread Synchronization Constructs
Hybrid Thread Synchronization Constructs

Но вот что интересно - я вот точно помню, если я совсем еще от старости из ума не выжил, что когда-то в "официальной" документации по .NET API от MS четко говорилось, типа: "- Поцоны, вот это вот 'user-mode synchronization object', а вот это вот 'kernel-mode synchronization object'". А теперь, вот, я там поискал-поискал, и даже слов таких не нашел.

Очевидно, что даже Thread.Sleep потребует ядерного вызова

А, вот, не знаете, случайно, Thread.Yield() тоже "ядерный", или все-таки нет? Потому что "легковесный" SpinLock считается "user space", а он, по хорошему, должен его использовать, чтобы совсем уж не крутиться в бешенном цикле непрерывно пытаясь сделать Interlocked.CompareExchange.

На собеседованиях, когда уже совсем начинают забадывать всей этой многопоточностью (я уже забыл когда крайний раз по работе с ней дело имел, т.к. у меня сплошь всякие веб-сервисы на ASP.NET, а там сплошной async/await) люблю еврейский вопрос-на-вопрос: что делает Thread.Sleep(0). На правильный ответ другой вопрос - чем это тогда отличается от Thread.Yield() :))

Потому что "легковесный" SpinLock считается "user space"

Поэтому я и говорю, что сначала надо договориться о терминах. Тот же Monitor по большей части user-space, и только в самом крайнем случае (который я отметил), может уйти в kernel. Потому он и "гибрид". Если SpinLock в худшем случае уходит в kernel, он ядерный, в вашем понимании терминов?

Пролистал слегка Таненбаума "Современные ОС" (уже лет 15+ как даже с полки ни разу не снимал). И все более-менее в памяти всплыло. Все разделение связано с виртуальным адресным пространством процесса, которое делится на адреса (диапазон адресов) собственно процесса, и диапазон адресов ОС (ядра). Если какой-то объект/структура или код находится в диапазоне адресов ядра - то это и есть объект или код "kernel-space", если в "процессном" диапазоне адресов, то "user-space". Т.е. "ядерный вызов", можно считать, это тот вызов который выполняет какой-то код находящийся в диапазоне адресов ядра. Есть, вроде бы, какие-то экзотические ОС, где все по-другому устроено, но и в Windows и в Linux все примерно как-то так.

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

Главное отличие кода ядра от кода пользовательского режима - это уровень привилегий, с которыми этот код выполняется. Уровень привилегий опреопределяется режимом работы процессора. Для современных ОС можно беез большой натяжки считать что этих уровня два: привилегированный для режима ядра и непривилегированный для пользовательского режима. Диапазон адресов - это уже вторично.

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

Как-то так.

Если какой-то объект/структура или код находится в диапазоне адресов ядра - то это и есть объект или код "kernel-space"

И снова есть нюансы. Если с файлом понятно, что наружу (в user-space) торчит 32-битный HANDLE, а вся остальная информация о файле расположена в адресах ядра, то файл - ядерный объект.

А если весь объект лежит в user-space, но для работы с ним вызывается ядерный код, выходит, объект не ядерный, но переключение требуется.

А что такое ядерный код в данном случае? Который лежит по адресам ядра, или же который выполняется в привилегированном кольце защиты, куда он попадает через syscall/sysenter?

Фишка "ядерных" адресов памяти лишь в том, что они шарятся между всеми процессами путём настройки таблиц трансляции виртуальных адресов (мы же помним что у каждого процесса своё адресное пространство?)

В x86 уровень привелигий опеределяется содержимым селектора CS. Формально, другой CS:EIP - другой адрес, другой код. Хотя те же страницы с кодом могут быть замаплены на userspace CS, тогда тот же код доступен и в userspace. Но можно считать, что это не тот же код, а копия.

Что касается данных, я считаю данные ядерными, если они недоступны с user уровнем привилегий.

Monitor.Enter(_lock);
try
{
await FooAsync();
}
finally
{
Monitor.Exit(_lock);
}

Такое довольно просто в рантайме обнаружить и кинуть Exception, но в стандартные таски это не завезли.

В рантайме оно не обязательно будет срабатывать. Если ожидаемое в await выполнится синхронно (например, заранее), то асинхронного ожидания не будет, управление останется в том же потоке и Exit сработает нормально. Поэтому ошибка получится плавающей.
А если этот код выполняется в однопоточном контексте синхронизации (например, в UI-потоке Windows Forms или WPF), то управление вернется в тот же пооток и ошибки не будет вообще.

PS await - он не обязательно про Task: там может быть что угодно. К примеру, у меня в свежей библиотеке есть класс, который ни разу не Task/ValueTask, но у него в одном из методов написано await this;

В рантайме оно не обязательно будет срабатывать

Ну, это, конечно, неправильно, но лучше, чем ничего - вероятность того, что такое улетит в продакшн уже меньше. А чтобы было правильно, достаточно компилятор заставить перед каждым (!) await ставить что-типа if (!Monitor.CheckAwaitAllowed()) throw new Exception("bruh"); тогда эта штука будет работать в 100% случаев, хотя и в рантайме.

PS await - он не обязательно про Task: там может быть что угодно. К примеру, у меня в свежей библиотеке есть класс, который ни разу не Task/ValueTask, но у него в одном из методов написано await this;

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

await 100; //await Task.Duration(100);

Так делать, конечно, нехорошо, но прикольно :) await можно использовать и в более общем виде, как оператор «преврати всё что идёт ниже, в большую толстую лямбду и дай мне» и делать с этим что угодно. Так тоже делать нехорошо :)

Заодно сделал возможность писать штуки вроде
await 100; //await Task.Duration(100);

public static class Ololo
{
    public static TaskAwaiter GetAwaiter(this int value) =>
        Task.Delay(value).GetAwaiter();
}

За такие вещи потом обычно большое спасибо говорят, те кто это читает :D

Раз уж заморочились с новым Lock, то почему было не сделать его асинхронным

Да, хотелось бы иметь возможность применять асинхронщину внутри. Что тут скажешь... Ждем)

Например, потому что Lock наверняка поддерживает семантику потока-владельца блокировки (Monitor - поддерживает, Critical Section и Mutex в Windows - тоже). Такая семантика нужна, чтобы позволить из одного и того же потока захватывать одну и ту же блокировку несколько раз (главное, чтобы блокировка освобождалась потом то же число раз). Нужно это, потому что так программировать сильно проще - внутри кода вызываемого метода не надо заморачиваться проверкой, не захвачена ли уже эта блокировка вызывающим методом и захватывать, а потом освобождать ее только когда она не была захвачена.
А как вы сделаете такую же семантику для асинхронного выполнения? Поток в процессе асинхронного выполнения-то запросто поменяться может. Делать владельцем Execution Context (он в асинхронном потоке управления не меняется) ? Это громоздко, и поддержка со стороны ОС теряется. А так как как блокировка гораздо чаще накладывается на кусок, в котором асинхронное ожидание отсутствует, то для Lock общего назначнения так лучше не делать. Делать отдельный примитив синхронизации?

PS Eсли же вам нужна асинхронная блокировка без семантики владения, то уже давно для этого есть SemaphoreSlim с пределом в 1. Но, таки да, тут try-finally придется ручками писать. Недостаточек.

Спасибо за развернутый ответ по асинхронщине в Lock.
Мы сейчас используем SemaphoreSlim для подобных случаев) Вот как только переехали в одном из проектов на асинхронность, то сразу же заменили Lock на SemaphoreSlim. Пока полет нормальный.

Есть AsyncLock из известного пакета.

var list = new List<int>(initialList)
{
    [^1] = 15
}; 

Когда-то я смотрел на похожую штуку в Python и молился чтобы этого не увидеть в своем ламповом C#. Что не версия, то завозят кучу синтаксического сахара, всё более изощренные ключевые слова и операторы. В пределе ситуация такая, что в какой-то момент ловишь передоз и синтаксический диабет.

Смущает, что индекс с 1 начинается, а не с 0... Как в прямой индексации

Не смущает. [^k] аналог [n-k], где n - длина коллекции

Если уж делать сахар, то похожим на нативный аналог, типа [0] - первый элемент с начала, [^0] - первый элемент с конца.

В случае [^1] - может быть путаница при чтении кода.

[^0] - это как бы конец первого элемента с конца.

Прикол в том, что семантика близка к плюсовым итераторам begin() и end(). Которая в мире C# мягко говоря непривычна.

Вспомнилось... А в Perl отрицательный индекс как раз используется для индексации с конца. Так что можно считать символ ^ как знак минус в контексте индекса массива.

База. Думаю, что найдутся ошибки при такой инициализации объекта. Будто 2 разных человека делали :) (думаю, так и есть).

Короче, я так понял, что нам предлагают толстый слой семантического сахара практически без новой семантики (свойство - это семантически, вообще-то, пара методов, и индексатор - тоже).

А что до params-последовательностей (как я понял - предлагаются именно они, а не коллекции, ибо там IEnumerable а не ICollection), то это может оказаться даже вредным: У IEnumerable-то свойства Count нет, и вообще для него нет гарантий что число элементов последовательности считается быстро, как O(1).

А также я боюсь за интерфейсы для ref struct при использовании их с generic: сейчас-то generic поддерживается напрямую в IL, исполняющей средой в рантайме. И я боюсь, а не придется ли теперь их поддерживать компилятору инстанциированием generic на этапе компиляции, как template в С++. А то мне до сих пор памятны простыни невнятных сообщений об ошибках инстанциирования от компилятора MS VC++ 2005, и видеть такие же в C# я не хочу. Ну и возможное при этом портирование стирания типов из Java мне тоже не нравится. Короче, к этому нововведению я подозрителен.

а не придется ли теперь их поддерживать компилятору инстанциированием generic на этапе компиляции

Так, для структур раньше так и было, на каждый тип своя версия кода. И одна версия на все классы.

Хочется сказать: "В первый раз?")

В защиту params для IEnumerable хочу сказать, что это действительно удобно. Не нужно писать new List<MyClass>() { obj } в аргументе, если хочешь закинуть один объект. И не нужно делать .ToArray()

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

Вызывает не самые приятные флешбеки про SFINAE.

Слышал про такое "явление" от разработчиков C++. Что-то на тяжелом)

Концепция простая, Substitation Failure Is Not An Error. Компилятор, когда ловит прям ошибку подстановки типа, не завершает работу с ошибкой, а просто отбрасывает этот вариант, продолжает работу.

Скрытый текст
Вот например трейт на проверку, есть ли в типе метод "hello"
Вот например трейт на проверку, есть ли в типе метод "hello"

В данном случае ошибку может вызвать выражение decltype(&T::hello) . Если метода hello нет, то это ошибка подстановки, но SFINAE напрямую говорит нам что это не ошибка :) И этот вариант просто будет отброшен. Останется по умолчанию тот первый вариант, который будет типа false. Если же метод hello есть и ошибки подстановки не произойдет, то для такого типа будет true и метод можно вызвать

Через такое игольное ушко протянуто почти всё мета-программирование с++. Т.е. у нас в императивном стиле нет мета-информации, мы не можем перебрать в цикле поля, методы и всё такое. (рефлексия в С++26 планируется). Мы можем лишь делать предположения и проверять наши предположения

Спасибо за подробный ответ. Суть понятна)
p.s. Рефлексия в C++ звучит интересно. Надо будет глянуть :)

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

А какие есть основания полагать, будто IsHeldByCurrentThread будет false, если взятие блокировки за пределами try? Вероятность освобождения внутри блока try? А это не bad design тогда?

Да, тут больше про вероятность освобождения внутри тела try. Так сказать: "на всякий случай".

Sign up to leave a comment.