C# - ResourceManager - кешируем или как?
Нигде особо это не обсуждается. В МСДНе тоже глухо по этой теме. Лишь на Stackoverflow один-два вопроса нашёл. Вроде сошлись, что надо кешировать, но импакт по производительности маленький. А я тут недавно засунул этот ResourceManager в самый базовый класс для всех классов - каждая сущность может иметь DisplayName и Description. И там значит при любом создании любого инстанса любой сущности создаётся этот ResourceManager и он ищет локализованные значения для DisplayName и Description. Только он всякий раз сам себя снова создаёт и грузит файлы ресурсов. И вот если я в цикле создал сотню объектов, у каждого из которых пару десятков полей, каждое из которых имеет DisplayName и Description, и инициализируется своим инстансом ResourceManager... Это ж сколько тысяч раз эти файлы ресурсов открылись и закрылись, не говоря уже о самом ResourceManager?..
По идее, его вообще синглетоном надо на всё приложение сделать, тем более, что он, как пишут в МСДНе, потокобезопасен. Т.е. даже если это сайт для многих пользователей, то лишь при старте сайта один раз создал его и всё. Но вот нигде не встречал описания сценария, когда ResourceManager организован как синглетон на всё время жизни приложения. Везде в примерах его создают и юзают по месту использования. И такое ощущение, что миллионы народу делают то же самое не задумываясь.
Что интересно, в генерящихся ресурсах, создающихся по шаблону resx файлов, уже есть свой закешированный ResourceManager (см. ниже) - для каждого типа Strings закеширован. И если кешировать самому ResourceManager, то Strings создаст свою закешированную копию.
public class Strings { private static global::System.Resources.ResourceManager resourceMan; ... /// <summary> /// Returns the cached ResourceManager instance used by this class. /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] public static global::System.Resources.ResourceManager ResourceManager { get { if (object.ReferenceEquals(resourceMan, null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("...Strings", typeof(Strings).Assembly); resourceMan = temp; } return resourceMan; } } }
Полагаю что такая же беда, как и в яве - out of order execution. Но добавление лишней переменной от этого (в яве) не спасает.
Если по-простому: создание/инициализация объекта это много инструкций. Вместо "выделили память, инициализировали поля, выполнили конструктор, присвоили адрес переменной" VM может выполнить "выделить память, присвоили адрес переменной, инициализировать поля, ...".
А в это время второй поток подходит к if-у, видит что наш синглтон уже не null и радостно возвращает его значение. А там у нас недоинициализированный объект.
Вобщем, чтобы отказаться от своего менеджера, который надо кешировать, и использовать встроенный закешированный в ресурсных файлах, приходится юзать рефлексию. Но вроде такое, что внизу, всё равно быстрее, чем грузить ресурсы постоянно
var type = typeof(...Strings); DisplayName = type .GetProperty($"{propName}Name", BindingFlags.Public | BindingFlags.Static | BindingFlags.GetProperty) .GetValue(null) .ToString(); Description = type .GetProperty($"{propName}Description", BindingFlags.Public | BindingFlags.Static | BindingFlags.GetProperty) .GetValue(null) .ToString();
Но либо придётся писать такую портянку на каждый класс, чьи свойства имеют "имя и описание", либо затолкать в базовый класс, и в него передавать тип ресурса и строковое имя свойства. Если в базовый, то портянка уже будет в цепочке параметров конструкторов... Блин, нет идеального аккуратного решения.
Думаю, что если посмотришь более внимательно, то найдешь где-нибудь ссылку на то, что ResourceManager аккуратно кеширует запрошенные данные...
В смысле кеширует? Загружает их один раз? Так это и так понятно. Нигде не встречал отдельного упоминания о кешировании - ни в описании в МСДНе, ни в исходниках. Т.е. открыл их и просто поиском по "cach" - ничего.
Проблема не в том, что сам менеджер кеширует или нет, а в том, что ничего не мешает создать несколько этих менеджеров, и каждый загрузит ресурсы заново. И я привёл кусок кода из класса, сгенеренного дефолтной студийной утилитой PublicResXFileCodeGenerator
Сам этот класс кеширует менеджера. Т.е. если обращаться к ресурсам из класса, то всё закешировано, а если как везде указано - из самим созданного менеджера, то нет. Тогда ты должен сам заботиться о кешировании этого менеджера.
Т.е. юзать этот менеджер самому вообще не надо - он юзается под капотом сгенеренным классом доступа к ресурсам и уже закеширован.
Единственная проблема с этим классом - он дубовый и мало что может. Если надо руками в коде выбирать имена ресурсов - пойдёт. А если я получаю эти имена в виде строк, то надо конструировать вот такие костыли, что я раньше приводил.
https://learn.microsoft.com/en-us/dotnet/api/system.resour...
ImportantAlthough the ResourceManager class is supported in Windows 8.x apps, we do not recommend its use. Use this class only when you develop Portable Class Library projects that can be used with Windows 8.x apps.
Если уж так сильно хочется Localization in .NET - https://learn.microsoft.com/en-us/dotnet/core/extensions/l...
А я тут недавно засунул этот ResourceManager в самый базовый класс для всех классов
Не кажется ли это - не совсем оптимальным решением?
Если уж так сильно хочется Localization in .NET - https://learn.microsoft.com/en-us/dotnet/core/extensions/l...
Вот это не подходит
<FullTypeName><.Locale>.resx
Хочу запихать ресурсы в Юнити, а он распознает только до первого расширения (до точки), и использует <FullTypeName> как уникальный ключ ресурса. Т.е. если поместить несколько файлов с разными локалями, то он посчитает их одним.
Т.е. юзать этот менеджер самому вообще не надо - он юзается под капотом сгенеренным классом доступа к ресурсам и уже закеширован.
Единственная проблема с этим классом - он дубовый и мало что может. Если надо руками в коде выбирать имена ресурсов - пойдёт. А если я получаю эти имена в виде строк, то надо конструировать вот такие костыли, что я раньше приводил.
Хмм, тогда можно попробовать использовать этот менеджер из сгенеренного файла ресурсов, вместо того, чтобы создавать свой. Т.е.
...Strings.ResourceManager.GetString(...)
Не знаю как там в вашем юнити, но вот что попалось
https://phrase.com/blog/posts/localizing-unity-games-offic...
Спасибо за ссылки, но все расширения для Юнити идут лесом. Они конечно хотят подсадить всех на свои поделки. Но за ними уже давно наблюдается, что это либо сначало платное, либо бесплатное с тенденцией стать платным плагином, либо годами недоделано. При этом всячески режется возможность использовать что-то сторонее, или чтобы это было максимально геморройно. Чтобы ты отказался и перешёл на их поделки или купил что-то в их магазине.
Но кроме того, у меня всё локализованные ресурсы идут не от Юнити, а от моих либ на Дотнете. Поэтому я хочу как можно меньше привязываться к Юнити. Хочу использовать его лишь для UI, которое бы получало уже локализованные данные из слоя ниже (из условной бизнес логики).
С Юнити одна проблема, которая не позволяет использовать обычный генератор доступа к локализованным ресурсам - Юнити использует имена файлов ресурсов как кникальные ключи. Причём не различает даже файлы, положенные в разные папки - считает их одним и тем же ресурсом. А генератор генерит каждую локаль с одинаковым именем, но разным расширением. Достаточно было бы генерить различающиеся имена и всё. Я не хочу сейчас сам подробно этим заниматься - я довёл до состояния, когда оно запускается и роется в локалях, а как их различать - потом буду думать. Может, своё чё напишу расширающее. Есть вроде готовые генераторы на шаблонах, с куда большими возможностями, но их искать и смотреть надо...
тогда можно и готовое стибрить
https://blazorise.com/docs/helpers/localization
https://github.com/Megabit/Blazorise/tree/master/Source/Bl...