0
\$\begingroup\$

Is there a way to unify or refactor two methods that do basically the same but the difference being of handling a type of object or a collection of the same type of object?

The following is an implementation of two methods that have very similar behavior. They handle a cache for an API, so when someone asks for a value or a set of values, it verifies that the entry is in cache already. If not, they call the method that will return from an external API: a value or a set of values. Finally the methods, update the cache and return the value/values.

public async Task<T> GetValue<T>(string cacheKey, Func<Task<T>> getItem, MemoryCacheEntryOptions memoryCacheEntryOptions = null)
{
    if (_memoryCache.TryGetValue(cacheKey, out T cacheEntry))
    {
        return cacheEntry;
    }
    else
    {
        cacheEntry = await getItem();

        return SetValue(cacheKey, cacheEntry, memoryCacheEntryOptions);
    }
}

public async Task<IList<T>> GetValues<T>(string cacheKey, Func<Task<IList<T>>> getItems, MemoryCacheEntryOptions memoryCacheEntryOptions = null)
{
    if (_memoryCache.TryGetValue(cacheKey, out IList<T> cacheEntries))
    {
        return cacheEntries;
    }
    else
    {
        cacheEntries = await getItems();

        return SetValues(cacheKey, cacheEntries, memoryCacheEntryOptions);
    }
}
\$\endgroup\$
6
  • 1
    \$\begingroup\$ Guys the code is compiling. If it does not compile (it does) you can abstract the idea of the methods. \$\endgroup\$
    – Marcus48
    Commented May 4, 2021 at 19:53
  • 1
    \$\begingroup\$ Syntax option: public async Task<T> GetValue<T>(string cacheKey, Func<Task<T>> getItem, MemoryCacheEntryOptions memoryCacheEntryOptions = null) => _memoryCache.TryGetValue(cacheKey, out T cacheEntry) ? cacheEntry : SetValue(cacheKey, await getItem(), memoryCacheEntryOptions); \$\endgroup\$
    – aepot
    Commented May 4, 2021 at 20:21
  • 1
    \$\begingroup\$ Btw, GetValue<T> would be enough, as it can do the job for GetValues<T>, look: public Task<IList<T>> GetValues<T>(string cacheKey, Func<Task<IList<T>>> getItems, MemoryCacheEntryOptions memoryCacheEntryOptions = null) => GetValue(cacheKey, getItems, memoryCacheEntryOptions); \$\endgroup\$
    – aepot
    Commented May 4, 2021 at 20:38
  • 1
    \$\begingroup\$ Because T isn't a type name but like a placeholder for any real type. With generic type you tell compiler "I don't know the real type but it will be the same type here, here and here". T and T is the same type, the same for List<T>. But you can drop details about IList because you don't use the interface methods which IList provides. Then IList can be just a T. \$\endgroup\$
    – aepot
    Commented May 5, 2021 at 6:29
  • 1
    \$\begingroup\$ Awesome, so I can use T to represent a T, IList<T>, IList<IList<T>>, etc as long as I don't use methods from IList. And if I do, I can do what @Kevin answered. Thanks, I learned something really useful!!!!!!!!!!!!!!!!!!!!! \$\endgroup\$
    – Marcus48
    Commented May 5, 2021 at 10:13

1 Answer 1

3
\$\begingroup\$

I'm not sure I fully understand the question, but the first thing that leapt to mind is:

<T> can encompass a IList<SomeType> already.

I mean, the first 75% of GetValue() already works with using IList as the generic type:

public async Task<T> GetValue<T>(
        string cacheKey,
        Func<Task<T>> getItem,
        MemoryCacheEntryOptions memoryCacheEntryOptions = null)
{
    if (_memoryCache.TryGetValue(cacheKey, out T cacheEntry))
    {
        return cacheEntry;
    }
    else
    {
        cacheEntry = await getItem(); 

        return SetValue(cacheKey, cacheEntry, memoryCacheEntryOptions);
    }
}

... memoryCache.TryGetValue() works exactly the same, and will operate correctly. cacheEntry will be the correct type (an IList of something.) getItem() works perfectly, too, since it's a Func that returns the appropriate IList.

Literally the only thing that's different is SetValue vs SetValues. Which, from your original post, looks to be the call into the 3rd party API.

Well, it begs the question of whether SetValue() in the vendor API will work for an IList? If it does, your probably is immediately solved: just use GetValue for everything and call it a day.

Assuming that it doesn't, maybe the question would be whether something like this would work:

public async Task<T> GetValue<T>(string cacheKey, Func<Task<T>> getItem, MemoryCacheEntryOptions memoryCacheEntryOptions = null)
{
    return GetValue(cacheKey, getItem, x => SetValue(cacheKey, x, memoryCacheEntryOptions));
}
public async Task<T> GetValues<T>(string cacheKey, Func<Task<T>> getItem, MemoryCacheEntryOptions memoryCacheEntryOptions = null)
{
    return GetValue(cacheKey, getItem, x => SetValues(cacheKey, x, memoryCacheEntryOptions));
}

private async Task<T> GetValue<T>(string cacheKey, Func<Task<T>> getItem, Func<T, Task<T>> setItem)
{
    if (_memoryCache.TryGetValue(cacheKey, out T cacheEntry))
    {
        return cacheEntry;
    }
    else
    {
        cacheEntry = await getItem(); 

        return setItem(cacheEntry);
    }
}
\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.