我有以下代码循环遍历对象集合并将不同的属性转储到Excel。每隔一行上的整个j++看起来都不是很优雅。我可以在集合中的对象之间循环并转储属性吗?

int rowIndex = 2;
foreach (BookInfo book in books)
{
    int j = 1;
    excelExport.SetCell(j, rowIndex, book.BookId);
    j++;
    excelExport.SetCell(j, rowIndex, book.Book);
    j++;
    excelExport.SetCell(j, rowIndex, book.System);
    j++;
    excelExport.SetCell(j, rowIndex, book.Age);
    j++;
    excelExport.SetCell(j, rowIndex, book.StartDate);
    j++;
    excelExport.SetCell(j, rowIndex, book.Pages);
    rowIndex++;
}


我一直在开头和后面不断添加新列中间,所以我想避免对列名称进行硬编码。

评论

您基本上是对其进行硬编码。您可以轻松地以“ excelExport.SetCell(1,rowIndex,book.BookId)”等格式编写每一行,并用1/2行代码完成相同的操作。

@Moozhe-但是代码可以更灵活地进行更改,我一直在beginnign不断添加新列

不会excelExport.SetCell(j ++,rowIndex,book.BookId);带你去你想去的地方?

您在其他评论中说要轻松添加列,但这是一件危险的事情。您一直在更改列的含义。这就是为什么最好对列索引使用常量。如果您想将列保留为空怎么办?我当然会创建一种方法,也可以在一本书上执行SetCell操作。此方法势必会随着时间的推移而增长,因此请保持整洁。

#1 楼

我认为您的代码是完全可以理解的。

这是一个主意。 (如果不清楚,这里的建议更多是出于娱乐和教育目的,而不是认真的建议。原始的过程代码很好,但是很有趣的是看看它如何以功能样式完成。) >
var funcs = new List<Func<BookInfo, object>>()
{
    info=>info.BookId,
    info=>info.Book,
    info=>info.System // etc.
}
int rowIndex = 2;
foreach (BookInfo bookInfo in books)
{
    int columnIndex = 1;
    foreach(var func in funcs)
    {
        excelExport.SetCell(columnIndex, rowIndex, func(bookInfo));
        columnIndex += 1;
    }
    rowIndex += 1;
}


但是,它仍然具有令人失望的大量可变突变。为什么我们需要局部变量来完全跟踪行和列?那是您要消除的优雅部分。


不知道是不是拖钓...还是沉迷于lambdas


我们才刚刚开始。我几乎还没有开始使用lambda。怎么样了?

var funcs = new List<Func<BookInfo, object>>()
{
    info=>info.BookId,
    info=>info.Book,
    info=>info.System // etc.
}
var cells = bookInfos.SelectMany(
  (bookInfo, row)=>
    funcs.Select(
      (func, col)=>
        new {row, col, item = func(bookInfo)}));
foreach(var cell in cells)
    excel.SetCell(cell.col, cell.row, cell.item);


现在,我们有了一个选择器,该选择器包含一个lambda,该lambda包含一个选择器,该选择器包含一个在lambda列表上迭代的lambda。我们还摆脱了每个索引突变。

当然,它具有太多的解释性局部变量。我们应该能够做到这一点,而不会变异除了循环变量以外的任何变量。让我们消除除一个变量之外的所有变量突变:

foreach(var cell in 
  bookInfos.SelectMany(
    (bookInfo, row)=>
      new List<Func<BookInfo, object>>()
      {
        info=>info.BookId,
        info=>info.Book,
        info=>info.System // etc.
      }.Select(
        (func, col)=>
          new {row, col, item = func(bookInfo)})))
  excel.SetCell(cell.col, cell.row, cell.item);


现在我们已经说明了这句老话:每种编程语言最终都非常类似于Lisp -非常糟糕。

正如斯科特·里皮(Scott Rippey)在回答中指出的那样,我们实际上根本不需要将属性捕获为lambda。我们可以捕获值:

foreach(var cell in 
  bookInfos.SelectMany(
    (bookInfo, row)=>
      new object[]
      {
        bookInfo.BookId,
        bookInfo.Book,
        bookInfo.System // etc.
      }.Select(
        (item, col)=>
          new {row, col, item})))
  excel.SetCell(cell.col, cell.row, cell.item);


实际上还不错。

但是就像我说的那样,您的原始代码就可以了。

评论


\ $ \ begingroup \ $
不知道是不是拖钓...还是沉迷于lambdas
\ $ \ endgroup \ $
– cHao
2011-12-28 22:00

\ $ \ begingroup \ $
@cHao:哦,我们才刚刚开始。
\ $ \ endgroup \ $
–埃里克·利珀特
2011-12-28 22:06

\ $ \ begingroup \ $
@EricLippert我的答案与您的答案相同……只是没有疯狂地沉迷于lambdas。疯。
\ $ \ endgroup \ $
–斯科特·里皮(Scott Rippey)
2011-12-28 22:38

\ $ \ begingroup \ $
哇,我喜欢(bookInfo,row)=>新的List >()。真好:-)
\ $ \ endgroup \ $
–ΩmegaMan
2011-12-28 22:44

\ $ \ begingroup \ $
@phoog:嘿,我的工作效率很高。我刚刚添加了一个新的错误消息以重载分辨率,就这样。
\ $ \ endgroup \ $
–埃里克·利珀特
2011-12-28 22:57

#2 楼

这非常简单;使用数组和for循环:

int rowIndex = 2;
foreach (BookInfo book in books)
{
    var columns = new object[]{
        book.BookId,
        book.Book,
        book.System,
        book.Age,
        book.StartDate,
        book.Pages,
    };
    for (int j = 0; j < columns.Length; j++) {
        excelExport.SetCell(j + 1, rowIndex, columns[j]);
    }
    rowIndex++;
}


#3 楼

这个

excelExport.SetCell(j, rowIndex, book.BookId);
j++;


等效于此:

excelExport.SetCell(j++, rowIndex, book.BookId);


<sarcasm>现在,您一行可以做两件事! </sarcasm>

现在公认这不是一个很好的解决方案,但确实可以解决您对外观的担心。就像埃里克·利珀特(Eric Lippert)指出的那样,有一些原因不应该这样做。

在您的评论中,您已经注意到这些价值观正在演变。考虑到这一点,请考虑“开放式封闭原则”,您的出口代码应开放以进行扩展,而封闭以进行修改。由于您“不断添加导出内容”,因此您显然违反了原则的封闭部分。

更好的解决方案可能是让BookInfo定义导出内容。

foreach(var book in books) 
{
    var columnIndex = 1;

    foreach(var exportValue in book.ExportValues)
    {
         excelExport.SetCell(columnIndex, rowIndex, exportValue);
         columnIndex += 1;
    }

    rowIndex++; 
}


使用上面的代码,导出器现在已关闭以进行修改(无须更改),但可以扩展以进行扩展(book.ExportValues可以根据需要增长/收缩)。 br />

评论


\ $ \ begingroup \ $
您的观点是不建议您提出建议?
\ $ \ endgroup \ $
–leora
2011-12-28 21:47

\ $ \ begingroup \ $
一线做两件事更糟。一个陈述应该做一件事;与执行一件事的语句相比,执行多项操作的语句更难于理解,难以重构和调试。
\ $ \ endgroup \ $
–埃里克·利珀特
2011-12-28 21:48

\ $ \ begingroup \ $
我将使用“ excelExport.SetCell(j ++,rowIndex,book.BookId)”解决方案。我显然同意不要仅仅为了它而立即做某事,但说真的,我认为“将函数应用于下一个列”比“将函数应用于当前列”然后“递增”要简单。柱”。可惜的是,它没有出色的语法,但是(a)我认为使用列表可能太复杂了;(b)对数字进行硬编码使以后更改行顺序变得更加困难。我仍然认为“ excelExport.SetCell(j ++,rowIndex,book.BookId)”是最简单和最好的。
\ $ \ endgroup \ $
–杰克五世。
2012年1月12日在16:32

#4 楼

每次遇到不正确的代码时,请尝试想象您希望代码看起来像什么。通常,您可以创建一个更好地传达代码意图的抽象,从而简化可读性和将来的可维护性。

在这种情况下,将繁琐的代码封装在更高的抽象中以产生以下结果:

一个代表可以放入值的行的对象,也可以跳过以开始将值放入下一行,始终正确地为我处理行和列索引。结果代码更易于编写,读取和更改。


这是必需的RowFiller类:

var row = new RowFiller(excelExport, startRowIndex: 2, startColumnIndex: 1);
foreach (BookInfo book in books) {
  row.Put(book.BookId);
  row.Put(book.Book);
  row.Put(book.System);
  row.Put(book.Age);
  row.Put(book.StartDate);
  row.Put(book.Pages);
  row.Skip();
}    


(注意:我不知道excelExport的类型,所以我只是假设它是Excel

#5 楼

由于您在每次迭代中都要重新声明j,因此其值实际上是静态的。因此,一种“更优雅”的方法是:

foreach (BookInfo book in books)
{
    excelExport.SetCell(1, rowIndex, book.BookId);
    excelExport.SetCell(2, rowIndex, book.Book);
    excelExport.SetCell(3, rowIndex, book.System);
    excelExport.SetCell(4, rowIndex, book.Age);
    excelExport.SetCell(5, rowIndex, book.StartDate);
    excelExport.SetCell(6, rowIndex, book.Pages);

    rowIndex++;
}


评论


\ $ \ begingroup \ $
因为我不断在前面添加列,并且我不想每次需要在中间插入新列时都必须移动其他每列的列号\
\ $ \ endgroup \ $
–leora
2011-12-28 21:51

\ $ \ begingroup \ $
@leora然后,在该问题中应该增加其他要求。我们无法读懂您的想法-就问题的读者所知,没有理由要有一个变量。
\ $ \ endgroup \ $
– djacobson
2011-12-28 21:53

#6 楼

我个人将避免使用j++填充Excel单元,而是使用常量。像这样的东西:

foreach (BookInfo book in books)
{
    excelExport.SetCell(Constants.BookIdCellIndex, rowIndex, book.BookId);
    excelExport.SetCell(/*index of other cell*/ .....);
}


评论


\ $ \ begingroup \ $
我不会。太有企业精神。使得不得不寻找一个地方来弄清楚代码到底在做什么。这真的不应该那么复杂。
\ $ \ endgroup \ $
– cHao
2011-12-28 21:57

\ $ \ begingroup \ $
@cHao:考虑到有一天您决定重新组织Excel中的列。您将如何以“简单”的方式在此循环代码中处理它。 ??对于索引声明,唯一需要更改的是常量的索引。与使用循环变量的代码相比,该代码非常健壮和可调用。
\ $ \ endgroup \ $
–提格伦
2011-12-28 22:02

\ $ \ begingroup \ $
除那部分内容之外,您必须半步了解应用程序才能弄清楚什么是Constants.BookIdCellIndex或它的用途。如果要对其进行硬编码,请对其进行硬编码。如果您希望它具有灵活性,则可以按所需顺序传递一个包含东西的数组。不是这个。看起来它是由Java迷煮熟的。 (顺便说一句,不是我的不赞成。)
\ $ \ endgroup \ $
– cHao
2011-12-28 22:06

\ $ \ begingroup \ $
@cHao:!! ...如果我有一个从同一Excel读取数据的函数,该怎么办。像一个优秀的开发人员一样,您可以将常量声明移出。只是一个例子,也就是说,您指出的问题与上下文有关,因此与该问题无关。祝好运。
\ $ \ endgroup \ $
–提格伦
2011-12-28 22:28

\ $ \ begingroup \ $
如果您需要在两个不同的位置使用这些常量,则可以使用它。但是直到那时,YAGNI。而且,如果我要使用相同的常量进行读写,那么我不希望它们易于修改,尤其是在应用程序中途。我想要那些刻在石头上的吸盘,以免有些笨拙地决定移动一些色谱柱并破坏向后兼容性。
\ $ \ endgroup \ $
– cHao
2011-12-29 6:05

#7 楼

我会提倡布莱恩(Bryan)建议的列索引常量方法,但会有所变化。所做的更改使您可以更轻松地在靠近前端插入列时满足易于维护的要求:

    const int BOOK_ID    = 1; 
    const int BOOK       = BOOK_ID + 1; 
    const int SYSTEM     = BOOK + 1; 
    const int AGE        = SYSTEM + 1; 
    const int START_DATE = AGE + 1; 
    const int PAGES      = PAGES + 1; 


在书本和系统之间添加一列?没问题:

    const int BOOK_ID    = 1;              //unchanged line
    const int BOOK       = BOOK_ID + 1;    //unchanged line
    const int NEW_COLUMN = BOOK + 1;       //new line
    const int SYSTEM     = NEW_COLUMN + 1; //changed line
    const int AGE        = SYSTEM + 1;     //unchanged line
    const int START_DATE = AGE + 1;        //unchanged line
    const int PAGES      = PAGES + 1;      //unchanged line


#8 楼

由于您的列定义似乎是静态的,因此我将为您的列索引创建内容

const int BookIdColumn = 1;
const int BookColumn = 2;
const int SystemColumn = 3;
const int AgeColumn = 4;
const int StartDateColumn = 5;
const int PagesColumn = 6;

int rowIndex = 2;
foreach (BookInfo book in books)
{
      excelExport.SetCell(BookIdColumn, rowIndex, book.BookId);
      excelExport.SetCell(BookColumn, rowIndex, book.Book);
      excelExport.SetCell(SystemColumn, rowIndex, book.System);
      excelExport.SetCell(AgeColumn, rowIndex, book.Age);
      excelExport.SetCell(StartDateColumn, rowIndex, book.StartDate);
      excelExport.SetCell(PagesColumn, rowIndex, book.Pages);
}


#9 楼

可以使用反射来避免对本书的属性进行硬编码。我认为必须显示所有公共财产。然后在您的示例中,使用书的索引定义行,并使用属性的索引定义j

注意:我注释掉了对excel的实际调用,这就是您的实际调用;

这告诉Linq,我感到将中级C#开发人员与高级开发人员分开是因为他们现在在所有数据结构上都考虑IEnumerable / IQuerable

以下内容将在LinqPad中运行:

void Main()
{
    var books = new List<BookInfo>() { new BookInfo() { BookId = 1, Book="War N Peace", StartDate= DateTime.Now  },
                                       new BookInfo() { BookId = 2, Book="Visual Basic .NET Code Security Handbook", StartDate=DateTime.Now.AddYears(1) }};


    var publicProps = typeof(BookInfo).GetProperties( BindingFlags.Instance | BindingFlags.Public );

    books.Select ((bk, index) => new { Book = bk, BookIndex = index} )
         .ToList()
         .ForEach(bk => publicProps.Select ((prp, index) => new {Prop = prp, Index = index } )
                                   .ToList()
                                   .ForEach(pp => //excelExport.SetCell(bk.BookIndex,  pp.Index, pp.Prop.GetValue(bk.Book, null)); 
                                                  Console.WriteLine ("RowIndex as ({0}) J as ({1}) reflected value as ({2})", bk.BookIndex,  pp.Index, pp.Prop.GetValue(bk.Book, null)))
                  );

/* Output
RowIndex as (0) J as (0) reflected value as (1)
RowIndex as (0) J as (1) reflected value as (War N Peace)
RowIndex as (0) J as (2) reflected value as (12/28/2011 3:33:15 PM)
RowIndex as (1) J as (0) reflected value as (2)
RowIndex as (1) J as (1) reflected value as (Visual Basic .NET Code Security Handbook)
RowIndex as (1) J as (2) reflected value as (12/28/2012 3:33:15 PM)
*/                                 


}

// Define other methods and classes here

public class BookInfo
{
    public int BookId { get; set; }
    public string Book { get; set; }
    public DateTime StartDate { get; set; }

}


编辑:删除了多余的列表。

评论


\ $ \ begingroup \ $
@jamal很荣幸您能考虑我的回答,以将其编辑为适当的措词。唯一有疑问的是改变工作方式。我最初的意图是坚持“顺风而行”,作为方向或方向。 “更改”本身就是可行的,但我知道其原始含义,因此将其更改为方向可以更好地理解。谢谢。
\ $ \ endgroup \ $
–ΩmegaMan
2014年6月9日下午4:14

\ $ \ begingroup \ $
感谢您的澄清。我将其更改为错字,我只是想确定一下。
\ $ \ endgroup \ $
– Jamal♦
2014年6月9日下午4:19