Skip to content

Commit f6ca88a

Browse files
committed
ValueLazy
1 parent 65ca739 commit f6ca88a

File tree

9 files changed

+452
-71
lines changed

9 files changed

+452
-71
lines changed

Examples/BasicsAndExtensions/Program.cs

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -914,22 +914,63 @@ static void ConsoleWrite(ReadOnlySpan<char> readOnlySpan)
914914
}
915915
#endregion
916916

917-
#region SLazy<T>
917+
#region SLazy<T> + ValueLazy<T>
918918
{
919919
Console.WriteLine(" SLazy<T>----------------------");
920920
Console.WriteLine();
921-
Console.WriteLine(@" SLazy<T> is a struct alternative to Lazy<T> with some");
922-
Console.WriteLine(@" minor differences to: ToString, Equals, GetHashCode, and");
923-
Console.WriteLine(@" default constructor. There are benchmarks included in");
924-
Console.WriteLine(@" Towel's documentation.");
921+
Console.WriteLine(@" SLazy<T> is a faster Lazy<T> when using the default");
922+
Console.WriteLine(@" LazyThreadSafetyMode.ExecutionAndPublication setting.");
925923
Console.WriteLine();
926924

927925
SLazy<string> slazy = new(() => "hello world");
928926

929-
Console.WriteLine(@$" SLazy<string> slazy = new(() => ""hello world"");");
930-
Console.WriteLine(@$" slazy.IsValueCreated: {slazy.IsValueCreated}");
931-
Console.WriteLine(@$" slazy.Value: {slazy.Value}");
932-
Console.WriteLine(@$" slazy.IsValueCreated: {slazy.IsValueCreated}");
927+
Console.WriteLine(@$" SLazy<string> slazy = new(() => ""hello world"");");
928+
Console.WriteLine(@$" slazy.IsValueCreated: {slazy.IsValueCreated}");
929+
Console.WriteLine(@$" slazy.Value: {slazy.Value}");
930+
Console.WriteLine(@$" slazy.IsValueCreated: {slazy.IsValueCreated}");
931+
Console.WriteLine();
932+
933+
Console.WriteLine(@$" ValueLazy<T> is even faster than SLazy<T> but it");
934+
Console.WriteLine(@$" is unsafe as it will potentially call the factory");
935+
Console.WriteLine(@$" delegate multiple times if the struct is copied.");
936+
Console.WriteLine(@$" So please use ValueLazy<T> with caution.");
937+
Console.WriteLine();
938+
939+
ValueLazy<string> valueLazy = new(() => "hello world");
940+
941+
Console.WriteLine(@$" ValueLazy<string> valueLazy = new(() => ""hello world"");");
942+
Console.WriteLine(@$" valueLazy.IsValueCreated: {valueLazy.IsValueCreated}");
943+
Console.WriteLine(@$" valueLazy.Value: {valueLazy.Value}");
944+
Console.WriteLine(@$" valueLazy.IsValueCreated: {valueLazy.IsValueCreated}");
945+
Console.WriteLine();
946+
947+
Console.WriteLine(@$" Here is the main different between SLazy<T> and ValueLazy<T>:");
948+
Console.WriteLine();
949+
950+
int sLazyCount = 0;
951+
SLazy<int> sLazy1 = new(() => ++sLazyCount);
952+
SLazy<int> sLazy2 = sLazy1;
953+
Console.WriteLine(@$" int sLazyCount = 0;");
954+
Console.WriteLine(@$" SLazy<int> sLazy1 = new(() => ++sLazyCount);");
955+
Console.WriteLine(@$" SLazy<int> sLazy2 = sLazy1;");
956+
Console.WriteLine(@$" Console.WriteLine(sLazy1.Value); -> {sLazy1.Value}");
957+
Console.WriteLine(@$" Console.WriteLine(sLazy2.Value); -> {sLazy2.Value}");
958+
Console.WriteLine();
959+
960+
int valueLazyCount = 0;
961+
ValueLazy<int> valueLazy1 = new(() => ++valueLazyCount);
962+
ValueLazy<int> valueLazy2 = valueLazy1;
963+
Console.WriteLine(@$" int valueLazyCount = 0;");
964+
Console.WriteLine(@$" ValueLazy<int> valueLazy1 = new(() => ++valueLazyCount);");
965+
Console.WriteLine(@$" ValueLazy<int> valueLazy2 = valueLazy1;");
966+
Console.WriteLine(@$" Console.WriteLine(valueLazy1.Value); -> {valueLazy1.Value}");
967+
Console.WriteLine(@$" Console.WriteLine(valueLazy2.Value); -> {valueLazy2.Value}");
968+
Console.WriteLine();
969+
970+
Console.WriteLine(@$" Because the ValueLazy<T> was copied, it called the factory delegate");
971+
Console.WriteLine(@$" multiple times. That is why SLazy<T> is safe to use but you need to");
972+
Console.WriteLine(@$" be careful when using ValueLazy<T>.");
973+
933974
Pause();
934975
}
935976
#endregion

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,7 @@ Console.WriteLine("Value: " + Value);
925925
public class MyClass { }
926926
```
927927
928-
## SLazy&lt;T&gt;
928+
## SLazy&lt;T&gt; + ValueLazy&lt;T&gt;
929929
930930
```cs
931931
// SLazy<T> is a faster Lazy<T> when using the default
@@ -935,6 +935,11 @@ SLazy<string> slazy = new(() => "hello world");
935935
Console.WriteLine(slazy.IsValueCreated); // False
936936
Console.WriteLine(slazy.Value); // hello world
937937
Console.WriteLine(slazy.IsValueCreated); // True
938+
939+
// ValueLazy<T> is even faster than SLazy<T> but it
940+
// is unsafe as it will potentially call the factory
941+
// delegate multiple times if the struct is copied.
942+
// So please use ValueLazy<T> with caution.
938943
```
939944
940945
> [Initialization Benchmarks](https://zacharypatten.github.io/Towel/benchmarks/SLazyInitializationBenchmarks.html)<br/>

Sources/Towel/ValueLazy.cs

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
using System;
2+
3+
namespace Towel
4+
{
5+
/// <summary>Provides support for lazy initialization.</summary>
6+
/// <typeparam name="T">The type of value that is being lazily initialized.</typeparam>
7+
public struct ValueLazy<T>
8+
{
9+
internal Func<T>? _func;
10+
internal T? _value;
11+
12+
/// <summary>True if <see cref="Value"/> has been initialized.</summary>
13+
public bool IsValueCreated => _func is null;
14+
15+
/// <summary>Gets the lazily initialized value.</summary>
16+
public T Value => _func is null ? _value! : SafeGetValue();
17+
18+
internal T SafeGetValue()
19+
{
20+
Func<T>? func = _func;
21+
if (func is not null)
22+
{
23+
lock (func)
24+
{
25+
if (_func is not null)
26+
{
27+
try
28+
{
29+
_value = _func();
30+
_func = null;
31+
}
32+
catch (Exception exception)
33+
{
34+
_func = () => throw exception;
35+
throw;
36+
}
37+
}
38+
}
39+
}
40+
return _value!;
41+
}
42+
43+
/// <summary>Constructs a new <see cref="ValueLazy{T}"/> from a <typeparamref name="T"/>.</summary>
44+
/// <param name="value">The value to initialize <see cref="Value"/> with.</param>
45+
public ValueLazy(T value)
46+
{
47+
_func = null;
48+
_value = value;
49+
}
50+
51+
/// <summary>Constructs a new <see cref="ValueLazy{T}"/> from a <see cref="Func{T}"/>.</summary>
52+
/// <param name="func">The method used to initialize <see cref="Value"/>.</param>
53+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="func"/> is null.</exception>
54+
public ValueLazy(Func<T> func)
55+
{
56+
if (func is null) throw new ArgumentNullException(nameof(func));
57+
_value = default;
58+
_func = func;
59+
}
60+
61+
/// <summary>Constructs a new <see cref="ValueLazy{T}"/> from a <see cref="Func{T}"/>.</summary>
62+
/// <param name="func">The method used to initialize <see cref="Value"/>.</param>
63+
/// <exception cref="ArgumentNullException">Thrown when <paramref name="func"/> is null.</exception>
64+
public static implicit operator ValueLazy<T>(Func<T> func) => new(func);
65+
66+
/// <summary>Constructs a new <see cref="ValueLazy{T}"/> from a <typeparamref name="T"/>.</summary>
67+
/// <param name="value">The value to initialize <see cref="Value"/> with.</param>
68+
public static implicit operator ValueLazy<T>(T value) => new(value);
69+
70+
/// <summary>Checks for equality between <see cref="Value"/> and <paramref name="obj"/>.</summary>
71+
/// <param name="obj">The value to compare to <see cref="Value"/>.</param>
72+
/// <returns>True if <see cref="Value"/> and <paramref name="obj"/> are equal or False if not.</returns>
73+
public override bool Equals(object? obj)
74+
{
75+
if (obj is ValueLazy<T> slazy)
76+
{
77+
obj = slazy.Value;
78+
}
79+
return (Value, obj) switch
80+
{
81+
(null, null) => true,
82+
(_, null) => false,
83+
(null, _) => false,
84+
_ => Value!.Equals(obj),
85+
};
86+
}
87+
88+
/// <summary>Returns a string that represents <see cref="Value"/>.</summary>
89+
/// <returns>A string that represents <see cref="Value"/></returns>
90+
public override string? ToString() => Value?.ToString();
91+
92+
/// <summary>Gets the hash code of <see cref="Value"/>.</summary>
93+
/// <returns>The hash code of <see cref="Value"/>.</returns>
94+
public override int GetHashCode() => Value?.GetHashCode() ?? default;
95+
}
96+
}

Tools/Towel_Benchmarking/SLazyBenchmarks.cs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public class SLazyInitializationBenchmarks
1212
private Lazy<int>[]? lazys;
1313
private Lazy<int>[]? lazysExecutionAndPublication;
1414
private SLazy<int>[]? slazys;
15+
private ValueLazy<int>[]? valueLazys;
1516

1617
[Params(1, 10, 100, 1000, 10000)]
1718
public int N;
@@ -38,6 +39,12 @@ public void IterationSetup()
3839
{
3940
slazys[i] = new(() => i);
4041
}
42+
43+
valueLazys = new ValueLazy<int>[N];
44+
for (int i = 0; i < N; i++)
45+
{
46+
valueLazys[i] = new(() => i);
47+
}
4148
}
4249

4350
[GlobalCleanup]
@@ -72,6 +79,15 @@ public void SLazy()
7279
temp = slazys![i].Value;
7380
}
7481
}
82+
83+
[Benchmark]
84+
public void ValueLazy()
85+
{
86+
for (int i = 0; i < N; i++)
87+
{
88+
temp = valueLazys![i].Value;
89+
}
90+
}
7591
}
7692

7793
[Tag(Program.Name, "SLazy Caching")]
@@ -118,6 +134,16 @@ public void SLazy()
118134
temp = value.Value;
119135
}
120136
}
137+
138+
[Benchmark]
139+
public void ValueLazy()
140+
{
141+
ValueLazy<int> value = new(() => -1);
142+
for (int i = 0; i < N; i++)
143+
{
144+
temp = value.Value;
145+
}
146+
}
121147
}
122148

123149
[Tag(Program.Name, "SLazy Construction")]
@@ -154,5 +180,14 @@ public void SLazy()
154180
_ = new SLazy<int>(() => i);
155181
}
156182
}
183+
184+
[Benchmark]
185+
public void ValueLazy()
186+
{
187+
for (int i = 0; i < N; i++)
188+
{
189+
_ = new ValueLazy<int>(() => i);
190+
}
191+
}
157192
}
158193
}

Tools/Towel_Testing/SLazy.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
namespace Towel_Testing
77
{
88
[TestClass]
9-
public class Slazy_Testing
9+
public class SLazy_Testing
1010
{
1111
[TestMethod]
1212
public void Testing()

0 commit comments

Comments
 (0)