Olá!
A ideia é usar propriedades lazy cached em todos os lugares em objetos imutáveis, onde normalmente usaríamos métodos de processamento pesado sem argumentos. E o artigo - como projetá-lo e por quê.
Acessando uma propriedade preguiçosa de um objeto visualmente
:
1) - , — ,
2) (, SRP)
3) ,
TL; DR na parte inferior.
Por que mal?
public sealed record Integer(int Value);
Value int. , :
public sealed record Integer(int Value)
{
public Integer Triple() => new Integer(Value * 3);
}
, , . ,
public int SomeMethod(Integer number)
{
var tripled = number.Triple();
if (tripled.Value > 5)
return tripled.Value;
else
return 1;
}
,
public int SomeMethod(Integer number)
=> number.Tripled > 5 ? number.Tripled.Value : 1;
, , , . , , Tripled .
?
- . , , .
- . , , ( — ).
- . immutable object, ,
EqualsGetHashCode, - , .
, , . , :
public sealed record Number(int Value)
{
public int Number Tripled => tripled.GetValue(@this => new Number(@this.Value * 3), @this);
private FieldCache<Number> tripled;
}
, , Cacheable. source- , - . — , .
:
1 ( ?):
public sealed record Number(int Value)
{
public int Number Tripled => new Number(@this.Value * 3);
}
( )
2 ( Lazy<T>):
public sealed record Number : IEquatable<Number>
{
public int Value { get; init; } // ,
public int Number Tripled => tripled.Value;
private Lazy<Number> tripled;
public Number(int value)
{
Value = value;
tripled = new(() => value * 3); // , this-
}
// Equals, , , Lazy<T>
public bool Equals(Number number) => Value == number.Value;
// GetHashCode
public override int GetHashCode() => Value.GetHashCode();
}
, . , ? , .
, with, , (-). Lazy, .
3 ( ConditionalWeakTable):
public sealed record Number
{
public Number Tripled => tripled.GetValue(this, @this => new Integer(@this.Value * 3));
private static ConditionalWeakTable<Number, Number> tripled = new();
}
. ValueType ConditionalWeakTable -. , - ( , , 6 , , ).
4 ( ):
public sealed record Number
{
public int Value { get; init; }
public Number Tripled { get; }
public Number(int value)
{
Value = value;
Tripled = new Number { Value = value * 3 };
}
}
stackoverflow, , "" — , .
- , , .
? EqualsGetHashCodetrue0. , , . ,EqualsGetHashCode, .- . , , .
- ,
GetValue, ,ConditionalWeakTable. -,Lazy<T>. -
with,initializedholder, — .
!
, :
public struct FieldCache<T> : IEquatable<FieldCache<T>>
{
private T value;
private object holder; // , generic
// , , Equals
public bool Equals(FieldCache<T> _) => true;
public override int GetHashCode() => 0;
}
GetValue :
public struct FieldCache<T> : IEquatable<FieldCache<T>>
{
public T GetValue<TThis>(Func<TThis, T> factory, TThis @this) where TThis : class // record - . ,
{
// (, - null)
if (!ReferenceEquals(@this, holder))
lock (@this)
{
if (!ReferenceEquals(@this, holder))
{
// , FieldCache , - . , , ,
value = factory(@this);
holder = @this;
}
}
return value;
}
}
, :
public sealed record Number(int Value)
{
public int Number Tripled => tripled.GetValue(@this => new Number(@this.Value * 3), @this);
private FieldCache<Number> tripled;
}
, , FieldCache — Lazy<T>.
| Method | Mean |
|---|---|
| BenchFunction | 4,599.1638 ns |
| Lazy | 0.6717 ns |
| FieldCache | 3.6674 ns |
| ConditionalWeakTable | 25.0521 ns |
BenchFunction — - , , . . , FieldCache<T> , Lazy<T>.
, , , .
TL;DR
: , .
E as abordagens existentes bem conhecidas, muito provavelmente, não permitem que isso seja feito de maneira bonita, então você tem que escrever a sua própria.