2

I have class X that implements an interface IX. I also have a repository class dedicated for X, which uses lambda expresions as parameters:

public interface IX
{
}

public class X : IX
{
    ....
}

public class XRepository : IRepository<X>
{
    public IEnumerable<X> Filter(Func<X, bool> filterFunc)
    {
        ...
    }
}

I need to make the repository class work with the interface IX, therefore I add IRepository<IX> to the interfaces being implemented:

public class XRepository : IRepository<X>, IRepository<IX>
{
    public IEnumerable<X> Filter(Func<X, bool> filterFunc)
    {
        ...
    }
    public IEnumerable<IX> Filter(Func<IX, bool> filterFunc)
    {
        // I need to call the same filter method as above, but 
        // in order to do so I must convert the Func<IX, bool> to Func<X, bool>.
    }
}

I must convert the Func<IX, bool> to Func<X, bool>, but since the code is written in C# 3.0 using .NET 3.5, I cannot benefit from Type covariance, which was introduced in 4.0.

A simple solution could be to use Func<X, bool> newFunc = x => filterFunc(x);, where filterFunc is of type Func<IX, bool>. This would compile and one might expect it to run fine, but I assume it will not. The problem is that I am using 3rd party framework for the filter implementation, namely FluentNhibernate. I know it uses expression trees to strip the passed into the lambda expression member access condition (like x => x.Name == "John") in order to build native SQL query (like WHERE Name = 'John'). The above solution would produce a Func<X, bool> that is not such expression and I fear it will fail to translate. So I need to create the same lambda expression but with the compatible type. Knowing that X implements IX, it is obvious that any code inside a Func<IX, bool> will work for objects of type X. It is not obvious for me, however, how can I perform this conversion.

I assume this can be done using expression trees. I also fear my performance will suffer greatly. Even if I decide to have another solution to my scenario, I will still appreciate the suggested way to translate one lambda into a similar another.


Edit:

To clarify more about the issue I am experiencing, I wrote the following test, simulating the real-life scenarion I am facing:

    Func<IX, bool> filter = y => y.Name == "John";
    Func<X, bool> compatibleFilter = y => filter(y);


    ...
    // Inside the Filter(Func<X, bool> filter method)
    using(var session = nhibernateSessionFactory.OpenSession())
    {

        IEnumerable<X> xx = session.Query<X>().Where(z => compatibleFilter(z)).ToList();
    }

so, at the ToList() method I receive the following exception

Unable to cast object of type 'NHibernate.Hql.Ast.HqlParameter' to type 'NHibernate.Hql.Ast.HqlBooleanExpression'.

This confirms my assumption that Flunet NHiberante cannot correctly handle the compatibleFilter argument.

So what I want is a way to convert the Func to Func or as suggested by John Skeet, an Expression<Func<IX, bool>> to an Expression<Func<X, bool>> which have the same body (y => y.Name = "John").


Edit 2:

Finally I made it happen! The correct way is not to use Func<X, bool>, but Expression<Func<X, bool>>.

 Expression<Func<IX, bool>> filter = y => y.Name == "John Skeet";
 Expression<Func<X, bool>> compatibleFilter = Expression.Lambda<Func<X, bool>>(
    filter.Body, 
    filter.Parameters);

This produces the correct SQL query.IX, bool

Ivaylo Slavov
  • 8,839
  • 12
  • 65
  • 108

3 Answers3

3

A simple solution could be to use Func<X, bool> newFunc = x => filterFunc(x); where filterFunc is of type Func<IX, bool>. This would compile and one might expect it to run fine, but I assume it will not.

Why assume, when you can test? It should work absolutely fine. After all, you're passing an argument of type X for a parameter of type IX, which causes no type safety concerns.

You'll then need to convert from IEnumerable<X> to IEnumerable<IX>, which can be done with Cast for instance:

public IEnumerable<IX> Filter(Func<IX, bool> filterFunc)
{
    Func<X, bool> newFilter = x => filterFunc(x);
    return Filter(newFilter).Cast<IX>();
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • I think you got me wrong on this, but I know that the snppet `Func newFunc = x => filterFunc(x);` will work perfectly. I fear FluentNhibernate might fail to parse it when building the native SQL query. – Ivaylo Slavov Mar 11 '12 at 20:29
  • @IvayloSlavov: If your method signature is truly in terms of `Func` rather than `Expression>` then NHibernate can't understand your code anyway. A delegate is just a delegate; it doesn't support introspection. Is it *actually* using expression trees instead? That would be a slightly different matter, with a different solution. – Jon Skeet Mar 11 '12 at 20:31
  • Do you suggest to switch from `Func` to `Expression>`? – Ivaylo Slavov Mar 11 '12 at 20:34
  • 1
    @IvayloSlavov: I suggest you work out what you should do for `X` entirely separately from the covariance part. Do you have *anything* working at the moment? Have you validated the SQL being generated? I suspect if it's *really* using `Func` at the moment, you may be pulling down the whole of the database table for each query! – Jon Skeet Mar 11 '12 at 20:37
  • I edited my post with the result I achieved and details of the code that is called. – Ivaylo Slavov Mar 11 '12 at 21:13
  • @IvayloSlavov: You seem to have missed my point, that if you start off with a `Func` that can't be translated anyway. Why do you still do that in your updated sample? (`Func filter = y => y.Name == "John";`) Try that with `Func filter = x => x.Name == "John";` and no intermediate step, and you'll still get a failure. – Jon Skeet Mar 11 '12 at 21:16
  • In response to your previous comment, I have everything working in the database and configuration. Currently I got over the issue, it appears that when I had `z = compatibleFunc(z)`, I was adding another 'layer' of nesting. But when I directly used the `compatibleFunc` Fluent NHibernate managed to handle the expression and to produce the valud result. – Ivaylo Slavov Mar 11 '12 at 21:30
  • @IvayloSlavov: Using `Func`? I would check your generated SQL very carefully. It'll get the right result, but *not* in the way that you want. – Jon Skeet Mar 11 '12 at 21:31
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/8762/discussion-between-ivaylo-slavov-and-jon-skeet) – Ivaylo Slavov Mar 11 '12 at 21:37
  • @IvayloSlavov: Sorry, chat really isn't good for the way I work. But check your SQL. (How much do you understand about the difference between `Func` and `Expression>` btw?) – Jon Skeet Mar 11 '12 at 21:38
  • OK, no chat. I do know that the Expression is a more-detailed representation of the lambda, by means of providing metadata for its structure. I assume (do not know for sure because I did not have opportunity to play in depth with it), that the expression can be used to understand what the code of the delegate is in descriptive fashion, in my case, that it contains equality comparison of a field and a constant value. I suppose this is what Fluent uses to build the query. Still I am stuck at this now. – Ivaylo Slavov Mar 11 '12 at 21:50
  • @IvayloSlavov: Yes. Without that, NHibernate *can't* work out what to put in the query. Which is why I'm concerned that your currently supposedly-working code using `Func` is probably *not* performing the query you think it is... and it's important to get that right before worrying about covariance. – Jon Skeet Mar 11 '12 at 21:52
  • Thanks. I got it working by using expressions, see my last update. – Ivaylo Slavov Mar 11 '12 at 22:00
  • @IvayloSlavov: Good. Turns out it was a good job that the covariance question came up so you could find out the bigger problem :) – Jon Skeet Mar 11 '12 at 22:05
  • yes, indeed a working code is a bigger issue sometimes than the one with errors in it. Thanks again for helping me figure out the problems I had. – Ivaylo Slavov Mar 11 '12 at 22:07
0

As i understand it correctly, covariance is a language feature. So it does not depend directly on .net 4.

Brian Mains
  • 50,520
  • 35
  • 148
  • 257
mo.
  • 3,474
  • 1
  • 23
  • 20
  • 4
    It's a language feature, a framework feature (the delegates/interfaces involved have to be *declared* to be covariant/contravariant) and a CLR feature (which has been in since .NET 2.0). – Jon Skeet Mar 11 '12 at 20:26
0

why do you use your concrete type isnt IX enough:

public class IXRepository : IRepository<IX>
{
    public IEnumerable<X> Filter(Func<IX, bool> filterFunc)
    {
        ...
    }
}
mo.
  • 3,474
  • 1
  • 23
  • 20