I'm using DispatchProxy
to create a "cached" IEnumerable<T>
that will evaluate its elements once upon first enumeration, then buffer them for subsequent enumerations. I'm aware that this might not be idiomatic C#, but the fact is is that I'm expecting a large number of elements that I'd like to avoid computing immediately (so a Collection
is not appropriate). I'm also expecting multiple partial enumerations over potentially expensive to compute objects. I'm using a dynamic proxy because I would like to extend the caching behavior to any IEnumerable<T>
derived from one that has already been cached.
My question is whether or not it's possible to catch calls to extension methods using DispatchProxy
? Specifically, calls to extension methods that return a new enumerable with additional elements (e.g., Append
) so that I can wrap the result in a new instance of my proxy class? I can see how this might not be possible, but just in case it is, I thought I'd ask.
This is my proxy class:
public class CachedEnumerable<TElement> : DispatchProxy
{
public static IEnumerable<TElement> Create(IEnumerable<TElement> elements)
{
var proxy = Create<IEnumerable<TElement>, CachedEnumerable<TElement>>();
var cached = proxy as CachedEnumerable<TElement>;
cached._enumerator = elements.GetEnumerator();
return proxy;
}
private IEnumerable<TElement> Target
{
get
{
var enumerator = GetEnumeratorImpl();
while (enumerator.MoveNext())
{
yield return enumerator.Current;
}
}
}
private readonly List<TElement> _cache = [];
private IEnumerator<TElement>? _enumerator;
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
if (targetMethod?.Name == nameof(IEnumerable.GetEnumerator))
{
return GetEnumeratorImpl();
}
if (targetMethod?.ReturnType == typeof(IEnumerable<TElement>))
{
// Extension methods would ideally be dispatched to this code.
return Make(targetMethod.Invoke(Target, args) as IEnumerable<TElement>);
}
return targetMethod?.Invoke(Target, args);
}
private IEnumerator<TElement> GetEnumeratorImpl()
{
foreach (var element in _cache)
{
yield return element;
}
while (_enumerator.MoveNext())
{
_cache.Add(_enumerator.Current);
yield return _enumerator.Current;
}
}
}
Calls to GetEnumerator
are forwarded to GetEnumeratorImpl
as expected, but extension method calls are simply dispatched to their normal implementation.
No, it's not possible because extension methods are static methods and there is no virtual dispatch that DispatchProxy
can hook itself into.
Can't you just have CachedEnumerable<T>
implement IEnumerable<T>
with the existing logic?
public class CachedEnumerable2<TElement> : IEnumerable<TElement> {
private IEnumerator<TElement> _enumerator;
private readonly List<TElement> _cache = [];
public CachedEnumerable2(IEnumerable<TElement> enumerable) {
_enumerator = enumerable.GetEnumerator();
}
public IEnumerator<TElement> GetEnumerator() => GetEnumeratorImpl();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private IEnumerator<TElement> GetEnumeratorImpl() {
foreach (var element in _cache) {
yield return element;
}
while (_enumerator.MoveNext()) {
_cache.Add(_enumerator.Current);
yield return _enumerator.Current;
}
}
}
Then you can make extension methods on it that "hide" the IEnumerable<T>
methods you want:
public static class Extensions {
public static CachedEnumerable2<TSource> Append<TSource>(this CachedEnumerable2<TSource> source, TSource element) {
// logic
}
}