C#配置文件深度解析:App.config与.settings文件的核心区别与实战策略
1. 项目概述为什么C#开发者绕不开配置文件干了这么多年C#开发从桌面程序到Web服务我发现一个项目里最不起眼但又最要命的部分往往就是配置文件。新手可能觉得不就是几个键值对吗放哪儿不是放但真到了项目上线、需要动态调整参数、或者不同环境部署的时候一个混乱的配置管理能让你加班到怀疑人生。今天我们就来彻底盘一盘C#里最经典的两个配置文件App.config以及它的Web兄弟Web.config和.settings文件。这不仅仅是知道怎么用更要搞清楚它们的设计哲学、适用场景以及那些官方文档里不会写的“坑”。简单来说App.config是.NET Framework时代遗留下来的“元老”它是一个基于XML的、声明式的配置文件你可以往里塞连接字符串、应用设置、甚至整个WCF服务的配置节。而.settings文件则是Visual Studio提供的一种强类型、设计时支持的配置管理方式它会在背后自动生成一个Settings类让你能用Properties.Settings.Default.MySetting这样的属性来访问编译时还会帮你生成一个app.config文件。很多人分不清它们的关系经常混用导致配置散落各处维护起来一头雾水。这篇文章我就结合自己踩过的坑和最佳实践帮你理清思路构建一个清晰、健壮的配置管理策略。2. 核心概念深度解析App.config与.settings的本质区别2.1 App.config灵活但原始的配置基石App.config是.NET应用程序配置的底层载体。对于可执行程序如WinForms、控制台应用在编译后它会改名为[你的程序集名称].exe.config并放在输出目录下。对于Web应用对应的就是Web.config。它的核心是一个XML文件结构非常自由。你可以在configuration根节点下定义各种标准或自定义的配置节。这种灵活性既是优点也是缺点。优点标准化支持.NET Framework内置了对许多标准配置节如appSettings,connectionStrings,system.serviceModel的读取支持使用ConfigurationManager类即可轻松访问。深度集成许多.NET框架和第三方库如Log4Net, Entity Framework都原生支持从App.config读取配置你只需要在对应节点下进行配置即可。环境无关配置文件独立于编译后的程序集修改配置无需重新编译代码特别适合部署后调整。缺点弱类型所有配置值读取出来都是字符串string需要开发者手动进行类型转换如int.Parse,bool.Parse容易引入运行时错误。魔法字符串在代码中访问配置需要使用字符串键名如ConfigurationManager.AppSettings[“MyKey”]。一旦键名拼写错误只有在运行时才会暴露问题重构也不友好。缺乏设计时支持在Visual Studio中没有智能提示和编译时检查来帮助你确认配置项的存在和类型。一个典型的App.config片段如下?xml version1.0 encodingutf-8? configuration appSettings add keyApiBaseUrl valuehttps://api.example.com/ add keyMaxRetryCount value3/ add keyEnableFeatureX valuetrue/ /appSettings connectionStrings add nameDefaultConnection connectionStringServer.;DatabaseMyDb;Trusted_ConnectionTrue; providerNameSystem.Data.SqlClient/ /connectionStrings startup supportedRuntime versionv4.0 sku.NETFramework,Versionv4.8/ /startup /configuration在代码中这样访问string apiUrl ConfigurationManager.AppSettings[ApiBaseUrl]; int maxRetry int.Parse(ConfigurationManager.AppSettings[MaxRetryCount]); string connStr ConfigurationManager.ConnectionStrings[DefaultConnection].ConnectionString;注意ConfigurationManager类位于System.Configuration程序集你需要为项目添加对此程序集的引用。在.NET Core/5及更高版本中这套机制已被新的IConfiguration体系取代但理解它对于维护遗留项目至关重要。2.2 .settings文件强类型的设计时利器.settings文件是Visual Studio提供的一种“设计器”文件扩展名通常是.settings如Settings1.settings。它的存在是为了解决App.config弱类型和魔法字符串的问题。当你双击项目中的.settings文件时会打开一个可视化的设计器界面。你可以在这里添加配置项并指定其名称、类型.NET基本类型如string,int,bool或通过浏览程序集选择自定义类型、作用域Application或User和值。它的工作原理是这样的设计时生成代码Visual Studio会根据你在设计器中的设置自动生成一个位于Properties命名空间下的Settings类默认是Properties.Settings。这个类继承自ApplicationSettingsBase为你定义的每个配置项生成一个强类型的属性。编译时同步同时它还会将Application作用域的设置值同步写入到项目的app.config文件中作为applicationSettings节下的内容。User作用域的设置则存储在每个用户的独立配置存储中如LocalAppData。运行时读取生成的Settings类封装了从app.config对于Application设置或用户隔离存储对于User设置读取配置的逻辑。优点强类型访问通过Properties.Settings.Default.MySetting访问直接得到int、bool等类型无需手动转换。编译时检查因为配置项是作为类的属性存在的如果你在代码中删除了某个属性引用编译器会报错。重命名配置项也可以通过重构工具安全进行。智能提示在代码编辑器中输入Properties.Settings.Default.后你会得到所有配置项的智能提示。作用域管理清晰区分了应用程序级全局、只读和用户级可读写、用户特定的配置。缺点灵活性受限配置结构相对固定不适合存储非常复杂的、嵌套的配置结构。与App.config耦合Application作用域的设置最终是存储在app.config里的你依然需要理解app.config的机制来处理部署和环境问题。主要适用于客户端应用对于Web应用ASP.NET Web Forms虽然也能用但不如新的配置系统方便且用户作用域在Web场景下意义不大。使用起来非常简单// 直接使用强类型属性 string apiUrl Properties.Settings.Default.ApiBaseUrl; int maxRetry Properties.Settings.Default.MaxRetryCount; bool isEnabled Properties.Settings.Default.EnableFeatureX; // 修改用户作用域的设置并保存 Properties.Settings.Default.UserTheme “Dark”; Properties.Settings.Default.Save(); // 保存用户设置2.3 关系梳理谁是谁的谁这是最容易混淆的点。简单来说.settings文件是设计时的“视图”和“代码生成器”。它为你提供了一种友好、安全的方式来定义和访问配置。App.config文件是运行时的“数据源”之一针对Application作用域设置。.settings设计器将Application作用域的配置“同步”到了App.config中。生成的Settings类是桥梁。它封装了从App.config读Application设置和用户隔离存储读写User设置读取数据的逻辑。所以你通常会在项目中同时看到两者。你通过.settings设计器管理配置而最终的配置值Application部分保存在App.config中随应用程序分发。3. 实战配置策略与高级用法理解了基本概念我们来看看在实际项目中如何有效地使用它们并处理一些复杂场景。3.1 混合使用策略扬长避短在我的项目中我通常采用一种混合策略使用.settings管理应用程序核心设置例如数据库连接字符串可加密、API端点、功能开关、超时时间等。利用其强类型和编译时检查的优势。保留App.config的appSettings节用于简单键值对和第三方库集成一些简单的、可能频繁变更的键值对或者某些第三方库强制要求配置在appSettings里我会直接在这里配置。但我会严格控制数量并考虑未来迁移到.settings。使用connectionStrings节管理数据库连接这是标准做法便于统一管理和可能的加密处理。复杂配置使用自定义配置节对于结构复杂的配置如一个包含多个服务器地址和权重的负载均衡器列表我会创建自定义的配置节和配置元素类。这需要继承ConfigurationSection和ConfigurationElement虽然稍显繁琐但能提供最强的结构化和类型安全。创建自定义配置节的示例首先定义配置类using System.Configuration; public class ServerElement : ConfigurationElement { [ConfigurationProperty(“name”, IsRequired true)] public string Name (string)base[“name”]; [ConfigurationProperty(“address”, IsRequired true)] public string Address (string)base[“address”]; [ConfigurationProperty(“weight”, DefaultValue 1)] public int Weight (int)base[“weight”]; } public class ServerCollection : ConfigurationElementCollection { protected override ConfigurationElement CreateNewElement() new ServerElement(); protected override object GetElementKey(ConfigurationElement element) ((ServerElement)element).Name; public ServerElement this[int index] (ServerElement)BaseGet(index); } public class MyCustomSection : ConfigurationSection { [ConfigurationProperty(“servers”, IsDefaultCollection false)] [ConfigurationCollection(typeof(ServerCollection))] public ServerCollection Servers (ServerCollection)base[“servers”]; }然后在App.config中配置configuration configSections section name“myCustomSection” type“YourNamespace.MyCustomSection, YourAssembly”/ /configSections myCustomSection servers add name“Server1” address“192.168.1.10” weight“5”/ add name“Server2” address“192.168.1.11” weight“3”/ /servers /myCustomSection /configuration最后在代码中访问var section (MyCustomSection)ConfigurationManager.GetSection(“myCustomSection”); foreach (ServerElement server in section.Servers) { Console.WriteLine($“{server.Name}: {server.Address} (Weight: {server.Weight})”); }3.2 环境配置与变换一个关键的部署问题无论是App.config还是.settings直接硬编码在文件里的配置值都面临环境切换开发、测试、生产的问题。手动修改文件极易出错。解决方案1使用配置变换Configuration Transforms这是对于Web项目Web.config的官方支持。你可以创建Web.Debug.config、Web.Release.config甚至自定义的Web.Staging.config。在构建时MSBuild会根据当前构建配置将变换文件中的修改如替换、插入、删除节点应用到主Web.config上。但这主要适用于ASP.NET Web项目。解决方案2使用慢速但通用的文件替换对于桌面应用没有内置的变换机制。一种常见做法是在项目中维护多个配置模板文件如App.Debug.config、App.Release.config。在项目的生成后事件Post-build event中编写脚本或使用MSBuild任务根据当前构建配置将对应的模板文件复制到输出目录并重命名为App.config。也可以使用第三方NuGet包如SlowCheetah它为任何XML配置文件包括App.config提供了类似Web配置变换的功能。解决方案3将环境特定配置外置更现代和灵活的做法是不在App.config中存储任何环境敏感的配置如连接字符串、API密钥。而是在App.config中只存储一个环境标识符如add key“Environment” value“Development” /。根据这个标识符在应用程序启动时从一个外部文件如appsettings.Production.json、环境变量或配置服务中心加载该环境对应的具体配置值。这是.NET Core/5中IConfiguration体系的核心理念也强烈建议在.NET Framework新项目中借鉴。3.3 敏感信息处理连接字符串与密钥的安全直接把生产数据库密码写在App.config里是极其危险的。.NET Framework提供了一些基础的保护机制对connectionStrings进行加密可以使用.NET自带的aspnet_regiis工具对配置节进行加密。加密后配置文件里是一堆乱码但应用程序在运行时能正常解密读取。# 使用RSA密钥容器加密需要管理员权限 aspnet_regiis -pef “connectionStrings” [你的网站物理路径] -prov “RsaProtectedConfigurationProvider”在代码中你无需做任何改变ConfigurationManager.ConnectionStrings会自动解密。解密时需要运行应用程序的账户有访问RSA密钥容器的权限。这种方式适合服务器环境固定部署。使用User作用域存储敏感用户数据对于需要用户输入的密码等可以存储在.settings文件的User作用域设置中。这些数据会使用当前Windows用户的凭据进行加密后存储在用户的漫游或本地配置目录下相对安全。最佳实践对于真正的生产环境机密如数据库密码、第三方API密钥我建议永远不要将它们提交到源代码仓库的配置文件中。应该在开发环境使用占位符或本地机密。在部署时通过部署管道如Azure DevOps, Jenkins将真实的机密作为环境变量注入或者从安全的密钥库如Azure Key Vault, HashiCorp Vault中读取并在应用启动时动态设置到配置对象中。4. 从传统到现代向.NET Core/5配置体系迁移的思考如果你正在维护一个传统的.NET Framework项目但计划未来迁移到.NET 6/8等现代版本那么现在就开始规范配置管理会事半功倍。新的配置系统Microsoft.Extensions.Configuration更加强大、灵活和统一。提前做的准备集中配置访问不要在你的业务代码中到处散落着ConfigurationManager.AppSettings[...]或Properties.Settings.Default...的调用。创建一个或多个专门的配置类如AppConfig,DatabaseConfig在应用程序启动时一次性从配置文件读取所有配置并填充到这些类的实例中。然后通过依赖注入DI容器将这些配置实例注册为单例供其他类使用。这为你将来切换到从JSON文件、环境变量等读取配置扫清了障碍。区分环境配置立即开始实践环境特定配置外置的模式。哪怕只是简单地在项目根目录放一个appsettings.Development.json和一个appsettings.Production.json并在代码中实现一个简单的加载逻辑。减少对ConfigurationManager的静态依赖新的配置系统是基于接口IConfiguration的鼓励依赖注入。提前将静态调用包装成可注入的服务会让迁移更平滑。一个简单的配置集中化示例public class AppSettings { public string ApiBaseUrl { get; set; } public int MaxRetryCount { get; set; } public DatabaseSettings Database { get; set; } } public class DatabaseSettings { public string ConnectionString { get; set; } } // 在程序启动时如Global.asax Application_Start或控制台Main方法 var appSettings new AppSettings { ApiBaseUrl ConfigurationManager.AppSettings[“ApiBaseUrl”], MaxRetryCount int.Parse(ConfigurationManager.AppSettings[“MaxRetryCount”]), Database new DatabaseSettings { ConnectionString ConfigurationManager.ConnectionStrings[“DefaultConnection”]?.ConnectionString } }; // 然后将appSettings注册到你的DI容器中5. 常见陷阱、调试技巧与性能考量5.1 那些年我踩过的坑配置文件未复制到输出目录这是最常见的问题。确保App.config或.settings文件的“复制到输出目录”属性设置为“始终复制”或“如果较新则复制”。右键点击文件 - 属性 - 复制到输出目录。修改了.settings设计器但App.config没更新.settings文件修改后需要编译项目才会将Application作用域的更改同步到App.config。有时Visual Studio会抽风手动清理解决方案并重新生成可以解决。用户作用域设置不保存修改User作用域设置后必须调用Properties.Settings.Default.Save()方法更改才会持久化。Application作用域设置是只读的尝试修改会在运行时抛出异常。配置值包含特殊字符在XML中,,等字符需要转义分别为amp;,lt;,gt;。连接字符串中如果包含这些字符很容易导致配置文件解析失败。对于复杂的连接字符串可以考虑使用CDATA节。64位系统上的32位程序如果你的程序是32位x86的在64位系统上运行时访问某些特殊的配置区域如使用LocalUserAppDataPath可能会指向不同的目录导致找不到用户设置。要特别注意程序的平台目标设置。5.2 调试与排查配置文件到底在哪在代码中打印AppDomain.CurrentDomain.SetupInformation.ConfigurationFile可以获取当前运行时正在使用的配置文件完整路径。对于用户设置可以打印Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData)来找到用户配置存储的根目录。配置读取失败使用try-catch包裹配置读取代码并记录详细的异常信息。常见的异常有ConfigurationErrorsException配置格式错误和SettingsPropertyNotFoundException.settings中访问了不存在的属性。使用ConfigurationManager.OpenExeConfiguration这个方法可以让你以编程方式加载和检查任何配置文件的内容对于调试复杂的配置加载逻辑非常有用。5.3 性能与最佳实践缓存配置值ConfigurationManager的读取操作不是零成本的尤其是第一次访问时会解析整个XML文件。对于频繁访问的配置项应该在应用程序启动时读取一次并缓存在内存变量或静态属性中。.settings的默认实例Properties.Settings.Default是一个单例其内部已经实现了缓存。首次访问时会加载所有设置后续访问很快。所以不必自己再包装一层缓存。避免在热路径中频繁读取绝对不要在循环内部或每次请求都去读配置文件。这会造成不必要的I/O开销。监控配置文件变更对于需要动态更新的配置如功能开关可以使用FileSystemWatcher来监控配置文件的变化并在变化时重新加载配置。但要注意线程安全和文件锁的问题。更复杂的场景可以考虑使用像OptionsMonitor来自.NET Core这样的模式。配置文件是应用程序的“外置大脑”管理好它你的项目就成功了一半。从理解App.config和.settings的基本用法开始逐步建立起适合自己项目的配置分层、环境隔离和安全策略这是一个资深C#开发者必备的技能。在向现代.NET迁移的浪潮中提前用现代配置管理的思维来重构旧项目会让未来的升级之路平坦许多。记住清晰的配置结构不仅是为了机器能读懂更是为了你和你的团队成员在三个月甚至三年后还能一眼看明白这个程序到底是怎么跑起来的。

相关新闻

不熬夜的毕业秘诀[特殊字符]终于不用硬撑着写论文了

不熬夜的毕业秘诀[特殊字符]终于不用硬撑着写论文了

毕业季最消耗人的,从来不是论文难度本身。 是反复被导师打回的焦虑、是格式改不完的烦躁、是文献梳理毫无头绪、是答辩临近的手足无措。 以前总觉得,别人顺利毕业都是靠硬熬,后来才发现:选对工具,真的可以少走所有弯…

2026/10/12 6:17:44 阅读更多 →
Android Bitmap内存优化全解析:从原理到实战避坑指南

Android Bitmap内存优化全解析:从原理到实战避坑指南

1. 项目概述:从“内存怪兽”到性能基石在安卓开发的世界里,Bitmap(位图)是一个让人又爱又恨的存在。爱它,是因为它是图像展示的绝对核心,从应用图标到高清大图,从滤镜效果到游戏贴图&#xff0c…

2026/9/27 1:12:12 阅读更多 →
Cocos Creator跨平台游戏开发:从核心架构到性能优化实战

Cocos Creator跨平台游戏开发:从核心架构到性能优化实战

1. 项目概述:为什么Cocos引擎值得每一位开发者关注? 如果你是一名游戏开发者,或者对游戏开发感兴趣,那么“Cocos引擎”这个名字你一定不陌生。但你可能不知道的是,它早已不是那个只做2D小游戏的“轻量级”工具了。今天…

2026/10/9 19:53:47 阅读更多 →

最新新闻

Python自定义异常的全面指南(入门到实践)

Python自定义异常的全面指南(入门到实践)

前言 自定义异常是那种「语法一分钟学会、设计想三年」的东西。写一个 class MyError(Exception): pass 只需要一行,但什么时候该定义新异常、新异常该放在继承树的哪一层、异常里该不该带数据——这些问题没有语法能替你回答。 有一个错误很常见:为了「…

2026/10/12 6:17:40 阅读更多 →
748GB统一内存实测:桌面AI工作站如何本地跑万亿参数模型

748GB统一内存实测:桌面AI工作站如何本地跑万亿参数模型

前阵子有台机器刚发布就引起了不少讨论——748GB统一内存、桌面级规格、本地跑万亿参数级模型,我第一反应是“这怕不是把一整套小型机房塞进了塔式机箱里”。仔细看完参数和实际表现之后,我的结论更直接:这台设备真正考验人的地方&#xff0c…

2026/10/12 6:17:40 阅读更多 →
PVE迁移后网络失联?vmbr网桥绑定物理网卡失败排查与修复

PVE迁移后网络失联?vmbr网桥绑定物理网卡失败排查与修复

经常在虚拟化环境里折腾的人,应该都体会过那种“迁移一时爽,网络火葬场”的时刻。在PVE(Proxmox VE)平台上把整套虚拟化环境从一台物理机搬到另一台,本来以为改改启动顺序、导入下备份就能收工,结果重启之后…

2026/10/12 6:17:40 阅读更多 →
Python装饰器原理与实战全解

Python装饰器原理与实战全解

前言 装饰器(decorator)是 Python 里语法糖用得最成功的一处:deco 看上去像给函数加了个标签,实际上它做的只是把下面的函数对象传进 deco,再用返回值覆盖原来的名字。整个过程就是一次普通的函数调用加一次普通赋值。…

2026/10/12 6:17:40 阅读更多 →
C++解释器模式实战:从经典GoF到std::variant与现代变体

C++解释器模式实战:从经典GoF到std::variant与现代变体

C里的解释器模式,说实话有点像“教科书常驻嘉宾、实战里没人爱用”的设计模式。很多人一看到GoF那套词——AbstractExpression、TerminalExpression、NonterminalExpression——就觉得这是给编译器课准备的玩具,跟日常工作没关系。但你一旦开始写规则引擎…

2026/10/12 6:17:40 阅读更多 →
特价股票与全球数字创新网络效应:识别被错杀的成长股

特价股票与全球数字创新网络效应:识别被错杀的成长股

拿“特价股票”和“全球数字创新网络效应”放到同一个标题里,乍一看像是把商超折扣海报和某场科技峰会的主题硬凑在一起。我最初也觉得这两件事隔着十万八千里,直到拿真实标的做过几轮交叉验证,才发现这恰恰是一个被大多数人忽视的研投视角。…

2026/10/12 6:16:39 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/11 14:36:54 阅读更多 →