我正在尝试创建一个简单的报告工具,用户可以从一组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并完成了它,但如果我做了愚蠢的事情并为每种可能的类型创建一个类(现在我输入它out,听起来真的很糟糕,我知道),我可以使用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:我如何获得非泛型类型的泛型集合?
答案 0 :(得分:2)
好吧,我从建议的解决方案中汲取灵感,并实施了如下变化。我明白不想过多使用仿制药,但它仍然让我烦恼。毕竟,我想要几种类型的列(或值)。它是什么样的泛型。另外,我想提供一种内置机制来提供字段的格式。
我离开了IReport和IColumn接口非常简单,但我没有在IColumn接口中引用IValue接口。相反,我使用一个抽象类Value,在其中我为格式化和数据检索定义了一些基本框架(以字符串格式)。
在实际的IntValue / DoubleValue和Value基类之间,我添加了一个通用的Value类,它实现了通用的IValue接口,除了提供Data字段之外什么都不做,所以我不必在IntValue中这样做/ DoubleValue类,并实现AsFormattedString方法,该方法使用我在Value baseclass构造函数中创建的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 Worrier向上方暗示的访客模式,并测试整个设置。如果我做出愚蠢或糟糕的设计选择,请告诉我!我可能需要左右改变它为整个列提供一种格式,而不必为每个值提供它,但我认为基础框架就在那里。
答案 1 :(得分:1)
我认为对这些类型使用泛型会让你疯狂。我没有花太多时间来评估你使用仿制药的确切错误。 。 。因为我根本不相信你需要仿制药。
报告只需要一个列列表,它不关心列的类型
interface IReport
{
IEnumerable<IColumn> Columns {get;}
}
该列只需要一个值列表,实际上,一旦值可以自己关注,它就不关心值的类型。
interface IColumn
{
IEnumerable IValue Values {get;}
}
值只需要能够呈现自己(可能只是一个字符串,或者可能是&#34;在给定的矩形中绘制&#34;它的自我等)
interface IValue
{
string AsString();
}
您可以为不同类型(IntValue
,DoubleValue
等)设置类型化的值,并且一旦实现了IValue
界面,您就会笑。
这有意义吗?