局部函数和共享变量的奇怪行为

Weird behavior with local functions and shared variables

我正在为预算生成一个 Excel 文件,但本地函数有一个奇怪的行为。问题似乎出在共享变量 rowIndex 上。由于某种原因,总公式会覆盖每个部分第一个单元格的内容,但 "Total active" 标签不会覆盖帐户名称。有人可以解释是什么导致了这种奇怪的行为吗?

代码:

private void Export()
{
    var workbook = new Aspose.Cells.Workbook();
    var worksheet = workbook.Worksheets[0];

    int rowIndex = 0;

    var number = 0;
    var data = new List<dynamic>
    {
        new { Name = $"Account {++number}", Amount = 50, IsIncome = true, Active = true },
        new { Name = $"Account {++number}", Amount = 25, IsIncome = true, Active = false },
        new { Name = $"Account {++number}", Amount = 75, IsIncome = false, Active = true },
        new { Name = $"Account {++number}", Amount = 100, IsIncome = false, Active = false },
        new { Name = $"Account {++number}", Amount = 60, IsIncome = true, Active = false },
        new { Name = $"Account {++number}", Amount = 90, IsIncome = false, Active = true },
        new { Name = $"Account {++number}", Amount = 40, IsIncome = true, Active = true },
        new { Name = $"Account {++number}", Amount = 20, IsIncome = false, Active = false }
    };

    WriteExportSection("INCOME", data.Where(i => i.IsIncome).ToList());
    WriteExportSection("EXPENSES", data.Where(i => !i.IsIncome).ToList());

    workbook.Save("budget.xlsx");

    IEnumerable<int> WriteRowValues(IList<dynamic> items)
    {
        foreach(var item in items)
        {
            worksheet.Cells[rowIndex, 0].Value = item.Name;
            worksheet.Cells[rowIndex, 1].Value = item.Amount;
            worksheet.Cells[rowIndex, 2].Value = item.Active;
            if (item.Active)
            {
                yield return rowIndex;
            }
            rowIndex++;
        }
    }

    void WriteExportSection(string name, IList<dynamic> items)
    {
        worksheet.Cells[rowIndex, 0].Value = name;
        rowIndex++;
        worksheet.Cells[rowIndex, 0].Value = "Account";
        worksheet.Cells[rowIndex, 1].Value = "Amount";
        worksheet.Cells[rowIndex, 2].Value = "Is active";
        rowIndex++;
        var activeRows = WriteRowValues(items);
        rowIndex++;
        worksheet.Cells[rowIndex, 0].Value = "Total active";
        worksheet.Cells[rowIndex, 1].Formula = "SUM(" + string.Join(",", activeRows.Select(r => worksheet.Cells[r, 1].Name)) + ")";
        rowIndex++;
    }
}

这是因为WriteRowValues正在使用延迟执行。因为它使用 yield,所以它不会执行,直到您开始使用表达式迭代它:

activeRows.Select(r => worksheet.Cells[r, 1].Name))

因此,在您已经开始输出总行数之前,rowIndex 变量不会递增。这些行输出总数:

worksheet.Cells[rowIndex, 0].Value = "Total active";
worksheet.Cells[rowIndex, 1].Formula = "SUM(" + string.Join(",", activeRows.Select(r => worksheet.Cells[r, 1].Name)) + ")";

仍具有 rowIndex 的原始值,未增加。也就是数据的"first row"。

最简单的解决方法是立即强制执行:

var activeRows = WriteRowValues(items).ToList();

更合适的解决方法可能是完全放弃延迟执行,因为您似乎没有利用它,而且它可能会在以后引起问题(比如如果您最终迭代它两次):

List<int> WriteRowValues(IList<dynamic> items)
{
    var activeIndexes = new List<int>();
    foreach(var item in items)
    {
        worksheet.Cells[rowIndex, 0].Value = item.Name;
        worksheet.Cells[rowIndex, 1].Value = item.Amount;
        worksheet.Cells[rowIndex, 2].Value = item.Active;
        if (item.Active)
        {
            activeIndexes.Add(rowIndex);
        }
        rowIndex++;
    }
    return activeIndexes;
}