Java 和 C# 之间有很多相似之处——它们都是受 C 影响的语法有些相似的托管语言,但也有很多不同之处。在这篇博文中,我们将探讨 Java 开发人员在过渡到 C# 时应该注意的一些关键区别。
1 - 语法糖
让我们探讨 C# 提供的三种语言功能,以增强代码的可读性,而这些功能在 Java 中是不存在的。这绝不是一个详尽的清单。
命名参数和可选参数
在 C# 中,您可以使用参数的名称以任意顺序指定方法的参数。例如,考虑以下方法:
static void PrintHello(string name, string surname) =>
Console.WriteLine($"Hello, {name} {surname}!");
以下调用在结果方面是相同的:
PrintHello("John", "Doe");
PrintHello(name: "John", surname: "Doe");
PrintHello(surname: "Doe", name: "John");
此外,您可以为参数指定一个默认值,在这种情况下它变成可选的:
static void PrintHello(string value="World") =>
Console.WriteLine($"Hello, {value}!");
static void Main(string[] args)
{
PrintHello();
PrintHello("Peggy");
}
获取/设置属性
C# 中的属性是一个成员,它提供了一种访问、修改或计算私有字段值的通用方法。它类似于 Java 中的常规 getter/setter,具有特殊语法和自动实现它们的选项。
考虑以下具有自动实现属性的示例:
class Student
{
public string Name {get; init;}
public string? Department {get; set;}
}
在这里我们声明了自动实现的属性。该Name属性公开了 get 属性,以及只能在方法 ( init) 初始化期间使用的 setter。该Department属性公开了 getter 和 setter。然后我们可以相应地访问和修改属性:
var student = new Student { Name = "Alice", Department = "Computer Science"};
student.Department = "Chemistry";
Console.WriteLine(student.Department);
Console.WriteLine(student.Name);
Null-Conditional (Elvis) 和 Null-coalescing 运算符
与 Java 不同,C# 支持 Elvis ( ?.) 和 Null 合并运算符 ( ??)。这些运算符旨在简化使用可空值的工作。例如,这段代码:
string department = null;
if (student != null) {
department = student.Department;
}
可以替换为:
string department = student?.Department;
空合并运算符允许将val != null ? val : defaultValue表达式替换为val ?? defaultValue. 它还可以与 Elvis 运算符结合使用,例如:
Console.WriteLine(student?.Department ?? "Value not known");
2 - 异常
在 Java 中,有所谓的已检查异常和未检查异常。已检查异常是在编译时检查的,如果您使用抛出已检查异常的方法,则必须在代码中对其进行处理,否则将无法编译。另一方面,不必在代码中处理未经检查的异常。如果他们没有被抓住,他们会破坏他们抛出的线程。
C# 没有检查异常的概念,这始终由开发人员决定如何以及何时处理异常。
3 - IEnumerable 和 LINQ
C# 中的IEnumerable接口类似于Java 中的 Iterable接口 为了支持对 Iterable 对象的惰性计算,Java 8 引入了一个单独的 Stream API。在 C# 中,类似的功能直接通过Enumerable 类 提供。
下面是在C#中使用Enumerable的Where方法过滤偶数的例子:
IEnumerable<int> evenNums = Enumerable.Range(1, 1000)
.Take(5)
.Where(num => num % 2 == 0);
foreach (var num in evenNums)
{
Console.WriteLine(num);
}
在 Java 中,相同的代码将如下所示:
List<Integer> evenNums = Stream.iterate(1, i -> i + 1)
.limit(5)
.filter(num -> num % 2 == 0)
.toList();
evenNums.forEach(System.out::println);
除了如上例所示调用查询方法外,C# 还提供语言集成查询 ( LINQ ) 功能。这是一种允许直接在 C# 中对 IEnumberable 对象使用查询语言的技术。
过滤偶数的示例在 LINQ 样式语法中如下所示:
var nums = new List<int> { 1, 2, 3, 4, 5 };
IEnumerable<int> evenNums = from num in nums
where num % 2 == 0
select num;
foreach (var num in evenNums)
{
Console.WriteLine(num);
}
4 - 异步/等待
C# 提供了 async/await 关键字来以更具可读性和顺序性的方式编写异步代码。考虑以下示例,我们在其中声明一个异步方法,该方法在延迟 2 秒后生成一个随机整数。我们首先调用该方法两次以生成两个随机整数,然后在需要打印总和时等待结果:
static async Task<int> RandomIntAsync()
{
await Task.Delay(TimeSpan.FromSeconds(2));
return new Random().Next(0, 100);
}
static async Task Main(string[] args)
{
Task<int> val1 = RandomIntAsync();
Task<int> val2 = RandomIntAsync();
Console.WriteLine(await val1 + await val2);
}
5 - 可为空
默认情况下,C# 将所有值类型变量视为不可空的。要允许空值,类型必须显式标记为可为空,只需在类型定义中添加一个问号即可(例如“int?”)。这允许开发人员处理数据可能丢失或未定义的情况,例如:
class Person
{
public string Name { get; init; }
public int? Age { get; init; }
}
static async Task Main(string[] args)
{
var person = new Person { Name = "Bob"};
if (person.Age == null)
{
Console.WriteLine("Age not specified.");
}
}
除了可空值类型之外,您还可以指定可空引用类型,就像我们在上面的 Student 类示例中所做的那样,我们将 Department 字符串声明为可空。从历史上看,在 C# 中,默认情况下所有引用都可以为 null。但是从 C# 8 开始,默认情况下假定引用不可为空,除非明确说明,类似于值类型的行为方式。由于向后兼容的复杂性,默认情况下不启用此功能。
结论
在这里,我们简要概述了 C# 和 Java 之间的一些(但不是全部)主要差异。希望这能为从 Java 切换到 C# 提供一个起点!