Ленивая <T>реализация и .NET дженерики - PullRequest
15 голосов
/ 12 августа 2010

Я искал способы сделать ленивую инициализацию и нашел 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.

Теперь вот проблема:

Если я хочу скомпилировать-время проверки для 1-го ctor'а я добавлю ограничение

class Lazy<T> where T: new() {...}

на уровне класса.Это позволит мне использовать new T() для создания экземпляра;но это ограничение не является обязательным для второго ctor, и, что еще хуже, оно также ограничивает типы, которые я могу использовать (для типов с ctor по умолчанию)

Если я хочу иметь возможность использовать любой тип со вторым ctorЯ не буду устанавливать никаких ограничений, и в 1-м ctor будет использовать отражение, чтобы убедиться, что T имеет ctor по умолчанию.Однако при таком подходе будет отсутствовать проверка во время компиляции, и будет генерироваться исключение во время выполнения, только если 1-й код используется с неправильным типом.

Мой вопрос: могу ли я получить лучшее из обоих миров?

В идеале я хотел бы получить проверку во время компиляции для каждого использования ctor # 1, но в то же время иметь возможность использовать ctor # 2 для типов, которые не имеют ctor по умолчанию.

Как реализация Microsoft делает это?(У меня нет легкого доступа к источникам или библиотекам .NET 4).

РЕДАКТИРОВАТЬ: (После "Отражения" сборки MS)

Я проверил эталонную реализацию, и онане выполняет проверки во время компиляции.
Он использует отражение для случая 'ctor по умолчанию', конечно, сопровождается исключением времени выполнения, если дела идут плохо.

Ответы [ 5 ]

12 голосов
/ 12 августа 2010

Я ожидаю, что встроенная реализация просто использует Activator.CreateInstance<T> для простоты.Самый чистый способ обмануть меня - это отдельная фабрика:

// 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> { ... }
7 голосов
/ 12 августа 2010

Вы можете использовать статический фабричный метод вместо перегрузки в конструкторе:

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> защищенными внутренними, и предоставив статический фабричный класс, который вы всегда используете для создания экземпляров:

public static class Lazy {
    static Lazy<T> Create<T>( Func<T> ) { ... }
    static Lazy<T> Create<T>( ) where T : new() { ... }
}
2 голосов
/ 12 августа 2010

Почему бы вам просто не скачать параллельные расширения и установить Lazy<T> для 3.5? Прямая ссылка

0 голосов
/ 28 ноября 2011

Нечто подобное должно работать на вас.Мы использовали это в нашей локальной кодовой базе в течение года или около того, прежде чем перейти на 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;
    }
  }
}
0 голосов
/ 12 августа 2010

Мой вопрос таков: могу ли я получить лучшее из обоих миров?

Нет.

В принципе, у вас нет ограничений, проверяемых во время компиляции.

...