Pull to refresh

Comments 33

новость хорошая, но на момент прочтения статьи последняя доступная для скачивания версия .net на официальной странице 8.0.0-rc2

Свежие пакеты версии 8.0.0 только появляются в nuget. Думаю у них там CI/CD активно пашет. Ждем когда закончит)

Да, есть такой момент, но на момент публикации статьи через dotnet-install скрипт уже можно было установить 8.0.100. А теперь ещё и на официальной странице для скачивания появилась новая версия.

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

PS: даже сегодня релизнулся

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

Тауб крут, ему на конфе 20 минут выделили, он подсветил 3 основные улучшения и перед каждой из них упоминал, что их там сотни. Видно, что владеет информацией лучше всех. Просто как пулемет без остановок рассказывал, поправлял и прочее. Он уже делает заметки по 9-ому дотнету. Коллеги заржали дружно, когда он про заметки на 9-ый дотнет упомянул. По перфомансу у них там серьезные мозги в MS сидят.

Про новые фишки и изменения связанные с производительностью и оптимизации новой платформы почти ничего так и не рассказали, например о поддержке инструкций AVX-512. Или о пока не совсем понятном новом генераторе кода с технологией Dynamic Profile-Guided Optimization - что это, динамическая перекомпиляция кода в runtime на основе профилирования этого runtime?

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

Интересно было бы узнать, что там нового по отельной устанавливаемым компонентам (пакетам) для новой платформы, например про изменения в многочисленных фронтэнд системах для .NET обновлённых для 8 версии платформы (ASP, Blazor, MAUI...), и про новый .NET Aspire для облачных технологий клиент-серверного online взаимодействия Cloud Native. Хотя тут, наверное, на каждую такую технологию надо свою отдельную статью писать. Но это если там изменений много....

Не упомянули даже про улучшения поддержки контейнерной технологии.

Написали про добавленную поддержку AOT на macOS - и тут же ниже привели таблицу с размерами файлов (жаль, только для консольного проекта и без размера занятого в памяти), но там только Windows и Linux, а macOS туда не включили.

P.S.

В языке C# 12 появились первичные конструкторы классов - это здорово. Но я так и не понял - есть какие-то ключевые отличия от аналогичных конструкторов для record? Вот явно напрашивается теперь сравнение лоб в лоб.

Аргументы первичных конструкторов в классах создают что - поля? Всегда только поля? Всегда публичные или как-то можно это менять?

И никак не могут быть просто аргументами, значения которых далее могут быть использованы в инициализации явных полей/свойств?

P.P.S.

Так ключевое слово field для обращения к теневым полям свойств так и не завезли? "Обещали" кода-то. Вроде бы даже в Roslyn компиляторе в потрохах есть намёки на их поддержку (если ничего не путаю). Никто не в курсе - планы ещё есть, и когда же?

P.P.P.S.

Псевдонимы без ограничений понравились - особенно для кортежей (а то ранее можно было только та using myname = System.TupleValue<T1,T2,T3,T4,T5,T6,T7,T8> - до 8 полей (последний должен быть кортежем) - но это не позволяло делать именованные поля, а теперь вроде бы можно по классическому объявлять кортежи.

Да я понимаю - что ранее для таких случае предпочтительны были структуры, потом вообще record записи появились - просто синтаксис кортежей очень удобный, особенно как временный (на время разработки какого-то модуля программы) или для приватных каких-то вещей, или при кодогенерацию. Вот бы его ещё расширить оператором ".." для объединения нескольких кортежей, и итерацию по кортежу - вообще круто было бы

Не жду сравнения лоб в лоб, но вот по аналогии с рекордами Non-destructive mutation было бы неплохо иметь для того чтобы быстро мапить объекты с синтаксисом вида

var newObject = sourceObject with { NewCoolProperty = 42, AnotherOne = "test" };

Конечно, в статье не всё отражено, вставить всё просто невозможно. Что-то считаешь незначительным, что-то не для всех. Про производительность я соглашусь, но если начинать перечислять, то в комментарии всё равно кто-то придёт и напишет, что что-то забыли :). Поэтому решил ограничиться ссылкой на исчерпывающую статью. Тем более, лучше Тауба сложно сделать.
А вот про отдельные устанавливаемые пакеты действительно лучше сделать отдельные статьи (хотя тогда это будет копирка статей Microsoft), так как здесь хотелось выделить более общие вещи.

Спасибо, за большой комментарий. Постараемся в следующих статьях про .NET учесть.

Подскажите, не придумали в шарпе что-то для быстрого объявления потокобезопасных переменных? Что бы каждый get set не оборачивать в lock.

Зависит от того, что надо.

Варианты:

  • атрибут [Synchonization] существует с самого зарождения дотнета.

  • класс Interlocked для работы с простыми типами и со ссылками с тех же времён

  • класс ThreadLocal появился чуть позже

  • ещё есть контекст синхронизации для асинхронного кода

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

Задача такая:

У класса есть переменные, которые могут меняться и читаться в разных потоках: int , struct, String...

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

private int badTempTarget;
public int BadTempTarget { get { lock (Locker) { return badTempTarget; } } protected set { lock (Locker) { badTempTarget = value; } } }

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

Но, всё-таки я не совсем прав - вероятно Int64 (который long) всё-таки не атомарен (не знаю почему так - возможно тут вся суть в битности системы - т.е. на 64битном процессоре это атомарный тип, а на 32-битном нет (всё дело в поддержке соответствующих инструкций самим процессором) - вопрос только где эти 32-битные процессоры сейчас есть; но может дело не только в процессоре а ещё и в режиме работы с ним ОС - т.е. ОС должна быть 64битной, чтобы эти операции были атомарны; ну и само собой int является 32 битным - и атомарным на любой (32битной или 64битной) ОС) - т.к. как есть Interlocked.Read для данного типа (в т.ч. беззнакового). Само собой все 128-битные числа (включая decimal) тоже не атомарны!

Аналогичная история есть про 64-битный double - хотя он, в отличии, от int в .NET всегда 64битный, и для него нет функции Interlocked.Read (как и для decimal).

Указатели 64битные (в 64битном приложении) - для них всё тоже самое что для Int64, и 32 битные (в 32 битном приложении) - для них всё тоже самое что для Int32.

Ну а для присвоения значения никаких особых функций нет - но вероятно блокировка нужна - если тип не атомарный (чтобы коррелировать с блокировкой на чтение, но если применяется Interlocked.Read то не нужна блокировка при установке значения; или надо Interlocked.Exchange применять в этих случаях)

Пусть меня поправят, кто лучше разбирается в неблокирующей многопоточности NET

ну или вот тут почитать

P.S.

Правда при многопоточности, кроме атомарности есть ещё понятие кеша процессора - но тут в C# есть ключевое слово модификатор volatile при объявлении переменных и полей, и ещё Thread.VolitileRead - тут уже для любой битности это имеет значение (не надо только с Interlocked.Read сочетать)

Дополнение:

Thread.VolitileRead  устарела - заменена на

Volatile.Read

так же есть и

Volatile.Write

Но это всё как раз блокирующие операции (правда не на всех процессорах)

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

  • Класс Volatile также предоставляет операции чтения и записи для некоторых 64-разрядных типов, таких как Int64 и Double. Переменные операции чтения и записи в такой 64-разрядной памяти являются атомарными даже на 32-разрядных процессорах, в отличие от обычных операций чтения и записи.

  • В C# использование модификатора volatile для поля гарантирует, что каждый доступ к нему является энергонезависимой операцией памяти, но модификатор volatile не может применяться к элементам массива. Методы Volatile.Read и Volatile.Write можно использовать в элементах массива.

но тут нигде нет пока поддержки для 128 битных типов decimal и Int128 :-( для них пока только lock

Задача такая:

У класса есть переменные, которые могут меняться и читаться в разных потоках: int , struct, String...

Можно подробнее ?

*В большинстве случаев, когда возникают такие идеи и желания это означает, что всё делается неправильно :)

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

Если реальный код именно такой, и доступа к badTempTarget нигде больше нет, то там не нужен lock. Достаточно объявить поле volatile.

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

Контекст синхронизации - это не про потокобезопасность.

С таким видением сложного вопроса как у вас можно просить Маска кнопку "Полететь на Марс".

Вот тоже когда-то задумывался над подобной темой (ещё для WPF) проекта. Генераторы C# - Это, безусловно сила, но вот незадача - генераторы позволяют дополнить код (в рамках политики расширения partial class), но не позволяют его модифицировать (можно вручную запилить модификацию но там такая жуткая кустарщина выходить - просто жесть).

В C# до сих пор нет и partial-свойст - поэтому в общем случае всё не просто.

Но в данном тривиальном случае можно как-то так (сам генератор не привожу - это отдельная тема - это уже тема изучения " .Net Compiler Platform" - совсем уж офтопик)

[AttributeUsage(AttributeTargets.Property)]
public class AutoLockPropAttribute() : System.Attribute
{
	public enum AccessibleMode {None, Auto, Public, Protected, Internal, Private}
	
	public AccessibleMode AccessMode {get; set;} = AccessibleMode.Public;
	public string NameProp {get; set;}
}


internal interface IMyBaseLocker
{
	object Locker {get; set;}
}


public partial class MyClass : IMyBaseLocker
{
	[AutoLockProp] private int badTempTarget;
	
	 
	object IMyBaseLocker.Locker {get; set;} = new object();

}

//Сгенерированное часть класса в отдельном файле 
public partial class MyClass : IMyBaseLocker
{
	//[AutoLockProp]
	public int BadTempTarget { get { lock (((IMyBaseLocker)this).Locker) { return badTempTarget; } } protected set { lock (((IMyBaseLocker)this).Locker) { badTempTarget = value; } } }  
	 
}

Недостатка два:

  1. Сгенерированный код немного запаздывает за основным - т.е. сгенерированных свойств не будет пока не отработает билдер - а он ещё может споткнуться о желание использовать ещё не сгенерированное свойство - будет ошибка о ненайденном идентификаторе

  2. Нет "прямой" возможности как-то внести какие-то дополнения с сгенерированный код аксессора свойства.

Но первую проблему можно решать через  .Net Compiler Platform CodeFix - с генерации кода прямо в ээээ... designetime (т.е. сразу при редактировании текста - например завершении строки определения поля с костюмным атрибутом) - но это уже не генераторы а CodeFix

Вторую проблему можно решать расширив возможности кодогенерации и сам кастомный атрибут - как-то так

[AttributeUsage(AttributeTargets.Property | [AttributeUsage(AttributeTargets.Method)])] //По-хорошему это надо два разных атрибута определять - сделал в одном для краткости
public class AutoLockPropAttribute() : System.Attribute
{
	public enum AccessibleMode {None, Auto, Public, Protected, Internal, Private}
	public enum AutoPropAccessorsMode {None, Auto, SomeName=Auto, Named, Get, Set} //Get Set для методов
	
	public AccessibleMode AccessMode {get; set;} = AccessibleMode.Public;
	public string NameProp {get; set;}
	
	public string NameGetAccessor {get; set;} //для AutoPropAccessorsMode.Named или AutoPropAccessorsMode.Auto
	public string NameSetAccessor {get; set;} //для AutoPropAccessorsMode.Named или AutoPropAccessorsMode.Auto
	public AutoPropAccessorsMode Accessor {get; set;} = AutoPropAccessorsMode.Auto; //Для AutoPropAccessorsMode.Auto если имена акссесоров не заданы ищутся, по атрибуту с Get и Set режимом, а потом по шаблону $"{NameProp}Get" и $"{NameProp}Set"
}


internal interface IMyBaseLocker
{
	object Locker {get; set;}
}


public partial class MyClass : IMyBaseLocker
{
	[AutoLockProp] private int badTempTarget;
	[AutoLockProp(AccessMode=AutoLockPropAttribute.AutoPropAccessorsMode.Public)] 
	private partial int BadTempTargetGet(int value) => value+1; //Первый способ (имя метода с большой буквы - по имени генерируемого свойства)
	[AutoLockProp(NameProp="BadTempTarget",Accessor=AutoLockPropAttribute.AutoPropAccessorsMode.Set)] 
	protected partial int badTempTargetSet(int value) => value-1; //второй способ (здесь имя метода не важно - т.к. имя целеового свойства указано явно)
	//Могут быть и другие способы определения тела акссессора
	 
	object IMyBaseLocker.Locker {get; set;} = new object();

}

//Сгенерированное часть класса в отдельном файле 
public partial class MyClass : IMyBaseLocker
{
	//[AutoLockProp]
	public int BadTempTarget { get { lock (((IMyBaseLocker)this).Locker) { return BadTempTargetGet(badTempTarget); } } protected set { lock (((IMyBaseLocker)this).Locker) { badTempTarget = badTempTargetSet(value); } } }  
	//[AutoLockProp]
	private partial int BadTempTargetGet(int value);
	//[AutoLockProp]
	protected partial int badTempTargetSet(int value);
}

Код написан "на коленке" и не тестировался - просто для демонстрации идеи.

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

public partial class MyClass : IMyBaseLocker
{
	[AutoLockProp] private int badTempTarget;
	 
	object IMyBaseLocker.Locker {get; set;} = new object();
}

вместо (намеренно развернул все блоки кода на отдельные строки для наглядности):

public partial class MyClass : IMyBaseLocker
{
    private int badTempTarget;
	public int BadTempTarget 
    { 
      get 
      { 
        lock (Locker) 
        { 
          return badTempTarget; 
        } 
      } 
      protected set 
      { 
        lock (Locker) 
        { 
          badTempTarget = value; 
        } 
      } 
    }  

    private object Locker {get; set;} = new object();
}

в КОДЕ ВЫШЕ ОПЕЧАТКА:

вместо: AttributeTargets.Property

надо: AttributeTargets.Field

так как атрибут не на свойство а на поле - странно что онлайн компайлер C# не увидел эту ошибку

Господа, а встроенная поддержка JSON содержит все функции Newtonsoft JSON.NET, можно переходить?

И те, кто уже перешёл, как она по производительности по сравнению с ней?

Насколько я помню его не планировали как замену Newtonsoft, поэтому оттуда как раз и убрали редко используемые вещи, чтобы сделать его более легковесным и быстрым. И Джеймс Ньютон (автор "старого" Newtonsoft), на самом деле, он как раз один из авторов "нового" System.Text.Json.

Очень грустно всегда читать ваши статьи, потому что знаешь что завтра тебе снова идти на работу, где больше половины коллег даже про какой-нибудь yield не знают :)) Пытался недавно сделать кое-какие DTO-шки на record и на ревью с помоями смешали: "Деды наши обходились безо всяких record." На прошлом проекте было запрещено использовать LINQ потому что тимлид его не знал.

А не рассматривали попробовать сменить команду/работу?

Да, в принципе, можно было бы. Но. Вы знаете, я в разработке уже больше 20 лет, работал за это время местах в пяти, и, исходя из личного опыта, ни от какой работы я ничего хорошего уже не ожидаю. Энтерпрайз-программирование это не просто говнокод, а какой-то прямо мир говнокода. Крайний раз - прихожу с год назад на новый проект, открываю код и первое что я вижу это конструкцию switch экранов на восемь, блин. И мне говорят, типа: "Тут нам надо для другого кейса сделать очень похожее, но с некоторыми изменениями. Поэтому ты возьми это дело скопипасть, и типа разберись что там надо поменять". Мать же вашу **, как меня все это уже за*ло :(

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

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

Можно завести два метода: BeginRead и BeginWrite, которые возвращают нечто disposable (можно в виде ref struct, чтобы исключить аллокаций):

class Syncronized
{
  private ReaderWriterLockSlim _sync = new();
  
  public ref struct ReadLocker
  {
    private ReaderWriterLockSlim _sync;
    internal ReadLocker(ReaderWriterLockSlim sync)
    {
      _sync = sync;
      sync.EnterReadLock();
    }
    public void Dispose()
    {
      _sync.ExitReadLock();
    }
  }

  public ref struct WriteLocker
  {
    private ReaderWriterLockSlim _sync;
    internal ReadLocker(ReaderWriterLockSlim sync)
    {
      _sync = sync;
      sync.EnterWriteLock();
    }
    public void Dispose()
    {
      _sync.ExitWriteLock();
    }
  }

  public ReadLocker Read() => new(_sync);

  public WriteLocker Write() => new(_sync);

  public Something {get; set;}
}

// usage
var x = new Synchronized();
using (var _ = x.Write())
{
  x.Something.Whatever = 42;
}

Это, пожалуй, один самых простых вариантов.

Этот класс может быть базовым, можно расшарить блокировку между несколькими экземплярами... Дальше полёт фантазии.

Sign up to leave a comment.