首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C# 讀寫 INI 檔案完整指南 (五):總結、最佳實踐與進階封裝路線圖 C# 讀寫 INI 檔案完整指南 (五):總結、最佳實踐與進階封裝路線圖

     編輯:關於C語言

系列最終篇:彙整核心知識點、發布生產級封裝類別、提供進階架構路線圖 (強型別繫結、配置提供者整合、跨平台替代方案),助你在實際專案中優雅、安全、高效地使用 INI 檔案。

系列知識點總覽

篇章核心內容關鍵技術
一核心 API 與 P/Invoke 宣告WritePrivateProfileString/GetPrivateProfileString、CharSet.Unicode、SetLastError
二WinForms 交互流程與按鈕邏輯OpenFileDialog、驗證層、緩衝區擴容、預設值語義
三設計器代碼完整版InitializeComponent、控制項實例化、版面配置、事件綁定
四按鈕事件完整邏輯空值語義、狀態回饋 UX、Win32Exception 分層處理
五 (本篇)生產級封裝與架構演進IniFile 類別、強型別 Bind、IConfigurationProvider、跨平台方案

生產級封裝:IniFile 類別 (完整版)


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;

public sealed class IniFile : IDisposable
{
    private readonly string _path;
    private readonly object _sync = new object();
    private bool _disposed;

    // P/Invoke
    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern bool WritePrivateProfileString(string section, string key, string value, string filePath);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern uint GetPrivateProfileString(string section, string key, string defaultValue, StringBuilder retVal, uint size, string filePath);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern int GetPrivateProfileInt(string section, string key, int defaultValue, string filePath);

    [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
    private static extern uint GetPrivateProfileSection(string section, IntPtr keyValueBuffer, uint size, string filePath);

    public IniFile(string path)
    {
        if (string.IsNullOrWhiteSpace(path)) throw new ArgumentException("路徑不可為空", nameof(path));
        _path = Path.GetFullPath(path);
        // 確保目錄存在
        Directory.CreateDirectory(Path.GetDirectoryName(_path));
    }

    // === 寫入 ===
    public void Write(string section, string key, string value)
    {
        CheckDisposed();
        ValidateSectionKey(section, key);
        lock (_sync)
        {
            if (!WritePrivateProfileString(section, key, value ?? "", _path))
                ThrowWin32Error("WritePrivateProfileString");
        }
    }

    public void Write(string section, string key, int value) => Write(section, key, value.ToString());
    public void Write(string section, string key, double value) => Write(section, key, value.ToString("G17", CultureInfo.InvariantCulture));
    public void Write(string section, string key, bool value) => Write(section, key, value ? "1" : "0");
    public void Write(string section, string key, DateTime value) => Write(section, key, value.ToString("o")); // ISO 8601

    // === 讀取 (字串) ===
    public string Read(string section, string key, string defaultValue = "")
    {
        CheckDisposed();
        ValidateSectionKey(section, key);
        lock (_sync)
        {
            var sb = new StringBuilder(256);
            uint len = GetPrivateProfileString(section, key, defaultValue, sb, (uint)sb.Capacity, _path);
            if (len == sb.Capacity - 1)
            {
                sb.Capacity = (int)(len * 2);
                GetPrivateProfileString(section, key, defaultValue, sb, (uint)sb.Capacity, _path);
            }
            return sb.ToString();
        }
    }

    // === 讀取 (強型別) ===
    public T Read<T>(string section, string key, T defaultValue = default)
    {
        string val = Read(section, key, null);
        if (val == null) return defaultValue;

        try
        {
            if (typeof(T) == typeof(string)) return (T)(object)val;
            if (typeof(T) == typeof(int)) return (T)(object)int.Parse(val);
            if (typeof(T) == typeof(long)) return (T)(object)long.Parse(val);
            if (typeof(T) == typeof(double)) return (T)(object)double.Parse(val, CultureInfo.InvariantCulture);
            if (typeof(T) == typeof(bool)) return (T)(object)(val == "1" || val.Equals("true", StringComparison.OrdinalIgnoreCase));
            if (typeof(T) == typeof(DateTime)) return (T)(object)DateTime.ParseExact(val, "o", CultureInfo.InvariantCulture);
            if (typeof(T).IsEnum) return (T)Enum.Parse(typeof(T), val, true);
            // 可擴展:TypeConverter、JsonSerializer 等
        }
        catch { return defaultValue; }
        return defaultValue;
    }

    // === 刪除 ===
    public void DeleteKey(string section, string key)
    {
        CheckDisposed();
        ValidateSectionKey(section, key);
        lock (_sync)
            WritePrivateProfileString(section, key, null, _path);
    }

    public void DeleteSection(string section)
    {
        CheckDisposed();
        if (string.IsNullOrWhiteSpace(section)) throw new ArgumentException("區段不可為空");
        lock (_sync)
            WritePrivateProfileString(section, null, null, _path);
    }

    // === 列舉區段/鍵 ===
    public string[] GetSections()
    {
        CheckDisposed();
        lock (_sync)
        {
            // 先取得所需緩衝區大小
            uint size = GetPrivateProfileSection(null, IntPtr.Zero, 0, _path);
            if (size == 0) return Array.Empty<string>();

            IntPtr ptr = Marshal.AllocHGlobal((int)size);
            try
            {
                GetPrivateProfileSection(null, ptr, size, _path);
                string raw = Marshal.PtrToStringUni(ptr, (int)size);
                return ParseNullSeparated(raw);
            }
            finally { Marshal.FreeHGlobal(ptr); }
        }
    }

    public KeyValuePair<string, string>[] GetKeys(string section)
    {
        CheckDisposed();
        if (string.IsNullOrWhiteSpace(section)) throw new ArgumentException("區段不可為空");
        lock (_sync)
        {
            uint size = GetPrivateProfileSection(section, IntPtr.Zero, 0, _path);
            if (size == 0) return Array.Empty<KeyValuePair<string, string>>();

            IntPtr ptr = Marshal.AllocHGlobal((int)size);
            try
            {
                GetPrivateProfileSection(section, ptr, size, _path);
                string raw = Marshal.PtrToStringUni(ptr, (int)size);
                return ParseKeyValuePairs(raw);
            }
            finally { Marshal.FreeHGlobal(ptr); }
        }
    }

    private static string[] ParseNullSeparated(string raw)
    {
        var list = new List<string>();
        int start = 0;
        for (int i = 0; i < raw.Length; i++)
        {
            if (raw[i] == '\0')
            {
                if (i > start) list.Add(raw.Substring(start, i - start));
                start = i + 1;
                if (start < raw.Length && raw[start] == '\0') break; // 雙 \0 結束
            }
        }
        return list.ToArray();
    }

    private static KeyValuePair<string, string>[] ParseKeyValuePairs(string raw)
    {
        var list = new List<KeyValuePair<string, string>>();
        int start = 0;
        for (int i = 0; i < raw.Length; i++)
        {
            if (raw[i] == '\0')
            {
                if (i > start)
                {
                    string pair = raw.Substring(start, i - start);
                    int eq = pair.IndexOf('=');
                    if (eq > 0)
                        list.Add(new KeyValuePair<string, string>(pair.Substring(0, eq), pair.Substring(eq + 1)));
                }
                start = i + 1;
                if (start < raw.Length && raw[start] == '\0') break;
            }
        }
        return list.ToArray();
    }

    // === 輔助 ===
    private void ValidateSectionKey(string section, string key)
    {
        if (string.IsNullOrWhiteSpace(section)) throw new ArgumentException("區段不可為空", nameof(section));
        if (string.IsNullOrWhiteSpace(key)) throw new ArgumentException("鍵不可為空", nameof(key));
    }

    private void CheckDisposed()
    {
        if (_disposed) throw new ObjectDisposedException(nameof(IniFile));
    }

    private static void ThrowWin32Error(string api)
    {
        int err = Marshal.GetLastWin32Error();
        throw new Win32Exception(err, $"{api} failed (0x{err:X8})");
    }

    public void Dispose()
    {
        if (!_disposed)
        {
            _disposed = true;
            // 可選:Flush 快取、備份等
        }
    }
}

使用範例


using (var ini = new IniFile(@"C:\Config\app.ini"))
{
    // 寫入
    ini.Write("Database", "Server", "192.168.1.100");
    ini.Write("Database", "Port", 5432);
    ini.Write("Database", "UseSsl", true);
    ini.Write("Database", "Timeout", TimeSpan.FromSeconds(30));

    // 讀取強型別
    string server = ini.Read<string>("Database", "Server", "localhost");
    int port = ini.Read<int>("Database", "Port", 5432);
    bool ssl = ini.Read<bool>("Database", "UseSsl", false);
    TimeSpan timeout = ini.Read<TimeSpan>("Database", "Timeout", TimeSpan.FromSeconds(10)); // 需擴展解析

    // 列舉
    foreach (var section in ini.GetSections())
        Console.WriteLine($"[{section}]");
    foreach (var kv in ini.GetKeys("Database"))
        Console.WriteLine($"  {kv.Key} = {kv.Value}");
}

進階架構路線圖

Level 1:強型別設定類別繫結 (類似 Options Pattern)


public class DatabaseSettings
{
    public string Server { get; set; } = "localhost";
    public int Port { get; set; } = 5432;
    public bool UseSsl { get; set; }
    public TimeSpan Timeout { get; set; } = TimeSpan.FromSeconds(10);
}

// 擴充方法
public static class IniFileExtensions
{
    public static T Bind<T>(this IniFile ini, string section) where T : new()
    {
        var obj = new T();
        var props = typeof(T).GetProperties();
        foreach (var prop in props)
        {
            string val = ini.Read(section, prop.Name, null);
            if (val != null)
            {
                var converter = TypeDescriptor.GetConverter(prop.PropertyType);
                if (converter.CanConvertFrom(typeof(string)))
                    prop.SetValue(obj, converter.ConvertFromInvariantString(val));
            }
        }
        return obj;
    }

    public static void Save<T>(this IniFile ini, string section, T obj)
    {
        var props = typeof(T).GetProperties();
        foreach (var prop in props)
        {
            var val = prop.GetValue(obj);
            if (val != null)
                ini.Write(section, prop.Name, val.ToString());
        }
    }
}

// 使用
var db = ini.Bind<DatabaseSettings>("Database");
db.Port = 3306;
ini.Save("Database", db);

Level 2:ASP.NET Core IConfigurationProvider 整合


public class IniConfigurationSource : IConfigurationSource
{
    public string Path { get; set; }
    public bool Optional { get; set; }
    public bool ReloadOnChange { get; set; }

    public IConfigurationProvider Build(IConfigurationBuilder builder)
        => new IniConfigurationProvider(this);
}

public class IniConfigurationProvider : ConfigurationProvider
{
    private readonly IniConfigurationSource _source;
    private FileSystemWatcher _watcher;

    public IniConfigurationProvider(IniConfigurationSource source) => _source = source;

    public override void Load()
    {
        using (var ini = new IniFile(_source.Path))
        {
            Data.Clear();
            foreach (var section in ini.GetSections())
            {
                foreach (var kv in ini.GetKeys(section))
                {
                    Data[$"{section}:{kv.Key}"] = kv.Value; // 扁平化鍵
                }
            }
        }

        if (_source.ReloadOnChange)
        {
            _watcher = new FileSystemWatcher(Path.GetDirectoryName(_source.Path), Path.GetFileName(_source.Path))
            {
                NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size
            };
            _watcher.Changed += (s, e) => Load();
            _watcher.EnableRaisingEvents = true;
        }
    }

    public override void Dispose()
    {
        _watcher?.Dispose();
        base.Dispose();
    }
}

// 註冊
var config = new ConfigurationBuilder()
    .AddIniFile("appsettings.ini", optional: true, reloadOnChange: true)
    .Build();

Level 3:跨平台替代方案 (Linux/macOS/Container)

方案特點API 風格
ini-parser (NuGet)純 .NET、無 P/Invoke、支援註解/巢狀區段IniData[section][key]
Microsoft.Extensions.Configuration.Ini官方配置提供者、支援 reload、DI 整合IConfiguration["section:key"]
YAML/JSON (推薦新專案)標準化、工具鏈豐富、支援複雜結構強型別 Options Pattern

最佳實踐檢核表

  • ☐ 路徑:絕對路徑、目錄預建、權限檢查
  • ☐ 編碼:統一 UTF-16 LE (CharSet.Unicode),或雙版本封裝支援 ANSI
  • ☐ 執行緒安全:lock 保護所有 P/Invoke 呼叫
  • ☐ 錯誤處理:SetLastError=true + Win32Exception、區分檔案不存在/權限/共用違規
  • ☐ 資源釋放:IDisposable、Marshal.FreeHGlobal、GC.Collect (必要時)
  • ☐ 備份策略:寫入前 .bak、定期輪替
  • ☐ 測試覆蓋:空值、刪除、中文、長值、並發、跨平台
  • ☐ 遷移路徑:新專案優先 appsettings.json + Options Pattern,INI 僅作舊系統相容

結語:五篇系列從 kernel32.dll 底層 API 走到 IConfigurationProvider 架構整合,完整呈現了 C# 操作 INI 的演進全景。核心心法三句:封裝底層細節、強型別消除魔術字串、架構對齊現代配置模式。若新專案無舊系統包袱,請直接擁抱 appsettings.json + Microsoft.Extensions.Configuration;若必須用 INI,請拿走 IniFile 類別,它已準備好服務於生產環境。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved