以下是一个针对 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.变量与数据类型
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<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 密集型任务并行执行。
- 任务之间没有依赖。
- 不需要返回结果。
四、常用工具与框架
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:
- c# 使用
namespace
组织代码,java 使用package
。 - 程序入口是
main
方法(java 是main
)。
六、java 到 c# 的常见转换技巧
java | c# |
---|---|
system.out.println() | console.writeline() |
arraylist<t> | list<t> |
hashmap<k,v> | dictionary<k,v> |
interface | interface |
enum | enum |
try-catch-finally | try-catch-finally |
在 c# 中,反射(reflection) 是一种强大的机制,允许在运行时动态地获取类型信息、创建对象实例、调用方法、访问字段和属性等。对于从 java 转向 c# 的开发者来说,反射的概念是相似的,但 c# 的反射 api 更加简洁、直观,并且与语言特性(如 dynamic
、nameof
、linq
)结合更紧密。
七、反射
反射是指在 运行时(runtime) 动态地:
- 获取类型信息(如类名、方法、属性等)
- 创建对象实例
- 调用方法、访问字段或属性
- 检查程序集(assembly)的结构
java 与 c# 反射的对比
功能 | 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 等 |
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 emit或source generator优化性能
对于高性能场景(如 orm、序列化框架),可以使用:
system.reflection.emit
:动态生成 il 代码source generator
(c# 9+):编译时生成代码,避免运行时反射
性能问题
- 反射调用比直接调用慢(约慢 10~100 倍)
- 频繁使用
getmethod
、getproperty
会增加开销
解决方案
- 缓存反射结果(如
methodinfo
、propertyinfo
) - 使用
expression
构建委托 - 使用
dynamic
(在合适场景下) - 使用
system.reflection.dispatchproxy
实现代理 - 使用
system.text.json
、newtonsoft.json
等库已优化的反射机制
安全性
- 可以访问私有成员(需设置
bindingflags.nonpublic
) - 在部分受限环境中(如 uwp、aot 编译)可能受限
java 到 c# 反射的转换技巧
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() |
八、引入包(nuget 包管理)
在 c# 中,nuget 是官方推荐的包管理系统,类似于 java 中的 maven/gradle。它用于管理项目依赖项(如第三方库、框架等)。
nuget 包典型命名规则:[组织名].[功能模块].[平台/框架]
1.nuget 的作用
- 管理项目依赖(如
newtonsoft.json
、entityframework
等) - 自动下载、安装、更新依赖包
- 支持跨平台(windows、linux、macos)
2.常用方式
方法 1:通过 visual studio 引入
- 右键项目 → manage nuget packages
- 在 browse 标签页搜索包名(如
newtonsoft.json
) - 点击 install 安装包
- 安装完成后,会自动添加到项目中
方法 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 build | dotnet add package |
私有仓库 | settings.xml / repositories { maven { url "..." } } | dotnet nuget add source |
九、引用本地的 dll
有时你需要引用本地的 dll 文件(如团队内部开发的库、第三方未提供 nuget 包的库),可以通过以下方式实现。
1.添加本地 dll 引用
方法 1:通过 visual studio 添加
- 右键项目 → add → reference…
- 在弹出窗口中选择 browse
- 浏览并选择本地 dll 文件(如
mylibrary.dll
) - 点击 add → ok
方法 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 viewer
(fuslogvw.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.参数说明
参数 | 说明 |
---|---|
dllname | dll 文件名(如 "user32.dll" ) |
charset | 字符集(charset.ansi 、charset.unicode 、charset.auto ) |
callingconvention | 调用约定(默认为 callingconvention.winapi ,也可指定 thiscall 、stdcall 等) |
entrypoint | 可选,指定 dll 中函数的入口点(当方法名与 dll 函数名不同时使用) |
4.常见示例
示例 1:调用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);
示例 2:调用kernel32.dll的gettickcount
[dllimport("kernel32.dll")] public static extern uint gettickcount(); // 调用 uint tickcount = gettickcount(); console.writeline($"system uptime: {tickcount} ms");
示例 3:调用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);
5.结构体与指针传参
当调用的函数需要结构体或指针参数时,需使用 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}"); }
6.注意事项
安全性
- 调用非托管代码可能带来 安全风险(如缓冲区溢出、非法访问内存)。
- 需要 full trust 权限 才能执行 p/invoke 操作。
平台依赖性
dllimport
仅适用于 windows 平台(除非使用跨平台兼容的库)。- 某些 dll(如
user32.dll
、kernel32.dll
)是 windows 系统库,其他平台无法直接使用。
性能
- p/invoke 调用比纯托管代码慢(涉及 上下文切换 和 参数封送)。
- 频繁调用时应考虑缓存结果或使用
unsafe
代码优化。
参数封送(marshaling)
- 需要特别注意 数据类型映射(如
int
对应int32
,char*
对应string
)。 - 使用
marshalas
明确指定封送方式(如unmanagedtype.lpstr
、unmanagedtype.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() { // 调用 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 或其他非托管函数,实现更底层的系统级操作。结合良好的封装和错误处理,可以显著提升程序的功能性和灵活性。
总结
到此这篇关于java快速转c#的文章就介绍到这了,更多相关java快速转c#内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论