After thorough investigation I found out the solution would require mixing covariance and contravariance which is not supported currently. The closest solution (which compiles) actually does not allow easy access to IDisplayColumn.Selector as T argument in IColumnSet.Columns will be visible as object not IDisplayColumn so it's not an option:
public interface IDisplayColumn<in T>
{
string Title { get; set; }
int Order { get; set; }
Func<T, object> Selector { get; }
}
public class DisplayColumn<T> : IDisplayColumn<T>
{
public string Title { get; set; }
public int Order { get; set; }
public Func<T, object> Selector { get; set; }
}
public interface IColumnSet<out T>
{
Type TypeHandled { get; }
IEnumerable<T> Columns { get; }
}
public class ColumnSet<T> : IColumnSet<IDisplayColumn<T>>
{
public Type TypeHandled
{
get
{
return typeof(T);
}
}
public IEnumerable<IDisplayColumn<T>> Columns { get; set; }
}
I ended up translating Func<,> using expressions when creating which is a one-time operation with minimal overhead of casting when using selector:
public interface IDisplayColumn
{
string Title { get; set; }
bool Visible { get; set; }
int Order { get; set; }
Func<object, object> Value { get; }
T GetValue<T>(object source);
}
public class DisplayColumn<T>: IDisplayColumn
{
public string Title { get; set; }
public bool Visible { get; set; }
public int Order { get; set; }
public Func<object, object> Value { get; set; }
public override string ToString()
{
return Title;
}
public TValue GetValue<TValue>(object source)
{
return (TValue)Convert.ChangeType(Value(source), typeof(TValue));
}
public Func<T, object> Selector
{
set
{
Value = value.ConvertObject<T>();
}
}
}
public interface IColumnSet
{
Type TypeHandled { get; }
IEnumerable<IDisplayColumn> Columns { get; }
}
public class ColumnSet<T>: IColumnSet
{
public Type TypeHandled
{
get
{
return typeof(T);
}
}
public IEnumerable<IDisplayColumn> Columns { get; set; }
}
public static Func<object, object> ConvertObject<T>(this Func<T, object> func)
{
Contract.Requires(func != null);
var param = Expression.Parameter(typeof(object));
var convertedParam = new Expression[] { Expression.Convert(param, typeof(T)) };
Expression call;
call = Expression.Convert(
func.Target == null
? Expression.Call(func.Method, convertedParam)
: Expression.Call(Expression.Constant(func.Target), func.Method, convertedParam)
, typeof(object));
var delegateType = typeof(Func<,>).MakeGenericType(typeof(object), typeof(object));
return (Func<object, object>)Expression.Lambda(delegateType, call, param).Compile();
}
And the example of usage:
private class TestObject1
{
public int Id { get; set; }
public string Name { get; set; }
}
IDisplayColumn objectColumn = new DisplayColumn<TestObject1> { Title = "Column 1", Selector = (x) => x.Name };
var columnSets = new List<IColumnSet>
{
new ColumnSet<TestObject1>
{
Columns = new List<IDisplayColumn>
{
new DisplayColumn<TestObject1> { Title = "Column 1", Order = 3, Selector = x => x.Id },
new DisplayColumn<TestObject1> { Title = "Column 2", Order = 2, Selector = x => x.Name },
new DisplayColumn<TestObject1> { Title = "Column 3", Order = 1, Selector = x => x.Id.ToString(CultureInfo.InvariantCulture) + x.Name.ValueOrEmpty() },
}
}
};
So I will give myself the credit for this problem but if somebody can suggest a nicer solution using generics and variance, please feel free to post it as I will be happy to change the solution.