0

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 what T and U 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 a Func<>, and I know that Func<> 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 Expressions 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.

tobriand
  • 1,095
  • 15
  • 29
  • 2
    It's not really clear why you're using expression trees at all - and I'm having a hard time understanding why you've got *any* of this, to be honest. We could really do with more context... – Jon Skeet Apr 06 '15 at 11:23
  • I'm basing most of the usage of expression trees on what I know about parsing expressions from the `GetPropertyNameFromExpression` from the project at http://www.pochet.net/blog/2010/07/02/inotifypropertychanged-automatic-dependent-property-and-nested-object-support/ . This takes an Expression> and returns the name of the property. Other than that, as I say, main goal is 3 strings and an object reference; if I'm doing it thw wrong way, feel free to correct me. – tobriand Apr 06 '15 at 11:34
  • The context really isn't clear enough - you've *described* how you want to use this rather than *demonstrating* it, and the latter would be a much clearer approach. Currently the various bits don't really connect with each other. Note that as of C# 6, using `nameof` is a much cleaner way of specifying a property name in a compile-time safe manner. – Jon Skeet Apr 06 '15 at 11:39
  • 1
    Instead of using the non-generic `Expression`, use `LambdaExpression`, so you have access to the `Parameters` and to `Compile`. The `Expression>` are all `LambdaExpression` (see `public sealed class Expression : LambdaExpression`) – xanatos Apr 06 '15 at 11:43
  • @xanatos - Thanks for that; think that solves 80 - 90% of the issues. I'm only ever expecting to pass in Lamdas Expressions in any event, so will do the job nicely. – tobriand Apr 06 '15 at 13:03
  • @Jon Skeet - Thanks for the point towards *nameof*; will investigate, although since I want to be able to use the solution in VS2010 if I can, my guess is I don't have access to full C# 6 reliably. That said, always good to be aware of what's coming up once I'm only ever going to need VS2013. As for clarity, I'll update to give you an idea of how I'm going to be using these classes. – tobriand Apr 06 '15 at 13:04
  • Right, updated with addtional context, per @JonSkeet 's request. – tobriand Apr 06 '15 at 14:20
  • Why not just use an interface? – Chris Pitman Apr 06 '15 at 14:25
  • @ChrisPitman : Not clear how using an interface helps in a way that using an abstract class doesn't. Again, I've got to have the same methods that are currently on my abstract class on the interface, and if I'm going to get access to any of the Expression> properties, then the interface needs to be generic, meaning I just push the same problem back aa layer of abstraction (I don't eliminate it). – tobriand Apr 06 '15 at 14:36
  • 1
    Hmm. Okay, that's somewhat clearer, although it all seems rather complicated to me. It's still not clear what benefit you're getting from the non-generic base class though - and I'd note that if the generic derived class is the *only* derived class, you could at least make it simpler by declaring the methods abstract in the base class, or just using a non-generic interface. – Jon Skeet Apr 06 '15 at 15:59
  • Right, sorry. The issue arises in the `RegisterDependencies` method of `DependencyManager` - a skeleton of which I've outlined above. It'd probably fail (because I'm not great at using reflection yet), but should give enough of an idea as to why the issue would arise. – tobriand Apr 06 '15 at 18:44
  • @xanatos : Right, the pointer towards Lamda expressions basically solved my issue. If you'd like to post it as an answer, I'd be happy to accept. – tobriand Apr 07 '15 at 21:29

1 Answers1

0

Instead of using the non-generic Expression, use LambdaExpression, so you have access to the Parameters and to Compile. The Expression are all LambdaExpression (see the definition:

public sealed class Expression<TDelegate> : LambdaExpression

)

xanatos
  • 109,618
  • 12
  • 197
  • 280