如何在C#中将Excel状态栏引用传递给其他类?
我开发了一个基于C#的简易Excel XLL插件,核心功能如下:
- 从模板工作簿(XLSX)收集简单输入信息;
- 根据输入信息判断用户要从哪款第三方软件提取数据,调用对应DLL连接该软件;
- 接收标准化数据结构(在Interfaces.dll中定义的自定义对象集合)并写入Excel工作表。
项目结构上:
- 顶层入口是XLL项目,仅该项目知晓Excel相关内容,可通过ExcelDNA和Microsoft.Office.Interop.Excel获取Excel应用对象引用;
- Interfaces.dll作为独立项目,定义跨项目传递数据的IO类;
- 每个第三方软件对应一个同级项目,负责处理与该软件的COM交互,下层项目无需对接Excel对象。
现在遇到的问题:部分数据收集过程耗时较长,需要在Excel状态栏显示“提取流数据”“提取泵数据”这类提示信息。想知道是否有优雅的方式将XLL中的Excel应用对象引用传递给下层对象,或者能否仅传递Excel Application.StatusBar文本字段的引用?
现有代码示例:
xl.Application app = (xl.Application)ExcelDnaUtil.Application; // 从Excel-DNA环境获取Excel应用对象 app.StatusBar = "Starting export"; HysysExport.HysysExporter exporter = new HysysExport.HysysExporter(sim_version, CompMap, HexMap); results = exporter.GetData();
尝试的写法(但Visual Studio报错):
HysysExport.HysysExporter exporter = new HysysExport.HysysExporter(sim_version, CompMap, HexMap, ref app.StatusBar); results = exporter.GetData();
下层DLL的构造函数:
public HysysExporter(string simVersion, Dictionary<string, string> CompMap, Dictionary<string, string[]> HexMap, ref string xlStatus) { this.simVersion = simVersion; this.CompMap = CompMap; this.HexMap = HexMap; this.xlStatus = xlStatus; }
想了解:
- 这种直接传递
ref app.StatusBar的写法是否可行? - 类字段需要定义成什么形式才能存储字符串的引用而非副本?
- 针对Excel app.StatusBar字段,这种按引用传递的方式是否可行?
注:使用的是.NET 4.8,无法使用后续版本特性。
为什么直接传递ref app.StatusBar不可行
app.StatusBar是一个属性,不是字段,C#中无法直接对属性使用ref关键字传递——ref要求的是变量(字段、局部变量、数组元素),属性本质是get/set方法,没有直接的内存地址可以引用。即使你强行尝试,编译器也会报错,因为属性不符合ref参数的要求。
另外,字符串是.NET中的不可变类型,即使你传递了字符串的引用,当你给xlStatus赋值时,实际上是让它指向了新的字符串实例,并不会同步更新app.StatusBar的值,因为你只是修改了局部引用,而非修改属性本身。
优雅的实现方式(适配.NET 4.8)
1. 使用委托(最轻便的方式)
在下层项目中,不需要依赖Excel的类型,只需定义一个Action<string>类型的委托,用于更新状态文本。
下层DLL代码:
public class HysysExporter { private readonly Action<string> _updateStatus; private string _simVersion; private Dictionary<string, string> _compMap; private Dictionary<string, string[]> _hexMap; public HysysExporter(string simVersion, Dictionary<string, string> compMap, Dictionary<string, string[]> hexMap, Action<string> updateStatus) { _simVersion = simVersion; _compMap = compMap; _hexMap = hexMap; _updateStatus = updateStatus; } public YourDataStructure GetData() { // 提取流数据时更新状态 _updateStatus?.Invoke("提取流数据..."); // 执行流数据提取逻辑 // 提取泵数据时更新状态 _updateStatus?.Invoke("提取泵数据..."); // 执行泵数据提取逻辑 // 完成后重置状态栏(可选) _updateStatus?.Invoke("数据提取完成"); return results; } }
XLL项目调用代码:
xl.Application app = (xl.Application)ExcelDnaUtil.Application; app.StatusBar = "Starting export"; var exporter = new HysysExport.HysysExporter(sim_version, CompMap, HexMap, statusText => app.StatusBar = statusText); results = exporter.GetData(); // 可选:完成后恢复状态栏默认值 app.StatusBar = false;
这种方式的优点:
- 下层项目完全不需要引用Excel相关的程序集,保持了分层的独立性;
- 实现简单,无需额外定义接口或类;
- 符合依赖倒置原则,下层依赖抽象(委托)而非具体的Excel对象。
2. 定义状态更新接口(更规范的方式)
如果需要多个状态更新相关的操作(比如重置状态栏、显示进度等),可以在Interfaces.dll中定义一个接口,让XLL项目实现该接口,再传递给下层项目。
Interfaces.dll中的接口定义:
public interface IStatusUpdater { void UpdateStatus(string statusText); // 可以添加其他方法,比如ResetStatus() }
XLL项目中的实现类:
public class ExcelStatusUpdater : IStatusUpdater { private readonly xl.Application _excelApp; public ExcelStatusUpdater(xl.Application excelApp) { _excelApp = excelApp; } public void UpdateStatus(string statusText) { _excelApp.StatusBar = statusText; // 可选:添加DoEvents避免Excel假死 _excelApp.DoEvents(); } public void ResetStatus() { _excelApp.StatusBar = false; } }
下层DLL代码:
public class HysysExporter { private readonly IStatusUpdater _statusUpdater; // 其他字段... public HysysExporter(string simVersion, Dictionary<string, string> compMap, Dictionary<string, string[]> hexMap, IStatusUpdater statusUpdater) { _simVersion = simVersion; _compMap = compMap; _hexMap = hexMap; _statusUpdater = statusUpdater; } public YourDataStructure GetData() { _statusUpdater?.UpdateStatus("提取流数据..."); // 业务逻辑 _statusUpdater?.UpdateStatus("提取泵数据..."); // 业务逻辑 return results; } }
XLL项目调用代码:
xl.Application app = (xl.Application)ExcelDnaUtil.Application; app.StatusBar = "Starting export"; var statusUpdater = new ExcelStatusUpdater(app); var exporter = new HysysExport.HysysExporter(sim_version, CompMap, HexMap, statusUpdater); results = exporter.GetData(); statusUpdater.ResetStatus();
这种方式的优点:
- 扩展性更好,后续可以轻松添加更多状态相关的操作;
- 代码更规范,符合面向接口编程的思想;
- 下层项目仅依赖Interfaces.dll中的接口,与Excel实现完全解耦。
3. 使用封装类(传递可更新的状态容器)
如果不想用委托或接口,可以定义一个简单的封装类,持有一个可修改的字符串属性,XLL项目负责将该属性与Excel状态栏绑定。
Interfaces.dll中的封装类:
public class StatusContainer { private string _statusText; // 可以添加事件,当状态变化时通知外部更新 public event Action<string> StatusChanged; public string StatusText { get => _statusText; set { _statusText = value; StatusChanged?.Invoke(value); } } }
XLL项目代码:
xl.Application app = (xl.Application)ExcelDnaUtil.Application; app.StatusBar = "Starting export"; var statusContainer = new StatusContainer(); statusContainer.StatusChanged += text => { app.StatusBar = text; app.DoEvents(); }; var exporter = new HysysExport.HysysExporter(sim_version, CompMap, HexMap, statusContainer); results = exporter.GetData(); app.StatusBar = false;
下层DLL代码:
public class HysysExporter { private readonly StatusContainer _statusContainer; // 其他字段... public HysysExporter(string simVersion, Dictionary<string, string> compMap, Dictionary<string, string[]> hexMap, StatusContainer statusContainer) { _simVersion = simVersion; _compMap = compMap; _hexMap = hexMap; _statusContainer = statusContainer; } public YourDataStructure GetData() { _statusContainer.StatusText = "提取流数据..."; // 业务逻辑 _statusContainer.StatusText = "提取泵数据..."; // 业务逻辑 return results; } }
这种方式适合需要在多个地方共享状态更新逻辑的场景,但相比委托和接口,代码稍显繁琐。
关键注意事项
- 无论使用哪种方式,都要确保在长时间操作中定期调用
DoEvents(),避免Excel假死; - 操作完成后记得将状态栏重置为默认值(
app.StatusBar = false;),避免状态栏一直显示自定义文本。
内容的提问来源于stack exchange,提问作者RobBaker

