5

I have a wrapper around Cache library and have the following method to retrieve the cached valued based on Key:

public async Task<T> GetAsync<T>(string key) { var serializedObject = await _cache.GetStringAsync(key); return JsonConvert.DeserializeObject<T>(serializedObject); } 

The problem is there is nothing in cache, I receive an error from the DeserializeObject method. I'm stuck on what how to return either null or the default value of T when nothing is stored cache.

I tried:

public async Task<T> GetAsync<T>(string key) { var serializedObject = await _cache.GetStringAsync(key); if (serializedObject == null) return Task.CompletedTask; return JsonConvert.DeserializeObject<T>(serializedObject); } 

But Task.CompletedTask cannot be converted to Task<T>

I tried:

public async Task<T> GetAsync<T>(string key) { var serializedObject = await _cache.GetStringAsync(key); if (serializedObject == null) return Task.FromResult<T>(null); return JsonConvert.DeserializeObject<T>(serializedObject); } 

But null is not a valid parameter for T.

How do I get GetAsync to return either null or the default value of T?

Update

I got the following to work:

public async Task<T> GetAsync<T>(string key) { var serializedObject = await _cache.GetStringAsync(key); return serializedObject == null ? default(T) : JsonConvert.DeserializeObject<T>(serializedObject); } 
7
  • return null;/return default(T);? Commented Jul 10, 2018 at 22:42
  • No, null doesn't work either. Commented Jul 10, 2018 at 22:44
  • What is the type constraint on T? Commented Jul 10, 2018 at 22:49
  • there isn't one Commented Jul 10, 2018 at 22:54
  • default<T> works fine Commented Jul 10, 2018 at 23:09

4 Answers 4

10

You have three options:

  • If you do not need to support value types, add T : class constraint
  • Add a wrapper class that lets your caller know if accessing cache was successful, or
  • Add a method to construct and cache the new value on unsuccessful retrieval.

The first implementation lets your approach compile:

public async Task<T> GetAsync<T>(string key) where T : class { var serializedObject = await _cache.GetStringAsync(key); if (serializedObject == null) { return await Task.FromResult<T>(null); } return JsonConvert.DeserializeObject<T>(serializedObject); } 

Here is the second implementation:

class CacheResult<T> { public bool IsSuccess {get;} public T Value {get;} public CacheResult(T val, bool isSuccess) { Value = val; IsSuccess = isSuccess; } } public async Task<CacheResult<T>> GetAsync<T>(string key) { var serializedObject = await _cache.GetStringAsync(key); if (serializedObject == null) { return new CacheResult(default(T), false); } return new CacheResult( JsonConvert.DeserializeObject<T>(serializedObject) , true ); } 

Here is the third implementation:

public async Task<T> GetAsync<T>(string key, Func<string,T> make) { var serializedObject = await _cache.GetStringAsync(key); if (serializedObject == null) { var res = make(key); _cache.PutStringAsync(key, JsonConvert.SerializeObject(res)); return res; } return JsonConvert.DeserializeObject<T>(serializedObject); } 

The caller would need to provide a "factory" delegate to the second method in order to make a new object if a cached one is not available.

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

2 Comments

Interesting approach, does this have an advantage to returning default(T) ?
@Josh Yes, it lets the caller decide if there was a cached value when a key happens to be mapped to a default(T) value: for example, if T is, say, an int, this approach lets you decide if key is mapped to zero vs. there's no value for that key. Same goes for structs with all default values.
5

Have you tried this? return default(T);

Because you're returning the default value of T, you might as well return it synchronously.

3 Comments

You need to await Task.FromResult
return default(T); or return await Task.FromResult<T>(default(T)); should both work, the first option is just better
@BrunoLM Yeah, returning the default value of T synchronously does seem to make more sense :)
0

Here is a simple way to return a Task with a nullable type. Make sure your Task T is assigned the nullable operator, like "T?", and try a legit await call before defaulting to the default null value of T in the thread task call. The default value of any reference type is null. But your methods return type of Task<T?> must have the "?" to accept a nullable type, as well.

public async Task<T?> GetByIdAsync(int id) { return await _context.FindAsync<T>(id) ?? default; } 

Comments

0

None of the options are now working in C# 10 or above especially when you have set compiler property of treat warning as errors which is an important prerequisite of our project. So I resolve this by following in year 2023

public async Task<T> GetAsync<T>(string key) { var serializedObject = await _cache.GetStringAsync(key); if (serializedObject == null) { throw new NullReferenceException(nameof(serializedObject )); } var result = JsonConvert.DeserializeObject<T>(serializedObject); return result != null ? result : throw new NullReferenceException(nameof(result)); } 

Alternatively we can also get away by declaring Task<T?> instead of Task<T> if other code / interface allows that nullable implementation.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.