Skip to content

Commit

Permalink
Use the more low-level LazyInitializer to avoid the Lazy<> gotcha
Browse files Browse the repository at this point in the history
  • Loading branch information
EamonNerbonne committed Oct 4, 2021
1 parent e993fd8 commit 90379bd
Show file tree
Hide file tree
Showing 2 changed files with 13 additions and 6 deletions.
11 changes: 9 additions & 2 deletions src/ProgressOnderwijsUtils/Utils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,15 @@ public static bool FuzzyEquals(double x, double y)
return x == y || delta / magnitude < relativeEpsilon;
}

public static Lazy<T> Lazy<T>(Func<T> factory)
=> new Lazy<T>(factory, LazyThreadSafetyMode.ExecutionAndPublication);
public static Func<T> Lazy<T>(Func<T> factory)
{
//Ideally we'd use Lazy<T>, but that either caches exceptions or fails to lock around initialization. see: https://github.com/dotnet/runtime/issues/27421#issuecomment-424732179
var value = default(T?);
var initialized = false;
var sync = (object)factory;

return () => LazyInitializer.EnsureInitialized(ref value, ref initialized, ref sync, factory);
}

/// <summary>
/// Swap two objects.
Expand Down
8 changes: 4 additions & 4 deletions test/ProgressOnderwijsUtils.Tests/UtilsTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,14 +92,14 @@ public void LazyRetriesExceptions()
var sometimesFailingLazy = Utils.Lazy(() => ++count % 4 == 0 ? count : throw new("gotcha!"));

PAssert.That(() => count == 1);
PAssert.That(() => nonFailingLazy.Value == 2);
PAssert.That(() => nonFailingLazy() == 2);
PAssert.That(() => count == 2);
PAssert.That(() => nonFailingLazy.Value == 2, "A second read of the lazily computed value should not change the value");
PAssert.That(() => nonFailingLazy() == 2, "A second read of the lazily computed value should not change the value");
PAssert.That(() => count == 2, "A second read of the lazily computed value must not have side-effects");

_ = Assert.Throws<Exception>(() => _ = sometimesFailingLazy.Value);
_ = Assert.Throws<Exception>(() => _ = sometimesFailingLazy());
PAssert.That(() => count == 3);
PAssert.That(() => sometimesFailingLazy.Value == 4);
PAssert.That(() => sometimesFailingLazy() == 4);
}

[Fact]
Expand Down

0 comments on commit 90379bd

Please sign in to comment.