半临时报告的通用接口

Tom*_*ijk 5 c# generics wcf

我正在尝试创建一个简单的报告工具,用户可以从一组KPI,图表,聚合函数和其他参数中进行选择,单击一个按钮,然后调用wcf服务,然后返回一个自定义模型数据.然后可以将其显示在MVC/WPF应用程序中(可以是两者).

由于用户可能来自多个国家/地区,因此我希望使用数据注释以适合当前用户习惯的语言和数字格式的方式绘制所有数字和标题.

加载数据和所有东西工作得很好,没有任何问题.此外,我使用数据注释,因此所有语言/文化特定的设置都得到了照顾.当我尝试将所有数据放入我想要显示给用户的模型时,问题就开始了.

我要做的是有一个Report类,它包含一组列.每列可以是int/double/...值的列表.现在,因为我正在处理WCF并且上面的解释暗示(据我所知)泛型的使用,我假设我可以使用[KnownType]或[ServiceKnownType]进行类/ wcf操作,而实际使用的是基类型或接口作为返回值.从来没有真正试过这个,但我发现了一些对我来说似乎很合理的好解释,所以我想我不会对这部分有任何重大问题(至少我希望不会).

现在,我的界面就是这样(简化为专注于我遇到的实际问题):

public interface IReport<T> where T: IConvertible { ICollection<IColumn<T>> Columns { get; set; } }
public interface IColumn<T> where T: IConvertible { ICollection<IValue<T>> Values { get; set; } }
public interface IValue<T> where T: IConvertible { T Value { get; set; } }
Run Code Online (Sandbox Code Playgroud)

由于每列中的值可能是int/double/...,我假设我必须只为该值设置一个实际的类(我不认为我可以在集合类型上使用数据注释属性),因此:

public class IntValue: IValue<int>
{
    [DisplayFormat(DataFormatString = "{0:#,##0;-#,##0;'---'}", ApplyFormatInEditMode = true)]
    public int Value { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

当然,这看起来很奇怪,因为你可以让它成为一个泛型类,它实现了IValue并完成了它,但如果我做了愚蠢的事情并为每个可能的类型创建一个类(现在我输入它,那个听起来真的很糟糕,我知道),我可以使用DisplayFormat属性而不必担心它会呈现给用户的方式,它总是合适的.

现在,对于实现IColumn和IReport的类,这很简单:

public class Report<T>: IReport<T> where T: IConvertible 
{
    public ICollection<IColumn<T>> Columns { get; set; }
    public Report() { Columns=new List<IColumn<T>>(); }
}

public class Column<T>: IColumn<T> where T: IConvertible 
{
    public ICollection<IValue<T>> Values { get; set; }
    public Column() { Values = new List<IValue<T>>(); }
}
Run Code Online (Sandbox Code Playgroud)

从接口和类列表中,您将立即看到这使得无法生成某些列具有其他类型的报告.所以不可能创建一个报告,其中一些列是int,一些是double,...因为IReport中的泛型约束使您指定一个类型,所以对于所有列,您都会遇到这种情况,因为它会向下传播到值每一栏......这正是我想要的.

我觉得我没有到达任何地方,可能会遗漏一些非常简单的东西,所以在正确的方向上轻推将会受到赞赏.

TL; DR:如何获得非泛型类型的泛型集合?

Tom*_*ijk 2

好吧,我从建议的解决方案中获得了灵感,并实现了如下的变体。我知道不想过多使用泛型,但这仍然让我很恼火。毕竟,我想要多种类型的列(或值)。这就是泛型的用途。另外,我想提供一种内置机制来提供字段的格式。

我让 IReport 和 IColumn 接口非常简单,但我没有在 IColumn 接口中引用 IValue 接口。相反,我使用一个抽象类 Value,在其中定义一些用于格式化和数据检索的基本框架(即字符串格式)。

在实际的 IntValue/DoubleValue 和 Value 基类之间,我添加了一个通用 Value 类,该类实现了通用 IValue 接口,该接口除了提供 Data 字段之外什么也不做,因此我不必在 IntValue/DoubleValue 类中执行此操作,并且实现 AsFormattedString 方法,该方法使用普通的 ToString 方法,并使用我在 Value 基类构造函数中创建的 Formatter。

该格式化程序的实际实现在 IntValue/DoubleValue 类中提供,并且可以使用我已经硬编码的标准格式或类用户提供的自定义格式。

public interface IReport { ICollection<IColumn> Columns { get; set; } }
public interface IColumn { ICollection<Value> Values { get; set; } }

public interface IValue<T> where T: IConvertible { T Data { get; set; } }

public abstract class Value
{
    #region Formatting

    protected IFormatProvider Formatter { get; set; }
    protected abstract IFormatProvider GetFormatter();
    protected abstract string AsFormattedString();
    public override string ToString() { return AsFormattedString(); }

    #endregion

    public Value() { Formatter = GetFormatter(); }
}

public abstract class Value<T>: Value, IValue<T> where T: IConvertible
{
    #region IValue members

    public T Data { get; set; }

    #endregion

    #region Formatting

    protected override string AsFormattedString() { return Data.ToString(Formatter); }

    #endregion
}

public class IntValue: Value<int>
{
    public IntValue() { }
    public IntValue(string formatstring, int data) { Formatter = new IntFormatter(formatstring); Data = data; }

    #region Formatting

    protected override IFormatProvider GetFormatter() { return new IntFormatter(); }

    internal class IntFormatter: CustomFormatter
    {
        public IntFormatter() : this("{0:#,##0;-#,##0;'---'}") { }
        public IntFormatter(string formatstring) : base(formatstring) { }
    }

    #endregion
}

public class DoubleValue: Value<double>
{
    public DoubleValue() { }
    public DoubleValue(string formatstring, double data) { Formatter = new DoubleFormatter(formatstring); Data = data; }

    #region Formatting

    protected override IFormatProvider GetFormatter() { return new DoubleFormatter(); }

    internal class DoubleFormatter: CustomFormatter
    {
        public DoubleFormatter() : this("{0:0.#0;-0.#0;'---'}") { }
        public DoubleFormatter(string formatstring) : base(formatstring) { }
    }

    #endregion
}

public class ReportView: IReport
{
    public ICollection<IColumn> Columns { get; set; }
    public ReportView() { Columns = new List<IColumn>(); }
}

public class ReportColumn: IColumn
{
    public ICollection<Value> Values { get; set; }
    public ReportColumn() { Values = new List<Value>(); }
}
Run Code Online (Sandbox Code Playgroud)

它的用法如下:

    // Creating a report
    IReport report = new ReportView();

    // Adding columns
    IColumn mycolumn = new ReportColumn();
    mycolumn.Values.Add(new IntValue() { Data = 1 });
    mycolumn.Values.Add(new DoubleValue() { Data = 2.7 });
    mycolumn.Values.Add(new IntValue("{0:#,##0;-#,##0;'---'}", 15));
    mycolumn.Values.Add(new DoubleValue("{0:0.#0;-0.#0;'---'}", 2.9));
    report.Columns.Add(mycolumn);

    // Looping through each column, and get each value in the formatted form
    foreach(var column in report.Columns)
    {
        foreach(var value in column.Values) { value.ToString(); }
    }
Run Code Online (Sandbox Code Playgroud)

如果有什么需要添加/更正的内容,我很高兴听到。我将检查 Binary Worrier 上面暗示的访客模式,并测试整个设置。如果我做出了愚蠢或糟糕的设计选择,请告诉我!我可能需要左右稍微改变一下,以便为整个列提供一种单一格式,而不必为每个值提供它,但我认为基本框架就在那里。