Reputation: 13
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.
Upvotes: 1
Views: 60
Reputation: 4925
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
}
}
Upvotes: 0