"Provide a way for any alternative replacement algorithm to be implemented by the client and used by the cache."
As with almost any problem, the first step is to break it down into smaller sub-problems:
1) How can a replacement algorithm be represented?
2) How can the user specify their own replacement algorithm?
3) How can the cache use this replacement algorithm?
To solve this problem, we first need some way to pass a function (or set of functions) as a parameter. In C#, there are two main ways to do this: delegates and interfaces.
A delegate is the "direct" way to pass a function, although if you want to pass multiple functions together in a single group it is generally better to define an interface which contains all of the methods that will be needed.
Using interfaces, the user can pass an instance of an implementation of the interface (which you have defined) as as the parameter for the custom replacement algorithm. Your interface would have certain methods specified, which the custom implementation would have to specify the actual code for.
Now, with regards to your current code, I mentioned during our last call that having two separate data structures to deal with MRU and LRU makes creating a solution for requirement #5 a bit more difficult. That is because if you had a single LinkedList (for example) that you used for both LRU and MRU, you could then have this same LinkedList be used for the custom replacement algorithm too.
Or, as another way to think of it, imagine if we had a 6th requirement to allow a user to switch from LRU to MRU after a cache is created and populated with values - right now, you would have to create a "switch" function to copy values to/from the stack/queue, whereas with a single data structure holding the cache for either algorithm this wouldn't be necessary.
However, you don't necessarily have to change your MRU and LRU code before implementing #5!
As one option, you could just do something like "if MRU then A, else if LRU then B, else C", where "C" is a function that works for custom algorithms.
Or, to use the more "object-oriented" approach, you can take advantage of the fact that Stack, Queue, and LinkedList are all subclasses of "Collection" in C#. That is, using either delegates or interfaces, you can define functions for the replacement algorithm that accept a "Collection" type parameter.
Choosing this "object-oriented" option, you can then adapt your existing LRU and MRU functions to pass in the corresponding stack/queue to this function, such that LRU, MRU, and custom algorithms will all work with the same interface/delegate solution that you create.