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 inパラメーターをジェネリックにする必要があり、outは常にオブジェクトになります。

このようなシナリオを実装するには、カスタム Func を実装する必要がありますか、それとも dotnet がそのような目的に適した型を既に持っているのでしょうか?

現在、私が見ることができる唯一の解決策は、 Func< object, object > Selector プロパティを使用して非ジェネリック DisplayColumn クラスを作成し、各割り当てで引数を具体的な型にキャストすることですが、これは明らかに優れた解決策ではありません。もう 1 つのオプションは、ベースの非ジェネリック 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

徹底的な調査の結果、ソリューションには、現在サポートされていない共分散と反分散を混在させる必要があることがわかりました。IColumnSet.Columns の T 引数は IDisplayColumn ではなくオブジェクトとして表示されるため、実際には最も近いソリューション (コンパイル) では IDisplayColumn.Selector に簡単にアクセスできません。

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<,> を作成するときに式を使用して変換することになりました。これは、セレクターを使用する場合のキャストのオーバーヘッドを最小限に抑えた1回限りの操作です。

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 に答える