当前位置: 首页 > news >正文

Java 快速转 C# 教程

以下是一个针对 Java 开发者快速转向 C# 的简明教程,重点对比 Java 与 C# 的异同,帮助你快速上手。


项目结构:

  • .sln :解决方案文件,管理多个项目之间的依赖关系。
  • .csproj :项目文件,定义目标框架(如 net6.0)、依赖项(NuGet 包或本地 DLL)。
  • global.json: 控制 .NET SDK 行为
    • 指定 .NET SDK 版本 :确保项目使用特定版本的 SDK 构建(避免本地环境版本不一致)。
    • 控制项目扫描范围 :在多项目解决方案中,指定哪些目录参与构建。
    • 启用/禁用 SDK 安装提示 :控制是否自动下载未安装的 SDK 版本。
  • app.manifest: 应用程序清单文件
    • 声明应用程序权限 (如以管理员身份运行)。
    • 指定兼容性需求 (如支持的 Windows 版本)。
    • 启用 Visual Studio 高 DPI 支持 。
    • 配置应用程序隔离(Side-by-Side Assembly) 。
  • Program.cs :程序入口(包含 Main 方法)。

一、基础语法对比

1. 变量与数据类型

JavaC#
int a = 10;int a = 10;
String name = "Hello";string name = "Hello";
final int MAX = 100;const int MAX = 100;
var list = new ArrayList<>(); (Java 10+)var list = new List<string>();

C# 特色:

  • var 是隐式类型变量(编译器推断类型)。
  • dynamic 类型可动态赋值(类似 Object)。

2. 拓展方法

C# 的扩展方法允许你为现有类型 (包括密封类、接口、甚至第三方库的类型)“添加”方法,而无需修改其源代码或继承。这是 C# 特有的语法特性,Java 中无直接等价物(需通过工具类或继承实现)。

定义扩展方法

  • 必须在静态类 中定义。
  • 第一个参数使用 this 关键字,表示该方法扩展的目标类型
// 静态类:扩展方法容器
public static class StringExtensions {// 扩展 string 类型的方法public static bool IsNullOrEmpty(this string str) {return string.IsNullOrEmpty(str);}
}

使用拓展方法

string name = null;// 调用扩展方法(如同实例方法)
if (name.IsNullOrEmpty()) {Console.WriteLine("Name is null or empty");
}

值得注意的是,拓展方法作为一个语法糖对应的可以解决在Java中 xxUtils 的工具类。同时具有以下注意:

  • 无法访问内部方法/属性
  • 若类型本身有同名方法,实例方法优先于扩展方法
  • 避免过度使用,防止命名冲突(需显式导入命名空间)

3. 空运算符(Null Handling Operators)

C# 提供了强大的空值处理运算符,简化空值检查逻辑,避免 NullReferenceException

1. 空条件运算符(?.

用于安全访问对象的成员,若对象为 null 则返回 null 而非抛出异常。

Person person = GetPerson(); // 可能为 null// 安全访问属性和方法
int length = person?.Name?.Length ?? 0;
person?.SayHello();

对比 Java

  • Java 中需显式判断或使用 Optional

    int length = Optional.ofNullable(person).map(p -> p.getName()).map(String::length).orElse(0);
    
2. 空合并运算符(??

提供默认值,当左侧表达式为 null 时返回右侧值。

string name = null;
string displayName = name ?? "Guest"; // 如果 name 为 null,则使用 "Guest"

对比 Java

  • Java 中使用三元运算符或 Optional

    String displayName = name != null ? name : "Guest";
    // 或
    String displayName = Optional.ofNullable(name).orElse("Guest");
    
3. 空合并赋值运算符(??=

仅当变量为 null 时才赋值(C# 8.0+)。

string message = GetMessage();
message ??= "Default Message"; // 如果 GetMessage() 返回 null,则赋值为默认值

对比 Java

  • Java 中需显式判断:
    if (message == null) {message = "Default Message";
    }
    

4. 非空断言运算符(!

告知编译器某个表达式不为 null(C# 8.0+,用于可空引用类型上下文)。

string name = GetName()!; // 告诉编译器 GetName() 返回值不为 null

注意事项

  • 空条件运算符返回的类型可能是 null(需结合 ?? 使用)。
  • 空合并运算符适用于 null 检查,但不适用于值类型(如 int)。
  • 使用 ?.?? 组合可显著减少防御性代码(如嵌套 if 判断)。

二、面向对象编程

1. 类与对象

public class Person {// 字段private string name;// 属性(推荐封装字段)public string Name {get { return name; }set { name = value; }}// 构造函数public Person(string name) {this.name = name;}// 方法public void SayHello() {Console.WriteLine($"Hello, {name}");}
}

对比 Java:

  • C# 使用 property(属性)替代 Java 的 getter/setter
  • this 关键字用法相同。

2. 继承与接口

// 继承
public class Student : Person {public Student(string name) : base(name) {}
}// 接口
public interface IRunnable {void Run();
}public class Car : IRunnable {public void Run() {Console.WriteLine("Car is running");}
}

对比 Java:

  • C# 使用 : 替代 Java 的 extends/implements
  • 接口方法默认 public,无需显式声明。

三、C# 特有特性

1. 委托与事件(Delegates & Events)

// 委托(类似 Java 的函数式接口)
// 定义一个名为 Notify 的委托类型,它表示一种方法模板,要求方法返回 void 并接受一个 string 参数
// 类比 Java :类似 Java 中的函数式接口(如 Consumer<String>),但 C# 的委托更直接,可以直接绑定方法。
public delegate void Notify(string message);// 事件
public class EventPublisher {// 声明一个事件 OnNotify,其类型是 Notify 委托。事件本质上是委托的安全封装,外部只能通过 +=/-= 订阅/取消订阅,不能直接调用(如 OnNotify.Invoke() 会报错)。public event Notify OnNotify;// 调用 OnNotify?.Invoke(...) 触发事件,?. 是空值保护操作符(避免空引用异常)。只有当至少有一个订阅者时,才会执行。public void TriggerEvent() {OnNotify?.Invoke("Event triggered!");}
}// 使用
EventPublisher publisher = new EventPublisher();
publisher.OnNotify += (msg) => Console.WriteLine(msg);
publisher.TriggerEvent();
  • 订阅事件
    使用 +=运算符将一个 lambda 表达式 (msg) => Console.WriteLine(msg)绑定到 OnNotify 事件。当事件触发时,会执行此方法。

  • 触发事件
    调用 TriggerEvent() 后,所有订阅者都会收到 “Event triggered!” 消息。

2. LINQ(Language Integrated Query)

var numbers = new List<int> { 1, 2, 3, 4, 5 };
var even = numbers.Where(n => n % 2 == 0).ToList();

对比 Java:

  • 类似 Java Stream,但语法更简洁。

3. 异步编程(Async/Await)

public async Task DownloadDataAsync() {var client = new HttpClient();var data = await client.GetStringAsync("https://example.com");Console.WriteLine(data);
}

对比 Java:

  • 类似 CompletableFuture,但语法更直观。

Parallel.Invoke(() => {// 并行执行CPU密集任务
});
  • 多个 CPU 密集型任务并行执行。
  • 任务之间没有依赖。
  • 不需要返回结果。

四、常用工具与框架

JavaC#
Maven/GradleNuGet(包管理)
Spring.NET Core(框架)
JUnitxUnit/NUnit(测试框架)
IntelliJ IDEAVisual Studio / IntelliJ Rider(IDE)

五、项目结构与命名空间

// 文件:Program.cs
using System;
namespace MyApplication;class Program {static void Main(string[] args) {Console.WriteLine("Hello World!");}
}

对比 Java:

  • C# 使用 namespace 组织代码,Java 使用 package
  • 程序入口是 Main 方法(Java 是 main)。

六、Java 到 C# 的常见转换技巧

JavaC#
System.out.println()Console.WriteLine()
ArrayList<T>List<T>
HashMap<K,V>Dictionary<K,V>
interfaceinterface
enumenum
try-catch-finallytry-catch-finally

C# 中,反射(Reflection) 是一种强大的机制,允许在运行时动态地获取类型信息、创建对象实例、调用方法、访问字段和属性等。对于从 Java 转向 C# 的开发者来说,反射的概念是相似的,但 C# 的反射 API 更加简洁、直观,并且与语言特性(如 dynamicnameofLINQ)结合更紧密。


七、反射

反射是指在 运行时(runtime) 动态地:

  • 获取类型信息(如类名、方法、属性等)
  • 创建对象实例
  • 调用方法、访问字段或属性
  • 检查程序集(Assembly)的结构

Java 与 C# 反射的对比

功能JavaC#
获取类型对象MyClass.classobj.getClass()typeof(MyClass)obj.GetType()
获取方法clazz.getMethod("name", params...)type.GetMethod("Name")
调用方法method.invoke(obj, args)method.Invoke(obj, args)
获取属性clazz.getDeclaredField("name")type.GetProperty("Name")
获取程序集无直接等价物Assembly.GetExecutingAssembly()
动态创建实例clazz.newInstance()Activator.CreateInstance(type)
动态访问成员通过 Field/Method 对象通过 PropertyInfo/MethodInfo

1. 获取 Type 对象

// 通过类型名获取
Type type = typeof(string);// 通过对象获取
object obj = new Person();
Type type = obj.GetType();

2. 获取类成员信息(属性、方法、字段)

Type type = typeof(Person);// 获取所有属性
PropertyInfo[] properties = type.GetProperties();// 获取特定方法
MethodInfo method = type.GetMethod("SayHello");// 获取所有字段
FieldInfo[] fields = type.GetFields();

3. 动态创建实例

object person = Activator.CreateInstance(typeof(Person));

4. 调用方法

MethodInfo method = type.GetMethod("SayHello");
method.Invoke(person, null);

5. 访问属性

PropertyInfo prop = type.GetProperty("Name");
prop.SetValue(person, "Alice");
string name = (string)prop.GetValue(person);

6. 访问字段(不推荐,除非必要)

FieldInfo field = type.GetField("age", BindingFlags.NonPublic | BindingFlags.Instance);
field.SetValue(person, 30);
int age = (int)field.GetValue(person);

7. 获取程序集信息

Assembly assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.GetTypes()) {Console.WriteLine(type.Name);
}

8. 动态加载 DLL 并调用方法

Assembly assembly = Assembly.LoadFile("path/to/MyLibrary.dll");
Type type = assembly.GetType("MyNamespace.MyClass");
object instance = Activator.CreateInstance(type);
MethodInfo method = type.GetMethod("DoSomething");
method.Invoke(instance, null);

9. 使用 dynamic 替代部分反射操作

dynamic person = new ExpandoObject();
person.Name = "Bob";
person.SayHello = new Action(() => Console.WriteLine("Hello"));
person.SayHello(); // 无需反射即可调用

10. 使用 Expression 构建高性能的反射调用

Func<object> factory = Expression.Lambda<Func<object>>(Expression.New(typeof(Person))
).Compile();
object person = factory();

11. 使用 IL EmitSource Generator 优化性能

对于高性能场景(如 ORM、序列化框架),可以使用:

  • System.Reflection.Emit:动态生成 IL 代码
  • Source Generator(C# 9+):编译时生成代码,避免运行时反射

性能问题

  • 反射调用比直接调用慢(约慢 10~100 倍)
  • 频繁使用 GetMethodGetProperty 会增加开销

解决方案

  • 缓存反射结果(如 MethodInfoPropertyInfo
  • 使用 Expression 构建委托
  • 使用 dynamic(在合适场景下)
  • 使用 System.Reflection.DispatchProxy 实现代理
  • 使用 System.Text.JsonNewtonsoft.Json 等库已优化的反射机制

安全性

  • 可以访问私有成员(需设置 BindingFlags.NonPublic
  • 在部分受限环境中(如 UWP、AOT 编译)可能受限

Java 到 C# 反射的转换技巧

JavaC#
Class.forName("MyClass")Type.GetType("MyNamespace.MyClass")
clazz.newInstance()Activator.CreateInstance(type)
method.invoke(obj, args)method.Invoke(obj, args)
clazz.getDeclaredMethods()type.GetMethods()
clazz.getDeclaredFields()type.GetFields()
clazz.getDeclaredField("name")type.GetField("Name")
clazz.getDeclaredMethod("name", params...)type.GetMethod("Name", parameterTypes)
clazz.getInterfaces()type.GetInterfaces()


八、引入包(NuGet 包管理)

在 C# 中,NuGet 是官方推荐的包管理系统,类似于 Java 中的 Maven/Gradle。它用于管理项目依赖项(如第三方库、框架等)。

NuGet 包典型命名规则:[组织名].[功能模块].[平台/框架]

1. NuGet 的作用

  • 管理项目依赖(如 Newtonsoft.JsonEntityFramework 等)
  • 自动下载、安装、更新依赖包
  • 支持跨平台(Windows、Linux、macOS)

2. 常用方式

方法 1:通过 Visual Studio 引入
  1. 右键项目 → Manage NuGet Packages
  2. Browse 标签页搜索包名(如 Newtonsoft.Json
  3. 点击 Install 安装包
  4. 安装完成后,会自动添加到项目中
方法 2:通过 CLI 命令行
# 安装包
dotnet add package Newtonsoft.Json# 更新包
dotnet add package Newtonsoft.Json --version 13.0.1# 卸载包
dotnet remove package Newtonsoft.Json
方法 3:手动编辑 .csproj 文件

在项目文件中添加 <PackageReference>

<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net6.0</TargetFramework></PropertyGroup><ItemGroup><PackageReference Include="Newtonsoft.Json" Version="13.0.1" /></ItemGroup>
</Project>

3. NuGet 源配置

默认源是 nuget.org,但也可以配置私有源(如公司内部源):

# 添加私有源
dotnet nuget add source https://mycompany.com/nuget -n MyCompany

4. 对比 Java

功能Java (Maven/Gradle)C# (NuGet)
包管理pom.xml / build.gradle.csproj
安装包mvn install / gradle builddotnet add package
私有仓库settings.xml / repositories { maven { url "..." } }dotnet nuget add source

九、引用本地的 DLL

有时你需要引用本地的 DLL 文件(如团队内部开发的库、第三方未提供 NuGet 包的库),可以通过以下方式实现。

1. 添加本地 DLL 引用

方法 1:通过 Visual Studio 添加
  1. 右键项目 → Add → Reference…
  2. 在弹出窗口中选择 Browse
  3. 浏览并选择本地 DLL 文件(如 MyLibrary.dll
  4. 点击 AddOK
方法 2:手动编辑 .csproj 文件
<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>net6.0</TargetFramework></PropertyGroup><ItemGroup><Reference Include="MyLibrary"><HintPath>..\Libraries\MyLibrary.dll</HintPath></Reference></ItemGroup>
</Project>

2. 确保 DLL 被正确复制到输出目录

.csproj 中添加以下配置,确保 DLL 被复制到 bin 目录:

<ContentWithTargetPath Include="..\Libraries\MyLibrary.dll"><TargetPath>MyLibrary.dll</TargetPath><CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</ContentWithTargetPath>

3. 加载本地 DLL 的运行时行为

  • Windows:直接复制到 bin\Debug\net6.0 目录即可
  • Linux/macOS:确保 DLL 与主程序在同一目录,或设置 LD_LIBRARY_PATH / DYLD_LIBRARY_PATH

4. 注意事项

  • 强名称签名(Strong Name):如果 DLL 是强名称签名的,引用时需确保签名一致
  • 平台相关性:某些 DLL 仅支持特定平台(如 Windows 专用的 DLL)
  • 版本冲突:多个 DLL 依赖相同库的不同版本时,可能出现冲突(需手动绑定重定向)

常见问题与解决方案

1. 无法找到 DLL

  • 原因:DLL 未正确复制到输出目录
  • 解决:检查 .csproj 中是否设置了 <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

2. 加载 DLL 失败

  • 原因:DLL 依赖的其他库缺失
  • 解决:使用 Fusion Log Viewerfuslogvw.exe)查看绑定失败日志

3. 版本冲突

  • 原因:多个 DLL 依赖相同库的不同版本


十、DllImport(平台调用,P/Invoke)

在 C# 中,[DllImport("xxx.dll")]平台调用(Platform Invocation Services,P/Invoke) 的核心特性,用于直接调用 非托管代码(如 Windows API、C/C++ 编写的 DLL)。这是 Java 中没有的特性(Java 需要通过 JNI 调用本地代码)。


1. 基本概念

[DllImport]System.Runtime.InteropServices 命名空间下的特性(Attribute),用于声明某个方法的实现来自外部 DLL。它允许你在 C# 中直接调用 Windows API 或其他非托管函数。


2. 使用步骤

步骤 1:引入命名空间
using System.Runtime.InteropServices;
步骤 2:声明外部方法

使用 [DllImport("dll名称")] 特性修饰方法,指定 DLL 名称和调用约定(Calling Convention)。

[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, String text, String caption, uint type);
步骤 3:调用方法
MessageBox(IntPtr.Zero, "Hello from C#", "Greeting", 0);

3. 参数说明

参数说明
dllNameDLL 文件名(如 "user32.dll"
CharSet字符集(CharSet.AnsiCharSet.UnicodeCharSet.Auto
CallingConvention调用约定(默认为 CallingConvention.Winapi,也可指定 ThisCallStdCall 等)
EntryPoint可选,指定 DLL 中函数的入口点(当方法名与 DLL 函数名不同时使用)

4. 常见示例

示例 1:调用 user32.dllMessageBox
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);// 调用
MessageBox(IntPtr.Zero, "Hello from C#", "Greeting", 0);
示例 2:调用 kernel32.dllGetTickCount
[DllImport("kernel32.dll")]
public static extern uint GetTickCount();// 调用
uint tickCount = GetTickCount();
Console.WriteLine($"System uptime: {tickCount} ms");
示例 3:调用 gdi32.dllCreateDC
[DllImport("gdi32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);// 调用
IntPtr hdc = CreateDC("DISPLAY", null, null, IntPtr.Zero);

5. 结构体与指针传参

当调用的函数需要结构体或指针参数时,需使用 refoutIntPtr,并可能需要使用 StructLayoutMarshalAs 来控制内存布局。

示例:调用 user32.dllGetWindowRect
[StructLayout(LayoutKind.Sequential)]
public struct RECT
{public int Left;public int Top;public int Right;public int Bottom;
}[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);// 使用
RECT rect;
bool success = GetWindowRect(hWnd, out rect);
if (success)
{Console.WriteLine($"Window Rect: {rect.Left}, {rect.Top}, {rect.Right}, {rect.Bottom}");
}

6. 注意事项

安全性
  • 调用非托管代码可能带来 安全风险(如缓冲区溢出、非法访问内存)。
  • 需要 Full Trust 权限 才能执行 P/Invoke 操作。
平台依赖性
  • DllImport 仅适用于 Windows 平台(除非使用跨平台兼容的库)。
  • 某些 DLL(如 user32.dllkernel32.dll)是 Windows 系统库,其他平台无法直接使用。
性能
  • P/Invoke 调用比纯托管代码慢(涉及 上下文切换参数封送)。
  • 频繁调用时应考虑缓存结果或使用 unsafe 代码优化。
参数封送(Marshaling)
  • 需要特别注意 数据类型映射(如 int 对应 Int32char* 对应 string)。
  • 使用 MarshalAs 明确指定封送方式(如 UnmanagedType.LPStrUnmanagedType.BStr)。

7. 示例:封装一个 Windows API 工具类

using System;
using System.Runtime.InteropServices;public static class Win32Api
{[DllImport("user32.dll", CharSet = CharSet.Auto)]public static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);[DllImport("kernel32.dll")]public static extern uint GetTickCount();[StructLayout(LayoutKind.Sequential)]public struct RECT{public int Left;public int Top;public int Right;public int Bottom;}[DllImport("user32.dll")][return: MarshalAs(UnmanagedType.Bool)]public static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
}// 使用
class Program
{static void Main(){// 调用 MessageBoxWin32Api.MessageBox(IntPtr.Zero, "Hello from C#", "Greeting", 0);// 获取系统运行时间uint tickCount = Win32Api.GetTickCount();Console.WriteLine($"System uptime: {tickCount} ms");// 获取窗口位置Win32Api.RECT rect;bool success = Win32Api.GetWindowRect(new IntPtr(0x123456), out rect);if (success){Console.WriteLine($"Window Rect: {rect.Left}, {rect.Top}, {rect.Right}, {rect.Bottom}");}}
}

通过掌握 DllImport 和 P/Invoke,你可以在 C# 中直接调用 Windows API 或其他非托管函数,实现更底层的系统级操作。结合良好的封装和错误处理,可以显著提升程序的功能性和灵活性。

相关文章:

  • 科技晚报 AI 速递:今日科技热点一览 丨 2025 年 5 月 17 日
  • 网关GateWay——连接不同网络的关键设备
  • 【蓝桥杯省赛真题51】python石头运输 第十五届蓝桥杯青少组Python编程省赛真题解析
  • 基于 jQuery 的轻量级在线画册、电子书、产品目录插件及使用
  • 【Closure-Hayd】
  • Oracle 11.2.0.4 pre PSU Oct18 设置SSL连接
  • mac-M系列芯片安装软件报错:***已损坏,无法打开。推出磁盘问题
  • java中的循环结构
  • QMK 宏(Macros)功能详解(实战部分)
  • STM32烧录程序正常,但是运行异常
  • DeepSeek 赋能军事:重塑现代战争形态的科技密码
  • AgentCPM-GUI,清华联合面壁智能开源的端侧GUI智能体模型
  • 第三十四节:特征检测与描述-SIFT/SURF 特征 (专利算法)
  • 【赵渝强老师】在PostgreSQL中访问Oracle
  • 【漫话机器学习系列】264.内距(又称四分位差)Interquartile Range
  • 迁移学习:解锁AI高效学习与泛化能力的密钥
  • OGG 更新表频繁导致进程中断,见鬼了?非也!
  • 大语言模型 11 - 从0开始训练GPT 0.25B参数量 MiniMind2 准备数据与训练模型 DPO直接偏好优化
  • 高并发内存池------内存释放
  • Linux | mdadm 创建软 RAID
  • 全国多家健身房女性月卡延长,补足因月经期耽误的健身时间
  • 悬疑剧背后的女编剧:创作的差异不在性别,而在经验
  • 上海虹桥国际咖啡文化节开幕,推出茶咖文化特色街区、宝妈咖啡师培训
  • 定制基因编辑疗法治愈罕见遗传病患儿
  • 商务部:中方敦促美方尽快停止232关税措施
  • 微软宣布全球裁员约3%:涉及约6000人,侧重经理层