PAYATHON 2026

如何访问已完成 C# 软件中的 hash table

排障赵师傅

结论

Hashtable 或 Dictionary<TKey,TValue> 通常是程序运行时在内存中创建的对象,安装目录里一般没有对应的 .txt、.mdb 或其他固定文件。集合的结构定义可能来自 .exe 或 .dll 中的代码,表内数据通常不会直接“存放在 executable 里”。

如果出现类似下面的错误:

An item with the same key has already been added.
Key: 1

说明程序准备向集合添加 key 1 时,集合中已经有相同的 key。重复项可能来自程序逻辑、数据库、配置文件或报告模板。直接删除内存中的 key,只会影响当前进程,还可能破坏程序状态。排查时应先确认这个 key 来自哪里。

为什么在安装目录中找不到 hash table

在 C# 中,下面这样的集合通常位于进程内存中:

var reports = new Dictionary<int, Report>();
reports.Add(1, report);

程序退出后,该对象也会消失。只有软件将它序列化并写入数据库、配置文件或其他持久化存储,数据才会保留下来。

.exe 和 .dll 通常包含:

  • 编译后的 .NET IL 代码;
  • 类型和方法的元数据;
  • 字符串、图标等嵌入资源;
  • 少量写死在代码中的初始数据。

程序运行后动态加入集合的完整内容通常不在这些文件中。即使集合属于某个 static 字段,它仍主要存在于进程内存中。

.h 文件主要用于 C 和 C++,纯 C# 程序通常不会通过头文件描述 Hashtable。如果软件包中有 .h 文件,可能是因为其中还包含原生组件、SDK 或第三方库。

先确认错误是否真的来自 hash table

“key 已存在”并不代表安装目录中有一个名为 hash table 的文件。常见原因包括:

  1. Hashtable.Add() 或 Dictionary.Add() 收到了重复 key;
  2. 数据库插入操作违反了主键或唯一索引约束;
  3. 报告模板中的字段、列或控件使用了相同标识符;
  4. 配置文件中出现重复的名称、ID 或编号;
  5. 软件读取数据后调用 ToDictionary(),而源数据中存在重复值;
  6. 报告编号生成器、缓存或序列号没有正确更新。

排查时先取得完整错误信息,包括异常类型、内部异常和调用堆栈。例如:

System.ArgumentException
System.Collections.Generic.Dictionary`2.Add(...)

这类信息通常表示冲突发生在内存集合中。

如果错误中包含以下内容:

PRIMARY KEY constraint
UNIQUE constraint
duplicate key
Cannot insert duplicate key

应检查数据库,无需查找 .NET Hashtable。

建议的排查顺序

1. 保留现场并备份数据

修改前先备份软件的数据库、报告模板和配置文件。不要直接编辑生产数据库,也不要用十六进制编辑器修改 .exe 或 .dll。

如果软件支持导出配置、恢复默认设置或回滚报告模板,优先使用这些功能。

2. 复现并记录完整错误

记录以下信息:

  • 完整异常文本;
  • 异常类型;
  • 调用堆栈;
  • 出错报告的名称和编号;
  • 修改过的设置及修改时间;
  • 是否所有报告都会失败;
  • 是否只有当前用户或当前工作站会失败。

如果软件有日志,可以检查安装目录以及以下常见位置:

%APPDATA%
%LOCALAPPDATA%
%PROGRAMDATA%

还应查看 Windows Event Viewer 中的 Application 日志。日志的实际存储位置由软件设计决定,无法仅根据 C# 语言判断。

3. 查找持久化配置和数据源

可以在安装目录和配置目录中查找:

*.config
*.json
*.xml
*.ini
*.db
*.sqlite
*.mdf
*.sdf
*.mdb
*.accdb

.NET Framework 应用也可能在 ApplicationName.exe.config 中保存数据库连接信息,例如:

<connectionStrings>
  <add name="MainDatabase"
       connectionString="..." />
</connectionStrings>

不要公开含有用户名、密码、服务器地址或许可证信息的连接字符串。

部分设置可能保存在 Windows Registry 中,例如:

HKEY_CURRENT_USER\Software\<Vendor>\<Product>
HKEY_LOCAL_MACHINE\Software\<Vendor>\<Product>

注册表路径没有统一标准。修改前先导出相关项,并确认相关操作符合软件许可和公司的安全规定。

4. 对比正常环境

如果有另一台未修改相关设置且运行正常的工作站,可以比较以下内容:

  • 用户配置文件;
  • 报告模板;
  • 注册表项;
  • 数据库中的相关记录;
  • 软件版本和插件版本。

比较范围应集中在故障时间、报告类型和相关设置上,不要直接复制整套配置并覆盖生产环境。

5. 检查报告定义中的重复 ID

如果只有某个报告出错,先检查其报告模板。某项设置可能复制了字段、分组、参数或控件,同时保留了原来的 ID 1。生成报告时,程序会把这些项目加入字典,第二个 ID 1 就会触发异常。

遇到这种情况,应删除重复的报告元素、恢复模板,或重新创建受影响的报告。修改进程内存无法解决数据来源的问题。

有源码时如何查看集合

如果可以调试自己的代码,可以在断点处查看 Hashtable 或 Dictionary:

foreach (DictionaryEntry entry in table)
{
    Console.WriteLine($"Key={entry.Key}, Value={entry.Value}");
}

对于泛型字典:

foreach (KeyValuePair<int, Report> entry in reports)
{
    Console.WriteLine($"Key={entry.Key}, Value={entry.Value}");
}

检查并删除指定 key 的写法如下:

if (reports.ContainsKey(1))
{
    reports.Remove(1);
}

旧式 Hashtable 的写法是:

if (table.ContainsKey(1))
{
    table.Remove(1);
}

整数 1 和字符串 "1" 是两个不同的 key:

table.Add(1, "integer key");
table.Add("1", "string key");

因此,这两项可以同时存在。

如果问题出在重复调用 Add(),删除已有项未必符合业务要求。需要先确认程序应该拒绝重复、更新原值,还是生成新的 ID。字典索引器会替换已有值:

reports[1] = report;

Add() 在 key 已存在时会抛出异常:

reports.Add(1, report);

不了解业务规则时,不要把所有 Add() 直接改成索引器。

只有编译文件时能否查看

如果程序是托管的 .NET 程序,并且已经获得公司授权、软件许可也允许,可以使用反编译器检查 .exe 和 .dll 中的类型和方法。可搜索:

Hashtable
Dictionary<
.Add(
ToDictionary(
ContainsKey(

也可以搜索错误消息中的原始英文文本,定位抛出异常的位置。

反编译可以帮助了解程序如何建立集合,但通常无法显示某次运行中的实时内容。查看运行时对象需要附加调试器或分析进程转储。商业软件可能启用了混淆、反调试或完整性校验。擅自修改程序集可能违反许可条款,也可能导致数字签名失效。

进程转储中可能含有客户资料、凭据或其他敏感信息,只能在公司授权的测试环境中采集和保存。

哪些信息最有助于继续定位

相比提供大量 .h 或 .dll 文件,以下信息更有助于定位问题:

  • 完整错误消息和异常类型;
  • 调用堆栈;
  • 软件使用的数据库类型;
  • 修改过的设置名称;
  • 问题是否只影响某个报告;
  • 日志中错误前后的记录;
  • 是否能在测试环境中稳定复现。

提交这些信息前,应对密码、连接字符串、客户数据、许可证号和商业机密进行脱敏。

这类故障需要确认重复 key 1 的来源,无需寻找所谓的“hash table 文件”。可以先检查完整异常、报告模板、配置和数据库记录。这通常比修改可执行文件或运行时内存更安全。

备注:内容仅供参考。