3

I'm trying to creating a dynamic base mapping with fluent nhibernate.

What I'm doing is checking in by a BaseMap< T > : ClassMap< T > if for example: (typeof(ICategorizedEntity).IsAssignableFrom(typeof(T)))

If so, I wanna map a property named "Category" which belongs to ICategorizedEntity's interface, but the Map(Func) function only accepts T's properties, so I tried guessing a little with linq and came up with this:

   Expression<Func<ICategorizedEntity, object>> exp = x => x.Category;
   var parameter = Expression.Parameter(typeof (T));
   var lmd = Expression.Lambda<Func<T, object>>(exp, parameter);
   Map(lmd);

Which doesn't work, because deep inside the 'Map' function it checks the following:

   MemberExpression memberExpression = (MemberExpression) null;
   if (expression.NodeType == ExpressionType.Convert)
       memberExpression = ((UnaryExpression) expression).Operand as MemberExpression;
   else if (expression.NodeType == ExpressionType.MemberAccess)
       memberExpression = expression as MemberExpression;
   if (enforceCheck && memberExpression == null)
       throw new ArgumentException("Not a member access", "expression");

And I get the "Not a member access\r\nParameter name: expression".

How can I create and cast a MemberExpression or anything similar which will work?

Adam Tal
  • 5,911
  • 4
  • 29
  • 49

2 Answers2

2

Func<DerivedFromT,object> represents a method that accepts a DerivedFromT parameter. Func<T,object> represents a method that accepts a T parameter. Through delegate variance introduced in C# 4, you can cast Func<T,object> to Func<DerivedFromT,object>, but not the other way round (as you’re requesting).

Think about what this means:

public class Person { }

public class Student : Person { }

public static class School
{
    public static object Greet(Person person)
    {
        return null; 
    }

    public static object Promote(Student student)
    { 
        return null; 
    }
}

In this case, the Greet method matches the delegate Func<Person,object>, whilst the Promote method matches the delegate Func<Student,object>.

Func<Person, object> greetPerson = School.Greet;
Func<Student, object> promoteStudent = School.Promote;

We can cast Greet to Func<Student,object>; if we can greet a Person, then we can also greet a Student (who is guaranteed to be a specialized form of Person).

Func<Student, object> greetStudent = greetPerson;

However, we cannot cast Promote to Func<Person,object>; although we can promote a Student, we cannot promote any Person in general, unless he/she happens to be a Student.

Func<Person, object> promotePerson = promoteStudent;   // Will not compile.

If we know that our Person is a student, we can indicate this by casting it:

Func<Person, object> promotePerson =
    personWhoIsActuallyStudent => promoteStudent((Student)personWhoIsActuallyStudent);
Douglas
  • 53,759
  • 13
  • 140
  • 188
  • Thanks for you nice explanation Douglas, But I think you misunderstood what I tried to achieve.. Good old conversion did the trick: Map(x => ((ICategorizedEntity)x).Category); – Adam Tal Feb 11 '12 at 00:02
  • I admit that I really didn’t understand your question, so my post was based mostly on your title. However, I assumed that the answer would probably lie in my last line of code, which performs the in-lambda cast you required. – Douglas Feb 11 '12 at 10:32
2

Thanks Douglas, you lead me to the right (simple) answer.

I went too far trying to find it..

Good old conversion (inside the lambda expression) did the trick:

   Map(x => ((ICategorizedEntity)x).Category);
Adam Tal
  • 5,911
  • 4
  • 29
  • 49