2010-07-08 12 views
22

. NET 4.0 ma ładną klasę narzędziową o nazwie System.Lazy, która inicjuje leniwy obiekt. Chciałbym użyć tej klasy do projektu 3.5. Pewnego razu zobaczyłem implementację gdzieś w odpowiedzi na pytanie o stackoverflow, ale nie mogę go już znaleźć. Czy ktoś ma alternatywną implementację Lazy? Nie wymaga wszystkich funkcji bezpieczeństwa wątków w wersji 4.0 systemu.Implementacja Lazy <T> dla .NET 3.5

Aktualizacja:

Odpowiedzi zawierają non wątek bezpieczne i bezpieczna wersja wątek.

Odpowiedz

25

Oto implementacja, której używam.

/// <summary> 
/// Provides support for lazy initialization. 
/// </summary> 
/// <typeparam name="T">Specifies the type of object that is being lazily initialized.</typeparam> 
public sealed class Lazy<T> 
{ 
    private readonly object padlock = new object(); 
    private readonly Func<T> createValue; 
    private bool isValueCreated; 
    private T value; 

    /// <summary> 
    /// Gets the lazily initialized value of the current Lazy{T} instance. 
    /// </summary> 
    public T Value 
    { 
     get 
     { 
      if (!isValueCreated) 
      { 
       lock (padlock) 
       { 
        if (!isValueCreated) 
        { 
         value = createValue(); 
         isValueCreated = true; 
        } 
       } 
      } 
      return value; 
     } 
    } 

    /// <summary> 
    /// Gets a value that indicates whether a value has been created for this Lazy{T} instance. 
    /// </summary> 
    public bool IsValueCreated 
    { 
     get 
     { 
      lock (padlock) 
      { 
       return isValueCreated; 
      } 
     } 
    } 


    /// <summary> 
    /// Initializes a new instance of the Lazy{T} class. 
    /// </summary> 
    /// <param name="createValue">The delegate that produces the value when it is needed.</param> 
    public Lazy(Func<T> createValue) 
    { 
     if (createValue == null) throw new ArgumentNullException("createValue"); 

     this.createValue = createValue; 
    } 


    /// <summary> 
    /// Creates and returns a string representation of the Lazy{T}.Value. 
    /// </summary> 
    /// <returns>The string representation of the Lazy{T}.Value property.</returns> 
    public override string ToString() 
    { 
     return Value.ToString(); 
    } 
} 
+0

Mam dwa problemy z tym: Po pierwsze, lepiej jest" zablokować "obiekt prywatny niż" zablokować (ten) ", ponieważ nie można kontrolować, kto może zablokować twoją instancję 'Lazy'. Po drugie, nie wydaje mi się, żeby 'isValueCreated'' 'volatile' field służyło jakiemukolwiek celowi, gdy już używasz krytycznej sekcji (prawda? Popraw mnie jeśli się mylę). – Aaronaught

+0

Zgadzam się, że lotny jest używany, gdy blokowanie nie jest używane. Od MSDN: Lotny modyfikator jest zwykle używany w przypadku pola, do którego dostęp ma wiele wątków bez użycia instrukcji lock do serializowania dostępu. Używanie lotnego modyfikatora zapewnia, że ​​jeden wątek pobiera najbardziej aktualną wartość zapisaną przez inny wątek. –

+0

Ja zmodyfikowałem odpowiedź. –

10

Jeśli nie potrzebujesz zabezpieczenia gwintów, bardzo łatwo jest połączyć je z metodą fabryczną. Używam jeden bardzo podobny do następującego:

public class Lazy<T> 
{ 
    private readonly Func<T> initializer; 
    private bool isValueCreated; 
    private T value; 

    public Lazy(Func<T> initializer) 
    { 
     if (initializer == null) 
      throw new ArgumentNullException("initializer"); 
     this.initializer = initializer; 
    } 

    public bool IsValueCreated 
    { 
     get { return isValueCreated; } 
    } 

    public T Value 
    { 
     get 
     { 
      if (!isValueCreated) 
      { 
       value = initializer(); 
       isValueCreated = true; 
      } 
      return value; 
     } 
    } 
} 
+0

Ktoś, kto mógłby to skopiować: Łatwo się tutaj pomylić z inicjatorem. Upewnij się, aby uchwycić swoje wartości! –

+0

@Rex: Masz na myśli, jeśli inicjujesz instancję 'Lazy ' z pętli? Czy 'System.Lazy' robi trochę magii, by uniknąć normalnych niebezpieczeństw przechwytywania? – Aaronaught

+0

pętla jest dobrym przykładem. Wątpię, czy prawdziwy "Lazy" jest inny, chociaż nie jestem pewien. Pomyślałem, że wskażę to, ponieważ widzę, że wiele osób ma kłopoty z tego rodzaju wzorcem. –

2

Nieco uprościć wersji Aaron

public class Lazy<T> where T : new() 
{ 
    private T value; 

    public bool IsValueCreated { get; private set;} 

    public T Value 
    { 
    get 
    { 
     if (!IsValueCreated) 
     { 
      value = new T(); 
      IsValueCreated = true; 
     } 
     return value; 
    } 
    } 
} 
+0

Wymaga domyślnego ctorka –

+4

@BC: Tak, to właśnie oznacza 'where T: new()'. –

-1

jakieś zabawne (ale nie bardzo użytkowej) rzeczy można dodać: niejawny Coversion od delegata:

public static implicit operator Lazy<T>(Func<T> initializer) 
{ 
    return new Lazy<T>(initializer); 
} 

i użytkowania

private static Lazy<int> Value = new Func<int>(() => 24 * 22); 

Kompilator C# ma pewien problem z wykonaniem tej konwersji, na przykład przypisanie wyrażenia lambda nie działa, ale jest to jeszcze jedna rzecz, która powoduje, że twoi koledzy myślą nieco :)

+0

lambdas może być drzewem wyrażeń lub delegatami, więc kompilator nie uważa ich za jednych lub drugich. z tego samego powodu, dla którego nie można umieścić lambda w var. Naprawdę denerwujące czasami ... –

+0

Masz rację, ale ten kod nie działa nawet z metodami: ' public static int NewValue() { powrót 24 * 15; } public static Lazy V = NewValue; // Błąd kompilacji, potrzebuje nowego Func (NewValue) ' – STO

Powiązane problemy