diff --git a/Lazyy/Lazyy.Test/LazyTest.cs b/Lazyy/Lazyy.Test/LazyTest.cs new file mode 100644 index 0000000..2f3044b --- /dev/null +++ b/Lazyy/Lazyy.Test/LazyTest.cs @@ -0,0 +1,69 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using NUnit.Framework; + +namespace Lazyy.Test +{ + public class SingleThreadTest + { + private static IEnumerable Lazy() + { + int countForSingle = 0; + int countForMulti = 0; + yield return new TestCaseData(LazyFactory.CreateSingleLazy(() => ++countForSingle)); + yield return new TestCaseData(LazyFactory.CreateMultiLazy(() => Interlocked.Increment(ref countForMulti))); + } + + [Test] + public void SupplierCannotBeNull() + { + Assert.Throws(() => LazyFactory.CreateSingleLazy(null)); + Assert.Throws(() => LazyFactory.CreateMultiLazy(null)); + } + + [TestCaseSource(nameof(Lazy))] + public void TestLazyLaunchFunctionOnly1Time(ILazy lazy) + { + for (int i = 0; i < 100; i++) + { + Assert.AreEqual(1, lazy.Get()); + } + } + + [TestCaseSource(nameof(Lazy))] + public void GetShouldNotChangeValue(ILazy lazy) + { + var value = lazy.Get(); + Assert.AreEqual(1, value); + for (int i = 0; i < 25; i++) + { + Assert.AreEqual(value, lazy.Get()); + } + } + + [Test] + public void RaceConditionsCheck() + { + var count = 0; + var lazy = LazyFactory.CreateMultiLazy(() => Interlocked.Increment(ref count)); + var threads = new Thread[10]; + + for (int i = 0; i < threads.Length; ++i) + { + threads[i] = new Thread(() => lazy.Get()); + } + + foreach (var thread in threads) + { + thread.Start(); + } + foreach (var thread in threads) + { + thread.Join(); + } + + Assert.AreEqual(1, count); + } + } +} \ No newline at end of file diff --git a/Lazyy/Lazyy.Test/Lazyy.Test.csproj b/Lazyy/Lazyy.Test/Lazyy.Test.csproj new file mode 100644 index 0000000..5186ebf --- /dev/null +++ b/Lazyy/Lazyy.Test/Lazyy.Test.csproj @@ -0,0 +1,20 @@ + + + + net5.0 + + false + + + + + + + + + + + + + + diff --git a/Lazyy/Lazyy.sln b/Lazyy/Lazyy.sln new file mode 100644 index 0000000..9e846bc --- /dev/null +++ b/Lazyy/Lazyy.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lazyy", "Lazyy\Lazyy.csproj", "{DB81654E-7FB5-4DF1-BEB0-8E701E1A3A6A}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lazyy.Test", "Lazyy.Test\Lazyy.Test.csproj", "{96805220-7A8C-4502-9503-91EB73541008}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DB81654E-7FB5-4DF1-BEB0-8E701E1A3A6A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB81654E-7FB5-4DF1-BEB0-8E701E1A3A6A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB81654E-7FB5-4DF1-BEB0-8E701E1A3A6A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB81654E-7FB5-4DF1-BEB0-8E701E1A3A6A}.Release|Any CPU.Build.0 = Release|Any CPU + {96805220-7A8C-4502-9503-91EB73541008}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96805220-7A8C-4502-9503-91EB73541008}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96805220-7A8C-4502-9503-91EB73541008}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96805220-7A8C-4502-9503-91EB73541008}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/Lazyy/Lazyy/ILazy.cs b/Lazyy/Lazyy/ILazy.cs new file mode 100644 index 0000000..139066e --- /dev/null +++ b/Lazyy/Lazyy/ILazy.cs @@ -0,0 +1,13 @@ +namespace Lazyy +{ + /// + /// интерфейс ленивого вычисления + /// + public interface ILazy + { + /// + /// вызывает вычисление один раз и возвращает один и тот же обьект, полученный при вычислении + /// + public T Get(); + } +} \ No newline at end of file diff --git a/Lazyy/Lazyy/LazyFactory.cs b/Lazyy/Lazyy/LazyFactory.cs new file mode 100644 index 0000000..d4ba51d --- /dev/null +++ b/Lazyy/Lazyy/LazyFactory.cs @@ -0,0 +1,22 @@ +using System; + +namespace Lazyy +{ + /// + /// создает обьекты для работы либо в однопоточном, либо в многопоточном режиме + /// + public static class LazyFactory + { + /// + /// создает обьект в однопоточном режиме + /// + public static ILazy CreateSingleLazy(Func supplier) + => new LazySingle(supplier); + + /// + /// создает обьект в многопоточном режиме + /// + public static ILazy CreateMultiLazy(Func supplier) + => new LazyMulti(supplier); + } +} \ No newline at end of file diff --git a/Lazyy/Lazyy/LazyMulti.cs b/Lazyy/Lazyy/LazyMulti.cs new file mode 100644 index 0000000..ea058f4 --- /dev/null +++ b/Lazyy/Lazyy/LazyMulti.cs @@ -0,0 +1,46 @@ +using System; +using System.Threading; + +namespace Lazyy +{ + /// + /// реализация ILazy в многопоточном режиме + /// + public class LazyMulti : ILazy + { + private T _value; + private bool _isGenerated = false; + private Func _supplier; + private readonly Object _lockObject = new(); + + /// + /// создает обьект в многопоточном режиме + /// + public LazyMulti(Func supplier) + => _supplier = supplier ?? throw new ArgumentNullException(); + + /// + /// вызывает вычисление один раз и возвращает один и тот же обьект, полученный при вычислении + /// + public T Get() + { + if (_isGenerated) + { + return _value; + } + + lock (_lockObject) + { + if (Volatile.Read(ref _isGenerated)) + { + return _value; + } + + _value = _supplier(); + Volatile.Write(ref _isGenerated, true); + _supplier = null; + return _value; + } + } + } +} \ No newline at end of file diff --git a/Lazyy/Lazyy/LazySingle.cs b/Lazyy/Lazyy/LazySingle.cs new file mode 100644 index 0000000..43413d9 --- /dev/null +++ b/Lazyy/Lazyy/LazySingle.cs @@ -0,0 +1,36 @@ +using System; + +namespace Lazyy +{ + /// + /// реализация ILazy в однопоточном режиме + /// + public class LazySingle : ILazy + { + private T _value; + private Func _supplier; + private bool _isGenerated = false; + + /// + /// создает обьект в однопоточном режиме + /// + public LazySingle(Func supplier) + => _supplier = supplier ?? throw new ArgumentNullException(); + + /// + /// вызывает вычисление один раз и возвращает один и тот же обьект, полученный при вычислении + /// + public T Get() + { + if (_isGenerated) + { + return _value; + } + + _isGenerated = true; + _value = _supplier(); + _supplier = null; + return _value; + } + } +} \ No newline at end of file diff --git a/Lazyy/Lazyy/Lazyy.csproj b/Lazyy/Lazyy/Lazyy.csproj new file mode 100644 index 0000000..a184b89 --- /dev/null +++ b/Lazyy/Lazyy/Lazyy.csproj @@ -0,0 +1,9 @@ + + + + Exe + net5.0 + Windows + + + diff --git a/Lazyy/Lazyy/Program.cs b/Lazyy/Lazyy/Program.cs new file mode 100644 index 0000000..c7811b5 --- /dev/null +++ b/Lazyy/Lazyy/Program.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading; + +namespace Lazyy +{ + class Program + { + static void Main(string[] args) + { + var number1 = 2; + var number2 = 2; + var single = LazyFactory.CreateMultiLazy(() => + { + number1 += number1; + return number1; + }); + var multi = LazyFactory.CreateMultiLazy(() => + { + number2 *= number2; + return number2; + }); + + var threads = new Thread[5]; + for (int i = 0; i < threads.Length; i++) + { + threads[i] = new Thread(() => multi.Get()); + } + + foreach (var t in threads) + { + Console.WriteLine(multi.Get()); + Console.WriteLine("!!!"); + Console.WriteLine(single.Get()); + Console.WriteLine("!!!"); + } + + foreach (var thread in threads) + { + thread.Start(); + } + + foreach (var thread in threads) + { + thread.Join(); + } + } + } +} \ No newline at end of file