Есть ещё один прикол. Если стоит задача переключения языка на лету, то тут наступает огромное разочарование. Приведу несколько кусочков кода компонента PropertyGrid, они хорошо иллюстрируют всю боль происходящего.
public abstract class MemberDescriptor {
public virtual string DisplayName {
get {
DisplayNameAttribute displayNameAttr = Attributes[typeof(DisplayNameAttribute)] as DisplayNameAttribute;
if (displayNameAttr == null || displayNameAttr.IsDefaultAttribute()) {
return displayName;
}
return displayNameAttr.DisplayName;
}
}
public virtual string Description {
get {
if (description == null) {
description = ((DescriptionAttribute) Attributes[typeof(DescriptionAttribute)]).Description;
}
return description;
}
}
public virtual string Category {
get {
if (category == null) {
category = ((CategoryAttribute)Attributes[typeof(CategoryAttribute)]).Category;
}
return category;
}
}
}
Если вкратце, то у однажды загруженных свойств описание кэшируется и при смене языка остаётся навсегда тем же. С категорией то же самое. А вот показываемое имя изменяется.
Короче, боль.
И эта реализация имеет проблемы: Раб он как бы не совсем двигатель. В данном случае корректнее будет ввести интерфейс IEngine вместо абстрактного базового класса Engine.
Тогда нужен динамический регистратор. В .NET в сборках банально нету кода, который запускается при загрузке её в домен приложения, то есть аналога DllMain.
У меня в проектах достаточно часто встречается сборка всех потомков рефлексией. Но не в статическом конструкторе, а через статическое свойство, которое инициализирует список потомков лениво, при первом запросе.
Эта практика хорошо работает, если надо делать поддержку различных входных данных или выбор пользователем каких-то вещей.
Сделать всё на основе прямых вызовов, когда проект не монолитный и допускает расширения, всё равно не получится.
Такое решение имеет другую проблему: в какой момент и кто должен вызвать этот метод?
В некоторых скриптовых языках базовый класс может получить уведомление о том, что в области видимости появился его потомок. В .NET такое сделать невозможно в силу ленивости сборок и классов.
Зависимость базового класса от потомков в последнем примере — в общем-то не слишком хорошо.
Однако этот код может стать основой для загрузки типов динамически с помощью рефлексии по модели плагинов. Но это уже совсем другая история.
Кстати, в той реализации, которую Вы здесь видите, у таски есть состояния «активна», «заблокирована» и «завершена» и планировщик по-разному себя с ними ведёт.
Там выше есть строчка:
#define TASK_WAIT_FOR(Object) this->WaitFor(Object); this->state = __LINE__; return false; case __LINE__:
Так вот, WaitFor указывает планировщику, что таска заблокирована на указанном объекте, после чего сохраняется состояние. Выполнение с точки case __LINE__: начнётся после того, как объект станет сигнальным. Это чем-то напоминает дескрипторы, на которых выполняются блокирующие вызовы в операционке.
Это прерывания по приходу внешних сигналов. Всего же векторов прерываний куда больше. Только надо иметь в виду, что прерывания таймеров могут использоваться какими-то библиотеками, которые «застолбили» их за собой.
Поздравляю, Вы изобрели очередную простенькую кооперативную многозадачность.
Основная проблема здесь в том, что семантика его использования достаточно страшная и непонятная.
Если сделать нечто наподобие такого (код большой, прячу под спойлер и отбрасываю много чего)
Что-то мешает иметь в посылке с координатами ещё и время формирования этой посылки, и считать в этой самой шкале времени, а не по событиям передачи данных?
Если вкратце, то у однажды загруженных свойств описание кэшируется и при смене языка остаётся навсегда тем же. С категорией то же самое. А вот показываемое имя изменяется.
Короче, боль.
По-простому не выходит.
Эта практика хорошо работает, если надо делать поддержку различных входных данных или выбор пользователем каких-то вещей.
Сделать всё на основе прямых вызовов, когда проект не монолитный и допускает расширения, всё равно не получится.
В некоторых скриптовых языках базовый класс может получить уведомление о том, что в области видимости появился его потомок. В .NET такое сделать невозможно в силу ленивости сборок и классов.
Однако этот код может стать основой для загрузки типов динамически с помощью рефлексии по модели плагинов. Но это уже совсем другая история.
Там выше есть строчка:
#define TASK_WAIT_FOR(Object) this->WaitFor(Object); this->state = __LINE__; return false; case __LINE__:
Так вот, WaitFor указывает планировщику, что таска заблокирована на указанном объекте, после чего сохраняется состояние. Выполнение с точки case __LINE__: начнётся после того, как объект станет сигнальным. Это чем-то напоминает дескрипторы, на которых выполняются блокирующие вызовы в операционке.
Основная проблема здесь в том, что семантика его использования достаточно страшная и непонятная.
Если сделать нечто наподобие такого (код большой, прячу под спойлер и отбрасываю много чего)
Да, макросы, но результат явно менее страшный, чем кодирование руками конечного автомата.
Как-то для pet-проектов 250$ — это дороговато.