2

I am trying to write a simple caching mechanism. Basically, whenever a method is called, its return value should be saved in a cache. Using AOP, my simplified CacheAspect looks as follows.

using Castle.DynamicProxy;

public class CacheAspect : IInterceptor
{
    private object cache;

    public void Intercept(IInvocation invocation)
    {
        if (cache is null)
        {
            invocation.Proceed();
            cache = invocation.ReturnValue;

            return;
        }

        invocation.ReturnValue = cache;
    }
}

However, when the aspect intercepts a method that uses yield return, it only caches the compiler-generated state machine and not the materialized result. Therefore, I would like the aspect to fail-fast in that case.

Therefore I want to deduct from a method's return value, whether or not it uses yield return. So far, I have only found this solution that gets the job done.

private static bool IsReturnTypeCompilerGenerated(IInvocation invocation) =>
    invocation
        .ReturnValue
        .GetType()
        .GetCustomAttribute(typeof(CompilerGeneratedAttribute), inherit: true)
        is object;

My issue with this is that I don't know, what other compiler-generated types there are and in which situations they arise. Is it possible, that I exclude methods from my caching mechanism that shouldn't be excluded? Or put differently: Is there a way to more specifically target methods that use yield return?

  • 6
    Plenty of `IEnumerable` implementations won't have a materialized result and yet won't use the C# iterator block language feature to create the objects. You can't just assume any other `IEnumerable` implementation will be fully materialized, or even that they will produce the same results each time they're iterated. And of course there are return types other than `IEnumerable` that defer work until later, such as anything deal with asynchrony, streams, etc. – Servy Aug 17 '20 at 21:28
  • 1
    Can't you do a type check to see if the object is an enumerable and then force the enumeration (regardless of whether it has been enumerated or not) and then cache the result? Or create a wrapper object to encapsulate the intended object and have the caching interface, wrap & unwrap the objects when requested? This is more of an XY question as I don't know the full circumstances of what you're trying to achieve thus my answer is limited. http://xyproblem.info/ – Kieran Devlin Aug 17 '20 at 21:42
  • 3
    Does this answer your question? [Correct way to check if IEnumerable is created by a yield keyword](https://stackoverflow.com/questions/34183713/correct-way-to-check-if-ienumerablet-is-created-by-a-yield-keyword) – CoolBots Aug 17 '20 at 23:00
  • 2
    Your question has already been answered. See duplicate. That said, it's my opinion that your goal is flawed in any case. That a method might return a lazily-evaluated enumeration should be of no consequence to your cache. It is _correct_ that the lazily-evaluated enumeration is what's cached, not the materialized result. In the rare case where it is desirable to cache the materialized result, it should be up to the _caller_, not the cache, to make that determination and implement. E.g. it would invoke via the cache a separate method that does the evaluation itself. – Peter Duniho Aug 18 '20 at 00:55

0 Answers0