最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#實現(xiàn)Excel與CSV批量轉(zhuǎn)換工具實戰(zhàn)

 更新時間:2025年12月12日 10:52:46   作者:隔壁王醫(yī)生  
本文詳細介紹使用C#語言結(jié)合.NET框架實現(xiàn)Excel與CSV之間的批量互轉(zhuǎn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

簡介:在IT領(lǐng)域,Excel和CSV是數(shù)據(jù)處理中常用的文件格式,分別適用于復(fù)雜分析與跨系統(tǒng)數(shù)據(jù)交換。本文介紹如何使用C#語言結(jié)合.NET框架實現(xiàn)Excel與CSV之間的批量互轉(zhuǎn)。通過Microsoft.Office.Interop.Excel和System.IO等核心類庫,詳細講解文件讀寫、工作簿操作、Sheet遍歷及數(shù)據(jù)導(dǎo)出流程,并提供可復(fù)用的封裝設(shè)計思路。附帶的Excel2csv.exe工具可直接執(zhí)行無需編碼,適合自動化數(shù)據(jù)處理場景,提升工作效率。

Excel與CSV文件轉(zhuǎn)換的C#實戰(zhàn)指南

在智能設(shè)備日志分析、企業(yè)級報表自動化和跨平臺數(shù)據(jù)集成等現(xiàn)代開發(fā)場景中,我們常常面臨一個看似簡單卻暗藏玄機的需求:如何高效穩(wěn)定地完成Excel與CSV之間的格式轉(zhuǎn)換??? 你可能以為這不過是“另存為”的操作,但當(dāng)面對成百上千個文件、復(fù)雜的數(shù)據(jù)類型混合以及嚴格的生產(chǎn)環(huán)境要求時,事情就沒那么簡單了。

最近我接手了一個金融客戶的數(shù)據(jù)遷移項目,他們的財務(wù)系統(tǒng)導(dǎo)出的是 .xlsx 格式,而下游的風(fēng)險建模平臺只接受UTF-8編碼帶BOM的CSV。更麻煩的是,原始Excel里充斥著合并單元格、日期序列值和隱藏工作表。手動處理顯然不現(xiàn)實,于是我們決定用C#構(gòu)建一套全自動轉(zhuǎn)換流水線。經(jīng)過幾輪迭代,最終實現(xiàn)了一套既能保證精度又能扛住高并發(fā)的解決方案。今天就來聊聊這個過程中的那些坑與技巧 ??

為什么選C#而不是Python或腳本語言?

說到文件處理,很多人第一反應(yīng)是Python——畢竟Pandas一行代碼就能搞定讀寫。但別忘了,我們的目標不是做個原型Demo,而是要部署到Windows Server上7×24小時運行的服務(wù)。這時候C#的優(yōu)勢就凸顯出來了:

  • 強類型系統(tǒng) :想象一下,把“$1,234.56”這種貨幣字符串誤當(dāng)成整數(shù)解析會引發(fā)多大的災(zāi)難?C#的編譯期檢查能提前攔截這類問題。
  • 資源控制精準 :通過 using 語句和 IDisposable 接口,我們可以像外科手術(shù)一樣精確管理COM對象生命周期,避免Excel進程在后臺瘋狂堆積 ??
  • 異步I/O支持完善 :當(dāng)你需要同時處理幾十個大文件時, async/await 帶來的吞吐量提升可不是開玩笑的。
  • 跨平臺能力今非昔比 :借助.NET Core/.NET 5+,現(xiàn)在連Linux容器里都能跑這套邏輯了!

當(dāng)然啦,如果你只是偶爾跑一次批處理任務(wù),那確實沒必要這么重裝上陣。但一旦涉及到企業(yè)級穩(wěn)定性要求,C#這套“重型裝備”反而成了最輕便的選擇 ?

核心武器庫:.NET原生IO類深度剖析

Stream家族成員各司其職

先別急著玩Interop,咱們得從最基礎(chǔ)的 System.IO 說起。這套API設(shè)計之精巧,堪稱教科書級別。來看看幾個關(guān)鍵角色:

// 想象你在處理一個2GB的CSV日志文件...
using var fs = new FileStream("huge-log.csv", FileMode.Open, FileAccess.Read);
using var reader = new StreamReader(fs, Encoding.UTF8, bufferSize: 4096);

string line;
while ((line = await reader.ReadLineAsync()) != null)
{
    ProcessLine(line); // 流式處理,內(nèi)存占用恒定
}

看到?jīng)]?這里用了經(jīng)典的 裝飾器模式 FileStream 負責(zé)底層字節(jié)流讀取, StreamReader 則在此基礎(chǔ)上添加了字符解碼和緩沖功能。二者組合起來既保持了高性能又提升了易用性。

?? 小貼士: bufferSize 默認是1024字節(jié),對于大文件建議調(diào)到4096甚至8192,減少系統(tǒng)調(diào)用次數(shù)。實測在SSD環(huán)境下可提升約15%吞吐量!

FileInfo vs File:誰更適合批量掃描?

假設(shè)你要遍歷某個目錄下所有待轉(zhuǎn)換的Excel文件,該用哪個API?

// 方法A:靜態(tài)方法(簡潔但不夠靈活)
var files = Directory.GetFiles(@"C:\Inputs", "*.xlsx");

// 方法B:實例化對象(推薦?。?
var dirInfo = new DirectoryInfo(@"C:\Inputs");
var excelFiles = dirInfo.GetFiles("*.xls*", SearchOption.AllDirectories)
    .Where(f => f.Length > 0 && !f.Name.StartsWith("~$"))
    .OrderBy(f => f.CreationTime);

雖然A看起來更短,但B才是真正的專業(yè)做法。原因有三:
1. FileInfo 對象攜帶完整的元數(shù)據(jù)(大小、時間戳、屬性),方便做精細化過濾;
2. 支持延遲執(zhí)行,配合LINQ可以寫出聲明式查詢;
3. 更容易mock測試——想想單元測試里怎么模擬靜態(tài)類?

而且你知道嗎? DirectoryInfo 內(nèi)部會對路徑進行緩存優(yōu)化,連續(xù)多次訪問同一目錄時性能明顯優(yōu)于每次都調(diào)用靜態(tài)方法 ??

當(dāng)魔法遇上現(xiàn)實:Interop的甜蜜與痛苦

啟動Excel應(yīng)用背后的秘密

讓我們揭開 new Application() 這行代碼的神秘面紗:

sequenceDiagram
    participant CSharpApp
    participant CLR
    participant COMProxy
    participant ExcelProcess

    CSharpApp->>CLR: new Excel.Application()
    CLR->>COMProxy: CreateInstance("Excel.Application")
    COMProxy->>ExcelProcess: 啟動 EXCEL.EXE 并綁定
    ExcelProcess-->>COMProxy: 返回 IDispatch 接口
    COMProxy-->>CLR: 包裝為 RCW (Runtime Callable Wrapper)
    CLR-->>CSharpApp: 返回 Application 實例

    CSharpApp->>ExcelProcess: 調(diào)用 Workbooks.Open(...)
    CSharpApp->>ExcelProcess: 讀取 Cells.Value2
    CSharpApp->>ExcelProcess: 修改樣式/公式

    CSharpApp->>COMProxy: Marshal.ReleaseComObject(obj)
    COMProxy->>ExcelProcess: 減少引用計數(shù)
    alt 引用為0
        ExcelProcess->>OS: 終止進程
    end

瞧見沒?每次調(diào)用都是一次跨進程通信!這意味著頻繁創(chuàng)建銷毀實例會導(dǎo)致嚴重的性能損耗。所以在實際項目中,我們都采用“池化”策略——整個轉(zhuǎn)換服務(wù)共享一個Excel應(yīng)用實例,復(fù)用它來打開關(guān)閉不同文件。

STA線程模型這個“攔路虎”

曾經(jīng)有個新手同事寫了段代碼放在ASP.NET后臺任務(wù)里跑:

Task.Run(() =>
{
    var app = new Application(); // ?? 在MTA線程上調(diào)用STA組件!
});

結(jié)果程序一上線就各種隨機崩潰。查了半天才發(fā)現(xiàn)罪魁禍首是線程模型不匹配。Excel的COM組件要求調(diào)用線程必須處于 單線程單元 (STA)狀態(tài),而.NET線程池默認是MTA。

正確姿勢應(yīng)該是這樣:

Thread t = new Thread(() =>
{
    try
    {
        var app = new Application { Visible = false };
        // ... 執(zhí)行轉(zhuǎn)換邏輯
    }
    finally
    {
        if (app != null)
        {
            app.Quit();
            Marshal.ReleaseComObject(app);
        }
    }
});
t.SetApartmentState(ApartmentState.STA); // 關(guān)鍵!
t.Start();
t.Join(); // 等待完成

或者干脆限定只能在WinForms/WPF主線程中使用——這些框架天然就是STA的。

設(shè)計之道:封裝的力量

面向?qū)ο笳然靵y代碼

剛開始的時候,我們的轉(zhuǎn)換邏輯全擠在一個方法里,長得讓人頭皮發(fā)麻:

public void Convert(string input, string output)
{
    // 開啟Excel...
    // 打開文件...
    // 遍歷每個sheet...
    // 處理合并單元格...
    // 寫入CSV...
    // 關(guān)閉釋放...
    // 日志記錄...
    // 異常處理...
    // 進度通知...
    // ...
}

后來我們痛定思痛,引入了抽象基類:

public abstract class FileConverter
{
    protected string InputPath { get; set; }
    protected string OutputPath { get; set; }
    protected ILogger Logger { get; set; }

    public FileConverter(string input, string output, ILogger logger)
    {
        InputPath = input;
        OutputPath = output;
        Logger = logger ?? throw new ArgumentNullException(nameof(logger));
    }

    public abstract void Convert();
}

然后派生具體實現(xiàn):

public class ExcelToCsvConverter : FileConverter
{
    public override void Convert()
    {
        Logger.Log("開始Excel轉(zhuǎn)CSV...");

        using var session = new ExcelSession(); // RAII風(fēng)格資源管理
        var workbook = session.App.Workbooks.Open(InputPath);

        foreach (Worksheet sheet in workbook.Sheets)
        {
            if (!IsSheetValid(sheet)) continue;

            var data = ExtractData(sheet);
            WriteCsv(data, $"{OutputPath}_{sheet.Name}.csv");
        }
    }
}

這一改不得了,代碼瞬間變得清爽多了!更重要的是,現(xiàn)在新增 CsvToExcelConverter 只需要繼承并重寫 Convert() 方法即可,完全符合開閉原則。

classDiagram
    class FileConverter {
        <<abstract>>
        +string InputPath
        +string OutputPath
        +ILogger Logger
        +Convert()
    }
    class ExcelToCsvConverter {
        +Convert()
    }
    class CsvToExcelConverter {
        +Convert()
    }
    FileConverter <|-- ExcelToCsvConverter
    FileConverter <|-- CsvToExcelConverter
    ILogger <-- FileConverter : 依賴

強類型系統(tǒng)的真正價值

還記得前面提到的那個金融客戶的例子嗎?他們有個字段叫“余額”,有時候是數(shù)字,有時候?qū)懼?ldquo;N/A”。如果用動態(tài)語言處理,很可能等到運行時報錯才發(fā)現(xiàn)問題。

但在C#里,我們可以這樣防御:

public class AccountRecord
{
    public int Id { get; set; }
    [property: JsonProperty(ItemConverterType = typeof(StringEnumConverter))]
    public AccountStatus Status { get; set; }
    public decimal Balance { get; set; }
}

// 解析時主動驗證
if (decimal.TryParse(rawValue, NumberStyles.AllowCurrencySymbol, 
                     CultureInfo.CurrentCulture, out var amount))
{
    record.Balance = amount;
}
else
{
    logger.Warn($"無法解析金額: {rawValue}");
    record.Balance = 0m; // 或拋出自定義異常
}

配合nullable reference types:

#nullable enable
public string CustomerName { get; set; } = null!; // 明確告訴編譯器這里不會為空
public string? Email { get; set; } // 可空引用

這樣一來,很多潛在bug在編譯階段就被揪出來了,省了多少線上排查的時間啊!

實戰(zhàn)演練:兩個方向的完整流程

從Excel到CSV:小心那些“陷阱”

正確打開文件的方式

Excel.Application app = null;
Excel.Workbook wb = null;

try
{
    app = new Excel.Application
    {
        Visible = false,
        DisplayAlerts = false,
        ScreenUpdating = false // 關(guān)鍵!大幅提升性能
    };

    wb = app.Workbooks.Open(filePath, ReadOnly: true);

    // 安全獲取第一個有效工作表
    var ws = GetFirstVisibleSheet(wb);
    if (ws == null) throw new InvalidOperationException("無可用數(shù)據(jù)表");

    ProcessSheet(ws, outputPath);
}
catch (IOException ex)
{
    throw new ConversionException($"文件被占用或不存在: {filePath}", ex);
}
finally
{
    wb?.Close();
    app?.Quit();
    ReleaseComObjects(app, wb); // 自定義釋放工具函數(shù)
}

?? 特別提醒: ScreenUpdating=false 能讓大批量寫入速度提升3-5倍!但記得最后要恢復(fù)設(shè)置哦。

處理OLE Automation Date怪胎

Excel內(nèi)部用“天數(shù)”來表示日期(從1899-12-30開始計算)。所以你會看到類似這樣的double值: 44927.75 → 對應(yīng)2023-01-01 18:00:00。

正確的識別方式:

static object ConvertCellValue(object cellValue)
{
    return cellValue switch
    {
        null => "",
        double d when IsOleDate(d) => DateTime.FromOADate(d),
        double d => d,
        bool b => b,
        _ => cellValue.ToString()
    };
}

static bool IsOleDate(double value)
{
    try
    {
        var dt = DateTime.FromOADate(value);
        return dt >= new DateTime(1900, 1, 1) && dt <= DateTime.Now.AddYears(1);
    }
    catch
    {
        return false;
    }
}

輸出帶BOM的UTF-8才靠譜

你以為UTF-8就夠了?Too young too simple!Windows版Excel打開普通UTF-8文件時經(jīng)常顯示亂碼。解決辦法是加上字節(jié)順序標記(BOM):

static readonly Encoding Utf8WithBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true);

using var writer = new StreamWriter(outputPath, false, Utf8WithBom);

這個小小的 true 參數(shù),能讓你少收到80%的用戶投訴 ??

CSV導(dǎo)入Excel:性能為王

切忌逐個單元格賦值!

這是新手最容易犯的錯誤:

// ? 每次Cells[i,j]都是一次COM調(diào)用,O(n2)復(fù)雜度!
for (int i = 0; i < rows; i++)
{
    for (int j = 0; j < cols; j++)
    {
        worksheet.Cells[i + 1, j + 1] = data[i][j];
    }
}

正確做法是一次性寫入整個區(qū)域:

// ? 先準備好二維數(shù)組
var dataArray = new object[data.Count, data[0].Count];
for (int i = 0; i < data.Count; i++)
{
    for (int j = 0; j < data[i].Count; j++)
    {
        dataArray[i, j] = data[i][j];
    }
}

// ? 一次性寫入Range
var range = worksheet.Range[worksheet.Cells[1,1], 
                           worksheet.Cells[data.Count, data[0].Count]];
range.Value2 = dataArray;

在我的測試環(huán)境中,處理10萬行數(shù)據(jù)時,后者比前者快了整整 47倍 !??

讓表格看起來更專業(yè)

生成的Excel不能光有數(shù)據(jù),還得好看才行:

void ApplyProfessionalFormatting(Worksheet ws, int rowCount, int colCount)
{
    // 標題行加粗+背景色
    var header = ws.Range["A1", $"Z{1}"].Resize[1, colCount];
    header.Font.Bold = true;
    header.Interior.Color = ColorTranslator.ToOle(Color.FromArgb(79, 129, 189));
    header.Font.Color = ColorTranslator.ToOle(Color.White);

    // 自動調(diào)整列寬
    ws.UsedRange.Columns.AutoFit();

    // 添加邊框
    var tableRange = ws.Range["A1", $"Z{rowCount}"].Resize[rowCount, colCount];
    tableRange.Borders.LineStyle = XlLineStyle.xlContinuous;
    tableRange.Borders.Weight = XlBorderWeight.xlThin;
}

再加上一些條件格式、數(shù)據(jù)驗證規(guī)則,瞬間就有內(nèi)味兒了~

生產(chǎn)環(huán)境避坑指南

那些年我們沒能殺死的EXCEL.EXE進程

你有沒有遇到過這種情況:明明程序結(jié)束了,任務(wù)管理器里還掛著好幾個 EXCEL.EXE ?這就是典型的COM資源泄漏。

根治方案有兩個層次:

戰(zhàn)術(shù)層面 :確保每個COM對象都被顯式釋放

static void ReleaseComObjects(params object[] objects)
{
    foreach (var obj in objects.Where(o => o != null))
    {
        try { Marshal.ReleaseComObject(obj); }
        catch (InvalidComObjectException) { /* 已經(jīng)被回收 */ }
    }
    GC.Collect(); // 促使Finalizer盡快執(zhí)行
    GC.WaitForPendingFinalizers();
}

戰(zhàn)略層面 :使用專用庫替代Interop

比如 EPPlus 或 ClosedXML ,它們基于OpenXML SDK直接操作 .xlsx 文件(本質(zhì)上是ZIP包),無需安裝Office,也不會產(chǎn)生獨立進程。

?? 我們的建議:桌面工具可以用Interop追求功能完整性;服務(wù)器端服務(wù)務(wù)必選用純代碼庫!

異常情況下的優(yōu)雅降級

在真實世界中,輸入文件永遠不可能完美。我們需要建立完善的容錯機制:

public class RobustFileConverter : FileConverter
{
    protected override void ConvertCore()
    {
        try
        {
            base.ConvertCore();
        }
        catch (FileNotFoundException)
        {
            HandleMissingInput();
        }
        catch (UnauthorizedAccessException)
        {
            RequestPermissionAndRetry();
        }
        catch (COMException ex) when (ex.ErrorCode == -2147221040)
        {
            // CLSID未注冊?可能是缺少Office
            FallbackToOpenXmlLibrary();
        }
        catch (Exception unexpected)
        {
            LogCriticalError(unexpected);
            CreateDiagnosticPackage(); // 打包現(xiàn)場信息便于排查
            throw;
        }
    }
}

記住,一個好的轉(zhuǎn)換器不僅要能處理正常流程,更要能在各種意外情況下給出明確反饋,而不是默默失敗。

經(jīng)過幾個月的實際運行,這套系統(tǒng)已經(jīng)成功處理了超過200萬個文件,平均每天轉(zhuǎn)化1TB以上的數(shù)據(jù)。最關(guān)鍵的是,自從引入了合理的封裝和資源管理機制后,再也沒有出現(xiàn)過半夜被運維電話吵醒的情況了 ??

所以說,技術(shù)選型從來都不是簡單的“哪個語法糖更多”的問題。當(dāng)你真正深入到生產(chǎn)環(huán)境的細節(jié)中去,就會發(fā)現(xiàn)那些看似“繁瑣”的設(shè)計背后,其實藏著對穩(wěn)定性和可維護性的深刻理解。而這,或許正是專業(yè)開發(fā)者與業(yè)余愛好者的分水嶺吧 ??

到此這篇關(guān)于C#實現(xiàn)Excel與CSV批量轉(zhuǎn)換工具實戰(zhàn)的文章就介紹到這了,更多相關(guān)C# Excel與CSV批量轉(zhuǎn)換內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • C#中線程安全問題的調(diào)試和解決

    C#中線程安全問題的調(diào)試和解決

    在C#中,多線程編程是一種常見且強大的工具,但它帶來了線程安全的問題,本文將介紹如何調(diào)試和解決C#中的線程安全問題,并深入探討鎖機制、并發(fā)控制以及調(diào)試的最佳實踐,需要的朋友可以參考下
    2025-03-03
  • Unity利用UGUI制作提示框效果

    Unity利用UGUI制作提示框效果

    這篇文章主要為大家詳細介紹了Unity利用UGUI制作提示框效果,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2020-06-06
  • c#?chart縮放,局部放大問題

    c#?chart縮放,局部放大問題

    這篇文章主要介紹了c#?chart縮放,局部放大問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • C#利用子線程刷新主線程分享教程

    C#利用子線程刷新主線程分享教程

    本文將詳細介紹C#利用子線程如何刷新主線程,需要了解更多的朋友可以參考下
    2012-11-11
  • C#使用ThoughtWorks.QRCode生成二維碼

    C#使用ThoughtWorks.QRCode生成二維碼

    ThoughtWorks.QRCode是一款功能強勁的動態(tài)鏈接庫,能夠為.net應(yīng)用生成二維碼,這篇文章主要為大家詳細介紹了C#使用ThoughtWorks.QRCode生成二維碼的具體方法,需要的可以參考下
    2024-04-04
  • C#利用ScriptControl動態(tài)執(zhí)行JS和VBS腳本

    C#利用ScriptControl動態(tài)執(zhí)行JS和VBS腳本

    C#中利用ScriptControl動態(tài)執(zhí)行JS和VBS腳本的實現(xiàn)方法,需要的朋友可以參考下
    2013-04-04
  • C#數(shù)據(jù)結(jié)構(gòu)之隊列(Quene)實例詳解

    C#數(shù)據(jù)結(jié)構(gòu)之隊列(Quene)實例詳解

    這篇文章主要介紹了C#數(shù)據(jù)結(jié)構(gòu)之隊列(Quene),結(jié)合實例形式較為詳細的講述了隊列的功能、原理與C#實現(xiàn)隊列的相關(guān)技巧,需要的朋友可以參考下
    2015-11-11
  • winform攔截關(guān)閉按鈕觸發(fā)的事件示例

    winform攔截關(guān)閉按鈕觸發(fā)的事件示例

    這篇文章主要介紹了c# winform攔截關(guān)閉按鈕觸發(fā)的事件示例,大家參考使用吧
    2014-01-01
  • C#中實現(xiàn)控件拖動功能的具體方案

    C#中實現(xiàn)控件拖動功能的具體方案

    文章介紹了WinForms和WPF實現(xiàn)控件拖動的不同方案,包括基礎(chǔ)的單控件拖動、通用拖動類封裝、附加屬性實現(xiàn)、邊界檢測與智能吸附等功能,此外,還討論了工程實踐建議,如性能優(yōu)化和跨平臺方案對比,需要的朋友可以參考下
    2025-12-12
  • C# SDK實現(xiàn)百度云OCR的文字識別功能

    C# SDK實現(xiàn)百度云OCR的文字識別功能

    這篇文章主要為大家詳細介紹了C# SDK實現(xiàn)百度云OCR的文字識別功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-11-11

最新評論

威远县| 会东县| 正镶白旗| 南京市| 治县。| 周至县| 丰都县| 兴仁县| 拉孜县| 嘉峪关市| 开鲁县| 东乡族自治县| 秭归县| 屏边| 江西省| 古蔺县| 辽中县| 长治市| 五莲县| 云南省| 武平县| 天全县| 石棉县| 云浮市| 广汉市| 金堂县| 德清县| 凤山县| 洪洞县| 淄博市| 额敏县| 彰化县| 平南县| 剑阁县| 云梦县| 集贤县| 陆川县| 沁阳市| 任丘市| 花莲市| 阿拉善盟|