以下是一个针对 Java 开发者快速转向 C# 的简明教程,重点对比 Java 与 C# 的异同,帮助你快速上手。
项目结构:
Java | C# |
---|---|
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 |
C# 特色:
var
是隐式类型变量(编译器推断类型)。dynamic
类型可动态赋值(类似 Object)。C# 的扩展方法允许你为现有类型 (包括密封类、接口、甚至第三方库的类型)“添加”方法,而无需修改其源代码或继承。这是 C# 特有的语法特性,Java 中无直接等价物(需通过工具类或继承实现)。
定义扩展方法
// 静态类:扩展方法容器
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 的工具类。同时具有以下注意:
C# 提供了强大的空值处理运算符,简化空值检查逻辑,避免 NullReferenceException
。
?.
)用于安全访问对象的成员,若对象为 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);
??
)提供默认值,当左侧表达式为 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");
??=
)仅当变量为 null
时才赋值(C# 8.0+)。
string message = GetMessage();
message ??= "Default Message"; // 如果 GetMessage() 返回 null,则赋值为默认值
对比 Java
if (message == null) {
message = "Default Message";
}
!
)告知编译器某个表达式不为 null
(C# 8.0+,用于可空引用类型上下文)。
string name = GetName()!; // 告诉编译器 GetName() 返回值不为 null
null
(需结合 ??
使用)。null
检查,但不适用于值类型(如 int
)。?.
和 ??
组合可显著减少防御性代码(如嵌套 if
判断)。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:
property
(属性)替代 Java 的 getter/setter
。this
关键字用法相同。// 继承
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:
:
替代 Java 的 extends/implements
。public
,无需显式声明。// 委托(类似 Java 的函数式接口)
// 定义一个名为 Notify 的委托类型,它表示一种方法模板,要求方法返回 void 并接受一个 string 参数
// 类比 Java :类似 Java 中的函数式接口(如 Consumer),但 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!” 消息。
var numbers = new List<int> { 1, 2, 3, 4, 5 };
var even = numbers.Where(n => n % 2 == 0).ToList();
对比 Java:
public async Task DownloadDataAsync() {
var client = new HttpClient();
var data = await client.GetStringAsync("https://example.com");
Console.WriteLine(data);
}
对比 Java:
CompletableFuture
,但语法更直观。Parallel.Invoke(() => {
// 并行执行CPU密集任务
});
Java | C# |
---|---|
Maven/Gradle | NuGet(包管理) |
Spring | .NET Core(框架) |
JUnit | xUnit/NUnit(测试框架) |
IntelliJ IDEA | Visual Studio / IntelliJ Rider(IDE) |
// 文件:Program.cs
using System;
namespace MyApplication;
class Program {
static void Main(string[] args) {
Console.WriteLine("Hello World!");
}
}
对比 Java:
namespace
组织代码,Java 使用 package
。Main
方法(Java 是 main
)。Java | C# |
---|---|
System.out.println() |
Console.WriteLine() |
ArrayList |
List |
HashMap |
Dictionary |
interface |
interface |
enum |
enum |
try-catch-finally |
try-catch-finally |
在 C# 中,反射(Reflection) 是一种强大的机制,允许在运行时动态地获取类型信息、创建对象实例、调用方法、访问字段和属性等。对于从 Java 转向 C# 的开发者来说,反射的概念是相似的,但 C# 的反射 API 更加简洁、直观,并且与语言特性(如 dynamic
、nameof
、LINQ
)结合更紧密。
反射是指在 运行时(runtime) 动态地:
功能 | Java | C# |
---|---|---|
获取类型对象 | MyClass.class 或 obj.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 等 |
Type
对象// 通过类型名获取
Type type = typeof(string);
// 通过对象获取
object obj = new Person();
Type type = obj.GetType();
Type type = typeof(Person);
// 获取所有属性
PropertyInfo[] properties = type.GetProperties();
// 获取特定方法
MethodInfo method = type.GetMethod("SayHello");
// 获取所有字段
FieldInfo[] fields = type.GetFields();
object person = Activator.CreateInstance(typeof(Person));
MethodInfo method = type.GetMethod("SayHello");
method.Invoke(person, null);
PropertyInfo prop = type.GetProperty("Name");
prop.SetValue(person, "Alice");
string name = (string)prop.GetValue(person);
FieldInfo field = type.GetField("age", BindingFlags.NonPublic | BindingFlags.Instance);
field.SetValue(person, 30);
int age = (int)field.GetValue(person);
Assembly assembly = Assembly.GetExecutingAssembly();
foreach (Type type in assembly.GetTypes()) {
Console.WriteLine(type.Name);
}
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);
dynamic
替代部分反射操作dynamic person = new ExpandoObject();
person.Name = "Bob";
person.SayHello = new Action(() => Console.WriteLine("Hello"));
person.SayHello(); // 无需反射即可调用
Expression
构建高性能的反射调用Func<object> factory = Expression.Lambda<Func<object>>(
Expression.New(typeof(Person))
).Compile();
object person = factory();
IL Emit
或 Source Generator
优化性能对于高性能场景(如 ORM、序列化框架),可以使用:
System.Reflection.Emit
:动态生成 IL 代码Source Generator
(C# 9+):编译时生成代码,避免运行时反射GetMethod
、GetProperty
会增加开销MethodInfo
、PropertyInfo
)Expression
构建委托dynamic
(在合适场景下)System.Reflection.DispatchProxy
实现代理System.Text.Json
、Newtonsoft.Json
等库已优化的反射机制BindingFlags.NonPublic
)Java | C# |
---|---|
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() |
在 C# 中,NuGet 是官方推荐的包管理系统,类似于 Java 中的 Maven/Gradle。它用于管理项目依赖项(如第三方库、框架等)。
NuGet 包典型命名规则:[组织名].[功能模块].[平台/框架]
Newtonsoft.Json
、EntityFramework
等)Newtonsoft.Json
)# 安装包
dotnet add package Newtonsoft.Json
# 更新包
dotnet add package Newtonsoft.Json --version 13.0.1
# 卸载包
dotnet remove package Newtonsoft.Json
.csproj
文件在项目文件中添加
:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0TargetFramework>
PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
ItemGroup>
Project>
默认源是 nuget.org,但也可以配置私有源(如公司内部源):
# 添加私有源
dotnet nuget add source https://mycompany.com/nuget -n MyCompany
功能 | Java (Maven/Gradle) | C# (NuGet) |
---|---|---|
包管理 | pom.xml / build.gradle |
.csproj |
安装包 | mvn install / gradle build |
dotnet add package |
私有仓库 | settings.xml / repositories { maven { url "..." } } |
dotnet nuget add source |
有时你需要引用本地的 DLL 文件(如团队内部开发的库、第三方未提供 NuGet 包的库),可以通过以下方式实现。
MyLibrary.dll
).csproj
文件<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0TargetFramework>
PropertyGroup>
<ItemGroup>
<Reference Include="MyLibrary">
<HintPath>..\Libraries\MyLibrary.dllHintPath>
Reference>
ItemGroup>
Project>
在 .csproj
中添加以下配置,确保 DLL 被复制到 bin
目录:
<ContentWithTargetPath Include="..\Libraries\MyLibrary.dll">
<TargetPath>MyLibrary.dllTargetPath>
<CopyToOutputDirectory>PreserveNewestCopyToOutputDirectory>
ContentWithTargetPath>
bin\Debug\net6.0
目录即可LD_LIBRARY_PATH
/ DYLD_LIBRARY_PATH
.csproj
中是否设置了 PreserveNewest
Fusion Log Viewer
(fuslogvw.exe
)查看绑定失败日志在 C# 中,[DllImport("xxx.dll")]
是 平台调用(Platform Invocation Services,P/Invoke) 的核心特性,用于直接调用 非托管代码(如 Windows API、C/C++ 编写的 DLL)。这是 Java 中没有的特性(Java 需要通过 JNI 调用本地代码)。
[DllImport]
是 System.Runtime.InteropServices
命名空间下的特性(Attribute),用于声明某个方法的实现来自外部 DLL。它允许你在 C# 中直接调用 Windows API 或其他非托管函数。
using System.Runtime.InteropServices;
使用 [DllImport("dll名称")]
特性修饰方法,指定 DLL 名称和调用约定(Calling Convention)。
[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);
参数 | 说明 |
---|---|
dllName |
DLL 文件名(如 "user32.dll" ) |
CharSet |
字符集(CharSet.Ansi 、CharSet.Unicode 、CharSet.Auto ) |
CallingConvention |
调用约定(默认为 CallingConvention.Winapi ,也可指定 ThisCall 、StdCall 等) |
EntryPoint |
可选,指定 DLL 中函数的入口点(当方法名与 DLL 函数名不同时使用) |
user32.dll
的 MessageBox
[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);
kernel32.dll
的 GetTickCount
[DllImport("kernel32.dll")]
public static extern uint GetTickCount();
// 调用
uint tickCount = GetTickCount();
Console.WriteLine($"System uptime: {tickCount} ms");
gdi32.dll
的 CreateDC
[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);
当调用的函数需要结构体或指针参数时,需使用 ref
、out
或 IntPtr
,并可能需要使用 StructLayout
和 MarshalAs
来控制内存布局。
user32.dll
的 GetWindowRect
[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}");
}
DllImport
仅适用于 Windows 平台(除非使用跨平台兼容的库)。user32.dll
、kernel32.dll
)是 Windows 系统库,其他平台无法直接使用。unsafe
代码优化。int
对应 Int32
,char*
对应 string
)。MarshalAs
明确指定封送方式(如 UnmanagedType.LPStr
、UnmanagedType.BStr
)。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()
{
// 调用 MessageBox
Win32Api.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 或其他非托管函数,实现更底层的系统级操作。结合良好的封装和错误处理,可以显著提升程序的功能性和灵活性。