嵌套列表<> 属性 用于摘要 class
Nested List<> property for abstract class
感谢您的精彩回复。然而,在更深入地阅读 HashPsi 最初的评论时,我发现有几个用户都想将这个应用程序与几个不同但非常相似的报告一起使用,例如有更多可用的列,可以是 included/removed 在任何特定于用户的报告。
无需为每种用户类型制定解决方案,每个用户都可以简单地查看他们的数据和最初导入数据的 select 过滤器。
为了在下面的行级别编辑中保持非常简单,我设置了一个非常简单的 id 和 20x 字符串,然后列出预解析的 currency/numbers,可用于 select 下拉数字过滤器,或者值之间的范围。类似的列表用于 sheet 级别的字符串下拉列表。
完成的应用程序将使用一个由 sheet id(或类似)分隔的数据库。
您在这里有什么想法,我是否应该使用更多泛型,例如下面的示例(这些示例非常好,如果有点高级,因为我仍然是一个可怜的菜鸟:))。
public abstract class Entity
{
public virtual int Id { get; set; }//todo - autoincrement
}
class Sheet
{
public List<Row> Rows { get; set; }
public List<ParsedToStringList> ParsedStringLists { get; set; }
}
public class Row : Entity
{
public int RowId { get; set; } //unique key
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
public string String4 { get; set; }
public string String5 { get; set; }
public string String6 { get; set; }
public string String7 { get; set; }
public string String8 { get; set; }
public string String9 { get; set; }
public string String10 { get; set; }
public string String11 { get; set; }
public string String12 { get; set; }
public string String13 { get; set; }
public string String14 { get; set; }
public string String15 { get; set; }
public string String16 { get; set; }
public string String17 { get; set; }
public string String18 { get; set; }
public string String19 { get; set; }
public string String20 { get; set; }
public List<ParsedToNumber> ParsedNumbers { get; set; }
}
public class ParsedToNumber
{
public int ColumnPosition { get; set; }
public decimal Number { get; set; }
public int RowId { get; set; } //Foreign key
}
public class ParsedToStringList
{
public int ColumnPosition { get; set; }
public List<Filter> FilteredStrings { get; set; }
public int RowId { get; set; } //Foreign key
}
public class Filter
{
public string FilterString { get; set; }
}
class Program
{
static void Main(string[] args)
{
var sheet=new Sheet();
sheet.Rows.Add(new Row(){
Id = 1, RowId = 303, String1 = "ABC123", String2 = "Abc Ltd", String3 = "£550.00",
String4 = "£150.00", String5 = "Wholesale", ParsedNumbers = new List<ParsedToNumber>(){
new ParsedToNumber() {ColumnPosition = 3, Number = 550.00m, RowId = 303},
new ParsedToNumber() {ColumnPosition = 4, Number = 150.00m, RowId = 303}
} });
sheet.Rows.Add(new Row(){
Id = 2, RowId = 607, String1 = "XYZ808", String2 = "XYZ Unlimited", String3 = "£999.99",
String4 = "£55.55", String5 = "Wholesale", ParsedNumbers = new List<ParsedToNumber>() {
new ParsedToNumber() {ColumnPosition = 3, Number = 999.99m, RowId = 607},
new ParsedToNumber() {ColumnPosition = 4, Number = 55.55m, RowId = 607}
} });
//This provides for persistance of the data in string format
//and conversion of columns which can be parsed are created
//ready for future in dropdown or range selections .
}
}
我真的很喜欢这个,需要重新审视它。我假设我可以将 class 行中的列表单元格更改为可以使用从抽象 class 单元格继承的具体对象进行转换的内容。
代码的目标是创建一个动态的单元格行,如果可能,可以将其解析为整数、货币、字符串(和其他)类型并供用户使用。数据是从会计数据源的 csv 文件中导入的。
我最初创建了一组整数、货币、字符串字段,效率低下但更简单(如果这种方法更好请说)。
class Sheet
{
public List<Row> Rows { get; set; }
}
public class Row
{
public string Dummy { get; set; }
public List<Cell> Cells { get; set; }//Intellisence at <Cell> advises
} //incorrect number of type parameters?
public class Column
{
public string Name { get; set; }
}
public enum ColumnType
{
String,
Number,
Currency
}
public abstract class Cell<T>
{
public Column Column { get; set; }
public T Data { get; set; }
public abstract void Parse( object obj );
}
public class StringCell : Cell<string>
{
public override void Parse( object obj )
{
throw new NotImplementedException();
}
}
您可以这样做,但需要您将值类型框起来:
public class Row
{
public string Dummy { get; set; }
public List<Cell<Object>> Cells { get; set; }
}
但恕我直言,这很麻烦,因为您总是必须通过转换检查单元格中的对象类型。如果您的单元格内容能够识别它所拥有的内容类型,那就更好了。我认为这就是您已经在尝试做的事情。我将从我的单元格内容的基础 class 开始:
public abstract class ContentBase
{
public ContentType Type;
public Object Value;
}
请注意 ContentType
是您的 ColumnType
除了重命名。然后你可以像这样拥有你的StringContent
......(你可以根据需要更改set
的实现。)
public class StringContent : ContentBase
{
private string _value;
public ContentType Type
{
get { return ContentType.String; }
private set;
}
public string Value
{
get { return _value; }
set { _value = (string)value; }
}
}
现在,您可以让 Cell
不用担心它的内容(因此您可能不需要像 abstract
那样拥有它)。
public abstract class Cell<>
{
public Column Column { get; set; }
public ContentBase Content { get; set; }
}
并让您的 Row
像这样更简单:
public class Row
{
public string Dummy { get; set; }
public List<Cell> Cells { get; set; }
}
在创建对象模型来表示 table/spreadsheet 时,通常需要牢记 2 个主要注意事项:
将电子表格解析为对象模型。在该级别,列的预期值类型应决定单元格的构造方式。
对象模型的使用(数据的操作和转换,渲染到 UI)。在那个级别,客户端代码几乎肯定需要区分列允许的不同类型的值(即,此代码需要打开 cell/column 的值类型)。例如,UI 中的单元格数据格式需要知道该单元格是否包含文本、数字或货币金额。因此,完全通用的 Cell 模型将毫无用处。
为了促进模型在解析和后续使用中的实用性,您可以使用单元格工厂,根据与列关联的值类型创建单元格。对于单元格的下游使用,您可以使用方法 return 单元格为每种可能的类型(字符串、整数、小数)保存的值。
像这样:
class Sheet {
public List<Row> Rows { get; set; }
}
public class Row {
public string Dummy { get; set; }
public List<Cell> Cells { get; set; }
}
public class Column {
public string Name { get; set; }
public ValueType ValueType { get; set; }
}
public class Cell {
public Column Column { get; private set; }
public object Data { get; private set; }
public int GetValueInt() {
return Column.ValueType == ValueType.Number ? (int)Data : 0;
}
public string GetValueString() {
// could also return Data.ToString() is Data is not null
return Column.ValueType == ValueType.String ? (string)Data : null;
}
public decimal GetValueCurrenty() {
return Column.ValueType == ValueType.Currency ? (decimal)Data : 0;
}
// factory for cells
public static Cell MakeCell(object data, Column column) {
// fail-early if the data does not match the value type specified by the column
switch (column.ValueType) {
case ValueType.String:
if (!(data is string)) {
throw new ArgumentException("Invalid data for column containing strings");
}
break;
case ValueType.Number:
if (!(data is int)) {
throw new ArgumentException("Invalid data for column containing ints");
}
break;
case ValueType.Currency:
if (!(data is decimal)) {
throw new ArgumentException("Invalid data for column containing decimals");
}
break;
}
var cell = new Cell { Column = column, Data = data };
return cell;
}
}
由于 int
Date
和 string
除了 object
之外不共享通用类型,因此您不能在此模型中使用泛型。
考虑为所有单元格值类型创建一个通用接口 IValue
并从那里开始:
public enum ColumnType
{
String,
Date,
Currency
}
public interface IValue
{
void Parse(object obj);
}
public class Sheet
{
public List<Column> Columns { get; set; }
public Column this[int column] { get { return Columns[column]; } set { Columns[column]=value; } }
}
public class Column
{
public string Name { get; set; }
public ColumnType Type { get; set; }
public List<Cell> Rows { get; set; }
public Cell this[int row] { get { return Rows[row]; } set { Rows[row]=value; } }
}
public class Cell
{
public Column Column { get; set; }
public IValue Data { get; set; }
}
public class StringValue : IValue
{
public StringValue(string value) { Value=value; }
public string Value { get; set; }
public void Parse(object obj)
{
Value=obj.ToString();
}
}
public class DecimalValue : IValue
{
public DecimalValue(decimal value) { Value=value; }
public decimal Value { get; set; }
public void Parse(object obj)
{
if(obj is decimal)
{
Value=(decimal)obj;
}
}
}
public class DateValue : IValue
{
public DateValue(DateTime value) { Value=value; }
public DateTime Value { get; set; }
public void Parse(object obj)
{
if(obj is DateTime)
{
Value=(DateTime)obj;
}
}
}
class Program
{
static void Main(string[] args)
{
var sheet=new Sheet() { Columns = new List<Column>() };
sheet.Columns.Add(new Column()
{
Type=ColumnType.String,
Name="Item",
Rows=new List<Cell>()
});
sheet.Columns.Add(new Column()
{
Type=ColumnType.Date,
Name="Date",
Rows=new List<Cell>()
});
sheet.Columns.Add(new Column()
{
Type=ColumnType.Currency,
Name="Amount",
Rows=new List<Cell>()
});
sheet[0].Rows.Add(new Cell() { Column=sheet[0], Data=new StringValue("AAB") });
sheet[0].Rows.Add(new Cell() { Column=sheet[0], Data=new StringValue("AAC") });
sheet[0].Rows.Add(new Cell() { Column=sheet[0], Data=new StringValue("ABA") });
sheet[1].Rows.Add(new Cell() { Column=sheet[1], Data=new DateValue(DateTime.Now) });
sheet[1].Rows.Add(new Cell() { Column=sheet[1], Data=new DateValue(DateTime.Now) });
sheet[1].Rows.Add(new Cell() { Column=sheet[1], Data=new DateValue(DateTime.Now) });
sheet[2].Rows.Add(new Cell() { Column=sheet[2], Data=new DecimalValue(1000m) });
sheet[2].Rows.Add(new Cell() { Column=sheet[2], Data=new DecimalValue(1200m) });
sheet[2].Rows.Add(new Cell() { Column=sheet[2], Data=new DecimalValue(870m) });
sheet[0][1].Data.Parse("CCC");
var check=(sheet[0][1].Data as StringValue).Value;
// check == "CCC"
}
}
为了让您的生活更轻松,我会创建您自己的集合,而不是使用 List<Cell>
和 List<Column>
来实现 AddColumn()
和 AddCells()
等方法。您将必须继承自 System.Collections.ObjectModel.Collection<>
.
感谢您的精彩回复。然而,在更深入地阅读 HashPsi 最初的评论时,我发现有几个用户都想将这个应用程序与几个不同但非常相似的报告一起使用,例如有更多可用的列,可以是 included/removed 在任何特定于用户的报告。
无需为每种用户类型制定解决方案,每个用户都可以简单地查看他们的数据和最初导入数据的 select 过滤器。 为了在下面的行级别编辑中保持非常简单,我设置了一个非常简单的 id 和 20x 字符串,然后列出预解析的 currency/numbers,可用于 select 下拉数字过滤器,或者值之间的范围。类似的列表用于 sheet 级别的字符串下拉列表。
完成的应用程序将使用一个由 sheet id(或类似)分隔的数据库。 您在这里有什么想法,我是否应该使用更多泛型,例如下面的示例(这些示例非常好,如果有点高级,因为我仍然是一个可怜的菜鸟:))。
public abstract class Entity
{
public virtual int Id { get; set; }//todo - autoincrement
}
class Sheet
{
public List<Row> Rows { get; set; }
public List<ParsedToStringList> ParsedStringLists { get; set; }
}
public class Row : Entity
{
public int RowId { get; set; } //unique key
public string String1 { get; set; }
public string String2 { get; set; }
public string String3 { get; set; }
public string String4 { get; set; }
public string String5 { get; set; }
public string String6 { get; set; }
public string String7 { get; set; }
public string String8 { get; set; }
public string String9 { get; set; }
public string String10 { get; set; }
public string String11 { get; set; }
public string String12 { get; set; }
public string String13 { get; set; }
public string String14 { get; set; }
public string String15 { get; set; }
public string String16 { get; set; }
public string String17 { get; set; }
public string String18 { get; set; }
public string String19 { get; set; }
public string String20 { get; set; }
public List<ParsedToNumber> ParsedNumbers { get; set; }
}
public class ParsedToNumber
{
public int ColumnPosition { get; set; }
public decimal Number { get; set; }
public int RowId { get; set; } //Foreign key
}
public class ParsedToStringList
{
public int ColumnPosition { get; set; }
public List<Filter> FilteredStrings { get; set; }
public int RowId { get; set; } //Foreign key
}
public class Filter
{
public string FilterString { get; set; }
}
class Program
{
static void Main(string[] args)
{
var sheet=new Sheet();
sheet.Rows.Add(new Row(){
Id = 1, RowId = 303, String1 = "ABC123", String2 = "Abc Ltd", String3 = "£550.00",
String4 = "£150.00", String5 = "Wholesale", ParsedNumbers = new List<ParsedToNumber>(){
new ParsedToNumber() {ColumnPosition = 3, Number = 550.00m, RowId = 303},
new ParsedToNumber() {ColumnPosition = 4, Number = 150.00m, RowId = 303}
} });
sheet.Rows.Add(new Row(){
Id = 2, RowId = 607, String1 = "XYZ808", String2 = "XYZ Unlimited", String3 = "£999.99",
String4 = "£55.55", String5 = "Wholesale", ParsedNumbers = new List<ParsedToNumber>() {
new ParsedToNumber() {ColumnPosition = 3, Number = 999.99m, RowId = 607},
new ParsedToNumber() {ColumnPosition = 4, Number = 55.55m, RowId = 607}
} });
//This provides for persistance of the data in string format
//and conversion of columns which can be parsed are created
//ready for future in dropdown or range selections .
}
}
我真的很喜欢这个,需要重新审视它。我假设我可以将 class 行中的列表单元格更改为可以使用从抽象 class 单元格继承的具体对象进行转换的内容。
代码的目标是创建一个动态的单元格行,如果可能,可以将其解析为整数、货币、字符串(和其他)类型并供用户使用。数据是从会计数据源的 csv 文件中导入的。 我最初创建了一组整数、货币、字符串字段,效率低下但更简单(如果这种方法更好请说)。
class Sheet
{
public List<Row> Rows { get; set; }
}
public class Row
{
public string Dummy { get; set; }
public List<Cell> Cells { get; set; }//Intellisence at <Cell> advises
} //incorrect number of type parameters?
public class Column
{
public string Name { get; set; }
}
public enum ColumnType
{
String,
Number,
Currency
}
public abstract class Cell<T>
{
public Column Column { get; set; }
public T Data { get; set; }
public abstract void Parse( object obj );
}
public class StringCell : Cell<string>
{
public override void Parse( object obj )
{
throw new NotImplementedException();
}
}
您可以这样做,但需要您将值类型框起来:
public class Row
{
public string Dummy { get; set; }
public List<Cell<Object>> Cells { get; set; }
}
但恕我直言,这很麻烦,因为您总是必须通过转换检查单元格中的对象类型。如果您的单元格内容能够识别它所拥有的内容类型,那就更好了。我认为这就是您已经在尝试做的事情。我将从我的单元格内容的基础 class 开始:
public abstract class ContentBase
{
public ContentType Type;
public Object Value;
}
请注意 ContentType
是您的 ColumnType
除了重命名。然后你可以像这样拥有你的StringContent
......(你可以根据需要更改set
的实现。)
public class StringContent : ContentBase
{
private string _value;
public ContentType Type
{
get { return ContentType.String; }
private set;
}
public string Value
{
get { return _value; }
set { _value = (string)value; }
}
}
现在,您可以让 Cell
不用担心它的内容(因此您可能不需要像 abstract
那样拥有它)。
public abstract class Cell<>
{
public Column Column { get; set; }
public ContentBase Content { get; set; }
}
并让您的 Row
像这样更简单:
public class Row
{
public string Dummy { get; set; }
public List<Cell> Cells { get; set; }
}
在创建对象模型来表示 table/spreadsheet 时,通常需要牢记 2 个主要注意事项:
将电子表格解析为对象模型。在该级别,列的预期值类型应决定单元格的构造方式。
对象模型的使用(数据的操作和转换,渲染到 UI)。在那个级别,客户端代码几乎肯定需要区分列允许的不同类型的值(即,此代码需要打开 cell/column 的值类型)。例如,UI 中的单元格数据格式需要知道该单元格是否包含文本、数字或货币金额。因此,完全通用的 Cell 模型将毫无用处。
为了促进模型在解析和后续使用中的实用性,您可以使用单元格工厂,根据与列关联的值类型创建单元格。对于单元格的下游使用,您可以使用方法 return 单元格为每种可能的类型(字符串、整数、小数)保存的值。
像这样:
class Sheet {
public List<Row> Rows { get; set; }
}
public class Row {
public string Dummy { get; set; }
public List<Cell> Cells { get; set; }
}
public class Column {
public string Name { get; set; }
public ValueType ValueType { get; set; }
}
public class Cell {
public Column Column { get; private set; }
public object Data { get; private set; }
public int GetValueInt() {
return Column.ValueType == ValueType.Number ? (int)Data : 0;
}
public string GetValueString() {
// could also return Data.ToString() is Data is not null
return Column.ValueType == ValueType.String ? (string)Data : null;
}
public decimal GetValueCurrenty() {
return Column.ValueType == ValueType.Currency ? (decimal)Data : 0;
}
// factory for cells
public static Cell MakeCell(object data, Column column) {
// fail-early if the data does not match the value type specified by the column
switch (column.ValueType) {
case ValueType.String:
if (!(data is string)) {
throw new ArgumentException("Invalid data for column containing strings");
}
break;
case ValueType.Number:
if (!(data is int)) {
throw new ArgumentException("Invalid data for column containing ints");
}
break;
case ValueType.Currency:
if (!(data is decimal)) {
throw new ArgumentException("Invalid data for column containing decimals");
}
break;
}
var cell = new Cell { Column = column, Data = data };
return cell;
}
}
由于 int
Date
和 string
除了 object
之外不共享通用类型,因此您不能在此模型中使用泛型。
考虑为所有单元格值类型创建一个通用接口 IValue
并从那里开始:
public enum ColumnType
{
String,
Date,
Currency
}
public interface IValue
{
void Parse(object obj);
}
public class Sheet
{
public List<Column> Columns { get; set; }
public Column this[int column] { get { return Columns[column]; } set { Columns[column]=value; } }
}
public class Column
{
public string Name { get; set; }
public ColumnType Type { get; set; }
public List<Cell> Rows { get; set; }
public Cell this[int row] { get { return Rows[row]; } set { Rows[row]=value; } }
}
public class Cell
{
public Column Column { get; set; }
public IValue Data { get; set; }
}
public class StringValue : IValue
{
public StringValue(string value) { Value=value; }
public string Value { get; set; }
public void Parse(object obj)
{
Value=obj.ToString();
}
}
public class DecimalValue : IValue
{
public DecimalValue(decimal value) { Value=value; }
public decimal Value { get; set; }
public void Parse(object obj)
{
if(obj is decimal)
{
Value=(decimal)obj;
}
}
}
public class DateValue : IValue
{
public DateValue(DateTime value) { Value=value; }
public DateTime Value { get; set; }
public void Parse(object obj)
{
if(obj is DateTime)
{
Value=(DateTime)obj;
}
}
}
class Program
{
static void Main(string[] args)
{
var sheet=new Sheet() { Columns = new List<Column>() };
sheet.Columns.Add(new Column()
{
Type=ColumnType.String,
Name="Item",
Rows=new List<Cell>()
});
sheet.Columns.Add(new Column()
{
Type=ColumnType.Date,
Name="Date",
Rows=new List<Cell>()
});
sheet.Columns.Add(new Column()
{
Type=ColumnType.Currency,
Name="Amount",
Rows=new List<Cell>()
});
sheet[0].Rows.Add(new Cell() { Column=sheet[0], Data=new StringValue("AAB") });
sheet[0].Rows.Add(new Cell() { Column=sheet[0], Data=new StringValue("AAC") });
sheet[0].Rows.Add(new Cell() { Column=sheet[0], Data=new StringValue("ABA") });
sheet[1].Rows.Add(new Cell() { Column=sheet[1], Data=new DateValue(DateTime.Now) });
sheet[1].Rows.Add(new Cell() { Column=sheet[1], Data=new DateValue(DateTime.Now) });
sheet[1].Rows.Add(new Cell() { Column=sheet[1], Data=new DateValue(DateTime.Now) });
sheet[2].Rows.Add(new Cell() { Column=sheet[2], Data=new DecimalValue(1000m) });
sheet[2].Rows.Add(new Cell() { Column=sheet[2], Data=new DecimalValue(1200m) });
sheet[2].Rows.Add(new Cell() { Column=sheet[2], Data=new DecimalValue(870m) });
sheet[0][1].Data.Parse("CCC");
var check=(sheet[0][1].Data as StringValue).Value;
// check == "CCC"
}
}
为了让您的生活更轻松,我会创建您自己的集合,而不是使用 List<Cell>
和 List<Column>
来实现 AddColumn()
和 AddCells()
等方法。您将必须继承自 System.Collections.ObjectModel.Collection<>
.