diff --git a/Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj b/Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj
new file mode 100644
index 0000000..76fefe2
--- /dev/null
+++ b/Tasks/Lazy/Lazy.Tests/Lazy.Tests.fsproj
@@ -0,0 +1,28 @@
+
+
+
+ net9.0
+ latest
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Tasks/Lazy/Lazy.Tests/LazyTests.fs b/Tasks/Lazy/Lazy.Tests/LazyTests.fs
new file mode 100644
index 0000000..1f78db2
--- /dev/null
+++ b/Tasks/Lazy/Lazy.Tests/LazyTests.fs
@@ -0,0 +1,107 @@
+module Lazy.Tests
+
+open NUnit.Framework
+open FsUnit
+
+open System
+open System.Threading
+open System.Threading.Tasks
+
+let numberOfGets = 256
+let numberOfThreads = 256
+
+let mutable evaluationCount = 0
+
+let testSupplier supplier = fun () ->
+ Interlocked.Increment &evaluationCount |> ignore
+ supplier ()
+
+let testObject1, testObject2 = new Object (), new Object ()
+
+let suppliersWithResult: (unit -> obj) list = [
+ fun () -> 80808;
+ fun () -> "qwertyuiop".Substring (0, 6);
+ fun () -> Math.Round (782.192, 1);
+ fun () ->
+ if evaluationCount < 10 then testObject1
+ else testObject2;
+ fun () ->
+ if evaluationCount < 10 then testObject2
+ else raise (new SystemException ());
+]
+
+let suppliersWithException: (unit -> obj) list = [
+ fun () ->
+ raise (new InvalidOperationException ())
+ 100;
+ fun () ->
+ raise (new ArgumentException ())
+ "abc";
+]
+
+let testSuppliersWithResult = suppliersWithResult |> List.map testSupplier
+let testSuppliersWithException = suppliersWithException |> List.map testSupplier
+
+let results: obj list = [80808; "qwerty"; 782.2; testObject1; testObject2]
+let exceptions = [typeof; typeof]
+
+let testCasesWithResult = Seq.zip testSuppliersWithResult results |> Seq.map TestCaseData
+let testCasesWithException = Seq.zip testSuppliersWithException exceptions |> Seq.map TestCaseData
+
+let testForSingleThread_WithResult (result: 'a) (countConstraint: int -> bool) (testLazy: ILazy<'a>) =
+ for _ in { 1 .. numberOfGets } do
+ testLazy.Get () |> should equal result
+ countConstraint evaluationCount |> should be True
+
+let testForSingleThread_WithException (exc: Type) (countConstraint: int -> bool) (testLazy: ILazy<'a>) =
+ for _ in { 1 .. numberOfGets } do
+ testLazy.Get |> should throw exc
+ countConstraint evaluationCount |> should be True
+
+[]
+let resetEvaluationCount () =
+ evaluationCount <- 0
+
+[]
+let testSingleThreadLazy_WithResult (supplier: unit -> obj, result: obj) =
+ supplier
+ |> SingleThreadLazy
+ |> testForSingleThread_WithResult result (( = ) 1)
+
+[]
+let testSingleThreadLazy_WithException (supplier: unit -> obj, exc: Type) =
+ supplier
+ |> SingleThreadLazy
+ |> testForSingleThread_WithException exc (( = ) 1)
+
+[]
+let testThreadSafeLazy_WithResult (supplier: unit -> obj, result: obj) =
+ let testLazy = supplier |> ThreadSafeLazy
+ { 1 .. numberOfThreads }
+ |> Seq.map (fun _ -> Task.Run (
+ fun () -> testForSingleThread_WithResult result (( = ) 1) testLazy))
+ |> Task.WaitAll
+
+[]
+let testThreadSafeLazy_WithException (supplier: unit -> obj, exc: Type) =
+ let testLazy = supplier |> ThreadSafeLazy
+ { 1 .. numberOfThreads }
+ |> Seq.map (fun _ -> Task.Run (
+ fun () -> testForSingleThread_WithException exc (( = ) 1) testLazy))
+ |> Task.WaitAll
+
+[]
+let testLockFreeLazy_WithResult (supplier: unit -> obj, result: obj) =
+ let testLazy = supplier |> LockFreeLazy
+ { 1 .. numberOfThreads }
+ |> Seq.map (fun _ -> Task.Run (
+ fun () -> testForSingleThread_WithResult result (( >= ) numberOfGets) testLazy))
+ |> Task.WaitAll
+
+[]
+let testLockFreeLazy_WithException (supplier: unit -> obj, exc: Type) =
+ let testLazy = supplier |> LockFreeLazy
+ { 1 .. numberOfThreads }
+ |> Seq.map (fun _ -> Task.Run (
+ fun () -> testForSingleThread_WithException exc (( >= ) numberOfGets) testLazy))
+ |> Task.WaitAll
diff --git a/Tasks/Lazy/Lazy.Tests/Program.fs b/Tasks/Lazy/Lazy.Tests/Program.fs
new file mode 100644
index 0000000..96b133f
--- /dev/null
+++ b/Tasks/Lazy/Lazy.Tests/Program.fs
@@ -0,0 +1,4 @@
+module Program
+
+[]
+let main _ = 0
diff --git a/Tasks/Lazy/Lazy.sln b/Tasks/Lazy/Lazy.sln
new file mode 100644
index 0000000..e547247
--- /dev/null
+++ b/Tasks/Lazy/Lazy.sln
@@ -0,0 +1,28 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.0.31903.59
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lazy", "Lazy\Lazy.fsproj", "{ACEDBB1D-CD4A-4C1E-B4AC-88257DD98315}"
+EndProject
+Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Lazy.Tests", "Lazy.Tests\Lazy.Tests.fsproj", "{7AE9BEB1-1946-4B5A-865A-2062DA8FCB00}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {ACEDBB1D-CD4A-4C1E-B4AC-88257DD98315}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {ACEDBB1D-CD4A-4C1E-B4AC-88257DD98315}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {ACEDBB1D-CD4A-4C1E-B4AC-88257DD98315}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {ACEDBB1D-CD4A-4C1E-B4AC-88257DD98315}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7AE9BEB1-1946-4B5A-865A-2062DA8FCB00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7AE9BEB1-1946-4B5A-865A-2062DA8FCB00}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7AE9BEB1-1946-4B5A-865A-2062DA8FCB00}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7AE9BEB1-1946-4B5A-865A-2062DA8FCB00}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/Tasks/Lazy/Lazy/ILazy.fs b/Tasks/Lazy/Lazy/ILazy.fs
new file mode 100644
index 0000000..6fef851
--- /dev/null
+++ b/Tasks/Lazy/Lazy/ILazy.fs
@@ -0,0 +1,6 @@
+namespace Lazy
+
+/// An interface representing a lazy evaluation.
+type ILazy<'a> =
+ /// Get the result evaluated lazily.
+ abstract member Get: unit -> 'a
diff --git a/Tasks/Lazy/Lazy/Lazy.fsproj b/Tasks/Lazy/Lazy/Lazy.fsproj
new file mode 100644
index 0000000..59ac8c0
--- /dev/null
+++ b/Tasks/Lazy/Lazy/Lazy.fsproj
@@ -0,0 +1,15 @@
+
+
+
+ net9.0
+ true
+
+
+
+
+
+
+
+
+
+
diff --git a/Tasks/Lazy/Lazy/LockFreeLazy.fs b/Tasks/Lazy/Lazy/LockFreeLazy.fs
new file mode 100644
index 0000000..956b89d
--- /dev/null
+++ b/Tasks/Lazy/Lazy/LockFreeLazy.fs
@@ -0,0 +1,42 @@
+namespace Lazy
+
+open System
+open System.Threading
+
+/// Class representing thread safe lazy evaluation without locks.
+/// Creates an instance with the given `supplier` to evaluate.
+type LockFreeLazy<'a when 'a: equality>(supplier: unit -> 'a) =
+ []
+ let mutable supplier = Some supplier
+ let initialResult: 'a option * exn option = None, None
+ let mutable result = initialResult
+
+ /// Run supplier and set the result if it was not already set, then clear the supplier.
+ let tryEvaluate () =
+ let currentSupplier = supplier
+ match currentSupplier with
+ | Some func ->
+ try
+ let localResult = Some (func ())
+ Interlocked.CompareExchange (&result, (localResult, None), initialResult)
+ |> ignore
+ with
+ | e ->
+ Interlocked.CompareExchange (&result, (None, Some e), initialResult)
+ |> ignore
+ supplier <- None
+ | None -> ()
+
+ /// Get the result evaluated lazily.
+ let getResult () =
+ tryEvaluate ()
+ match result with
+ | Some value, _ -> value
+ | _, Some e -> raise e
+ | _ -> raise (new ArgumentException "unexpected match pattern")
+
+ interface ILazy<'a> with
+ member _.Get (): 'a = getResult ()
+
+ /// Get the result evaluated lazily.
+ member _.Get (): 'a = getResult ()
diff --git a/Tasks/Lazy/Lazy/SingleThreadLazy.fs b/Tasks/Lazy/Lazy/SingleThreadLazy.fs
new file mode 100644
index 0000000..7d3d0ce
--- /dev/null
+++ b/Tasks/Lazy/Lazy/SingleThreadLazy.fs
@@ -0,0 +1,35 @@
+namespace Lazy
+
+open System
+
+/// Class representing a lazy evaluation for a single thread.
+/// Creates an instance with the given `supplier` to evaluate.
+type SingleThreadLazy<'a when 'a: equality>(supplier: unit -> 'a) =
+ let mutable supplier = Some supplier
+ let mutable result: 'a option = None
+ let mutable thrownException: Exception option = None
+
+ /// Run supplier if it was not already run, set result and clear the supplier.
+ let tryEvaluate () =
+ match supplier with
+ | Some func ->
+ try
+ result <- Some (func ())
+ with
+ | e -> thrownException <- Some e
+ supplier <- None
+ | None -> ()
+
+ /// Get the result evaluated lazily.
+ let getResult () =
+ tryEvaluate ()
+ match result, thrownException with
+ | Some value, _ -> value
+ | _, Some e -> raise e
+ | _ -> raise (new ArgumentException "unexpected match pattern")
+
+ interface ILazy<'a> with
+ member _.Get (): 'a = getResult ()
+
+ /// Get the result evaluated lazily.
+ member _.Get (): 'a = getResult ()
diff --git a/Tasks/Lazy/Lazy/ThreadSafeLazy.fs b/Tasks/Lazy/Lazy/ThreadSafeLazy.fs
new file mode 100644
index 0000000..44e43ba
--- /dev/null
+++ b/Tasks/Lazy/Lazy/ThreadSafeLazy.fs
@@ -0,0 +1,39 @@
+namespace Lazy
+
+open System
+
+/// Class representing thread safe lazy evaluation.
+/// Creates an instance with the given `supplier` to evaluate.
+type ThreadSafeLazy<'a when 'a: equality>(supplier: unit -> 'a) =
+ []
+ let mutable supplier = Some supplier
+ let mutable result: 'a option = None
+ let mutable thrownException: Exception option = None
+ let lockObject = new Object ()
+
+ /// Run supplier if it was not already run, set result and clear the supplier.
+ let tryEvaluate () =
+ lock lockObject (fun () ->
+ match supplier with
+ | Some func ->
+ try
+ result <- Some (func ())
+ with
+ | e -> thrownException <- Some e
+ supplier <- None
+ | None -> ()
+ )
+
+ /// Get the result evaluated lazily.
+ let getResult () =
+ tryEvaluate ()
+ match result, thrownException with
+ | Some value, _ -> value
+ | _, Some e -> raise e
+ | _ -> raise (new ArgumentException "unexpected match pattern")
+
+ interface ILazy<'a> with
+ member _.Get (): 'a = getResult ()
+
+ /// Get the result evaluated lazily.
+ member _.Get (): 'a = getResult ()