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 |
对应的项目引用关系是:
Demo(C# WPF) → VbInterop(VB.NET 类库) → CSharpInterop(C# 类库)
这里的箭头表示调用方引用被调用方。主界面不需要直接了解底层 C# 工具类,它只依赖 VB.NET 服务;而 VB.NET 服务通过引用 C# 类库完成具体操作。
二、为什么不同语言可以互相调用?
C# 和 VB.NET 都是 .NET 语言。编译后,项目会生成包含类型元数据和中间语言(IL)的程序集。只要调用方能够引用该程序集,并且被调用类型和成员对外公开,另一种 .NET 语言就可以通过这些元数据理解并调用它们。
因此,跨语言调用的关键不是“把一种语言的源码嵌进另一种语言”,而是:
- 将可复用代码放进类库项目;
- 将要使用的类型和成员声明为
public; - 为调用方添加项目引用或程序集引用;
- 确保目标框架兼容,并使用双方都能理解的 .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# 类库。这样的分层能把界面、业务组织和基础工具拆开,也让调用路径一目了然。
六、运行后会发生什么?
示例界面默认填入姓名 Codex 和数字 12、30。点击“运行互操作示例”后,调用顺序如下:
- C# WPF 事件处理程序解析两个整数;
- C# 创建
VbInterop.InteropDemoService并调用CreateSummary; - VB.NET 调用 C# 的
MessageFormatter.CreateGreeting; - VB.NET 调用 C# 的
Calculator.Add; - 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 逐步接入新代码。

浙公网安备 33010602011771号