2

我正在设计一个通用列定义类,它将充当实体属性的选择器,所有这些都是为了更容易管理 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< object, object > Selector 属性创建非泛型 DisplayColumn 类,并将参数转换为每个分配中的具体类型,这显然不是一个很好的解决方案。另一种选择是继承基本的非泛型 DisplayColumn 类并将泛型选择器放在继承的泛型类中,但是在呈现数据时使用此表达式将需要在继承的泛型类中调用泛型方法,这对于性能和代码质量标准来说确实是不可接受的。

4

2 回答 2

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

} }

于 2013-09-30T09:27:20.360 回答
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<,>,这是一个一次性操作,使用选择器时的转换开销最小:

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

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

于 2013-10-02T08:40:28.897 回答