I have a generic class that I wish to store in a collection. As such, I've gone down the usual approach of creating an abstract class that the generic inherits from, and adding the generic properties on top (nb: new to this approach, so possible I'm still doing something wrong).
The net result is, my generic class looks like:
public class DependencyDeclaration<TDependantHost, TDependant, TFoundationHost, TFoundation> : DependencyDeclaration {
public DependencyDeclaration(Expression<Func<TDependandHost, TDependant>> dependantRef, Expression<Func<TFoundationHost, TFoundation>> foundationRef, Expression<Func<TDependantHost, TFoundationHost>> foundationHostRef)
public Expression<Func<TDependantHost, TDependant>> DependantRef {get;private set;}
public Expression<Func<TDependantHost, TDependant>> FoundationRef {get;private set;}
public Expression<Func<TDependantHost, TDependant>> FoundationHostRef {get; private set;}
}
And the base class:
public abstract class DependencyDeclaration {
public string GetDependantName() {
// Some code to return the name of the leaf property from
// DependantRef, roughly akin to GetPropertyNameFromExpression
// in BindableObjectBase from
// http://www.pochet.net/blog/2010/07/02/inotifypropertychanged-automatic-dependent-property-and-nested-object-support/
var e = this
.GetType()
.GetProperties()
.Where(p=>p.Name=="DependantRef")
.First()
.GetValue(this) as Expression;
return GetPropertyName(e).Name; // GetPropertyName requires a Func<T,U>, so this doesn't work as is.
}
public string GetFoundationName(Expression e) {
// ... similar implementation to GetDependantName
}
public string GetFoundationHostName(Expression e) {
// ... similar implementation to GetDependantName
}
public object GetFoundationHostRef(object dependantHost, Expression e) {
// Evaluates this.FoundationHostRef via reflection against
// dependantHost.
// Will function based on the comment below about using
// LambdaExpression, so not that worried about this one.
// Something like, though:
var e = this
.GetType()
.GetProperties()
.Where(p=>p.Name=="DependantRef")
.First()
.GetValue(this) as LambdaExpression;
if (e == null)
return;
var foundationHostRefFunc = e.Compile()
return foundationHostRefFunc(dependantHost);
}
// Returns the PropertyInfo of a property via an Expression
// as provided [here][http://stackoverflow.com/a/672212/847721]
public GetPropertyInfo(Expression<Func<T, U>> targetExpression)
{
// ... Magic expression parsing stuff ...
}
}
A list of such declarations would be populated on static construction of classes supporting ISupportsDependencyManager
by using e.g.
public class MyClassWithCalculatedProperties : ISupportsDependencyManager {
// MyPropertySource inherits from INotifyPropertyChanged
public object MyPropertySource {get;set;}
// Trite example calculated property. Obviously actual calculated
// properties could be arbitrary.
public int MyCalculatedProperty
{
get {
return MyPropertySource.SomeProperty + 50;
}
}
private static DependencyDeclaration MyCalculatedPropertyDependencies =
new DependencyDelcaration<MyClassWithCalculatedProperties, int, MyPropertySource, int>(
(dependantHost)=>dependantHost.MyCalculatedProperty,
(foundationHost)=>foundationHost.SomeProperty,
(dependantHost)=>dependantHost.MyPropertySource
);
static MyClassWithCalculatedProperties()
{
// Static constructor iterates over the properties in the type
// using DependencyManager and appends them to a list that
// DependencyManager maintains of DependencyDeclaration instances
//
// This is where I'm going to generate a list of DependencyDeclarations - see below.
DependencyManager
.RegisterDependencies(
typeof(MyClassWithCalculatedProperties)
);
}
public MyClassWithCalculatedProperties()
{
// Constructor for MyClassWithCalculatedProperties
// Attach event handlers that allow calculated properties to
// have notifications on them.
DependencyManager.AttachPropertyChangedHandlers(this)
}
}
// This isn't finished yet, so expect bugs in ANY event
// (not certain I'm using reflection right)
public class DependencyManager
{
private static ConcurrentDictionary<Type, ConcurrentBag<DependencyDeclaration>> _ParsedDependencies;
// This is where I need the abstract class and/or interface. It
// allows all DependencyDeclarations to co-exist in the same
// collection to avoid needing to parse the type using reflection
// more than once in a given program execution.
public static RegisterDependencies(Type targetType)
{
var dependencyDeclarations =
targetType
.GetFields(System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic)
.Where(fi => fi.FieldType == typeof(DependencyDeclaration))
.Select(fi => fi.GetValue(targetType));
ConcurrentBag<DependencyDeclaration> container = GetManagedDependencyContainer(targetType);
dependencyDeclarations
.Cast<DependencyDeclaration>()
.ToList()
.ForEach(d => container.Add(d));
}
/*
* Other methods of DependencyManager
*/
}
This presents me with two problems:
- At the point when I need to invoke
GetUonTExpression<T,U>
, I don't know whatT
andU
are (I'm sure they're buried in the arg to be passed, but not sure how to extract or invoke in any event). Probably via reflection, but not sure the specific bits of reflection to use. - If I actually want to evaluate the expression, I can't find a way to do so - the compile method is missing, probably because it's not know to be a
Func
expression. However, whilst I know it'll be aFunc<>
, and I know thatFunc<>
will have two type args, I don't know how to specify that, as opposed to what those type args will be.
Anyone have an idea how to go about this?
NB that what I'm actually intending to do with these Expression
s may come into it too. The actual class has 4 typeargs, and three expressions. All three are used as ways of specifying the name of a property (for property change notification purposes), but the 3rd also needs to be able to be compiled to provide me with a way of getting to the current value of the expression when evaluated against an object of type T. This means, in most cases I don't really care about the inner Func
, just string that the Expression.Body
's rightmost leaf evaluates to, but in one case at least, I do care about compiling and evaluating the Func
.
NB2: This code is expected to run through a bunch of expressions every time objects using it are created, but it's NOT expected to be run after object creation - so I'm ok with reflection in principle.
EDIT: Updated code substantially to give a clearer idea of what this is used for.