You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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;
}

想了解:

  1. 这种直接传递ref app.StatusBar的写法是否可行?
  2. 类字段需要定义成什么形式才能存储字符串的引用而非副本?
  3. 针对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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 02:58:12