15 三月 2024
我们将讨论 C# 中的方法和语言结构:哪些使用好,哪些不好。 当然,在好或不好下,我们考虑以下因素:从 C# 翻译后生成的 Java 代码的可读性和可维护性如何。
C# 有一些紧凑的语言结构,可以完成很多隐藏的工作。当您将这些结构翻译成另一种语言时,您必须隐式地再现这些隐藏的部分,而在大多数情况下,代码确实会失去其原始设计,并且差异很大。
自动属性在 C# 程序员中的应用非常广泛。使用这种属性,程序员可以通过 get 和/或 set 方法与隐藏字段交互。C# 确实可以让我们从自动属性的实际实现中分心,并使用非常紧凑的结构来声明它们。但是,在 Java 中,我们没有这样的语言结构,因此有必要明确地声明属性:作为字段和访问控制方法:
public int Value { get; set; }
在Java中变成:
private int auto_Value;
public int get_Value()
{
return auto_Value;
}
public void set_Value(int value)
{
auto_Value = value;
}
现在它不是一个完整的成员,而是三个独立的成员。想象一下,对于每个自动属性,相同的代码都会重复,它会是什么样子?让我们避免这样的不愉快的经历。但是怎么做呢?
尝试用一个对象来替换自动属性,该对象可以提供对私有字段的访问控制。可能有一个这样对象的哈希表。如果我们遵循对某些数据进行有限访问的设计,这将是一个好方法。自动属性可能看起来不错,但我们没有必要在没有必要的情况下使用它们。
在 C# 中,我们为结构体(值类型)设计了专门的内存逻辑。结构体的生命周期受限于栈帧或包含对象的生命周期,而且结构体被频繁复制–当我们将结构体作为函数参数传递、从函数返回或赋值给某个字段时,我们使用的是值而不是引用。改变复制时,我们不会改变原始值。将值类型转换为 Java 时,我们必须重新创建相同的逻辑,尽管 Java 类始终是引用类型。频繁复制现在成了一个问题–为了存储每个副本,我们都要从堆中分配内存,这就给垃圾回收器带来了过多负担。如果我们将性能作为我们的兴趣之一,我们就必须将 C# 代码从内存管理细节中抽象出来。但如何抽象呢?
最简单的方法就是让值类型不可变。如果没有可变状态,就没有必要复制该状态,以防止出现不确定的行为。
现在是时候讨论仅改变代码的视觉属性而不改变行为的语言构造了。 例如:
public class Item
{
string name;
string price;
public Item(string name, int price) => (this.name, this.price) = (name, price);
public string ToString() => $"Name = {name}, Price = {price}";
public string Name => name;
public int Price => price;
}
我们在这里看到了元组解构(表达式实际上并不创建元组)、插值字符串字面、表达式方法和属性。
由于委托是方法声明的一种简短形式,因此非常适合使用。让我们看看下面的示例:
using System;
using System.Linq;
class Program
{
delegate int ChangeNumber(int arg);
static void Main()
{
Console.WriteLine("Input some numbers");
int[] numbers = Console.ReadLine().Split(" ").Select(int.Parse).ToArray();
Console.WriteLine("Input addition");
int addition = int.Parse(Console.ReadLine());
ChangeNumbers(n => n + addition, numbers);
Console.WriteLine("Result :");
Console.WriteLine(string.Join(" ", numbers.Select(n => n.ToString())));
}
static void ChangeNumbers(ChangeNumber change, int[] numbers)
{
for(int i = 0; i < numbers.Length; i++)
{
numbers[i] = change(numbers[i]);
}
}
}
对于那个 n => n + addition
表达式,我们可以生成 Java 匿名类表达式:
// translated to Java code
interface ChangeNumber
{
int invoke(int arg);
}
// ...static void main(String[] args)...
// anonymous class expression for Java 7 or older version
changeNumbers(new ChangeNumber()
{
public int invoke(int n)
{
return n + addition;
}
}, numbers);
// or lambda expression for higher Java 8 or newer version
changeNumbers(n -> n + addition, numbers);
C# 有许多语言结构,它们可能会让我们的代码看起来很简单,在语法糖的背后隐藏着庞大的实现。其中一些构造值得商榷,几乎不值得支持,而另一些则非常灵活,易于重现。在设计时,最好通过对象而不是 C# 专用语言结构来组成抽象。 当谈到性能时,我们应该保持对内存管理的抽象,从而避免通过双重成本来模拟内存管理。