I have a class that encapsulates a PowerShell Runspace
object which, among many other things, has methods for getting and setting variables in the session state. There is no apparent mechanism for enumerating the variables, only get/set.
To simplify access to the variables from my code I've been using a specific indexer class. After I started writing another indexer for a different collection I thought it would be useful to have a generic implementation that would work for both and satisfy the DRY principle.
After several minor issues, much thought, discussion and testing I've come up with an indexer implementation that looks like it should be useful in a variety of situations and would like some input on what I can do to improve on it.
Design goals include:
- Flexible get/set via function delegates
- Weak referencing to allow proper object collection
- Lightweight interface
- Minimal exceptions - default values where possible
- As SOLID as feasible.
The interface for the class is:
public interface IIndexer<TKey, TValue> : IDisposable
{
TValue this[TKey key] { get; set; }
}
I'm a little iffy about the IDisposable
here. Initially I had nothing to dispose of but I want to be sure that any delegates are disconnected from the indexer in case they were closed around an object that might prevent garbage collection. Nulling the delegate references in the Dispose
method is the best resolution I could find.
The Indexer
class implements the above by holding a WeakReference
to the object that holds the collection or otherwise grants access to whatever is being indexed, plus delegates to the methods that implement get and set on the indexer. Construction requires all three.
public class Indexer<TTarget, TKey, TValue> : IIndexer<TKey, TValue>
where TTarget : class
{
// Weak reference to target
private WeakReference _weaktarget;
// Get method
private Func<TTarget, TKey, TValue> _getter;
// Set method
private Action<TTarget, TKey, TValue> _setter;
public Indexer(TTarget target, Func<TTarget, TKey, TValue> getter, Action<TTarget, TKey, TValue> setter)
{
_weaktarget = new WeakReference(target);
_getter = getter;
_setter = setter;
}
public void Dispose()
{
_weaktarget = null;
_getter = null;
_setter = null;
}
public TValue this[TKey key]
{
get
{
// Get strong reference to target
TTarget target = _weaktarget?.Target as TTarget;
// invoke the get method if reasonable
if (target != null && _getter != null)
return _getter(target, key);
// ... otherwise return a default value
return default(TValue);
}
set
{
// Get strong reference to target
TTarget target = _weakref?.Target as TTarget;
// invoke set method if reasonable
if (inst != null && _setter != null)
_setter(target, key, value);
}
}
}
To aid in construction of the above I have a static IndexerFactory
class. The CreateIndexer
method both simplifies the signature (through type inference) and abstracts the implementation details from the user. The simple form is:
public static class IndexerFactory
{
public static IIndexer<TKey, TValue> CreateIndexer<TTarget, TKey, TValue>
(
TTarget target,
Func<TTarget, TKey, TValue> getter,
Action<TTarget, TKey, TValue> setter
)
where TTarget : class
=> new Indexer<TTarget, TKey, TValue>(target, getter, setter);
}
I have considered having the indexer implementation(s) as private sub-class(es) of the IndexerFactory
static class to increase the abstraction level. I may also use this class to define ToIndexer
extensions for things like IDictionary<>
. Discussions on these points defintely welcome.
Here's an example to show how this could be used:
public class Example : IDisposable { private Dictionary<string, object> _variables; public IIndexer<string, object> Variables { get; private set; } public Example() { Variables = IndexerFactory.CreateIndexer ( _variables, (inst, key) => { object result = null; inst.TryGetValue(key, out result); return result; }, (inst, key, value) => inst[key] = value ); } public void Dispose() { Variables.Dispose(); Variables = null; _variables = null; } }
I'm interested in any weaknesses you can find in the above code, discussions about the merits of the points mentioned above and any improvements that you can think of.