使用与Func <t,object =“”>选择器属性</t,>的逆转

时间:2013-09-30 09:06:14

标签: c# generics covariance contravariance

我正在设计一个通用列定义类,它将充当实体属性的选择器,所有这些都可以更轻松地管理LOB应用程序中不同方面的网格表示。

不幸的是,我碰到了一个试图在一个类中使用泛型参数的墙,它将包含在一个集合中。下面的SettingsContext类的示例实现解释了发生的事情:

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 class ColumnSet
{
    public Type TypeHandled { get; set; }
    public IEnumerable<IDisplayColumn<object>> Columns { get; set; }
}

public static class ColumnSetTest
{
    static ColumnSetTest()
    {
        // Cannot implicitly convert type 'DisplayColumn<System.Configuration.SettingsContext>' to 'IDisplayColumn<object>'.
        // An explicit conversion exists (are you missing a cast?)
        IDisplayColumn<object> testSingleColumn = new DisplayColumn<SettingsContext> {Title = "Test", Selector = x => x.Values };
        // another test with other type used as a source which should be assignable to DisplayColumn<object>
        testSingleColumn = new DisplayColumn<SettingsProvider> { Title="Another test", Selector = x => x.ApplicationName };

        // Cannot implicitly convert type 'System.Collections.Generic.List<IDisplayColumn<System.Configuration.SettingsContext>>'
        // to 'System.Collections.Generic.IEnumerable<IDisplayColumn<object>>'.
        // An explicit conversion exists (are you missing a cast?)
        var columnSets = new List<ColumnSet>
        {
            new ColumnSet
            {
                TypeHandled = typeof(SettingsContext),
                Columns = new List<IDisplayColumn<SettingsContext /* or object */>>
                {
                    new DisplayColumn<SettingsContext> {Title = "Column 1", Order = 1, Selector = x => x.IsReadOnly },
                    new DisplayColumn<SettingsContext> {Title = "Column 2", Order = 2, Selector = x => x.IsSynchronized },
                    new DisplayColumn<SettingsContext> {Title = "Column 3", Order = 3, Selector = x => x.Keys }
                }
            }
        };
    }
}

我如何理解协方差和逆变的目的这是真正期望的 - out参数应该用于IDisplayColumn testSingleColumn = new DisplayColumn赋值但我需要在参数泛型中使用Func,out将始终是一个对象。

如何实现这样的场景,是否需要实现自定义Func,或者dotnet已经有适合此类目的的类型?

目前我能看到的唯一解决方案是使用Func&lt;创建非泛型DisplayColumn类。对象,对象&gt;选择器属性和铸造参数到每个赋值中的具体类型,这显然不是一个很好的解决方案。另一种选择是继承基本非泛型DisplayColumn类并将通用选择器放在继承的泛型类中,但在呈现数据时使用此表达式需要在继承的泛型类中调用泛型方法,这在性能和代码质量标准中是不可接受的。

2 个答案:

答案 0 :(得分:0)

如果您使ColumnSet也是通用的,那么您可以指定用于它返回的可枚举列的类型。下面的代码将编译,我认为实现你的目标。

    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 class ColumnSet<T>
{
    public Type TypeHandled { get; set; }
    public IEnumerable<IDisplayColumn<T>> Columns { get; set; }
}

public static class ColumnSetTest
{
    static ColumnSetTest()
    {
      IDisplayColumn<SettingsContext> testSingleColumn = new DisplayColumn<SettingsContext> { Title = "Test", Selector = x => x.Values };


        var columnSets = new List<ColumnSet<SettingsContext>>
        {
            new ColumnSet<SettingsContext>
            {
                TypeHandled = typeof(SettingsContext),
                Columns = new List<IDisplayColumn<SettingsContext>>
                {
                    new DisplayColumn<SettingsContext> {Title = "Column 1", Order = 1, Selector = x => x.IsReadOnly },
                    new DisplayColumn<SettingsContext> {Title = "Column 2", Order = 2, Selector = x => x.IsSynchronized },
                    new DisplayColumn<SettingsContext> {Title = "Column 3", Order = 3, Selector = x => x.Keys }
                }
            }
        };

} }

答案 1 :(得分:0)

经过彻底调查后,我发现解决方案需要混合协方差和当前不支持的逆变。最接近的解决方案(编译)实际上不允许轻松访问IDisplayColumn.Selector,因为IColumnSet.Columns中的T参数将作为对象而不是IDisplayColumn可见,因此它不是一个选项:

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; }
}

我最终翻译了Func&lt;,&gt;在创建时使用表达式,这是一次性操作,使用选择器时投射开销最小:

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();
}

使用示例:

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() },
        }
    }
};

所以我会对这个问题给予自己的信任,但如果有人可以使用泛型和差异建议更好的解决方案,请随时发布,因为我很乐意改变解决方案。