0

Often when coding in C# I like to use this pattern for a lazy getter property:

private string _myProp;
string MyProp => _myProp ?? (_myProp = getMyProp());

I think this is pretty standard C# practice. The function getMyProp() is just some function that computes and returns a string. It is called just once to initialize the property, and henceforth it's cached. I can use this for any object type I like, not just string. However, when I try to do it for a primitive type like an int, I run into an issue. For example, if I try the following:

private int _operationCount;
int OperationCount => _operationCount ?? (_operationCount = GetOperationCountFromUser());

I get a compiler error here. It says:

Error CS0019 Operator '??' cannot be applied to operands of type 'int' and 'int'

I understand the error means that int is a primitive type, so we can't apply the null-checking operator to it, because an int is never null. But is there any way I can achieve what I want here? I'm looking for a solution with the following:

  • On the first access, the int property is initialized to the return value of the function call.
  • On subsequent accesses, the int property is retrieved from a loaded variable in memory i.e. the function is not called again.

Update: Why Write Properties Like This?

The original question just cited "laziness" as the reason for writing one of these properties, but as I collaborated on this question I realised that probably the primary use case I have for the above pattern using the ?? operator is enforced initialization. By forcing initialization of a property this way, it'll always be initialized the first time you access it. Meaning you don't have to worry about injecting an initial value into multiple constructors- which clutters them up on the one hand and also isn't reliable because you might forget to initialize in one of the constructors.

7
  • 3
    FYI with C# 8 you can now write that as string MyProp => _myProp ??= getMyProp(); Commented Jun 2, 2020 at 19:26
  • Have you considered using Lazy instead? Commented Jun 3, 2020 at 7:09
  • Yes - I suppose I am asking why you aren't using it? Your question mentioned Lazy multiple times - so I am asking why you chose not to use the "obvious" solution. Commented Jun 3, 2020 at 7:10
  • @mjwills To be more correct, the question mentions "lazy" rather than Lazy explicitly. Actually, I did try to use Lazy and it wouldn't compile unless the backing property was static. Which isn't always possible. But it's a valid use case in some cases, so I think having both options is important. Hmmm... I'm starting to realize probably the main reason I use these properties isn't really laziness, it's enforcing initialization... Commented Jun 3, 2020 at 7:15
  • 1
    Awesome, great to hear @ColmBhandal. Commented Jun 3, 2020 at 23:04

2 Answers 2

2

One solution is to make the backing type nullable, and put a call to the Value property on the RHS:

private int? _operationCount;
public int OperationCount => _operationCount ?? (_operationCount = GetOperationCountFromUser()).Value;

Here's a full working solution with a dummy implementation of the backing function:

using System;

public class Program
{
    public static void Main()
    {
        X x = new X();
        Console.WriteLine("Hello World " + x.OperationCount);
    }
}

class X
{
    private int? _operationCount;
    public int OperationCount => _operationCount ?? (_operationCount = GetOperationCountFromUser()).Value;
    private int GetOperationCountFromUser() => 38;
}   

Thanks to juharr's comment, the explanation for why this works is because the compiler converts nullable ?? expression to nullable.GetValueOrDefault(expresion) when nullable is a Nullable<T>. This explains why the RHS, the default, must be a primitive type, while the LHS is the nullable type.

Sign up to request clarification or add additional context in comments.

3 Comments

Actually you can make the filed Lazy<int> and then the property can be public int OperationCount => _operationCount.Value;
And the reason you don't need the Value on the left is because it will compile nullable ?? expersion to nullable.GetValueOrDefault(expresion) when nullable is a Nullable<T>. Also if you did nullable.Value ?? expression then the left is no longer a nullable value and thus cannot be used with ??
Yeah, the method has to be static for the initialization to work.
1

I personally don't see anything bad in accessing lazy object through Value (at least it shows you explicitly that there can be a delay when you getting the value). But you can skip even this and become very lazy. Unfortunately, you cannot define an implicit operator for conversion between two third party types, but you can inherit from Lazy<T> and define an implicit conversion for your type:

public class VeryLazy<T> : Lazy<T>
{
    public VeryLazy(Func<T> valueFactory) : base(valueFactory) { }
    public static implicit operator T (VeryLazy<T> lazy) => lazy.Value;
}

And usage becomes very lazy - you can use operationCount whenever you want to use int:

class X
{
 private readonly VeryLazy<int> _operationCount = new VeryLazy(GetOperationCountFromUser);
 public int OperationCount => _operationCount; // implicit conversion to int
 private static int GetOperationCountFromUser() => 38;
}

But I'm not that lazy, and I find code without implicit conversions more readable

class X
{
 private readonly Lazy<int> _operationCount = new Lazy(GetOperationCountFromUser);
 public int OperationCount => _operationCount.Value; // we see there can be delay
 private static int GetOperationCountFromUser() => 38;
}

Also Lazy<T> is better than using nullable types. Sometimes object which initialization you defer might not be available - lazy would return null in that case (e.g. when you tried to get something from the database, but there is no value). What would nullable field tell you in that case? You might end up trying to initialize it on each property access.

3 Comments

Lazy<T> and int are not types which you can modify - thus 3rd party types
Overall, you make some good points. There are cases to use Lazy, and people should consider it when writing a Lazy property. The implicit operator solution is pretty cool; I think it's probably too complex but nice to know. As for this "Lazy<T> is better than using nullable types"... sometimes, yes. It depends on use case. For example, if the backing method must be non-static, I don't think you can use a Lazy backing property. And if the backing function is not a call to a DB, and is guaranteed to always return an int, then it'll never be null, so it'll only ever be called once.
@ColmBhandal thanks, good points. Btw if you want backing method to be non-static then simply move _operationCount initialization to the constructor.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.