How to implement C# properties which need some time to evaluate their value.


Sometimes in your WPF applications you have properties for which you need to do some work to get their value. But of course you only want to spend the resources when the property value is needed (its getter is called). If no one is interested in the value, there is no use in providing it (and consuming the resources for this action). So you need some kind of lazy-loaded properties which are completely transparent to bindings, but how could you implement them?

Imagine you are writing an audio player which is able to view album cover images for the currently playing song. Most certainly your playlist is a list of TrackViewModels (or some similar class) which has properties like Artist, Album, Title and Cover (ViewModelBase is a class which implements INotifyPropertyChanged):

Please be aware that all programming examples do not exhibit production ready error handling for the sake of clarity.

class TrackViewModel : ViewModelBase
{
	// filepath of the mp3 file
	string filename;

	//ID3 tags
	public string Artist { get; }
	public string Album { get; }	
	public string Title { get; }

	public Uri CoverImageUri
	{
		get
		{
			// retrieve the folder.jpg, if it exists
			string coverFilename = Path.GetDirectoryName(filename) + "folder.jpg";
			if (File.Exists(coverFilename))
				return new Uri(coverFilename);

			return null;
		}
	}
}

In your user interface you would bind an Image’s Source to this CoverImageUri property, like so:

<Image Source="{Binding CoverImageUri}" />

This works like a charm until your users demand that covers they do not have should be automatically downloaded from the internet. Maybe your first idea is to just put the web lookup and download code in the getter:

public Uri CoverImageUri
{
	get
	{
		// retrieve the folder.jpg, if it exists
		string coverFilename = Path.GetDirectoryName(filename) + "folder.jpg";
		if (File.Exists(coverFilename))
			return new Uri(coverFilename);

		// check if we can find the cover online
		// if yes, download it and store it as folder.jpg
		using(var client = new WebClient())
		{
			var bytes = client.DownloadData(String.Format("http://[yourfavoritecoverwebsite]/{0}/{1}", Artist, Album);

			if (bytes != null)
			{
				using(var stream = new FileStream(coverFilename, FileMode.Create))
				{
					stream.Write(bytes, 0, bytes.Count);
				}

				return new Uri(coverFilename);
			}
		}

		return null;
	}
}

But of course, as this is executed synchronously it will block your user interface while downloading the file which is not acceptable for your users. You could use the IsAsync property of the WPF binding class when binding to the property, which tells the framework to retrieve the property value on a background thread like so:

<Image Source="{Binding CoverImageUri, IsAsync=true}" />

However, all normal consumers of the property (which are not using a binding) still experience the same old (potentially blocking) behavior. Even worse when you have multiple async bindings to a single property the getter may be called multiple times from different threads. Also imagine if you want to create a library view from these ViewModels, you have no control over the amount of threads WPF uses to retrieve the covers, in which order they are resolved and there is no possibility to cancel the downloading once it has started.

Of course, the solution is to move the downloading to a different thread. So, when the property getter is called for the first time, you return a default value and invoke an sync method which loads the actual data. Once finished, you set the property to the retrieved value and raise the normal property changed event so that the binding fetches the new value:

bool coverLoaded = false;
Uri coverImageUri;

public Uri CoverImageUri
{
	get
	{
		if (coverLoaded)
			return coverImageUri;

		coverLoaded = true;
		LoadCoverAsync().ContinueWith( (t) -> CoverImageUri = t.Result );
		return null; // or any default value
	}
	private set
	{
		if (coverImageUri != value)
		{
			coverImageUri = value;
			OnPropertyChanged();
		}
	}
}

async Task<Uri> LoadCoverAsync() {

	// retrieve the folder.jpg, if it exists
	string coverFilename = Path.GetDirectoryName(filename) + "folder.jpg";
	if (File.Exists(coverFilename))
		return new Uri(coverFilename);

	// check if we can find the cover online
	// if yes, download it and store it as folder.jpg
	using(var client = new WebClient())
	{
		var bytes = await client.DownloadDataAsync(String.Format("http://[yourfavoritecoverwebsite]/{0}/{1}", Artist, Album));
		if (bytes != null)
		{
			using(var stream = new FileStream(coverFilename, FileMode.Create))
			{
				stream.Write(bytes, 0, bytes.Count);
			}
			return new Uri(coverFilename);
		}
	}
}

Now you can bind normally to this property and when a binding requests the value for the first time, a default value is returned while the actual value is loaded asynchronously. By extending this sample with a CancellationToken and offloading the async method to a ThreadPool you have full control over the number of threads which are loading covers and could cancel the download at any time (e.g. when the user closes the associated view). By adding properties like IsLoadingCover and ErrorOnLoadingCover you can also easily show the status of the process in your user interface. (ProgressRing is just a nice little busy animation).

<Grid>
	<Image Source="{Binding CoverImageUri, TargetNullValue=nocover.png}" />
	<ProgressRing Visibility="{Binding IsLoadingImage, Converter={StaticResource BooleanToVisibilityConverter}}"
		HorizontalAlignment="Center" VerticalALignment="Center" />
	<TextBlock Text="Could not load image!" Visibility="{Binding ErrorOnLoadingCover, Converter={StaticResource BooleanToVisibilityConverter}}" />
</Grid>

This works, but if you have a lot of such lazy properties it clutters your ViewModel with loading properties. So you either could create a single IsBusy property but then you lose the fine-grained status or wrap the lazy property resolution in its own class:

class LazyProperty<T> : INotifyPropertyChanged
{
    CancellationTokenSource cancelTokenSource = new CancellationTokenSource();
    
    bool isLoading = false;
    bool errorOnLoading = false;

    T defaultValue;
    T _value;
    Func<CancellationToken, Task<T>> retrievalFunc;

    bool IsLoaded { get; set; }

    public bool IsLoading
    {
        get => isLoading;
        set
        {
            if (isLoading != value)
            {
                isLoading = value;
                OnPropertyChanged();
            }
        }
    }

    public bool ErrorOnLoading
    {
        get => errorOnLoading;
        set
        {
            if (errorOnLoading != value)
            {
                errorOnLoading = value;
                OnPropertyChanged();
            }
        }
    }

    public T Value
    {
        get
        {
            if (IsLoaded)
                return _value;

            if (!isLoading)
            {
                IsLoading = true;
                LoadValueAsync().ContinueWith((t) => 
                {
                    if (!t.IsCanceled)
                    {
                        if (t.IsFaulted)
                        {
                            _value = defaultValue;
                            ErrorOnLoading = true;
                            IsLoaded = true;
                            IsLoading = false;
                            OnPropertyChanged(nameof(Value));
                        } else
                        {
                            Value = t.Result;
                        }                            
                    }
                });
            }

            return defaultValue;
        }
        // if you want a ReadOnly-property just set this setter to private
        set
        {
            if (isLoading)
                // since we set the value now, there is no need
                // to retrieve the "old" value asynchronously
                CancelLoading();

            if (!EqualityComparer<T>.Default.Equals(_value, value))
            {
                _value = value;
                IsLoaded = true;
                IsLoading = false;
                ErrorOnLoading = false;

                OnPropertyChanged();
            }
        }
    }

    private async Task<T> LoadValueAsync()
    {
        return await retrievalFunc(cancelTokenSource.Token);
    }

    public void CancelLoading()
    {
        cancelTokenSource.Cancel();
    }

    public LazyProperty(Func<CancellationToken, Task<T>> retrievalFunc, T defaultValue)
    {
        this.retrievalFunc = retrievalFunc ?? throw new ArgumentNullException(nameof(retrievalFunc));
        this.defaultValue = defaultValue;

        _value = default(T);
    }

    /// <summary>
    /// This allows you to assign the value of this lazy property directly
    /// to a variable of type T
    /// </summary>        
    public static implicit operator T(LazyProperty<T> p)
    {
        return p.Value;
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnPropertyChanged([CallerMemberName]string propertyName = "")
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));            
    }
}

So, in your ViewModel you just have to provide the method which retrieves the value of the lazy property:

class TrackViewModel : ViewModelBase
{
	public LazyPropert<Uri> CoverImageUri { get; }

	public TrackViewModel
	{
		CoverImageUri = new LazyProperty<Uri>( (cancelToken) => LoadCoverAsync(cancelToken), new Uri("nocover.png"));
	}

	async Task<Uri> LoadCoverAsync(CancellationToken cancelToken) {
		// retrieve the folder.jpg, if it exists
		string coverFilename = Path.GetDirectoryName(filename) + "folder.jpg";
		if (File.Exists(coverFilename))
			return new Uri(coverFilename);

		// check if we can find the cover online
		// if yes, download it and store it as folder.jpg
		using(var client = new WebClient())
		{
			var bytes = await client.DownloadDataAsync(String.Format("http://[yourfavoritecoverwebsite]/{0}/{1}", Artist, Album), cancelToken);
			if (bytes != null)
			{
				using(var stream = new FileStream(coverFilename, FileMode.Create))
				{
					stream.Write(bytes, 0, bytes.Count);
				}
				return new Uri(coverFilename);
			}
		}
	}
}

The property can then be used in XAML like this:

<Grid>
	<Image Source="{Binding CoverImageUri.Value, TargetNullValue=nocover.png}" />
	<ProgressRing Visibility="{Binding CoverImageUri.IsLoading, Converter={StaticResource BooleanToVisibilityConverter}}"
		HorizontalAlignment="Center" VerticalALignment="Center" />	
	<TextBlock Text="Could not load image!" Visibility="{Binding CoverImageUri.ErrorOnLoading, Converter={StaticResource 	BooleanToVisibilityConverter}}" />
</Grid>