【问题标题】:Generic interfaces for semi-ad hoc report半即席报告的通用接口
【发布时间】:2016-01-04 14:20:36
【问题描述】:

我正在尝试创建一个简单的报告工具,用户可以在其中从一组 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; } }

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

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

当然,这看起来很奇怪,因为您可以将其设为实现 IValue 的通用类 Value 并完成它,但如果我做傻事并为每种可能的类型创建一个类(现在我键入它出来,这听起来很糟糕,我知道),我可以使用 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>>(); }
}

从接口和类列表中,您会立即看到,这使得在某些列具有其他类型的情况下无法生成报告。因此,不可能创建一个报告,其中一些列是 int,有些是 double,... 由于 IReport 中的通用约束让您指定一个类型,因此您对所有列都坚持这一点,因为它向下传播到每一列……这正是我真正想要的。

我觉得我无处可去,并且可能错过了一些非常简单的东西,因此我们将不胜感激。

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

【问题讨论】:

  • 您不能将您的报告创建为new IReport&lt;IConvertible&gt;吗?
  • 据我现在所见,这不允许我添加一个 int 或 double 列,因为它希望它们是 IConvertible,而不是任何 IConvertible 实现类。

标签: c# wcf generics


【解决方案1】:

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

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

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

该格式化程序的实际实现在 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>(); }
}

它是这样使用的:

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

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

【讨论】:

    【解决方案2】:

    我认为对类型使用泛型会让你发疯。我没有花太多时间准确评估您使用 generics 有什么问题。 . .因为我认为你根本不需要泛型。

    报告只需要列的列表,不关心列的类型

    interface IReport
    {
       IEnumerable<IColumn> Columns {get;}
    }
    

    该列只需要一个值列表,实际上,一旦值可以照顾自己,它并不关心值的类型。

    interface IColumn
    {
       IEnumerable IValue Values {get;}
    }
    

    该值只需要能够呈现其自身(可能只是作为字符串,或者可能在给定的矩形中“绘制”其自身等)

    interface IValue
    {
       string AsString();
    }
    

    您可以为不同类型(IntValueDoubleValue 等)提供类型化的 Value 实现,一旦它们实现了 IValue 接口,您就笑了。

    这有意义吗?

    【讨论】:

    • 确实如此,确实如此,谢谢。我会尽快尝试的。我假设数据注释部分只是进入 IValue 的实际实现。但是,如果我确实想强制类的用户实际提供值类型怎么办?现在它只是一组字符串,它确实有效。但是如果我想开始在计算中使用这些数据,...?
    • 接下来我会查看Visitor pattern 以对数据实现不同的聚合和计算。一旦这些值实现了一个已知的接口,访问者就可以对接口上的调用结果做自己喜欢的事情。例如您有一个“DoubleSum”访问者,它通过 IValues 并尝试对它们的值求和。如果它不能将任何值强制为 double 它可以抛出,或者将该单元格视为零,或者你喜欢的任何值。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多