- Published on
Java 开发者的 C# 快速入门指南
- Authors
- Name
- Alex
- @adams6688
长久以来,微软在开发者群群体中的形象一直偏负面,所以对微软的技术一直提不起兴趣。直到最近几年,微软的技术栈才开始逐渐被开发者所接受。
最近做一些东西,需要用到 C#,于是开始学习 C#。在学习过程中,发现 C# 和 Java 有很多相似之处,所以写下这篇文章,供其他 Java 开发者参考。
我不打算写一个大而全的东西,主要是给 Java 开发者一个快速入门的指南,并与 Java 进行对比。
代码排版风格上来讲
C# 喜欢使用 BSD 风格的代码排版,而 Java 则使用 K&R 风格的代码排版。
可以感一下:
using System;
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Hello from C#!");
}
}
public class Program {
public static void main(String[] args) {
System.out.println("Hello from Java!");
}
}
另外,在方法和类的命名上,C# 更倾向于使用 PascalCase,而 Java 方法则使用 camelCase。
当然,以上只是约定俗,C# 和 Java 都允许使用其他风格的代码排版。
语言特性
属性
C# 提供了属性(Properties)作为字段(Fields)的公共访问方式。这允许你在不暴露字段本身的情况下,控制对字段的访问(读取和写入),而不需要显式编写 getter 和 setter 方法。
public class Person
{
private int age;
public int Age
{
get => age;
set => age = value > 0 ? value : throw new ArgumentException();
}
}
在 Java 中,你需要手动编写 getter 和 setter 方法:
public class Person {
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
if (age > 0) {
this.age = age;
} else {
throw new IllegalArgumentException();
}
}
}
使用:person.Age = 30;
。自动属性:public string Name { get; set; }
。迁移提示:从 Java 的 Boilerplate 转向这个,能减少 30% 代码。
C# 的创新特性
运算符重载
C# 支持运算符重载,这允许你为自定义类型定义如何使用标准运算符(如 +、-、*、/ 等)。这使得自定义类型的使用更加直观。
public class Point
{
public int X { get; set; }
public int Y { get; set; }
public static Point operator +(Point p1, Point p2)
{
return new Point { X = p1.X + p2.X, Y = p1.Y + p2.Y };
}
}
Point p1 = new Point { X = 1, Y = 2 };
Point p2 = new Point { X = 3, Y = 4 };
Point p3 = p1 + p2; // 使用重载的 + 运算符
在 Java 中,你不能直接重载运算符,但可以通过方法来实现类似的功能。
ndexers(索引器)
C# 的索引器允许你使用数组语法访问对象的属性,这对于集合类非常有用。
public class StringCollection
{
private List<string> strings = new List<string>();
public string this[int index]
{
get => strings[index];
set => strings[index] = value;
}
}
StringCollection collection = new StringCollection();
collection[0] = "Hello";
Console.WriteLine(collection[0]); // 输出: Hello
在 Java 中,你可以使用方法来实现类似的功能,但语法和使用方式有所不同。
LINQ:查询革命
C# 通过 LINQ 提供了强大的数据查询能力,它允许你以声明方式编写类型安全的查询。LINQ 可以用于数组、枚举类型以及其他集合类型。
var results = from element in array
where element > 10
select element;
在 Java 中,你可以使用 Stream API 来实现类似的功能,但语法和使用方式有所不同。
List<Integer> results = Arrays.stream(array)
.filter(element -> element > 10)
.collect(Collectors.toList());
Java 的 Stream API 也提供了类似的功能,但 C# 的 LINQ 更加直观和易于使用。
Null 合并运算符(??)
C# 提供了 Null 合并运算符(??),用于简化对可能为 null 的值的处理。
string name = nullableName ?? "Default Name";
在 Java 中,你可以使用 Optional
类来处理可能为 null 的值,但语法更为冗长。
String name = optionalName.orElse("Default Name");
异步编程:async/await
async Task<string> GetDataAsync()
{
await Task.Delay(1000);
return "Data";
}
调用:var data = await GetDataAsync();
。Java 21+ 有 virtual threads,但语法不如 await 自然。
在 Java 中,你可以使用 CompletableFuture
来实现类似的功能,但语法和使用方式有所不同。
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "Data";
});
Java 的 virtual threads 使用方式如下:
Thread.startVirtualThread(() -> {
String data = GetDataAsync();
System.out.println(data);
});
函数参数传递
C# 支持按值传递和按引用传递参数。默认情况下,C# 方法参数是按值传递的,但你可以使用 ref
或 out
关键字来按引用传递参数。
public void UpdateValue(ref int value)
{
value += 10;
}
int number = 5;
UpdateValue(ref number);
Console.WriteLine(number); // 输出: 15
在 Java 中,所有对象都是按引用传递的,但基本类型(如 int、 boolean 等)是按值传递的。Java 没有类似 C# 的 ref
或 out
关键字。
public void updateValue(int[] value) {
value[0] += 10;
}
int[] number = {5};
updateValue(number);
System.out.println(number[0]); // 输出: 15
using 语句
C# 的 using
语句用于确保在使用完资源后自动释放它 们。它通常用于处理实现了 IDisposable
接口的对象,如文件流、数据库连接等。
using (var stream = new FileStream("file.txt", FileMode.Open))
{
// 使用 stream
} // 自动调用 stream.Dispose()
在 Java 中,你可以使用 try-with-resources
语句来实现类似的 功能,但语法和使用方式有所不同。
try (FileInputStream stream = new FileInputStream("file.txt")) {
// 使用 stream
} // 自动调用 stream.close()
委托(Delegates)和事件(Events)
C# 中的委托和事件提供了一种类型安全的方式来处理回调和事件通知。委托类似于 Java 中的函数接口,但 C# 的委托更为灵活。
public delegate void Notify(string message);
public class Notifier
{
public event Notify OnNotify;
public void Notify(string message)
{
OnNotify?.Invoke(message);
}
}
在 Java 中,你可以使用函数式接口和 Lambda 表达式来实现类似的功能,但 C# 的委托和事件更为直观。
@FunctionalInterface
public interface Notify {
void notify(String message);
}
public class Notifier {
private Notify onNotify;
public void setOnNotify(Notify onNotify) {
this.onNotify = onNotify;
}
public void notify(String message) {
if (onNotify != null) {
onNotify.notify(message);
}
}
}
扩展方法(Extension Methods)
C# 的扩展方法允许你为现有类型添加新方法,而无需修改原始类型。这对于增强现有类的功能非常有用。
public static class StringExtensions
{
public static string ToUpperFirst(this string str)
{
if (string.IsNullOrEmpty(str)) return str;
return char.ToUpper(str[0]) + str.Substring(1);
}
}
在 Java 中,你可以使用静态方法来实现类似的功能,但语法和使用方式有所不同。
public class StringUtils {
public static String toUpperFirst(String str) {
if (str == null || str.isEmpty()) return str;
return Character.toUpperCase(str.charAt(0)) + str.substring(1);
}
}
struct 值类型
C# 中的 struct
是一种值类型,允许你定义轻量级的数据结构。与 Java 的类不同,struct
是按值传递的,这意味着它们在赋值时会复制数据,而不是引用。
public struct Point
{
public int X { get; set; }
public int Y { get; set; }
}
Point p1 = new Point { X = 10, Y = 20 };
Point p2 = p1; // 复制数据
p2.X = 30; // 修改 p2 不影响 p1
Console.WriteLine(p1.X); // 输出: 10
在 Java 中,所有类都是引用类型,Java 没有类似 C# 的 struct
。Java 中的类是按引用传递的,这意味着当你将一个对象赋值给另一个变量时,实际上是复制了对象的引用,而不是对象本身。
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
Point p1 = new Point(10, 20);
Point p2 = p1; // 复制引用
p2.setX(30); // 修改 p2 也会影响 p1
System.out.println(p1.getX()); // 输出: 30
记录类型(Record Types)
C# 9 引入了记录类型(Record Types),它们是不可变的引用类型,主要用于存储数据。记录类型提供了内置的值相等性比较和简化的语法。
public record Person(string Name, int Age);
var person = new Person("Alice", 30);
Console.WriteLine(person.Name); // 输出: Alice
在 Java 中,你可以使用 record
关键字来定义类似的不可变数据 类型,但 C# 的记录类型提供了更多的功能和简化的语法。
public record Person(String name, int age) {}
Person person = new Person("Alice", 30);
System.out.println(person.name()); // 输出: Alice
运算符
可空
C# 支持可空类型(Nullable Types),允许值类型(如 int、bool 等)可以为 null。这在处理数据库或其他可能缺失数据的场景时非常有用。
int? nullableInt = null;
if (nullableInt.HasValue)
{
Console.WriteLine(nullableInt.Value);
}
在 Java 中,你可以使用 Optional
类来处理可能为 null 的值,但语法更为冗长。
Optional<Integer> optionalInt = Optional.empty();
if (optionalInt.isPresent()) {
System.out.println(optionalInt.get());
}
?? 运算符
C# 的 Null 合并运算符(??)允许你在一个表达式中提供默认值,如果左侧的值为 null,则返回右侧的值。
string name = nullableName ?? "Default Name";
在 Java 中,你可以使用 Optional
类来处理可能为 null 的值,但语 法更为冗长。
String name = optionalName.orElse("Default Name");
.? 运算符
C# 的 Null 条件运算符(?.)允许你在访问对象的属性 或方法时,如果对象为 null,则返回 null,而不是抛出 NullReferenceException。
string name = person?.Name;
在 Java 中,你可以使用 Optional
类来处理可能为 null 的值,但语 法更为冗长。
String name = person != null ? person.getName() : null;
空合并赋值运算符(??=)
C# 的空合并赋值运算符(??=)允许你在变量为 null 时为其赋值。
string name = null;
name ??= "Default Name"; // 如果 name 为 null,则赋值为 "Default Name
在 Java 中,你可以使用 Optional
类来处理可能为 null 的值,但语 法更为冗长。
String name = null;
if (name == null) {
name = "Default Name"; // 如果 name 为 null,则赋值为 "Default Name
}
委托合并运算符(+= 和 -=)
C# 的委托合并运算符(+= 和 -=)允许你将多个方法添加到同一个委托中,或者从委托中移除方法。这使得事件处理和回调更加灵活。
public delegate void Notify(string message);
public class Notifier
{
public event Notify OnNotify;
public void Notify(string message)
{
OnNotify?.Invoke(message);
}
}
Notifier notifier = new Notifier();
notifier.OnNotify += message => Console.WriteLine("Received: " + message);
notifier.Notify("Hello, World!"); // 输出: Received: Hello, World!
notifier.OnNotify -= message => Console.WriteLine("Received: " + message);
在 Java 中,你可以使用函数式接口和 Lambda 表达式来实现类似的功能,但 C# 的委托合并运算符更为直观。
@FunctionalInterface
public interface Notify {
void notify(String message);
}
public class Notifier {
private Notify onNotify;
public void setOnNotify(Notify onNotify) {
this.onNotify = onNotify;
}
public void notify(String message) {
if (onNotify != null) {
onNotify.notify(message);
}
}
}
Notifier notifier = new Notifier();
notifier.setOnNotify(message -> System.out.println("Received: " + message));
notifier.notify("Hello, World!"); // 输出: Received: Hello, World!
notifier.setOnNotify(null); // 移除通知
指针
C# 支持指针,但默认情况下是禁用的。你需要在项目文件中启用不安全代码(unsafe code)才能使用指针。
unsafe
{
int* p = stackalloc int[10]; // 分配一个整数数组
for (int i = 0; i < 10; i++)
{
p[i] = i;
}
for (int i = 0; i < 10; i++)
{
Console.WriteLine(p[i]); // 输出: 0 1 2 3
}
}
在 Java 中,你不能直接使用指针,但可以使用 Unsafe
类来实现类似 的功能,但这需要额外的配置和权限。
import sun.misc.Unsafe;
Unsafe unsafe = Unsafe.getUnsafe();
long address = unsafe.allocateMemory(10 * Integer.BYTES); // 分配一个整数
for (int i = 0; i < 10; i++) {
unsafe.putInt(address + i * Integer.BYTES, i);
}
for (int i = 0; i < 10; i++) {
System.out.println(unsafe.getInt(address + i * Integer.BYTES)); // 输出: 0 1 2 3
}
unsafe.freeMemory(address); // 释放内存
总结
C# 和 Java 在语法和特性上有很多相似之处,但 C# 提供了一些独特的功能,如属性、委托、LINQ 等,这些功能可以使代码更简洁、更易读。 对于 Java 开发者来说,学习 C# 可以帮助你更好地理解面向对象编程的概念,并掌握一些新的编程范式。 希望这篇文章能帮助你快速入门 C#,并在实际项目中应用这些知识。