【问题标题】:Using contravariance with Func<T, object> selector property对 Func<T, object> 选择器属性使用逆变
【发布时间】:2013-10-02 16:40:28
【问题描述】:

我正在设计一个通用列定义类,它将作为实体属性的选择器,所有这些都是为了更容易管理 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 in 参数通用,out 将始终是一个对象。

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

目前我能看到的唯一解决方案是使用 Func Selector 属性创建非泛型 DisplayColumn 类,并在每个分配中将参数转换为具体类型,这显然不是一个很好的解决方案。另一种选择是继承基非泛型 DisplayColumn 类并将泛型选择器放在继承的泛型类中,但是在呈现数据时使用此表达式将需要在继承的泛型类中调用泛型方法,这对于性能和代码质量标准来说确实是不可接受的。

【问题讨论】:

    标签: c# generics covariance contravariance


    【解决方案1】:

    如果您也将 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 }
                    }
                }
            };
    

    } }

    【讨论】:

    • 我稍微更新了这个问题,因为 testSingleColumn 变量和 columnsSets 集合都应该能够处理 DisplayColumn 的任何泛型类型,或者在最坏的情况下,通用基类和泛型 ColumnSet 不允许这样做.抱歉,如果原始问题不够具体。
    【解决方案2】:

    经过彻底调查,我发现解决方案需要混合协方差和逆变,目前不支持。最接近的解决方案(可编译)实际上不允许轻松访问 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,这是一个一次性操作,使用选择器时转换开销最小:

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

    因此,我将把这个问题归功于自己,但如果有人可以使用泛型和变体提出更好的解决方案,请随时发布它,因为我很乐意更改解决方案。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-12-27
      • 1970-01-01
      • 1970-01-01
      • 2017-03-05
      • 2015-11-23
      • 1970-01-01
      • 1970-01-01
      • 2017-03-05
      相关资源
      最近更新 更多