HiveBrain v1.2.0
Get Started
← Back to all entries
patterncsharpMinor

Maintaining a cache structure with elements

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
elementswithcachestructuremaintaining

Problem

I have some code which is designed for the following purposes:

  • Implement a cache structure which stores cached elements, as well as a method for reloading those elements.



  • Ensure the new structure can use any type of IEnumerable to store the elements.



  • Implement a dynamic cache which inherits from the standard cache, as well as storing additional methods for appending new elements and determining which elements to drop.



  • Ensure the dynamic cache can only use List to store the cached elements, in order to make use of List.AddRange and List.RemoveAll.



This works, but seems unnecessarily complicated. I essentially have 5 classes and 1 interface to pull this off, and having to declare TEnumerable as IEnumerable in the where clause of a class declaration feels odd at best.

Is there a simpler way to implement this?

```
internal interface ICache : IEnumerable
{
bool Contains(object element);
}

internal class DynamicCache : DynamicCache, TElement> {
internal DynamicCache(
Func> initElements
, Func> addElements
, Predicate dropElementsIf
)
: base(initElements, addElements, dropElementsIf)
{
}
}

internal class DynamicCache : Cache where TEnumerable : List {

protected Func AddElements;
protected Predicate DropElementsIf;

internal DynamicCache(
Func initElements
, Func addElements
, Predicate dropElementsIf
)
: base(initElements)
{
this.AddElements = addElements;
this.DropElementsIf = dropElementsIf;
}

protected override sealed void UpdateElements() {
if (!isInitialized) {
this.elements = InitElements();
this.isInitialized = true;
}
else {
this.elements.AddRange(AddElements());
this.elements.RemoveAll(DropElementsIf);
}
}
}

internal class Cache : Cache, TElement> {
internal Cache(Func> initElements)
: base(initElements)
{

Solution

Interface design is wanting.

This looks like this was made by mistake:

internal interface ICache : IEnumerable
{
    bool Contains(object element);
}


It should implement IEnumerable which already provides IEnumerable functionality.

internal interface ICache : IEnumerable
{
    bool Contains(TElement element);
}


This way you can do Cache : ICache instead of class Cache : IEnumerable, ICache. Which allows non-type-safe and redundant Contains(object) method.

More importantly though your ICache provides no additional functionality w.r.t any other IEnumerable, given IEnumerable already has a Contains extension method.

Before writing some component, you should be able to write interfaces thereof, and test the required functionality against those interfaces.

In general:

  • a cache is a specialized Key-Value store.



  • They are used to improve read performance where reads outnumber updates by far, and write are relatively expensive.



  • They exploit time locality (and sometimes space) locality.



The only way to access the elements in your cache is through enumerating them. Which precludes random access. For example you cannot get a Wrapper with a given id from the cache.

GetEnumerator modifies the underlying collection by calling UpdateElements();,
and this ruins the other two properties.
You cannot exploit there being many more reads than updates, because every read is an update.
You cannot exploit time locality because even if you are reading the same elements over and over again you are updating the collection.

Consider this:

DynamicCache wDynamicCache = //......

var wrapper = new Wrapper{id=10, name = "Wrapper 10"};
Console.WriteLine(wDynamicCache.Contains(wrapper)); // False
Console.WriteLine(wDynamicCache.Contains(wrapper)); // True
Console.WriteLine(wDynamicCache.Contains(wrapper)); // False


In order to exploit time (or other) locality is choosing the correct caching strategy.
For example if some data feed is updated on average once a day, you might decide to not hit the source if cache contains a data read in the past 6 hours.
Or you might want to have a fixed size cache which automatically evicts the least recently used when it is full and a value not contained therein is accessed.

But these types of common strategies cannot be implemented with a Predicate dropElementsIf. You might try to wrap the data with last access time etc details, as you already did in your test; but this would leak to the interface, as TElement is now a wrapper with implementation details. For example changing caching strategy will require recompiling the code using the cache.

Some other problems not specific to caches:

  • everytime Cache classes are accessed UpdateElements is called even though Cache classes are not dynamic by default.



  • protected bool isInitialized; is present on Cache


and also everytime Cache classes are accessed checked even if it consists of a fixed collection of elements.

-
Every overriding method of UpdateElements must start with copied :

if (!isInitialized) {
    this.elements = InitElements();
    this.isInitialized = true;
}

Code Snippets

internal interface ICache : IEnumerable
{
    bool Contains(object element);
}
internal interface ICache<TElement> : IEnumerable<TElement>
{
    bool Contains(TElement element);
}
DynamicCache<Wrapper> wDynamicCache = //......

var wrapper = new Wrapper{id=10, name = "Wrapper 10"};
Console.WriteLine(wDynamicCache.Contains(wrapper)); // False
Console.WriteLine(wDynamicCache.Contains(wrapper)); // True
Console.WriteLine(wDynamicCache.Contains(wrapper)); // False
if (!isInitialized) {
    this.elements = InitElements();
    this.isInitialized = true;
}

Context

StackExchange Code Review Q#46409, answer score: 5

Revisions (0)

No revisions yet.