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;
    }
  }
}