#c# #generics #static-constructor
#c# #общие положения #статический конструктор
Вопрос:
Я хотел использовать статический конструктор в производных классах для регистрации лямбд конструктора объекта для тех классов, которые будут вызываться при передаче определенного Type
объекта.
Поскольку статический конструктор вызывается только непосредственно перед созданием первого объекта этого класса, НО я хочу использовать результат статического конструктора, чтобы определить, какой объект создавать, этот механизм завершается с ошибкой, лямбда-конструктор не найден.
Есть ли какой-нибудь выход из этого?
public abstract class Cacheable
{
// ... details do not matter ...
}
public class Series: Cacheable
{
// ... details do not matter ...
}
public abstract class CacheableViewForm
{
static Dictionary<Type, Func<CacheableViewForm>> cacheableViewFormConstructors = new Dictionary<Type, Func<CacheableViewForm>>();
protected static void Register<CacheableViewFormClass, CacheableClass>()
where CacheableViewFormClass: CacheableViewForm, new()
where CacheableClass: Cacheable
{
cacheableViewFormConstructors[typeof(CacheableClass)] = (() => new CacheableViewFormClass());
}
public static CacheableViewForm CreateFromTargetType(Type cacheableType)
{
return cacheableViewFormConstructors[cacheableType]();
}
// ... details do not matter ...
}
public class SeriesViewForm: CacheableViewForm
{
static SeriesViewForm() {Register<SeriesViewForm, Series>();}
// ... details do not matter ...
}
// fails because CacheableViewForm.Register<> has not been executed yet!
CacheableViewForm newForm = CacheableViewForm.CreateFromTargetType(typeof(Series));
Комментарии:
1. «Есть ли какой-нибудь выход из этого?» — Я бы попытался найти другой подход, в принципе. Полагаться на то, когда именно будут выполняться статические конструкторы, затруднит анализ кода. Почему бы просто не создать отдельный метод, который вы вызываете явно?
2. (Обратите внимание, что статические конструкторы вызываются в других ситуациях, кстати, таких как статические методы, вызываемые для типа. Не обязательно создавать экземпляр. Но использование типа в качестве аргумента универсального типа не запускает статический конструктор, как вы заметили.)
3. Что ж, целью этого механизма было сохранить «регистрационный вызов» близким к соответствующему определению класса (в данном случае SeriesViewForm). Итак, если мне нужно добавить другой класс, производный от CacheableViewForm , я знаю, что должен добавить этот вызов к этому классу. Если регистрация выполнена, например, в моей MainForm, то мне нужно будет не забыть внести изменения в двух разных местах (MainForm И производный класс CacheableViewForm), чтобы расширить все это.
4. к вашему сведению, статический конструктор не принимает модификаторы доступа и не имеет параметров. Статический конструктор вызывается автоматически для инициализации класса перед созданием первого экземпляра или ссылкой на какие-либо статические члены. Статический конструктор не может быть вызван напрямую
5. Да, это неидеально, но имеет преимущество в работе. Другим подходом было бы использовать атрибуты для связывания соответствующих классов вместе, а затем проверять их с помощью reflection при запуске.
Ответ №1:
Мое решение состоит в том, чтобы перенести инициализацию (из статического конструктора производных классов) в статический конструктор базового класса (который вызывается автоматически перед выполнением моего первого вызова CreateFromTargetType
метода) и использовать отражение там для проверки производных классов. В производных классах я определил статические методы ( TargetType
), которые возвращают конкретные Type
данные, с которыми они работают.
Ни элегантный, ни компактный, и, конечно, не очень быстрый, но это позволяет мне поддерживать связь между производным классом (например, SeriesViewForm
) и типом данных, с которым он работает ( typeof(Series)
), близко друг к другу (в одном определении класса).
public abstract class CacheableViewForm: Form
{
static Dictionary<Type, Func<CacheableViewForm>> CacheableViewFormConstructors = new Dictionary<Type, Func<CacheableViewForm>>();
static CacheableViewForm()
{
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
foreach(Assembly assembly in allAssemblies)
{
Type[] cacheableViewFormTypes = assembly.GetExportedTypes().Where(t => typeof(CacheableViewForm).IsAssignableFrom(t) amp;amp; t != typeof(CacheableViewForm)).ToArray();
foreach (Type cacheableViewFormType in cacheableViewFormTypes)
{
MethodInfo mi = cacheableViewFormType.GetMethod("TargetType");
Type cacheableType = (Type)mi.Invoke(null, null);
Func<CacheableViewForm> ctorDelegate = (() => (CacheableViewForm)(Activator.CreateInstance(cacheableViewFormType)));
CacheableViewFormConstructors[cacheableType] = ctorDelegate;
}
}
}
public static CacheableViewForm CreateFromTargetType(Type cacheableType)
{
return CacheableViewFormConstructors[cacheableType]();
}
}
public class SeriesViewForm: CacheableViewForm
{
public static Type TargetType() {return typeof(Series);}
// ...
}