15 3月 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;
}
現在は 1 つの固体メンバーではなく、3 つの別々のメンバーが存在します。 同じコードが自動プロパティごとに繰り返されると想像してください。それはどのように見えるでしょうか? このような不快な経験は避けましょう。 しかし、どうやって?
自動プロパティを、プライベートフィールドへのアクセス制御を提供するオブジェクトに置き換えてみてください。そのようなオブジェクトのハッシュマップがあるかもしれません。一部のデータへのアクセスを制限する設計に従っている場合、それは良い方法です。自動プロパティは見栄えが良いかもしれませんが、必要がなければ使う必要はありません。
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#専用の言語構造ではなく、オブジェクトによって抽象化を構成する方が良いでしょう。 パフォーマンスの観点からすると、メモリ管理から抽象化を保ち、それを二重のコストでエミュレートすることから自由になるべきです。