C# 与 VB.NET 互操作实战:从 WPF 调用 VB.NET,再由 VB.NET 调用 C#

在维护 .NET 项目时,我们有时会遇到这样的情况:桌面程序主要用 C# 编写,但已有业务模块是 VB.NET;或者新旧模块分属不同语言,希望逐步复用,而不是一次性重写。

只要这些项目面向兼容的 .NET 目标框架,并通过程序集公开类型和成员,C# 与 VB.NET 就可以像调用普通 .NET 类库一样互相调用。本文以一个 .NET 8 WPF 示例说明完整过程:C# 界面调用 VB.NET 类库,VB.NET 类库再调用 C# 类库。

示例工程:包含 Demo.sln 的解决方案目录

示例使用 .NET 8、WPF 和项目引用;不依赖 COM。

一、先看工程和调用方向

工程拆成三个项目,各自负责一件事:

项目 语言 目标框架 职责
Demo C# net8.0-windows WPF 界面,读取输入并调用服务
VbInterop VB.NET net8.0 组合问候语和计算结果
CSharpInterop C# net8.0 提供问候语格式化和加法 API

C# 与 VB.NET 互操作示例的工程结构和项目引用方向

对应的项目引用关系是:

Demo(C# WPF) → VbInterop(VB.NET 类库) → CSharpInterop(C# 类库)

这里的箭头表示调用方引用被调用方。主界面不需要直接了解底层 C# 工具类,它只依赖 VB.NET 服务;而 VB.NET 服务通过引用 C# 类库完成具体操作。

二、为什么不同语言可以互相调用?

C# 和 VB.NET 都是 .NET 语言。编译后,项目会生成包含类型元数据和中间语言(IL)的程序集。只要调用方能够引用该程序集,并且被调用类型和成员对外公开,另一种 .NET 语言就可以通过这些元数据理解并调用它们。

因此,跨语言调用的关键不是“把一种语言的源码嵌进另一种语言”,而是:

  1. 将可复用代码放进类库项目;
  2. 将要使用的类型和成员声明为 public;
  3. 为调用方添加项目引用或程序集引用;
  4. 确保目标框架兼容,并使用双方都能理解的 .NET API。

三、C# 类库:提供两个简单 API

CSharpInterop 暴露两个静态类。一个负责生成问候语,另一个负责整数加法。

namespace CSharpInterop;

public static class MessageFormatter
{
    public static string CreateGreeting(string? name)
    {
        var displayName = string.IsNullOrWhiteSpace(name) ? "朋友" : name.Trim();
        return $"你好,{displayName}!";
    }
}

public static class Calculator
{
    public static int Add(int firstValue, int secondValue)
        => firstValue + secondValue;
}

注意类和方法都是 public。如果类型或方法只有程序集内部可见,VB.NET 项目即使添加了引用,也无法通过普通方式调用它们。

四、VB.NET 类库:调用 C#,再组织结果

VB.NET 项目通过 ProjectReference 引用 C# 类库。在 VB 文件中导入命名空间后,就可以直接使用其中的公开类型:

Imports CSharpInterop

Public Class InteropDemoService
    Public Function CreateSummary(
        name As String,
        firstValue As Integer,
        secondValue As Integer) As String

        Dim greeting As String = MessageFormatter.CreateGreeting(name)
        Dim sum As Integer = Calculator.Add(firstValue, secondValue)

        Return $"{greeting}{Environment.NewLine}VB.NET 调用了 C# Calculator.Add:{firstValue} + {secondValue} = {sum}"
    End Function
End Class

项目文件中的引用如下:

<ItemGroup>
  <ProjectReference Include="..\CSharpInterop\CSharpInterop.csproj" />
</ItemGroup>

这里的 CreateSummary 是跨语言边界上的服务入口。C# 界面只需传入姓名和两个整数,至于问候语如何生成、加法由谁实现,都由 VB.NET 服务和它依赖的 C# 类库负责。

五、C# WPF 界面:调用 VB.NET 服务

WPF 按钮事件由 C# 处理。代码先检查两个输入框是否为有效整数,再创建 VB.NET 服务并调用 CreateSummary:

using VbInterop;

private void RunInteropButton_Click(object sender, RoutedEventArgs e)
{
    if (!int.TryParse(FirstValueTextBox.Text, out var firstValue) ||
        !int.TryParse(SecondValueTextBox.Text, out var secondValue))
    {
        ResultTextBlock.Text = "请输入有效的整数。";
        return;
    }

    var service = new InteropDemoService();
    ResultTextBlock.Text = service.CreateSummary(
        NameTextBox.Text.Trim(), firstValue, secondValue);
}

WPF 项目只需引用 VB.NET 项目:

<ItemGroup>
  <ProjectReference Include="VbInterop\VbInterop.vbproj" />
</ItemGroup>

在 VB.NET 的类库项目中,再引用 C# 类库。这样的分层能把界面、业务组织和基础工具拆开,也让调用路径一目了然。

点击按钮后从 C# 界面到 VB.NET 服务再到 C# 类库的调用流程

六、运行后会发生什么?

示例界面默认填入姓名 Codex 和数字 12、30。点击“运行互操作示例”后,调用顺序如下:

  1. C# WPF 事件处理程序解析两个整数;
  2. C# 创建 VbInterop.InteropDemoService 并调用 CreateSummary;
  3. VB.NET 调用 C# 的 MessageFormatter.CreateGreeting;
  4. VB.NET 调用 C# 的 Calculator.Add;
  5. VB.NET 拼好文本并返回给 C# 界面显示。

结果大致如下:

你好,Codex!
VB.NET 调用了 C# Calculator.Add:12 + 30 = 42

这一次交互同时展示了两个方向:C# 调用 VB.NET 的公开类,VB.NET 再调用 C# 的公开类。

七、一个容易忽略的 WPF 项目细节

SDK 风格的 WPF 项目会按默认规则编译项目目录下的 C# 源文件。由于 CSharpInterop 类库位于 WPF 项目目录内部,如果不加区分,类库中的 .cs 文件也可能被 WPF 主项目的默认编译项收进去。

示例在 Demo.csproj 中显式排除了这部分源文件:

<ItemGroup>
  <Compile Remove="CSharpInterop/**/*.cs" />
  <ProjectReference Include="VbInterop\VbInterop.vbproj" />
</ItemGroup>

排除源码不会取消项目引用:CSharpInterop 仍由 VbInterop 引用并编译。这里的设置只是避免同一份 C# 类库源码同时进入 WPF 项目的编译输入。

八、构建和运行

在包含 Demo.sln 的解决方案目录打开终端,执行:

dotnet build .\Demo.sln -m:1
dotnet run --project .\Demo\Demo.csproj

第一条命令构建解决方案中的 WPF、VB.NET 和 C# 项目;第二条命令启动 WPF 示例。运行 WPF 界面需要 Windows 环境。

本示例已完成整个解决方案的构建,结果为 0 个警告、0 个错误。

九、实际项目中的几个建议

  • 只公开稳定的调用接口。 跨语言 API 越简单,后续维护越轻松;实现细节留在类库内部。
  • 统一目标框架。 本示例的两个类库面向 net8.0,WPF 应用面向 net8.0-windows。设计引用关系时应确认调用方能够引用被调用方的目标框架。
  • 明确空值和异常约定。 C# 的可空引用类型注解、VB.NET 的 Option Strict On 等设置能帮助发现部分问题,但参数的含义、空值策略和异常处理仍应由 API 设计者说明。
  • 让项目引用表达依赖方向。 界面依赖服务,服务依赖工具库;避免基础类库反向依赖界面项目。
  • 跨语言边界优先使用通用 .NET 类型。 字符串、整数、集合和明确的 DTO 通常比语言特有的实现细节更适合作为接口参数和返回值。

结语

C# 和 VB.NET 互操作的核心,是通过程序集引用共享公开的 .NET 类型和成员。把代码放入职责清晰的类库、设置好项目引用,并确认目标框架兼容,就能让两种语言在同一个解决方案中协作。

本文示例的结构可以概括为:C# 负责界面,VB.NET 负责服务编排,C# 类库提供可复用操作。这类拆分也适用于渐进式迁移:保留已有模块,再通过稳定的 .NET API 逐步接入新代码。

posted @ 2026-10-16 14:40  wuty007  阅读(5)  评论(0)    收藏  举报