Lazy <T> и .NET-дженерики
Я искал способы сделать ленивую инициализацию и нашел Lazy<T>
, который включен в .NET 4.
Я думал о том, чтобы выполнить собственную реализацию Lazy<T>
для .NET 3.5 (с более простой политикой с несколькими потоками), и я столкнулся с следующей проблемой:
Lazy имеет в основном два типа конструкторов:
class Lazy<T> {
public Lazy(){...} // ctor #1
который использует конструктор по умолчанию T для создания экземпляра T и
public Lazy(Func<T> func){...} // ctor #2
который позволяет вызывающему пользователю решить, как создается экземпляр T.
Теперь вот проблема:
Если мне нужна проверка времени компиляции для первого ctor, я добавлю ограничение
class Lazy<T> where T: new() {...}
на уровне класса. Это позволит мне использовать new T()
для создания экземпляра; но это ограничение не требуется для второго ctor, и, что еще хуже, оно также ограничивает типы, которые я могу использовать (для тех, у кого есть значение по умолчанию ctor)
Если я хочу использовать любой тип со вторым ctor, я не буду устанавливать никаких ограничений, и в 1-м ctor будет использоваться отражение, чтобы убедиться, что T
имеет значение по умолчанию ctor. Однако этот подход не будет проверять время компиляции и будет генерировать исключение только во время выполнения, если первый ctor используется с неправильным типом.
Мой вопрос: могу ли я получить лучшее из обоих миров?
В идеале я хотел бы получить проверку времени компиляции для каждого использования ctor # 1, но в то же время иметь возможность использовать ctor # 2 для типов, у которых нет значения по умолчанию ctor.
Как это делает реализация Microsoft? (У меня нет доступа к источникам или библиотекам .NET 4).
EDIT: (После "Reflector-ing" сборки MS)
Я проверил эталонную реализацию и не выполняет проверки времени компиляции.
Он использует отражение для случая "по умолчанию ctor", конечно, сопровождается исключением во время выполнения, если все идет плохо.
Ответы
Ответ 1
Я ожидаю, что встроенная реализация просто использует Activator.CreateInstance<T>
для простоты. Самый чистый способ, которым я могу обмануть это, - это отдельный factory:
// non-generic factory class with generic methods
public static class Lazy {
public static Lazy<T> Create<T>() where T : new() {
return Create<T>(() => new T());
}
public static Lazy<T> Create<T>(Func<T> ctor) { ... }
}
public class Lazy<T> { ... }
Ответ 2
Вы можете использовать статический метод factory вместо перегрузки для конструктора:
public class Lazy<T>
{
public Lazy( Func<T> f ) { /*...*/ }
public static Lazy<R> Default<R>() where R : T, new()
{
return new Lazy<R>( () => new R() );
}
}
Теперь это нарушает совместимость (в некоторой степени) с версией .NET 4.0 Lazy<T>
, но она обеспечивает безопасность времени компиляции для обоих типов использования.
Вы можете сделать это немного более чистым, сделав конструкторы для Lazy<T>
protected internal и предоставив статический класс factory, который вы всегда используете для создания экземпляров:
public static class Lazy {
static Lazy<T> Create<T>( Func<T> ) { ... }
static Lazy<T> Create<T>( ) where T : new() { ... }
}
Ответ 3
Почему вы просто не загружаете параллельные расширения и не устанавливаете Lazy<T>
для 3.5? Прямая ссылка
Ответ 4
Мой вопрос: могу ли я получить лучшее из оба мира?
Нет.
В принципе у вас нет ограничения на время компиляции.
Ответ 5
Что-то вроде этого должно работать на вас. Мы использовали это в нашей локальной базе кода в течение года или около того, прежде чем переходить к 4.0.
public class Lazy<T> {
private Func<T> func;
private T result;
private bool hasValue;
public Lazy(Func<T> func) {
this.func = func;
this.hasValue = false;
}
public T Value {
get {
if (!this.hasValue) {
this.result = this.func();
this.hasValue = true;
}
return this.result;
}
}
}