Argumentless methods are evil in OOP, and here's how to treat it

Hello!



The idea is to use lazy cached properties everywhere in immutable objects, where we would normally use processor-heavy methods with no arguments. And the article - how to design it and and why.





Accessing a lazy property of an object visually



Disclaimer

:

1) - , — ,

2) (, SRP)

3) ,



TL; DR at the very bottom.



Why evil?



. , Integer, :



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 .



?



  1. . , , .
  2. . , , ( — ).
  3. . immutable object, , Equals GetHashCode , - , .


, , . , :



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, , "" — , .





  1. , , . ?
  2. Equals GetHashCode true 0 . , , . , Equals GetHashCode , .
  3. . , , .
  4. , GetValue, , ConditionalWeakTable. -, Lazy<T>.
  5. with, initialized holder, — .


!



, :



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



: , .



And the well-known existing approaches, apparently, do not allow it to be done beautifully, so you have to write your own.




All Articles