只能包含图像或字符串的 C# 列表

C# List which could contain only image or string

NET 开发人员。 决定制作图层以从数据源中提取一些数据,以进一步在一些生成的文档中构建 tables。

也许问题很简单,但我有点困惑。

问题是 - 想要构建 table 可能包含字符串或图像的模型。 目前我得到了那组 类,但我不确定这是正确的方法:

public class TableModel
{
    public long RowsCount { get; set; }

    public long ColumnsCount { get; set; }

    public List<List<DataUnitModel>> Table { get; set; } 
}

public class DataUnitModel
{
    public object Element { get; set; }

    public Type ElementType { get; set; }
}

它应该是这样工作的(伪代码):

public void BuildTable(TableModel myTable)
{
    for (var i = 0; i < myTable.Rows)
    {
        for (var j = 0; j < myTable.Cols)
        {
            DocumentCurrentRange.Insert(myTable.Table[i][j]);
        }
    }
}     

更新:

数据类型 - 可以是简单的字符串或 jpg/png/bmp 图片

UPD2:

Txnks 伙计们,意识到我不需要 GetElement 功能,这里没用。

您的扩展方法 DataUnitModelExtensions.GetElement 所做的唯一一件事就是确保 DataUnitModel.Element 中包含的数据是 Imagestringnull.从您的问题来看,这是此 属性 可以 包含的唯一数据类型。所以你的扩展方法基本上没有任何用处。您在那里使用了两个非常适合类型转换的 as 强制转换,但是您 return 一个 object 这使得您的类型转换毫无用处。

如果要公开 Element 字段的内容,请使用 return Imagestring 两个不同的属性。由于您自己定义 DataUnitModel class,因此不需要扩展方法:

public class DataUnitModel
{
    public object Element { get; set; }

    public string ElementText { get { return Element as string; } }

    public Image ElementImage { get { return Element as Image; } }

    public Type ElementType { get; set; }
}

如果你想获取元素类型,你可以使用 GetType 作为你的 ElementType 属性,虽然一些简单的 bool 属性可以完成这项工作:

public class DataUnitModel
{
    public object Element { get; set; }

    public string ElementText { get { return Element as string; } }

    public Image ElementImage { get { return Element as Image; } }

    public Type ElementType { get { Element != null ? Element.GetType() : null; }

    public bool IsElementText { get { return Element is string; } }

    public bool IsElementImage { get { return Element is Image; } }
}

您可以使用接口:

public interface IDataUnitModel{}
public class StringDataUnitModel : IDataUnitModel
{
  public string Value {get;set;}
}
public class ImageDataUnitModel : IDataUnitModel
{
  public Image Value {get;set;}
}
//...
public List<IDataUnitModel> list = new List<IDataUnitModel>();
list.Add(new StringDataUnitModel{Value = "ABC"});
list.Add(new ImageDataUnitModel());

并且在检索时您可以检查对象的类型。您也只能输入图片和字符串。

如果 table 只能包含图像或字符串,您可以使用泛型。

看来你在这里采取了一个稍微错误的方法,即你正在解决错误的问题。你的 "table" 可以简单地是一个 List<List<object>>,它基本上携带与你的 类 现在携带的相同的信息(除了 "number of columns",如果列表是空的) .

如果我理解你的问题,每种元素类型都将以某种方式 "rendered"(作为报告,或在网页中,或转换为其他形式)。如果那是真的,我将集中精力定义所有元素的渲染步骤的共同点。因此,最终结果应该允许您编写如下内容:

var html = new StringBuilder();

foreach (var item in document.Items)
{
    // get the renderer for this item type
    var renderer = renderers[item.GetType()];

    // append to the report
    renderer.Append(html, item);
}

简化的报告模型可以是段落列表、tables、数字,例如:

<Document>

  <Paragraph>
     Some text
  </Paragraph>

  <Table>
     <HeaderRow>
        <Col>Num</Col> <Col>Name</Col> ...
     <HeaderRow>
     <Row>
        <Col>Num</Col> <Col>Name</Col> ...
     <Row>
  </Table>

  <Image Url="..." />

</Document>

或者每个元素也可能有一个子元素列表,但这是一个简单的例子。

然后您的模型将用以下内容表示:

interface IDocument 
{
    List<IItem> Items { get; }
}

interface IItem
{
    // common properties which apply to all items
    // (if they don't apply to all items, they
    // shouldn't be here)

    bool CanBreakAcrossPages { get; }
}

interface ITextualItem : IItem
{
    string TextFont { get; }
    float TextSize { get; }
    ...
}

class ParagraphItem : ITextualItem
{
    public bool CanBreakAcrossPages { get; set; }
    public string TextFont { get; set; }
    public float TextSize { get; set; }
    string Text { get; set; }
}

... you get the idea

然后,独立地,您可能有一个像这样的渲染器界面:

// there are several ways to do this, but this is
// just a simple way to avoid casting all over the 
// code, and still have generics for better type safety

interface IHtmlRenderer
{
    void Append(StringBuilder html, object element);
}

interface IHtmlRenderer<T> : IHtmlRenderer where T : IItem
{
    void Append(StringBuilder html, T element);
}

abstract class BaseHtmlRenderer<T> : IHtmlRenderer<T> where T : IItem
{
    public void Append(StringBuilder html, object element)
    {
        // this is the only place where we will cast
        this.Append(html, (T)element);
    }

    abstract public void Append(StringBuilder html, T element);
}

这使得创建不同的渲染器显而易见:

class ParagraphRenderer : BaseHtmlRenderer<ParagraphItem>
{
    public override void Append(StringBuilder html, ParagraphItem element)
    {
        // do stuff
    }
}

class ImageRenderer : BaseHtmlRenderer<ImageItem>
{
    public override void Append(StringBuilder html, ImageItem element)
    {
        // do stuff
    }
}

您剩下要做的就是为每个 IItem 类型(项目类型 -> 渲染器)创建渲染器的 映射。这甚至可以通过 reflection/dependency 注入来完成,因此当您决定创建新的渲染器类型时,一切都会 "just work":

// this can easily be done automatically through reflection

var renderers = new Dictionary<Type, Action<StringBuilder, object>>();
renderers[typeof(ImageItem)] = new ParagraphRenderer().Append;
renderers[typeof(ParagraphItem)] = new ParagraphRenderer().Append;

现在我们得到了我们想要的用法:

var html = new StringBuilder();

foreach (var item in document.Items)
{
    // get the renderer for this item type
    var renderer = renderers[item.GetType()];

    // append to the report
    renderer.Append(html, item);
}

这会将您带到 table 个项目,这些项目对您来说很有趣,因为它们显然可以包含图像和段落作为 table 个单元格。这意味着您可以像这样定义它们:

class TableItem : ITextualItem
{
    // note the recursion here: 
    // ITableItem is an IItem, and it contains a list of IItems,
    // meaning you can even have nested tables inside a single table cell    

    public List<IItem> HeaderCols { get; }
    public List<List<IItem>> Rows { get; }
}

然后渲染器再次变得有趣,因为它是一个复合(递归)渲染器:

public class TableRenderer : BaseHtmlRenderer<TableItem>
{
    // we need to be able to render individual cells
    private Dictionary<Type, Action<StringBuilder, object>> _renderers;

    public override void Append(StringBuilder html, TableItem element)
    {
        RenderHeaderRowStart(html);        
        foreach (var col in element.HeaderCols)
        {
            var cellRenderer = _renderers[col.GetType()];
            cellRenderer.Append(html);
        }
        RenderHeaderRowEnd(html);

        ...
    } 
}

这意味着您需要将字典传递给渲染器构造函数:

// renderers dictionary contains a TableRenderer, which also keeps
// a reference to this same dictionary
renderers[typeof(TableItem)] = new TableRenderer(renderers).Append;