Skip to content

Commit

Permalink
ValueLazy
Browse files Browse the repository at this point in the history
  • Loading branch information
ZacharyPatten committed Aug 3, 2021
1 parent 65ca739 commit f6ca88a
Show file tree
Hide file tree
Showing 9 changed files with 452 additions and 71 deletions.
59 changes: 50 additions & 9 deletions Examples/BasicsAndExtensions/Program.cs
Expand Up @@ -914,22 +914,63 @@ static void ConsoleWrite(ReadOnlySpan<char> readOnlySpan)
}
#endregion

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

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

Console.WriteLine(@$" SLazy<string> slazy = new(() => ""hello world"");");
Console.WriteLine(@$" slazy.IsValueCreated: {slazy.IsValueCreated}");
Console.WriteLine(@$" slazy.Value: {slazy.Value}");
Console.WriteLine(@$" slazy.IsValueCreated: {slazy.IsValueCreated}");
Console.WriteLine(@$" SLazy<string> slazy = new(() => ""hello world"");");
Console.WriteLine(@$" slazy.IsValueCreated: {slazy.IsValueCreated}");
Console.WriteLine(@$" slazy.Value: {slazy.Value}");
Console.WriteLine(@$" slazy.IsValueCreated: {slazy.IsValueCreated}");
Console.WriteLine();

Console.WriteLine(@$" ValueLazy<T> is even faster than SLazy<T> but it");
Console.WriteLine(@$" is unsafe as it will potentially call the factory");
Console.WriteLine(@$" delegate multiple times if the struct is copied.");
Console.WriteLine(@$" So please use ValueLazy<T> with caution.");
Console.WriteLine();

ValueLazy<string> valueLazy = new(() => "hello world");

Console.WriteLine(@$" ValueLazy<string> valueLazy = new(() => ""hello world"");");
Console.WriteLine(@$" valueLazy.IsValueCreated: {valueLazy.IsValueCreated}");
Console.WriteLine(@$" valueLazy.Value: {valueLazy.Value}");
Console.WriteLine(@$" valueLazy.IsValueCreated: {valueLazy.IsValueCreated}");
Console.WriteLine();

Console.WriteLine(@$" Here is the main different between SLazy<T> and ValueLazy<T>:");
Console.WriteLine();

int sLazyCount = 0;
SLazy<int> sLazy1 = new(() => ++sLazyCount);
SLazy<int> sLazy2 = sLazy1;
Console.WriteLine(@$" int sLazyCount = 0;");
Console.WriteLine(@$" SLazy<int> sLazy1 = new(() => ++sLazyCount);");
Console.WriteLine(@$" SLazy<int> sLazy2 = sLazy1;");
Console.WriteLine(@$" Console.WriteLine(sLazy1.Value); -> {sLazy1.Value}");
Console.WriteLine(@$" Console.WriteLine(sLazy2.Value); -> {sLazy2.Value}");
Console.WriteLine();

int valueLazyCount = 0;
ValueLazy<int> valueLazy1 = new(() => ++valueLazyCount);
ValueLazy<int> valueLazy2 = valueLazy1;
Console.WriteLine(@$" int valueLazyCount = 0;");
Console.WriteLine(@$" ValueLazy<int> valueLazy1 = new(() => ++valueLazyCount);");
Console.WriteLine(@$" ValueLazy<int> valueLazy2 = valueLazy1;");
Console.WriteLine(@$" Console.WriteLine(valueLazy1.Value); -> {valueLazy1.Value}");
Console.WriteLine(@$" Console.WriteLine(valueLazy2.Value); -> {valueLazy2.Value}");
Console.WriteLine();

Console.WriteLine(@$" Because the ValueLazy<T> was copied, it called the factory delegate");
Console.WriteLine(@$" multiple times. That is why SLazy<T> is safe to use but you need to");
Console.WriteLine(@$" be careful when using ValueLazy<T>.");

Pause();
}
#endregion
Expand Down
7 changes: 6 additions & 1 deletion README.md
Expand Up @@ -925,7 +925,7 @@ Console.WriteLine("Value: " + Value);
public class MyClass { }
```

## SLazy&lt;T&gt;
## SLazy&lt;T&gt; + ValueLazy&lt;T&gt;

```cs
// SLazy<T> is a faster Lazy<T> when using the default
Expand All @@ -935,6 +935,11 @@ SLazy<string> slazy = new(() => "hello world");
Console.WriteLine(slazy.IsValueCreated); // False
Console.WriteLine(slazy.Value); // hello world
Console.WriteLine(slazy.IsValueCreated); // True
// ValueLazy<T> is even faster than SLazy<T> but it
// is unsafe as it will potentially call the factory
// delegate multiple times if the struct is copied.
// So please use ValueLazy<T> with caution.
```

> [Initialization Benchmarks](https://zacharypatten.github.io/Towel/benchmarks/SLazyInitializationBenchmarks.html)<br/>
Expand Down
96 changes: 96 additions & 0 deletions Sources/Towel/ValueLazy.cs
@@ -0,0 +1,96 @@
using System;

namespace Towel
{
/// <summary>Provides support for lazy initialization.</summary>
/// <typeparam name="T">The type of value that is being lazily initialized.</typeparam>
public struct ValueLazy<T>
{
internal Func<T>? _func;
internal T? _value;

/// <summary>True if <see cref="Value"/> has been initialized.</summary>
public bool IsValueCreated => _func is null;

/// <summary>Gets the lazily initialized value.</summary>
public T Value => _func is null ? _value! : SafeGetValue();

internal T SafeGetValue()
{
Func<T>? func = _func;
if (func is not null)
{
lock (func)
{
if (_func is not null)
{
try
{
_value = _func();
_func = null;
}
catch (Exception exception)
{
_func = () => throw exception;
throw;
}
}
}
}
return _value!;
}

/// <summary>Constructs a new <see cref="ValueLazy{T}"/> from a <typeparamref name="T"/>.</summary>
/// <param name="value">The value to initialize <see cref="Value"/> with.</param>
public ValueLazy(T value)
{
_func = null;
_value = value;
}

/// <summary>Constructs a new <see cref="ValueLazy{T}"/> from a <see cref="Func{T}"/>.</summary>
/// <param name="func">The method used to initialize <see cref="Value"/>.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="func"/> is null.</exception>
public ValueLazy(Func<T> func)
{
if (func is null) throw new ArgumentNullException(nameof(func));
_value = default;
_func = func;
}

/// <summary>Constructs a new <see cref="ValueLazy{T}"/> from a <see cref="Func{T}"/>.</summary>
/// <param name="func">The method used to initialize <see cref="Value"/>.</param>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="func"/> is null.</exception>
public static implicit operator ValueLazy<T>(Func<T> func) => new(func);

/// <summary>Constructs a new <see cref="ValueLazy{T}"/> from a <typeparamref name="T"/>.</summary>
/// <param name="value">The value to initialize <see cref="Value"/> with.</param>
public static implicit operator ValueLazy<T>(T value) => new(value);

/// <summary>Checks for equality between <see cref="Value"/> and <paramref name="obj"/>.</summary>
/// <param name="obj">The value to compare to <see cref="Value"/>.</param>
/// <returns>True if <see cref="Value"/> and <paramref name="obj"/> are equal or False if not.</returns>
public override bool Equals(object? obj)
{
if (obj is ValueLazy<T> slazy)
{
obj = slazy.Value;
}
return (Value, obj) switch
{
(null, null) => true,
(_, null) => false,
(null, _) => false,
_ => Value!.Equals(obj),
};
}

/// <summary>Returns a string that represents <see cref="Value"/>.</summary>
/// <returns>A string that represents <see cref="Value"/></returns>
public override string? ToString() => Value?.ToString();

/// <summary>Gets the hash code of <see cref="Value"/>.</summary>
/// <returns>The hash code of <see cref="Value"/>.</returns>
public override int GetHashCode() => Value?.GetHashCode() ?? default;
}
}
35 changes: 35 additions & 0 deletions Tools/Towel_Benchmarking/SLazyBenchmarks.cs
Expand Up @@ -12,6 +12,7 @@ public class SLazyInitializationBenchmarks
private Lazy<int>[]? lazys;
private Lazy<int>[]? lazysExecutionAndPublication;
private SLazy<int>[]? slazys;
private ValueLazy<int>[]? valueLazys;

[Params(1, 10, 100, 1000, 10000)]
public int N;
Expand All @@ -38,6 +39,12 @@ public void IterationSetup()
{
slazys[i] = new(() => i);
}

valueLazys = new ValueLazy<int>[N];
for (int i = 0; i < N; i++)
{
valueLazys[i] = new(() => i);
}
}

[GlobalCleanup]
Expand Down Expand Up @@ -72,6 +79,15 @@ public void SLazy()
temp = slazys![i].Value;
}
}

[Benchmark]
public void ValueLazy()
{
for (int i = 0; i < N; i++)
{
temp = valueLazys![i].Value;
}
}
}

[Tag(Program.Name, "SLazy Caching")]
Expand Down Expand Up @@ -118,6 +134,16 @@ public void SLazy()
temp = value.Value;
}
}

[Benchmark]
public void ValueLazy()
{
ValueLazy<int> value = new(() => -1);
for (int i = 0; i < N; i++)
{
temp = value.Value;
}
}
}

[Tag(Program.Name, "SLazy Construction")]
Expand Down Expand Up @@ -154,5 +180,14 @@ public void SLazy()
_ = new SLazy<int>(() => i);
}
}

[Benchmark]
public void ValueLazy()
{
for (int i = 0; i < N; i++)
{
_ = new ValueLazy<int>(() => i);
}
}
}
}
2 changes: 1 addition & 1 deletion Tools/Towel_Testing/SLazy.cs
Expand Up @@ -6,7 +6,7 @@
namespace Towel_Testing
{
[TestClass]
public class Slazy_Testing
public class SLazy_Testing
{
[TestMethod]
public void Testing()
Expand Down

0 comments on commit f6ca88a

Please sign in to comment.