Deutsch

C# - ResourceManager - кешируем или как?

895  
alex445 коренной житель10.12.23 17:31
NEW 10.12.23 17:31 
Последний раз изменено 10.12.23 18:17 (alex445)

Нигде особо это не обсуждается. В МСДНе тоже глухо по этой теме. Лишь на 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;
        }
    }
}
#1 
alex445 коренной житель10.12.23 17:33
10.12.23 17:33 
в ответ alex445 10.12.23 17:31

Кстати, а зачем нужна промежуточная переменная temp? Почему сразу не присвоить resourceMan?

#2 
MrSanders коренной житель10.12.23 18:07
NEW 10.12.23 18:07 
в ответ alex445 10.12.23 17:33, Последний раз изменено 10.12.23 18:21 (MrSanders)

Полагаю что такая же беда, как и в яве - out of order execution. Но добавление лишней переменной от этого (в яве) не спасает.

Если по-простому: создание/инициализация объекта это много инструкций. Вместо "выделили память, инициализировали поля, выполнили конструктор, присвоили адрес переменной" VM может выполнить "выделить память, присвоили адрес переменной, инициализировать поля, ...".

А в это время второй поток подходит к if-у, видит что наш синглтон уже не null и радостно возвращает его значение. А там у нас недоинициализированный объект.

#3 
alex445 коренной житель10.12.23 19:47
NEW 10.12.23 19:47 
в ответ alex445 10.12.23 17:31, Последний раз изменено 10.12.23 19:50 (alex445)

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


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();
#4 
alex445 коренной житель10.12.23 19:49
NEW 10.12.23 19:49 
в ответ alex445 10.12.23 19:47, Последний раз изменено 10.12.23 19:49 (alex445)

Но либо придётся писать такую портянку на каждый класс, чьи свойства имеют "имя и описание", либо затолкать в базовый класс, и в него передавать тип ресурса и строковое имя свойства. Если в базовый, то портянка уже будет в цепочке параметров конструкторов... Блин, нет идеального аккуратного решения.

#5 
Murr патриот10.12.23 20:27
Murr
NEW 10.12.23 20:27 
в ответ alex445 10.12.23 17:31

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

#6 
Murr патриот10.12.23 20:30
Murr
NEW 10.12.23 20:30 
в ответ MrSanders 10.12.23 18:07

Угу... а если не инициализированный - запускает создание второй копии.


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

#7 
alex445 коренной житель10.12.23 21:22
NEW 10.12.23 21:22 
в ответ Murr 10.12.23 20:27, Последний раз изменено 10.12.23 21:27 (alex445)
Думаю, что если посмотришь более внимательно, то найдешь где-нибудь ссылку на то, что ResourceManager аккуратно кеширует запрошенные данные...

В смысле кеширует? Загружает их один раз? Так это и так понятно. Нигде не встречал отдельного упоминания о кешировании - ни в описании в МСДНе, ни в исходниках. Т.е. открыл их и просто поиском по "cach" - ничего.


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



Сам этот класс кеширует менеджера. Т.е. если обращаться к ресурсам из класса, то всё закешировано, а если как везде указано - из самим созданного менеджера, то нет. Тогда ты должен сам заботиться о кешировании этого менеджера.


Т.е. юзать этот менеджер самому вообще не надо - он юзается под капотом сгенеренным классом доступа к ресурсам и уже закеширован.


Единственная проблема с этим классом - он дубовый и мало что может. Если надо руками в коде выбирать имена ресурсов - пойдёт. А если я получаю эти имена в виде строк, то надо конструировать вот такие костыли, что я раньше приводил.

#8 
AlexNek патриот10.12.23 21:25
AlexNek
NEW 10.12.23 21:25 
в ответ alex445 10.12.23 17:31

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 в самый базовый класс для всех классов

Не кажется ли это - не совсем оптимальным решением?

#9 
alex445 коренной житель10.12.23 21:35
NEW 10.12.23 21:35 
в ответ AlexNek 10.12.23 21:25
Если уж так сильно хочется Localization in .NET - https://learn.microsoft.com/en-us/dotnet/core/extensions/l...

Вот это не подходит

<FullTypeName><.Locale>.resx

Хочу запихать ресурсы в Юнити, а он распознает только до первого расширения (до точки), и использует <FullTypeName> как уникальный ключ ресурса. Т.е. если поместить несколько файлов с разными локалями, то он посчитает их одним.

#10 
alex445 коренной житель10.12.23 21:37
NEW 10.12.23 21:37 
в ответ alex445 10.12.23 21:22

Т.е. юзать этот менеджер самому вообще не надо - он юзается под капотом сгенеренным классом доступа к ресурсам и уже закеширован.


Единственная проблема с этим классом - он дубовый и мало что может. Если надо руками в коде выбирать имена ресурсов - пойдёт. А если я получаю эти имена в виде строк, то надо конструировать вот такие костыли, что я раньше приводил.

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


...Strings.ResourceManager.GetString(...)

#11 
AlexNek патриот10.12.23 21:47
AlexNek
NEW 10.12.23 21:47 
в ответ alex445 10.12.23 21:35

Не знаю как там в вашем юнити, но вот что попалось

https://phrase.com/blog/posts/localizing-unity-games-offic...

https://github.com/needle-mirror/com.unity.localization

#12 
Murr патриот10.12.23 23:01
Murr
NEW 10.12.23 23:01 
в ответ alex445 10.12.23 21:22

а в том, что

-----

Тебе лениво посмотреть сорцы. Слазь, погляди - может что и поймешь...


он юзается под капотом сгенеренным классом доступа к ресурсам и уже закеширован

-----

В этом случае обычно есть возможность подменить управление ресурсами.

#13 
alex445 коренной житель11.12.23 00:37
NEW 11.12.23 00:37 
в ответ Murr 10.12.23 23:01

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

#14 
alex445 коренной житель11.12.23 00:44
NEW 11.12.23 00:44 
в ответ AlexNek 10.12.23 21:47, Последний раз изменено 11.12.23 00:48 (alex445)

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


Но кроме того, у меня всё локализованные ресурсы идут не от Юнити, а от моих либ на Дотнете. Поэтому я хочу как можно меньше привязываться к Юнити. Хочу использовать его лишь для UI, которое бы получало уже локализованные данные из слоя ниже (из условной бизнес логики).


С Юнити одна проблема, которая не позволяет использовать обычный генератор доступа к локализованным ресурсам - Юнити использует имена файлов ресурсов как кникальные ключи. Причём не различает даже файлы, положенные в разные папки - считает их одним и тем же ресурсом. А генератор генерит каждую локаль с одинаковым именем, но разным расширением. Достаточно было бы генерить различающиеся имена и всё. Я не хочу сейчас сам подробно этим заниматься - я довёл до состояния, когда оно запускается и роется в локалях, а как их различать - потом буду думать. Может, своё чё напишу расширающее. Есть вроде готовые генераторы на шаблонах, с куда большими возможностями, но их искать и смотреть надо...

#15 
AlexNek патриот11.12.23 18:44
AlexNek
NEW 11.12.23 18:44 
в ответ alex445 11.12.23 00:44

А сколько мегатонн текста планируется?

На каждый язык по джейсону и усё. Работы думаю на вечер. И файлы как встроенные ресурсы.

#16 
alex445 коренной житель11.12.23 18:50
NEW 11.12.23 18:50 
в ответ AlexNek 11.12.23 18:44

Вот что-то подобное я и сделаю, пожалуй.

#17 
AlexNek патриот11.12.23 20:27
AlexNek
NEW 11.12.23 20:27 
в ответ alex445 11.12.23 18:50