From 8ba30a2c2bd2dd281ca22b3e11dd32fba34c1785 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Tue, 23 Sep 2025 15:37:02 +0300 Subject: [PATCH 1/9] add: the main logic of the program --- LazyInterface/LazyInterface.sln | 25 +++++++++++ LazyInterface/LazyInterface/ILazy.cs | 11 +++++ .../LazyInterface/LazyInterface.csproj | 25 +++++++++++ .../LazyInterface/MultithreadedVersion.cs | 42 +++++++++++++++++++ LazyInterface/LazyInterface/Program.cs | 7 ++++ LazyInterface/LazyInterface/SimpleVersion.cs | 38 +++++++++++++++++ LazyInterface/LazyInterface/stylecop.json | 9 ++++ 7 files changed, 157 insertions(+) create mode 100644 LazyInterface/LazyInterface.sln create mode 100644 LazyInterface/LazyInterface/ILazy.cs create mode 100644 LazyInterface/LazyInterface/LazyInterface.csproj create mode 100644 LazyInterface/LazyInterface/MultithreadedVersion.cs create mode 100644 LazyInterface/LazyInterface/Program.cs create mode 100644 LazyInterface/LazyInterface/SimpleVersion.cs create mode 100644 LazyInterface/LazyInterface/stylecop.json diff --git a/LazyInterface/LazyInterface.sln b/LazyInterface/LazyInterface.sln new file mode 100644 index 0000000..308d07a --- /dev/null +++ b/LazyInterface/LazyInterface.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.13.35806.99 d17.13 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyInterface", "LazyInterface\LazyInterface.csproj", "{492B0043-2F2B-4D4B-9032-8C3EC5A941C5}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {492B0043-2F2B-4D4B-9032-8C3EC5A941C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {492B0043-2F2B-4D4B-9032-8C3EC5A941C5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {492B0043-2F2B-4D4B-9032-8C3EC5A941C5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {492B0043-2F2B-4D4B-9032-8C3EC5A941C5}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {ADEC834D-6E46-477C-871F-21879B4BA23D} + EndGlobalSection +EndGlobal diff --git a/LazyInterface/LazyInterface/ILazy.cs b/LazyInterface/LazyInterface/ILazy.cs new file mode 100644 index 0000000..8700e18 --- /dev/null +++ b/LazyInterface/LazyInterface/ILazy.cs @@ -0,0 +1,11 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +namespace LazyInterface +{ + internal interface ILazy + { + T Get(); + } +} \ No newline at end of file diff --git a/LazyInterface/LazyInterface/LazyInterface.csproj b/LazyInterface/LazyInterface/LazyInterface.csproj new file mode 100644 index 0000000..f43f26d --- /dev/null +++ b/LazyInterface/LazyInterface/LazyInterface.csproj @@ -0,0 +1,25 @@ + + + + Exe + net9.0 + enable + enable + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/LazyInterface/LazyInterface/MultithreadedVersion.cs b/LazyInterface/LazyInterface/MultithreadedVersion.cs new file mode 100644 index 0000000..32d8f47 --- /dev/null +++ b/LazyInterface/LazyInterface/MultithreadedVersion.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +namespace LazyInterface +{ + public class MultithreadedVersion : ILazy + { + private readonly object locker = new object(); + + private volatile Func? supplier; + + private T? result; + + public MultithreadedVersion(Func supplier) + { + if (supplier is null) + { + ArgumentNullException.ThrowIfNull(supplier); + } + + this.supplier = supplier; + } + + public T Get() + { + if (this.supplier is not null) + { + lock (this.locker) + { + if (this.supplier is not null) + { + this.result = this.supplier(); + this.supplier = null; + } + } + } + + return this.result; + } + } +} \ No newline at end of file diff --git a/LazyInterface/LazyInterface/Program.cs b/LazyInterface/LazyInterface/Program.cs new file mode 100644 index 0000000..47c97a4 --- /dev/null +++ b/LazyInterface/LazyInterface/Program.cs @@ -0,0 +1,7 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +using LazyInterface; + +Console.WriteLine("Hello world!"); \ No newline at end of file diff --git a/LazyInterface/LazyInterface/SimpleVersion.cs b/LazyInterface/LazyInterface/SimpleVersion.cs new file mode 100644 index 0000000..7df4672 --- /dev/null +++ b/LazyInterface/LazyInterface/SimpleVersion.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +namespace LazyInterface +{ + public class SimpleVersion : ILazy + { + private Func? supplier; + + private T? result; + + private bool flag; + + public SimpleVersion(Func supplier) + { + if (supplier is null) + { + ArgumentNullException.ThrowIfNull(supplier); + } + + this.supplier = supplier; + this.flag = false; + } + + public T Get() + { + if (!this.flag) + { + this.result = this.supplier!(); + this.flag = true; + this.supplier = null; + } + + return this.result; + } + } +} \ No newline at end of file diff --git a/LazyInterface/LazyInterface/stylecop.json b/LazyInterface/LazyInterface/stylecop.json new file mode 100644 index 0000000..ce12f73 --- /dev/null +++ b/LazyInterface/LazyInterface/stylecop.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "Kalinin Andrew", + "copyrightText": "Copyright (c) {companyName}. All rights reserved." + } + } +} From d9836412174be3c6a63fa79b6d7dd38cab3542c0 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Tue, 23 Sep 2025 15:53:43 +0300 Subject: [PATCH 2/9] refactor: made the interface public --- LazyInterface/LazyInterface/ILazy.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyInterface/LazyInterface/ILazy.cs b/LazyInterface/LazyInterface/ILazy.cs index 8700e18..42c65c3 100644 --- a/LazyInterface/LazyInterface/ILazy.cs +++ b/LazyInterface/LazyInterface/ILazy.cs @@ -4,7 +4,7 @@ namespace LazyInterface { - internal interface ILazy + public interface ILazy { T Get(); } From 3016b879d432143e4834e6bee6da51176ee1effa Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Wed, 24 Sep 2025 00:22:27 +0300 Subject: [PATCH 3/9] refactor: correct check for null --- LazyInterface/LazyInterface.sln | 8 +++++++- LazyInterface/LazyInterface/MultithreadedVersion.cs | 5 +---- LazyInterface/LazyInterface/SimpleVersion.cs | 5 +---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/LazyInterface/LazyInterface.sln b/LazyInterface/LazyInterface.sln index 308d07a..3a0e729 100644 --- a/LazyInterface/LazyInterface.sln +++ b/LazyInterface/LazyInterface.sln @@ -1,10 +1,12 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.13.35806.99 d17.13 +VisualStudioVersion = 17.13.35806.99 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyInterface", "LazyInterface\LazyInterface.csproj", "{492B0043-2F2B-4D4B-9032-8C3EC5A941C5}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LazyInterface.Tests", "LazyInterface.Tests\LazyInterface.Tests.csproj", "{38FDFD18-1B23-483D-A68C-9938CF478AD4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -15,6 +17,10 @@ Global {492B0043-2F2B-4D4B-9032-8C3EC5A941C5}.Debug|Any CPU.Build.0 = Debug|Any CPU {492B0043-2F2B-4D4B-9032-8C3EC5A941C5}.Release|Any CPU.ActiveCfg = Release|Any CPU {492B0043-2F2B-4D4B-9032-8C3EC5A941C5}.Release|Any CPU.Build.0 = Release|Any CPU + {38FDFD18-1B23-483D-A68C-9938CF478AD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38FDFD18-1B23-483D-A68C-9938CF478AD4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38FDFD18-1B23-483D-A68C-9938CF478AD4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38FDFD18-1B23-483D-A68C-9938CF478AD4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/LazyInterface/LazyInterface/MultithreadedVersion.cs b/LazyInterface/LazyInterface/MultithreadedVersion.cs index 32d8f47..bdbe9f7 100644 --- a/LazyInterface/LazyInterface/MultithreadedVersion.cs +++ b/LazyInterface/LazyInterface/MultithreadedVersion.cs @@ -14,10 +14,7 @@ public class MultithreadedVersion : ILazy public MultithreadedVersion(Func supplier) { - if (supplier is null) - { - ArgumentNullException.ThrowIfNull(supplier); - } + ArgumentNullException.ThrowIfNull(supplier); this.supplier = supplier; } diff --git a/LazyInterface/LazyInterface/SimpleVersion.cs b/LazyInterface/LazyInterface/SimpleVersion.cs index 7df4672..a128150 100644 --- a/LazyInterface/LazyInterface/SimpleVersion.cs +++ b/LazyInterface/LazyInterface/SimpleVersion.cs @@ -14,10 +14,7 @@ public class SimpleVersion : ILazy public SimpleVersion(Func supplier) { - if (supplier is null) - { - ArgumentNullException.ThrowIfNull(supplier); - } + ArgumentNullException.ThrowIfNull(supplier); this.supplier = supplier; this.flag = false; From 231a1473607c8e0c1b480c18d0c54fd0a8e3e881 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Wed, 24 Sep 2025 00:26:20 +0300 Subject: [PATCH 4/9] add: a single-threaded solution tests --- .../LazyInterface.Tests/.editorconfig | 4 ++ .../LazyInterface.Tests.csproj | 39 ++++++++++++++++ .../SingleThreadedTests.cs | 46 +++++++++++++++++++ .../LazyInterface.Tests/stylecop.json | 9 ++++ 4 files changed, 98 insertions(+) create mode 100644 LazyInterface/LazyInterface.Tests/.editorconfig create mode 100644 LazyInterface/LazyInterface.Tests/LazyInterface.Tests.csproj create mode 100644 LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs create mode 100644 LazyInterface/LazyInterface.Tests/stylecop.json diff --git a/LazyInterface/LazyInterface.Tests/.editorconfig b/LazyInterface/LazyInterface.Tests/.editorconfig new file mode 100644 index 0000000..bc75ec6 --- /dev/null +++ b/LazyInterface/LazyInterface.Tests/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# SA1600: Elements should be documented +dotnet_diagnostic.SA1600.severity = none diff --git a/LazyInterface/LazyInterface.Tests/LazyInterface.Tests.csproj b/LazyInterface/LazyInterface.Tests/LazyInterface.Tests.csproj new file mode 100644 index 0000000..a0feeff --- /dev/null +++ b/LazyInterface/LazyInterface.Tests/LazyInterface.Tests.csproj @@ -0,0 +1,39 @@ + + + + net9.0 + latest + enable + enable + false + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + diff --git a/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs b/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs new file mode 100644 index 0000000..df8d27f --- /dev/null +++ b/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +namespace LazyInterface.Tests +{ + public class SingleThreadedTests + { + [Test] + public void Get_ShoulCallSupplierOnlyOnceAndReturnCorrectValue_MultipleCalls() + { + int callCount = 0; + Func supplier = () => + { + callCount++; + return "abc"; + }; + var lazy = new SimpleVersion(supplier); + + lazy.Get(); + lazy.Get(); + lazy.Get(); + + var result = lazy.Get(); + + Assert.That(callCount, Is.EqualTo(1)); + Assert.That(result, Is.EqualTo("abc")); + } + + [Test] + public void Get_ShoulReturnTheSameObjectReference_MultipleCalls() + { + var lazy = new SimpleVersion>(() => []); + + var result1 = lazy.Get(); + var result2 = lazy.Get(); + Assert.That(result1, Is.SameAs(result2)); + } + + [Test] + public void Constructor_ShouldThrowArgumentNullException_SupplierIsNull() + { + Assert.Throws(() => new SimpleVersion(null!)); + } + } +} \ No newline at end of file diff --git a/LazyInterface/LazyInterface.Tests/stylecop.json b/LazyInterface/LazyInterface.Tests/stylecop.json new file mode 100644 index 0000000..ce12f73 --- /dev/null +++ b/LazyInterface/LazyInterface.Tests/stylecop.json @@ -0,0 +1,9 @@ +{ + "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json", + "settings": { + "documentationRules": { + "companyName": "Kalinin Andrew", + "copyrightText": "Copyright (c) {companyName}. All rights reserved." + } + } +} From 0e848dc9fadffe150e6ac2a0157b31a7ff8a6d58 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Wed, 24 Sep 2025 01:35:55 +0300 Subject: [PATCH 5/9] add: test for Multithreaded version --- .../LazyInterface.Tests/.editorconfig | 119 ++++++++++++++++++ .../LazyInterface.Tests/MultithreadedTests.cs | 51 ++++++++ .../SingleThreadedTests.cs | 6 +- 3 files changed, 173 insertions(+), 3 deletions(-) create mode 100644 LazyInterface/LazyInterface.Tests/MultithreadedTests.cs diff --git a/LazyInterface/LazyInterface.Tests/.editorconfig b/LazyInterface/LazyInterface.Tests/.editorconfig index bc75ec6..e9aae8b 100644 --- a/LazyInterface/LazyInterface.Tests/.editorconfig +++ b/LazyInterface/LazyInterface.Tests/.editorconfig @@ -2,3 +2,122 @@ # SA1600: Elements should be documented dotnet_diagnostic.SA1600.severity = none +csharp_indent_labels = one_less_than_current +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion + +[*.{cs,vb}] +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion +dotnet_style_namespace_match_folder = true:suggestion +[*.cs] +#### Стили именования #### + +# Правила именования + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Спецификации символов + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Стили именования + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +[*.vb] +#### Стили именования #### + +# Правила именования + +dotnet_naming_rule.interface_should_be_начинается_с_i.severity = suggestion +dotnet_naming_rule.interface_should_be_начинается_с_i.symbols = interface +dotnet_naming_rule.interface_should_be_начинается_с_i.style = начинается_с_i + +dotnet_naming_rule.типы_should_be_всечастиспрописнойбуквы.severity = suggestion +dotnet_naming_rule.типы_should_be_всечастиспрописнойбуквы.symbols = типы +dotnet_naming_rule.типы_should_be_всечастиспрописнойбуквы.style = всечастиспрописнойбуквы + +dotnet_naming_rule.не_являющиеся_полем_члены_should_be_всечастиспрописнойбуквы.severity = suggestion +dotnet_naming_rule.не_являющиеся_полем_члены_should_be_всечастиспрописнойбуквы.symbols = не_являющиеся_полем_члены +dotnet_naming_rule.не_являющиеся_полем_члены_should_be_всечастиспрописнойбуквы.style = всечастиспрописнойбуквы + +# Спецификации символов + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.типы.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.типы.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.типы.required_modifiers = + +dotnet_naming_symbols.не_являющиеся_полем_члены.applicable_kinds = property, event, method +dotnet_naming_symbols.не_являющиеся_полем_члены.applicable_accessibilities = public, friend, private, protected, protected_friend, private_protected +dotnet_naming_symbols.не_являющиеся_полем_члены.required_modifiers = + +# Стили именования + +dotnet_naming_style.начинается_с_i.required_prefix = I +dotnet_naming_style.начинается_с_i.required_suffix = +dotnet_naming_style.начинается_с_i.word_separator = +dotnet_naming_style.начинается_с_i.capitalization = pascal_case + +dotnet_naming_style.всечастиспрописнойбуквы.required_prefix = +dotnet_naming_style.всечастиспрописнойбуквы.required_suffix = +dotnet_naming_style.всечастиспрописнойбуквы.word_separator = +dotnet_naming_style.всечастиспрописнойбуквы.capitalization = pascal_case + +dotnet_naming_style.всечастиспрописнойбуквы.required_prefix = +dotnet_naming_style.всечастиспрописнойбуквы.required_suffix = +dotnet_naming_style.всечастиспрописнойбуквы.word_separator = +dotnet_naming_style.всечастиспрописнойбуквы.capitalization = pascal_case diff --git a/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs b/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs new file mode 100644 index 0000000..ffa284d --- /dev/null +++ b/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs @@ -0,0 +1,51 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +namespace LazyInterface.Tests +{ + public class MultithreadedTests + { + [Test] + public void Get_AvailabilityOfRaces_SupplierShouldInitializeOnlyOnce() + { + int callCount = 0; + var lazy = new MultithreadedVersion(() => + { + Interlocked.Increment(ref callCount); + Thread.Sleep(150); + return new object(); + }); + + int numOfThreads = 10; + var threads = new Thread[numOfThreads]; + var results = new object[numOfThreads]; + + for (int i = 0; i < numOfThreads; ++i) + { + int index = i; + threads[i] = new Thread(() => + { + results[index] = lazy.Get(); + }); + threads[i].Start(); + } + + foreach (var thread in threads) + { + thread.Join(); + } + + Assert.That(callCount, Is.EqualTo(1)); + var firstResult = results[0]; + Assert.That(results.All(res => ReferenceEquals(res, firstResult)), Is.True); + } + + + [Test] + public void ConstructorMultithreadVersion_SupplierIsNull_ShouldThrowArgumentNullException() + { + Assert.Throws(() => new MultithreadedVersion(null!)); + } + } +} \ No newline at end of file diff --git a/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs b/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs index df8d27f..a974493 100644 --- a/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs +++ b/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs @@ -7,7 +7,7 @@ namespace LazyInterface.Tests public class SingleThreadedTests { [Test] - public void Get_ShoulCallSupplierOnlyOnceAndReturnCorrectValue_MultipleCalls() + public void Get_MultipleCalls_ShoulCallSupplierOnlyOnceAndReturnCorrectValue() { int callCount = 0; Func supplier = () => @@ -28,7 +28,7 @@ public void Get_ShoulCallSupplierOnlyOnceAndReturnCorrectValue_MultipleCalls() } [Test] - public void Get_ShoulReturnTheSameObjectReference_MultipleCalls() + public void Get_MultipleCalls_ShoulReturnTheSameObjectReference() { var lazy = new SimpleVersion>(() => []); @@ -38,7 +38,7 @@ public void Get_ShoulReturnTheSameObjectReference_MultipleCalls() } [Test] - public void Constructor_ShouldThrowArgumentNullException_SupplierIsNull() + public void ConstructorSingleThreadVersion_SupplierIsNull_ShouldThrowArgumentNullException() { Assert.Throws(() => new SimpleVersion(null!)); } From 986c7a24d3d166983512bc0bda8961fdc3d8715c Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Wed, 24 Sep 2025 02:06:25 +0300 Subject: [PATCH 6/9] docs: added comments --- LazyInterface/LazyInterface.Tests/.editorconfig | 1 + .../LazyInterface.Tests/MultithreadedTests.cs | 1 - LazyInterface/LazyInterface/ILazy.cs | 9 +++++++++ LazyInterface/LazyInterface/LazyInterface.csproj | 2 +- .../LazyInterface/MultithreadedVersion.cs | 13 +++++++++++++ LazyInterface/LazyInterface/Program.cs | 7 ------- LazyInterface/LazyInterface/SimpleVersion.cs | 14 ++++++++++++++ 7 files changed, 38 insertions(+), 9 deletions(-) delete mode 100644 LazyInterface/LazyInterface/Program.cs diff --git a/LazyInterface/LazyInterface.Tests/.editorconfig b/LazyInterface/LazyInterface.Tests/.editorconfig index e9aae8b..c017690 100644 --- a/LazyInterface/LazyInterface.Tests/.editorconfig +++ b/LazyInterface/LazyInterface.Tests/.editorconfig @@ -73,6 +73,7 @@ dotnet_naming_style.pascal_case.required_prefix = dotnet_naming_style.pascal_case.required_suffix = dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case +csharp_prefer_simple_default_expression = true:suggestion [*.vb] #### Стили именования #### diff --git a/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs b/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs index ffa284d..dab033d 100644 --- a/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs +++ b/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs @@ -41,7 +41,6 @@ public void Get_AvailabilityOfRaces_SupplierShouldInitializeOnlyOnce() Assert.That(results.All(res => ReferenceEquals(res, firstResult)), Is.True); } - [Test] public void ConstructorMultithreadVersion_SupplierIsNull_ShouldThrowArgumentNullException() { diff --git a/LazyInterface/LazyInterface/ILazy.cs b/LazyInterface/LazyInterface/ILazy.cs index 42c65c3..9d0704f 100644 --- a/LazyInterface/LazyInterface/ILazy.cs +++ b/LazyInterface/LazyInterface/ILazy.cs @@ -4,8 +4,17 @@ namespace LazyInterface { + /// + /// Interface for lazy value calculation. + /// + /// Type of calculated value. public interface ILazy { + /// + /// Gets the calculated value. Performs the calculation on the first call, + /// returns the previously calculated value on subsequent attempts. + /// + /// Calculated value of type T. T Get(); } } \ No newline at end of file diff --git a/LazyInterface/LazyInterface/LazyInterface.csproj b/LazyInterface/LazyInterface/LazyInterface.csproj index f43f26d..e25af34 100644 --- a/LazyInterface/LazyInterface/LazyInterface.csproj +++ b/LazyInterface/LazyInterface/LazyInterface.csproj @@ -1,7 +1,7 @@  - Exe + Library net9.0 enable enable diff --git a/LazyInterface/LazyInterface/MultithreadedVersion.cs b/LazyInterface/LazyInterface/MultithreadedVersion.cs index bdbe9f7..47712d4 100644 --- a/LazyInterface/LazyInterface/MultithreadedVersion.cs +++ b/LazyInterface/LazyInterface/MultithreadedVersion.cs @@ -4,6 +4,10 @@ namespace LazyInterface { + /// + /// Thread-safe implementation of lazy evaluation. + /// + /// Type of calculated value. public class MultithreadedVersion : ILazy { private readonly object locker = new object(); @@ -12,6 +16,11 @@ public class MultithreadedVersion : ILazy private T? result; + /// + /// Initializes a new instance of the class. + /// + /// A function that calculates the value. + /// If supplier is null. public MultithreadedVersion(Func supplier) { ArgumentNullException.ThrowIfNull(supplier); @@ -19,6 +28,10 @@ public MultithreadedVersion(Func supplier) this.supplier = supplier; } + /// + /// Returns the calculated value. Guarantees a one-time calculation. + /// + /// Calculated value. public T Get() { if (this.supplier is not null) diff --git a/LazyInterface/LazyInterface/Program.cs b/LazyInterface/LazyInterface/Program.cs deleted file mode 100644 index 47c97a4..0000000 --- a/LazyInterface/LazyInterface/Program.cs +++ /dev/null @@ -1,7 +0,0 @@ -// -// Copyright (c) Kalinin Andrew. All rights reserved. -// - -using LazyInterface; - -Console.WriteLine("Hello world!"); \ No newline at end of file diff --git a/LazyInterface/LazyInterface/SimpleVersion.cs b/LazyInterface/LazyInterface/SimpleVersion.cs index a128150..d932227 100644 --- a/LazyInterface/LazyInterface/SimpleVersion.cs +++ b/LazyInterface/LazyInterface/SimpleVersion.cs @@ -4,6 +4,10 @@ namespace LazyInterface { + /// + /// Single-threaded implementation of lazy evaluation. + /// + /// Type of calculated value. public class SimpleVersion : ILazy { private Func? supplier; @@ -12,6 +16,11 @@ public class SimpleVersion : ILazy private bool flag; + /// + /// Initializes a new instance of the class. + /// + /// A function that calculates the value. + /// If supplier is null. public SimpleVersion(Func supplier) { ArgumentNullException.ThrowIfNull(supplier); @@ -20,6 +29,11 @@ public SimpleVersion(Func supplier) this.flag = false; } + /// + /// Returns the calculated value. Performs the calculation on the first call, + /// returns the previously calculated value on subsequent attempts. + /// + /// Calculated value. public T Get() { if (!this.flag) From 8628b2aac31bf6328ba797b93b3f151ffb37b102 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Wed, 24 Sep 2025 16:14:36 +0300 Subject: [PATCH 7/9] test: add test that supplier can return null --- .../LazyInterface.Tests/MultithreadedTests.cs | 18 ++++++++++++++++++ .../LazyInterface.Tests/SingleThreadedTests.cs | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs b/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs index dab033d..75f7ec8 100644 --- a/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs +++ b/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs @@ -41,6 +41,24 @@ public void Get_AvailabilityOfRaces_SupplierShouldInitializeOnlyOnce() Assert.That(results.All(res => ReferenceEquals(res, firstResult)), Is.True); } + [Test] + public void GetFromMultithreadedVersion_SupplierReturnsNull_ShouldReturnNull() + { + int callCount = 0; + var lazy = new MultithreadedVersion(() => + { + callCount++; + return null; + }); + + var result1 = lazy.Get(); + var result2 = lazy.Get(); + + Assert.That(callCount, Is.EqualTo(1)); + Assert.That(result1, Is.Null); + Assert.That(result2, Is.Null); + } + [Test] public void ConstructorMultithreadVersion_SupplierIsNull_ShouldThrowArgumentNullException() { diff --git a/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs b/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs index a974493..b0fd793 100644 --- a/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs +++ b/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs @@ -37,6 +37,24 @@ public void Get_MultipleCalls_ShoulReturnTheSameObjectReference() Assert.That(result1, Is.SameAs(result2)); } + [Test] + public void GetFromSingleThreadedVersion_SupplierReturnsNull_ShouldReturnNull() + { + int callCount = 0; + var lazy = new SimpleVersion(() => + { + callCount++; + return null; + }); + + var result1 = lazy.Get(); + var result2 = lazy.Get(); + + Assert.That(callCount, Is.EqualTo(1)); + Assert.That(result1, Is.Null); + Assert.That(result2, Is.Null); + } + [Test] public void ConstructorSingleThreadVersion_SupplierIsNull_ShouldThrowArgumentNullException() { From 3ea049b284eb6a2143f3481bacc3fd493bf03056 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Thu, 25 Dec 2025 21:22:55 +0300 Subject: [PATCH 8/9] refactor: added all the corrections requested by the reviewer --- .../LazyInterface.Tests/CommonTests.cs | 73 ++++++++++++++++++ .../LazyInterface.Tests/MultithreadedTests.cs | 77 +++++++------------ .../SingleThreadedTests.cs | 64 --------------- LazyInterface/LazyInterface/ILazy.cs | 23 +++--- .../LazyInterface/MultithreadedVersion.cs | 67 ++++++++-------- LazyInterface/LazyInterface/SimpleVersion.cs | 66 ++++++++-------- 6 files changed, 176 insertions(+), 194 deletions(-) create mode 100644 LazyInterface/LazyInterface.Tests/CommonTests.cs delete mode 100644 LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs diff --git a/LazyInterface/LazyInterface.Tests/CommonTests.cs b/LazyInterface/LazyInterface.Tests/CommonTests.cs new file mode 100644 index 0000000..d640c72 --- /dev/null +++ b/LazyInterface/LazyInterface.Tests/CommonTests.cs @@ -0,0 +1,73 @@ +// +// Copyright (c) Kalinin Andrew. All rights reserved. +// + +namespace LazyInterface.Tests; + +public class CommonTests +{ + public static IEnumerable> LazyImplementations() + { + yield return new SimpleVersion(() => new object()); + yield return new MultithreadedVersion(() => new object()); + } + + [TestCaseSource(nameof(LazyImplementations))] + public void Get_MultipleCalls_ShoulCallSupplierOnlyOnce(ILazy lazy) + { + int callCount = 0; + Func supplier = () => + { + callCount++; + return new object(); + }; + lazy = lazy is SimpleVersion ? new SimpleVersion(supplier) : new MultithreadedVersion(supplier); + + lazy.Get(); + lazy.Get(); + lazy.Get(); + + var result = lazy.Get(); + + Assert.That(callCount, Is.EqualTo(1)); + } + + [TestCaseSource(nameof(LazyImplementations))] + public void Get_MultipleCalls_ShoulReturnTheSameObjectReference(ILazy lazy) + { + lazy = lazy is SimpleVersion ? new SimpleVersion(() => new object()) : new MultithreadedVersion(() => new object()); + + var result1 = lazy.Get(); + var result2 = lazy.Get(); + Assert.That(result1, Is.SameAs(result2)); + } + + [TestCaseSource(nameof(LazyImplementations))] + public void Get_SupplierReturnsNull_ShouldReturnNull(ILazy lazy) + { + int callCount = 0; + lazy = lazy is SimpleVersion ? new SimpleVersion(() => + { + callCount++; + return null!; + }) : new MultithreadedVersion(() => + { + callCount++; + return null!; + }); + + var result1 = lazy.Get(); + var result2 = lazy.Get(); + + Assert.That(callCount, Is.EqualTo(1)); + Assert.That(result1, Is.Null); + Assert.That(result2, Is.Null); + } + + [TestCaseSource(nameof(LazyImplementations))] + public void Constructor_SupplierIsNull_ShouldThrowArgumentNullException(ILazy lazy) + { + Assert.Throws(() => new SimpleVersion(null!)); + Assert.Throws(() => new MultithreadedVersion(null!)); + } +} \ No newline at end of file diff --git a/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs b/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs index 75f7ec8..5e76e48 100644 --- a/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs +++ b/LazyInterface/LazyInterface.Tests/MultithreadedTests.cs @@ -2,67 +2,44 @@ // Copyright (c) Kalinin Andrew. All rights reserved. // -namespace LazyInterface.Tests +namespace LazyInterface.Tests; + +public class MultithreadedTests { - public class MultithreadedTests + [Test] + public void Get_AvailabilityOfRaces_SupplierShouldInitializeOnlyOnce() { - [Test] - public void Get_AvailabilityOfRaces_SupplierShouldInitializeOnlyOnce() + int callCount = 0; + var lazy = new MultithreadedVersion(() => { - int callCount = 0; - var lazy = new MultithreadedVersion(() => - { - Interlocked.Increment(ref callCount); - Thread.Sleep(150); - return new object(); - }); - - int numOfThreads = 10; - var threads = new Thread[numOfThreads]; - var results = new object[numOfThreads]; - - for (int i = 0; i < numOfThreads; ++i) - { - int index = i; - threads[i] = new Thread(() => - { - results[index] = lazy.Get(); - }); - threads[i].Start(); - } + Interlocked.Increment(ref callCount); + Thread.Sleep(150); + return new object(); + }); - foreach (var thread in threads) - { - thread.Join(); - } - - Assert.That(callCount, Is.EqualTo(1)); - var firstResult = results[0]; - Assert.That(results.All(res => ReferenceEquals(res, firstResult)), Is.True); - } + int numOfThreads = 10; + var threads = new Thread[numOfThreads]; + var results = new object?[numOfThreads]; + var barrier = new Barrier(numOfThreads); - [Test] - public void GetFromMultithreadedVersion_SupplierReturnsNull_ShouldReturnNull() + for (int i = 0; i < numOfThreads; ++i) { - int callCount = 0; - var lazy = new MultithreadedVersion(() => + int index = i; + threads[i] = new Thread(() => { - callCount++; - return null; + barrier.SignalAndWait(); + results[index] = lazy.Get(); }); - - var result1 = lazy.Get(); - var result2 = lazy.Get(); - - Assert.That(callCount, Is.EqualTo(1)); - Assert.That(result1, Is.Null); - Assert.That(result2, Is.Null); + threads[i].Start(); } - [Test] - public void ConstructorMultithreadVersion_SupplierIsNull_ShouldThrowArgumentNullException() + foreach (var thread in threads) { - Assert.Throws(() => new MultithreadedVersion(null!)); + thread.Join(); } + + Assert.That(callCount, Is.EqualTo(1)); + var firstResult = results[0]; + Assert.That(results.All(res => ReferenceEquals(res, firstResult)), Is.True); } } \ No newline at end of file diff --git a/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs b/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs deleted file mode 100644 index b0fd793..0000000 --- a/LazyInterface/LazyInterface.Tests/SingleThreadedTests.cs +++ /dev/null @@ -1,64 +0,0 @@ -// -// Copyright (c) Kalinin Andrew. All rights reserved. -// - -namespace LazyInterface.Tests -{ - public class SingleThreadedTests - { - [Test] - public void Get_MultipleCalls_ShoulCallSupplierOnlyOnceAndReturnCorrectValue() - { - int callCount = 0; - Func supplier = () => - { - callCount++; - return "abc"; - }; - var lazy = new SimpleVersion(supplier); - - lazy.Get(); - lazy.Get(); - lazy.Get(); - - var result = lazy.Get(); - - Assert.That(callCount, Is.EqualTo(1)); - Assert.That(result, Is.EqualTo("abc")); - } - - [Test] - public void Get_MultipleCalls_ShoulReturnTheSameObjectReference() - { - var lazy = new SimpleVersion>(() => []); - - var result1 = lazy.Get(); - var result2 = lazy.Get(); - Assert.That(result1, Is.SameAs(result2)); - } - - [Test] - public void GetFromSingleThreadedVersion_SupplierReturnsNull_ShouldReturnNull() - { - int callCount = 0; - var lazy = new SimpleVersion(() => - { - callCount++; - return null; - }); - - var result1 = lazy.Get(); - var result2 = lazy.Get(); - - Assert.That(callCount, Is.EqualTo(1)); - Assert.That(result1, Is.Null); - Assert.That(result2, Is.Null); - } - - [Test] - public void ConstructorSingleThreadVersion_SupplierIsNull_ShouldThrowArgumentNullException() - { - Assert.Throws(() => new SimpleVersion(null!)); - } - } -} \ No newline at end of file diff --git a/LazyInterface/LazyInterface/ILazy.cs b/LazyInterface/LazyInterface/ILazy.cs index 9d0704f..753fbff 100644 --- a/LazyInterface/LazyInterface/ILazy.cs +++ b/LazyInterface/LazyInterface/ILazy.cs @@ -2,19 +2,18 @@ // Copyright (c) Kalinin Andrew. All rights reserved. // -namespace LazyInterface +namespace LazyInterface; + +/// +/// Interface for lazy value calculation. +/// +/// Type of calculated value. +public interface ILazy { /// - /// Interface for lazy value calculation. + /// Gets the calculated value. Performs the calculation on the first call, + /// returns the previously calculated value on subsequent attempts. /// - /// Type of calculated value. - public interface ILazy - { - /// - /// Gets the calculated value. Performs the calculation on the first call, - /// returns the previously calculated value on subsequent attempts. - /// - /// Calculated value of type T. - T Get(); - } + /// Calculated value of type T. + T? Get(); } \ No newline at end of file diff --git a/LazyInterface/LazyInterface/MultithreadedVersion.cs b/LazyInterface/LazyInterface/MultithreadedVersion.cs index 47712d4..c76c9a3 100644 --- a/LazyInterface/LazyInterface/MultithreadedVersion.cs +++ b/LazyInterface/LazyInterface/MultithreadedVersion.cs @@ -2,51 +2,50 @@ // Copyright (c) Kalinin Andrew. All rights reserved. // -namespace LazyInterface +namespace LazyInterface; + +/// +/// Thread-safe implementation of lazy evaluation. +/// +/// Type of calculated value. +public class MultithreadedVersion : ILazy { - /// - /// Thread-safe implementation of lazy evaluation. - /// - /// Type of calculated value. - public class MultithreadedVersion : ILazy - { - private readonly object locker = new object(); + private readonly object locker = new object(); - private volatile Func? supplier; + private volatile Func? supplier; - private T? result; + private T? result; - /// - /// Initializes a new instance of the class. - /// - /// A function that calculates the value. - /// If supplier is null. - public MultithreadedVersion(Func supplier) - { - ArgumentNullException.ThrowIfNull(supplier); + /// + /// Initializes a new instance of the class. + /// + /// A function that calculates the value. + /// If supplier is null. + public MultithreadedVersion(Func supplier) + { + ArgumentNullException.ThrowIfNull(supplier); - this.supplier = supplier; - } + this.supplier = supplier; + } - /// - /// Returns the calculated value. Guarantees a one-time calculation. - /// - /// Calculated value. - public T Get() + /// + /// Returns the calculated value. Guarantees a one-time calculation. + /// + /// Calculated value. + public T? Get() + { + if (this.supplier is not null) { - if (this.supplier is not null) + lock (this.locker) { - lock (this.locker) + if (this.supplier is not null) { - if (this.supplier is not null) - { - this.result = this.supplier(); - this.supplier = null; - } + this.result = this.supplier(); + this.supplier = null; } } - - return this.result; } + + return this.result!; } } \ No newline at end of file diff --git a/LazyInterface/LazyInterface/SimpleVersion.cs b/LazyInterface/LazyInterface/SimpleVersion.cs index d932227..546fd2b 100644 --- a/LazyInterface/LazyInterface/SimpleVersion.cs +++ b/LazyInterface/LazyInterface/SimpleVersion.cs @@ -2,48 +2,46 @@ // Copyright (c) Kalinin Andrew. All rights reserved. // -namespace LazyInterface +namespace LazyInterface; + +/// +/// Single-threaded implementation of lazy evaluation. +/// +/// Type of calculated value. +public class SimpleVersion : ILazy { + private Func? supplier; + + private T? result; + + private bool isCalculated; + /// - /// Single-threaded implementation of lazy evaluation. + /// Initializes a new instance of the class. /// - /// Type of calculated value. - public class SimpleVersion : ILazy + /// A function that calculates the value. + /// If supplier is null. + public SimpleVersion(Func supplier) { - private Func? supplier; - - private T? result; + ArgumentNullException.ThrowIfNull(supplier); - private bool flag; + this.supplier = supplier; + } - /// - /// Initializes a new instance of the class. - /// - /// A function that calculates the value. - /// If supplier is null. - public SimpleVersion(Func supplier) + /// + /// Returns the calculated value. Performs the calculation on the first call, + /// returns the previously calculated value on subsequent attempts. + /// + /// Calculated value. + public T? Get() + { + if (!this.isCalculated) { - ArgumentNullException.ThrowIfNull(supplier); - - this.supplier = supplier; - this.flag = false; + this.result = this.supplier!(); + this.isCalculated = true; + this.supplier = null; } - /// - /// Returns the calculated value. Performs the calculation on the first call, - /// returns the previously calculated value on subsequent attempts. - /// - /// Calculated value. - public T Get() - { - if (!this.flag) - { - this.result = this.supplier!(); - this.flag = true; - this.supplier = null; - } - - return this.result; - } + return this.result; } } \ No newline at end of file From b09f145da4610be3d6f2273a84a5ef7acd0bb143 Mon Sep 17 00:00:00 2001 From: Andrew Kalinin Date: Thu, 25 Dec 2025 21:35:00 +0300 Subject: [PATCH 9/9] refactor: remove unnecessary "!" from return in MultithreadedVersion.Get() --- LazyInterface/LazyInterface/MultithreadedVersion.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LazyInterface/LazyInterface/MultithreadedVersion.cs b/LazyInterface/LazyInterface/MultithreadedVersion.cs index c76c9a3..9189b13 100644 --- a/LazyInterface/LazyInterface/MultithreadedVersion.cs +++ b/LazyInterface/LazyInterface/MultithreadedVersion.cs @@ -46,6 +46,6 @@ public MultithreadedVersion(Func supplier) } } - return this.result!; + return this.result; } } \ No newline at end of file