6

Just a quick and short one, this time. Func<T,TResult> is contravariant (EDIT : The Type Parameter T is). Now, I don't work with Func<T,TResult>, but rather with Expression<Func<T,TResult>>, and seem to have reached a dead end. UPDATE - FULL CODE SAMPLE :

public interface IColoredObject
{
    string Color { get; }
}

public class Item : IColoredObject
{
    public string Color { get; set; }

    public double Price { get; set; }
}

public partial class MainWindow : Window
{
    private IList<Item> _items;

    public IList<Item> Items
    {
        get
        {
            if (_items == null)
            {
                _items = new List<Item>();
                _items.Add(new Item() { Color = "black" });
                _items.Add(new Item() { Color = "blue" });
                _items.Add(new Item() { Color = "red" });
            }
            return _items;
        }
    }

    public MainWindow()
    {
        InitializeComponent();
        Expression<Func<IColoredObject, bool>> filter = x => x.Color == "black";
        Item i = Get(filter);
    }

    public Item Get(Expression<Func<Item, bool>> filter)
    {
        return Items.AsQueryable().Where(filter).FirstOrDefault();
    }
}

The call is made using an Expression<Func<IColoredObject, bool>> as argument and should, if I haven't misunderstood contravariance, work, because IColoredObject is less derived that Item.

What I get is a conversion Exception saying something like

cannot convert

System.Linq.Expressions.Expression`1[System.Func`2[MyNs.IColoredObject,System.Boolean]]

To

System.Linq.Expressions.Expression`1[System.Func`2[MyNs.Item,System.Boolean]]

Is there any way of fixing this and getting it to work?

EDIT:

Since there's some inaccuracy in what I've said, here's more background. Code sample updated. Furthermore, I checked what MSDN said about Func<T, TRes>:

public Item GetFunc(Func<Item, bool> filter)
{
    return Items.AsQueryable().Where(filter).FirstOrDefault();
}

As indicated by MS, this can be used with a contravariant Type param, as listed below:

 Func<IColoredObject, bool> filterFunc = x => x.Color == "black";
 GetFunc(filterFunc);

Which again makes me wonder why this works for Func<T, TRes> but not for Expression<Func<T, TRes>>...

FINALLY...

The checked answer was selected because it is what I eventually did. As I said somewhere in the comments below, the Get-Method utilizes NHibernate to fetch data. But obviously NHibernate has a feature of accepting queries over an interface and auto-selecting the types that implement the interface. This does not solve the issue itself, but as you can read below, there is not real solution, since what encountered here was expected behaviour.

Sebastian Edelmeier
  • 4,095
  • 3
  • 39
  • 60

4 Answers4

4

Expression<TDelegate> is a class so it can't have variance for generic parameters. There is also a big distinction between Delegate and Expression<Delegate>. While you can pass Item object into Func<IColoredObject, bool> and thus can convert Func<IColoredObject, bool> into Func<Item, bool>, Expression<Func<Item, bool>> is just like the code of the method taking Item and returning bool. You can analyze and change this code adding Item specific methods and properties, obviously this would not be possible for code working with IColoredObject.

It might be possible to change all entries of IColoredObject parameter in the Expression<Func<IColoredObject, bool>> into Item object using ExpressionVisitor. Below is a visitor which performs such conversion in simple cases (i.e. no explicit interface implementations). Probably, there is much more simple solution to your problem, but it's hard to find it without knowing further details.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

interface IGizmo
{
    bool Frobnicate();
}

class Gizmo : IGizmo
{
    public bool Frobnicate()
    {
        Console.WriteLine("Gizmo was frobnicated!");

        return true;
    }
}

public sealed class DelegateConversionVisitor : ExpressionVisitor
{
    IDictionary<ParameterExpression, ParameterExpression> parametersMap;

    public static Expression<Func<T2, TResult>> Convert<T1, T2, TResult>(Expression<Func<T1, TResult>> expr)
    {
        var parametersMap = expr.Parameters
            .Where(pe => pe.Type == typeof(T1))
            .ToDictionary(pe => pe, pe => Expression.Parameter(typeof(T2)));

        var visitor = new DelegateConversionVisitor(parametersMap);
        var newBody = visitor.Visit(expr.Body);

        var parameters = expr.Parameters.Select(visitor.MapParameter);

        return Expression.Lambda<Func<T2, TResult>>(newBody, parameters);
    }

    public DelegateConversionVisitor(IDictionary<ParameterExpression, ParameterExpression> parametersMap)
    {
        this.parametersMap = parametersMap;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        return base.VisitParameter(this.MapParameter(node));
    }

    private ParameterExpression MapParameter(ParameterExpression source)
    {
        var target = source;
        this.parametersMap.TryGetValue(source, out target);

        return target;
    }
}

class Program
{
    static void Main()
    {
        Expression<Func<IGizmo, bool>> expr = g => g.Frobnicate();

        var e2 = DelegateConversionVisitor.Convert<IGizmo, Gizmo, bool>(expr);

        var gizmo = new Gizmo();
        e2.Compile()(gizmo);
    }
}

Konstantin Oznobihin
  • 5,234
  • 24
  • 31
2

This line:

public Item Get(Expression<Func<Item, bool>> filter) { /* ... */  }

shoud be:

public Item Get(Expression<Func<IColoredObject, bool>> filter) { /* ... */  }

You'll have to work with the interface in this case if you want to call the Get method passing Expression<Func<IColoredObject, bool>>.

Leniel Maccaferri
  • 100,159
  • 46
  • 371
  • 480
  • This was my initial train of thought, but he might be filtering by methods or properties on object type `Item`. – Richard Jul 12 '12 at 13:24
  • Actually, this is impossible in my scenario. The Method passes the `Expression>` to NHibernate, which requires `Item` to be a mapped class. – Sebastian Edelmeier Jul 12 '12 at 13:40
  • So, you'll have to pass `Expression>` instead of `Expression>`. – Leniel Maccaferri Jul 12 '12 at 13:43
  • That resets me to the point where I was before I asked the question...This is impossible. The method is defined in my data access layer and gets called from the business logic with the interface-laden expression it gets from the UI...So, unless I don't find a way of converting `Expression>` to `Expression>`, that won't be available... – Sebastian Edelmeier Jul 12 '12 at 13:56
2

C# only has covariance and contravariance for interface and delegate types. For example this will work:

IEnumerable<Func<IColoredObject, bool>> ie1 = XXX;
IEnumerable<Func<Item, bool>> ie2 = ie1;                // works!

Note in the above example that the assignment ie2 = ie1 goes well even if the types are not the same. That's because Func<T, TResult> is contravariant ("in") in its first type parameter T, and IEnumerable<T> is covariant ("out") in its T. Here, Func<,> is a delegate and IEnumerable<> is an interface.

But Expression<TDelegate> is a class. C# does not support covariance/contravariance for class types.

Therefore Expression<Something> will never convert to Expression<SomethingAlmostTheSame>.

Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
1

I always get confused when trying to work out covariance and contravariance. But your example seems clear to me. Your method Get(...) requires an expression with type Item. Since Item is derived of IColoredObject, you cannot use an expression with type IColoredObject, only with type Item, or types derived of Item. IColoredObject is 'more abstract', and therefore anything expecting a type Item (or a derivative of it) cannot work with it.

Ask yourself: assume for a moment that Item has defined a property X that IColoredObject doesn't have, then how is the expression with type IColoredObject going to define property X?

Maarten
  • 22,527
  • 3
  • 47
  • 68
  • I was just confused because MSDN said it was contravariant on `T`, but to my understanding what you say seems plausible : Anything that waddles and quacks is a duck. Now I have a method that deals with Mandarin Ducks and may check specific Mandarin Duck traits instead of those of a generic duck...As you say, variance gets me confused, too. – Sebastian Edelmeier Jul 12 '12 at 13:38
  • Just so you know: Works with `Func`, but not with `Expression>` – Sebastian Edelmeier Jul 12 '12 at 14:00