From da4eeae3ddb0b6baee25af884a28096b7185f5e4 Mon Sep 17 00:00:00 2001 From: Roman Date: Wed, 28 Sep 2022 17:11:34 +0300 Subject: [PATCH 1/6] add tests and comments --- Lazy/Lazy/Lazy.sln | 31 ++++++++++++++++ Lazy/Lazy/Lazy/ILazy.cs | 14 +++++++ Lazy/Lazy/Lazy/Lazy.cs | 28 ++++++++++++++ Lazy/Lazy/Lazy/Lazy.csproj | 10 +++++ Lazy/Lazy/Lazy/MultithreadedLazy.cs | 43 ++++++++++++++++++++++ Lazy/Lazy/Lazy/SingleThreadedLazy.cs | 26 +++++++++++++ Lazy/Lazy/LazyTest/LazyTest.cs | 55 ++++++++++++++++++++++++++++ Lazy/Lazy/LazyTest/LazyTest.csproj | 23 ++++++++++++ Lazy/Lazy/LazyTest/Usings.cs | 1 + 9 files changed, 231 insertions(+) create mode 100644 Lazy/Lazy/Lazy.sln create mode 100644 Lazy/Lazy/Lazy/ILazy.cs create mode 100644 Lazy/Lazy/Lazy/Lazy.cs create mode 100644 Lazy/Lazy/Lazy/Lazy.csproj create mode 100644 Lazy/Lazy/Lazy/MultithreadedLazy.cs create mode 100644 Lazy/Lazy/Lazy/SingleThreadedLazy.cs create mode 100644 Lazy/Lazy/LazyTest/LazyTest.cs create mode 100644 Lazy/Lazy/LazyTest/LazyTest.csproj create mode 100644 Lazy/Lazy/LazyTest/Usings.cs diff --git a/Lazy/Lazy/Lazy.sln b/Lazy/Lazy/Lazy.sln new file mode 100644 index 0000000..b4863d0 --- /dev/null +++ b/Lazy/Lazy/Lazy.sln @@ -0,0 +1,31 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.2.32505.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LazyTest", "LazyTest\LazyTest.csproj", "{47362B64-59D5-4275-B189-4AE547D1B8A9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Lazy", "Lazy\Lazy.csproj", "{0067E211-7EEC-40BB-AF0E-B3E2BFE38A83}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {47362B64-59D5-4275-B189-4AE547D1B8A9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47362B64-59D5-4275-B189-4AE547D1B8A9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47362B64-59D5-4275-B189-4AE547D1B8A9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47362B64-59D5-4275-B189-4AE547D1B8A9}.Release|Any CPU.Build.0 = Release|Any CPU + {0067E211-7EEC-40BB-AF0E-B3E2BFE38A83}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0067E211-7EEC-40BB-AF0E-B3E2BFE38A83}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0067E211-7EEC-40BB-AF0E-B3E2BFE38A83}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0067E211-7EEC-40BB-AF0E-B3E2BFE38A83}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {1DDBE986-F70C-460F-BAC3-5E300605253C} + EndGlobalSection +EndGlobal diff --git a/Lazy/Lazy/Lazy/ILazy.cs b/Lazy/Lazy/Lazy/ILazy.cs new file mode 100644 index 0000000..11b878d --- /dev/null +++ b/Lazy/Lazy/Lazy/ILazy.cs @@ -0,0 +1,14 @@ +namespace Lazy; + +/// +/// Interface representing lazy computation +/// +/// Element type +public interface ILazy +{ + /// + /// Function that calls the calculation and returns the result + /// + /// Returns the result of the called function + T Get(); +} diff --git a/Lazy/Lazy/Lazy/Lazy.cs b/Lazy/Lazy/Lazy/Lazy.cs new file mode 100644 index 0000000..b720400 --- /dev/null +++ b/Lazy/Lazy/Lazy/Lazy.cs @@ -0,0 +1,28 @@ +namespace Lazy; + +/// +/// Abstract class, for subsequent implementation of the ILazy interface +/// +/// Element type +public abstract class Lazy : ILazy +{ + protected Func func; + protected T? value; + + /// + /// Сonstructor + /// + /// The object on the basis of which the calculation is performed + /// If func is null + public Lazy(Func func) + { + this.func = func ?? throw new ArgumentNullException(nameof(Func)); + } + + /// + /// Function that calls a calculation and returns results + /// + /// Element type + public abstract T Get(); +} + diff --git a/Lazy/Lazy/Lazy/Lazy.csproj b/Lazy/Lazy/Lazy/Lazy.csproj new file mode 100644 index 0000000..16e62dd --- /dev/null +++ b/Lazy/Lazy/Lazy/Lazy.csproj @@ -0,0 +1,10 @@ + + + + Library + net6.0 + enable + enable + + + diff --git a/Lazy/Lazy/Lazy/MultithreadedLazy.cs b/Lazy/Lazy/Lazy/MultithreadedLazy.cs new file mode 100644 index 0000000..44c8f5f --- /dev/null +++ b/Lazy/Lazy/Lazy/MultithreadedLazy.cs @@ -0,0 +1,43 @@ +namespace Lazy; + +/// +/// Multithreaded implementation of Lazy +/// +/// Element type +public class MultithreadedLazy : Lazy +{ + /// + /// Boolean variable for detecting the first call + /// + private bool isAlreadyCounted; + + /// + /// Lock + /// + private readonly Object lockObject = new(); + + /// + public MultithreadedLazy(Func func) : base(func) { } + + /// + public override T Get() + { + ///if the value has already been calculated, there should be no locks + if (!Volatile.Read(ref isAlreadyCounted)) + { + ///lock + lock (lockObject) + { + /// if the value was not calculated + if (!Volatile.Read(ref isAlreadyCounted)) + { + value = func() ?? throw new ArgumentNullException(nameof(Func)); + isAlreadyCounted = true; + Volatile.Write(ref isAlreadyCounted, true); + } + } + } + + return value!; + } +} \ No newline at end of file diff --git a/Lazy/Lazy/Lazy/SingleThreadedLazy.cs b/Lazy/Lazy/Lazy/SingleThreadedLazy.cs new file mode 100644 index 0000000..b5cf4e8 --- /dev/null +++ b/Lazy/Lazy/Lazy/SingleThreadedLazy.cs @@ -0,0 +1,26 @@ +namespace Lazy; + +/// +/// Singlethreaded implementation of Lazy +/// +/// Element type +public class SingleThreadedLazy : Lazy +{ + /// + public SingleThreadedLazy(Func func) : base(func) { } + + private bool isAlreadyCounted; + + /// + public override T Get() + { + if (!isAlreadyCounted) + { + isAlreadyCounted = true; + var paramName = ""; + value = func() ?? throw new ArgumentNullException(paramName); + } + + return value!; + } +} diff --git a/Lazy/Lazy/LazyTest/LazyTest.cs b/Lazy/Lazy/LazyTest/LazyTest.cs new file mode 100644 index 0000000..ae7b6fa --- /dev/null +++ b/Lazy/Lazy/LazyTest/LazyTest.cs @@ -0,0 +1,55 @@ +namespace LazyTest; + +public class Tests +{ + + private static IEnumerable CaseData() + { + var values = new List { 0, -1, 1 }; + int number = 0; + + static int Increment(int number) => number + 1; + + yield return new TestCaseData(new Lazy.SingleThreadedLazy(() => Increment(number)), 1); + + foreach (var value in values) + { + yield return new TestCaseData(new Lazy.SingleThreadedLazy(() => value), value); + yield return new TestCaseData(new Lazy.MultithreadedLazy(() => value), value); + } + } + + [TestCaseSource(nameof(CaseData))] + public void ShouldValueNotChangeForLazyInSinglethreadedMode(Lazy.ILazy lazy, int expectedValue) + { + for (int i = 0; i < 10; i++) + { + Assert.AreEqual(expectedValue, lazy.Get()); + } + } + + [Test] + public void ShouldValueNotChangeForLazyInMultithreadedMode() + { + static int Increment(int number) => number + 1; + Lazy.ILazy lazy = new Lazy.MultithreadedLazy(() => Increment(0)); + Thread[] threads = new Thread[8]; + + for (int i = 0; i < 8; i++) + { + threads[i] = new Thread(() => { + Assert.AreEqual(1, lazy.Get()); + }); + } + + foreach (var thread in threads) + { + thread.Start(); + } + + foreach (var thread in threads) + { + thread.Join(); + } + } +} \ No newline at end of file diff --git a/Lazy/Lazy/LazyTest/LazyTest.csproj b/Lazy/Lazy/LazyTest/LazyTest.csproj new file mode 100644 index 0000000..00642fd --- /dev/null +++ b/Lazy/Lazy/LazyTest/LazyTest.csproj @@ -0,0 +1,23 @@ + + + + net6.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/Lazy/Lazy/LazyTest/Usings.cs b/Lazy/Lazy/LazyTest/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/Lazy/Lazy/LazyTest/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file From e0e29532268898b2fc0a877418e0abb7a275e6f7 Mon Sep 17 00:00:00 2001 From: Roman Date: Mon, 17 Oct 2022 18:16:58 +0300 Subject: [PATCH 2/6] fixed tests --- Lazy/Lazy/Lazy/SingleThreadedLazy.cs | 3 +-- Lazy/Lazy/LazyTest/LazyTest.cs | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Lazy/Lazy/Lazy/SingleThreadedLazy.cs b/Lazy/Lazy/Lazy/SingleThreadedLazy.cs index b5cf4e8..2615de1 100644 --- a/Lazy/Lazy/Lazy/SingleThreadedLazy.cs +++ b/Lazy/Lazy/Lazy/SingleThreadedLazy.cs @@ -17,8 +17,7 @@ public override T Get() if (!isAlreadyCounted) { isAlreadyCounted = true; - var paramName = ""; - value = func() ?? throw new ArgumentNullException(paramName); + value = func() ?? throw new ArgumentNullException(); } return value!; diff --git a/Lazy/Lazy/LazyTest/LazyTest.cs b/Lazy/Lazy/LazyTest/LazyTest.cs index ae7b6fa..0d19915 100644 --- a/Lazy/Lazy/LazyTest/LazyTest.cs +++ b/Lazy/Lazy/LazyTest/LazyTest.cs @@ -24,7 +24,7 @@ public void ShouldValueNotChangeForLazyInSinglethreadedMode(Lazy.ILazy lazy { for (int i = 0; i < 10; i++) { - Assert.AreEqual(expectedValue, lazy.Get()); + Assert.That(lazy.Get(), Is.EqualTo(expectedValue)); } } @@ -38,7 +38,7 @@ public void ShouldValueNotChangeForLazyInMultithreadedMode() for (int i = 0; i < 8; i++) { threads[i] = new Thread(() => { - Assert.AreEqual(1, lazy.Get()); + Assert.That(lazy.Get(), Is.EqualTo(1)); }); } From bc30aa8568f5e52af46fa23f66254e850da25060 Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 4 Dec 2022 18:30:06 +0300 Subject: [PATCH 3/6] formatted --- Lazy/Lazy/Lazy/Lazy.cs | 2 +- Lazy/Lazy/Lazy/MultithreadedLazy.cs | 2 +- Lazy/Lazy/Lazy/SingleThreadedLazy.cs | 2 +- Lazy/Lazy/LazyTest/LazyTest.cs | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Lazy/Lazy/Lazy/Lazy.cs b/Lazy/Lazy/Lazy/Lazy.cs index b720400..9711e87 100644 --- a/Lazy/Lazy/Lazy/Lazy.cs +++ b/Lazy/Lazy/Lazy/Lazy.cs @@ -16,7 +16,7 @@ public abstract class Lazy : ILazy /// If func is null public Lazy(Func func) { - this.func = func ?? throw new ArgumentNullException(nameof(Func)); + this.func = func; } /// diff --git a/Lazy/Lazy/Lazy/MultithreadedLazy.cs b/Lazy/Lazy/Lazy/MultithreadedLazy.cs index 44c8f5f..3236bca 100644 --- a/Lazy/Lazy/Lazy/MultithreadedLazy.cs +++ b/Lazy/Lazy/Lazy/MultithreadedLazy.cs @@ -31,7 +31,7 @@ public override T Get() /// if the value was not calculated if (!Volatile.Read(ref isAlreadyCounted)) { - value = func() ?? throw new ArgumentNullException(nameof(Func)); + value = func(); isAlreadyCounted = true; Volatile.Write(ref isAlreadyCounted, true); } diff --git a/Lazy/Lazy/Lazy/SingleThreadedLazy.cs b/Lazy/Lazy/Lazy/SingleThreadedLazy.cs index 2615de1..0b1121e 100644 --- a/Lazy/Lazy/Lazy/SingleThreadedLazy.cs +++ b/Lazy/Lazy/Lazy/SingleThreadedLazy.cs @@ -17,7 +17,7 @@ public override T Get() if (!isAlreadyCounted) { isAlreadyCounted = true; - value = func() ?? throw new ArgumentNullException(); + value = func(); } return value!; diff --git a/Lazy/Lazy/LazyTest/LazyTest.cs b/Lazy/Lazy/LazyTest/LazyTest.cs index 0d19915..932c12c 100644 --- a/Lazy/Lazy/LazyTest/LazyTest.cs +++ b/Lazy/Lazy/LazyTest/LazyTest.cs @@ -1,8 +1,7 @@ namespace LazyTest; -public class Tests +public class LazyTests { - private static IEnumerable CaseData() { var values = new List { 0, -1, 1 }; From a29b273c3d13a887344c1dd5d8cbe96c8355236e Mon Sep 17 00:00:00 2001 From: Roman Date: Sun, 4 Dec 2022 18:38:40 +0300 Subject: [PATCH 4/6] formatted Lazy.cs --- Lazy/Lazy/Lazy/Lazy.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Lazy/Lazy/Lazy/Lazy.cs b/Lazy/Lazy/Lazy/Lazy.cs index 9711e87..bc15ef4 100644 --- a/Lazy/Lazy/Lazy/Lazy.cs +++ b/Lazy/Lazy/Lazy/Lazy.cs @@ -13,7 +13,6 @@ public abstract class Lazy : ILazy /// Сonstructor /// /// The object on the basis of which the calculation is performed - /// If func is null public Lazy(Func func) { this.func = func; From c54925509f9d3ade607294be09ba952352bb104f Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 8 Dec 2022 00:21:00 +0300 Subject: [PATCH 5/6] formatted --- Lazy/Lazy/Lazy/ILazy.cs | 2 +- Lazy/Lazy/Lazy/Lazy.cs | 4 ++-- Lazy/Lazy/Lazy/MultithreadedLazy.cs | 19 ++++++++++++------- Lazy/Lazy/Lazy/SingleThreadedLazy.cs | 11 ++++++++--- Lazy/Lazy/LazyTest/LazyTest.cs | 6 ++---- 5 files changed, 25 insertions(+), 17 deletions(-) diff --git a/Lazy/Lazy/Lazy/ILazy.cs b/Lazy/Lazy/Lazy/ILazy.cs index 11b878d..6320658 100644 --- a/Lazy/Lazy/Lazy/ILazy.cs +++ b/Lazy/Lazy/Lazy/ILazy.cs @@ -10,5 +10,5 @@ public interface ILazy /// Function that calls the calculation and returns the result /// /// Returns the result of the called function - T Get(); + T? Get(); } diff --git a/Lazy/Lazy/Lazy/Lazy.cs b/Lazy/Lazy/Lazy/Lazy.cs index bc15ef4..e0bfc9e 100644 --- a/Lazy/Lazy/Lazy/Lazy.cs +++ b/Lazy/Lazy/Lazy/Lazy.cs @@ -6,7 +6,7 @@ /// Element type public abstract class Lazy : ILazy { - protected Func func; + protected Func? func; protected T? value; /// @@ -22,6 +22,6 @@ public Lazy(Func func) /// Function that calls a calculation and returns results /// /// Element type - public abstract T Get(); + public abstract T? Get(); } diff --git a/Lazy/Lazy/Lazy/MultithreadedLazy.cs b/Lazy/Lazy/Lazy/MultithreadedLazy.cs index 3236bca..773a53d 100644 --- a/Lazy/Lazy/Lazy/MultithreadedLazy.cs +++ b/Lazy/Lazy/Lazy/MultithreadedLazy.cs @@ -14,30 +14,35 @@ public class MultithreadedLazy : Lazy /// /// Lock /// - private readonly Object lockObject = new(); + private readonly object lockObject = new(); /// public MultithreadedLazy(Func func) : base(func) { } /// - public override T Get() + public override T? Get() { - ///if the value has already been calculated, there should be no locks + // if the value has already been calculated, there should be no locks if (!Volatile.Read(ref isAlreadyCounted)) { - ///lock + // lock lock (lockObject) { - /// if the value was not calculated + // if the value was not calculated if (!Volatile.Read(ref isAlreadyCounted)) { + if (func == null) + { + throw new InvalidOperationException(); + } + value = func(); - isAlreadyCounted = true; + func = null; Volatile.Write(ref isAlreadyCounted, true); } } } - return value!; + return value; } } \ No newline at end of file diff --git a/Lazy/Lazy/Lazy/SingleThreadedLazy.cs b/Lazy/Lazy/Lazy/SingleThreadedLazy.cs index 0b1121e..135c668 100644 --- a/Lazy/Lazy/Lazy/SingleThreadedLazy.cs +++ b/Lazy/Lazy/Lazy/SingleThreadedLazy.cs @@ -12,14 +12,19 @@ public SingleThreadedLazy(Func func) : base(func) { } private bool isAlreadyCounted; /// - public override T Get() + public override T? Get() { if (!isAlreadyCounted) { - isAlreadyCounted = true; + if (func == null) + { + throw new InvalidOperationException(); + } + value = func(); + isAlreadyCounted = true; } - return value!; + return value; } } diff --git a/Lazy/Lazy/LazyTest/LazyTest.cs b/Lazy/Lazy/LazyTest/LazyTest.cs index 932c12c..9dc6fe1 100644 --- a/Lazy/Lazy/LazyTest/LazyTest.cs +++ b/Lazy/Lazy/LazyTest/LazyTest.cs @@ -32,13 +32,11 @@ public void ShouldValueNotChangeForLazyInMultithreadedMode() { static int Increment(int number) => number + 1; Lazy.ILazy lazy = new Lazy.MultithreadedLazy(() => Increment(0)); - Thread[] threads = new Thread[8]; + var threads = new Thread[8]; for (int i = 0; i < 8; i++) { - threads[i] = new Thread(() => { - Assert.That(lazy.Get(), Is.EqualTo(1)); - }); + threads[i] = new Thread(() => Assert.That(lazy.Get(), Is.EqualTo(1))); } foreach (var thread in threads) From 8cb0b66b3a3986d40eaa2f317e975404cdc235ba Mon Sep 17 00:00:00 2001 From: Roman Date: Thu, 8 Dec 2022 00:26:35 +0300 Subject: [PATCH 6/6] fixed test --- Lazy/Lazy/LazyTest/LazyTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lazy/Lazy/LazyTest/LazyTest.cs b/Lazy/Lazy/LazyTest/LazyTest.cs index 9dc6fe1..276dfb6 100644 --- a/Lazy/Lazy/LazyTest/LazyTest.cs +++ b/Lazy/Lazy/LazyTest/LazyTest.cs @@ -30,7 +30,7 @@ public void ShouldValueNotChangeForLazyInSinglethreadedMode(Lazy.ILazy lazy [Test] public void ShouldValueNotChangeForLazyInMultithreadedMode() { - static int Increment(int number) => number + 1; + static int Increment(int number) => Interlocked.Increment(ref number); Lazy.ILazy lazy = new Lazy.MultithreadedLazy(() => Increment(0)); var threads = new Thread[8];