拡張メンバー(Extension members)
C# 14では、extensionブロックにより、拡張メソッドに加えて静的メソッド、インスタンスプロパティ、静的プロパティが拡張メンバーとして使えるようになりました。
C#には拡張メソッドと呼ばれるものがあり、別クラスで定義した静的メソッドをインスタンスメソッドのように呼び出すことができます。
例えば以下のリストのように、Extendedクラスにある静的メソッドMethodは、あたかもClassクラスのメソッドのように呼び出すことができます。ポイントは、拡張メソッドのthisで修飾された引数(レシーバー)です。
public class Class
{
}
public static class Extended
{
public static void Method(this Class c, string s)
{
Console.WriteLine(s);
}
}
var c = new Class();
c.Method("Extended method"); // Extended method
C# 14では、新しく導入されたextensionブロックにより、インスタンスメソッドに加えて、インスタンスプロパティや静的メソッド、静的プロパティが拡張メンバーとして定義できるようになりました。extensionブロックはメソッド・プロパティを置く静的クラスに、以下の構文で配置されます。
extension(レシーバー)
{
プロパティ定義...
メソッド定義...
}
レシーバーは、従来の拡張メソッドと異なり、thisは不要です。このレシーバーを使ったプロパティ定義、メソッド定義をブロック内部に配置していきます。従来の書式において、レシーバーを一括指定するイメージです。
以下のリストは、インスタンスメソッド、インスタンスプロパティを定義する例です。
public static class Extended
{
private static int _field = 1;
extension(Class c) (1)
{
public void Method(string s) (2)
{
Console.WriteLine(s);
}
public int Property { get => _field; } (3)
}
}
(1)のように、extensionブロックをレシーバーとともに宣言します。(2)はインスタンスメソッドの定義、(3)はインスタンスプロパティの定義です。それぞれ、通常のメソッド定義、プロパティ定義と変わらないことに注目してください。従来の拡張メソッドでは、静的メソッドとして定義する必要があったうえ、引数にレシーバーが必要でした。
以下のリストは、静的メソッド、静的プロパティを定義する例です。
public static class Extended
{
…略…
extension(Class) (1)
{
public static void StaticMethod(string s) (2)
{
Console.WriteLine(s);
}
public static int StaticProperty { get => _field; } (3)
}
}
静的メンバーの定義では、(1)のように、extensionブロックのレシーバーに型名のみ指定します。そして(2)(3)のように、static修飾子を付けてメンバーを定義します。
ジェネリックな型についても拡張メンバーの利用が可能です。この場合、extension<T>というようにextensionブロック自体に型パラメーターを指定します。
public class GClass<T>
{
}
public static class Extended
{
…略…
extension<T>(GClass<T> t)
{
public T GenericMethod(T value) => value;
}
}
var g = new GClass<int>();
Console.WriteLine(g.GenericMethod(42)); // 42
null条件代入(Null-conditional assignment)
C# 14では、null条件メンバーアクセス演算子(?.)とnull条件要素アクセス演算子(?[])が、代入演算子または複合代入演算子の左辺で使用できるようになりました。
C#には、null条件演算子がいくつかあり、本来であればif文を使うところを、省略されたコードでの記述を可能にしてくれます。
| 演算子 | 概要 |
|---|---|
| null合体演算子(??) | a ?? b(aがnullならb、非nullならa) |
| null結合代入演算子(??=) | a ??= b(aがnullならbを代入、非nullならaのまま) |
| null条件メンバーアクセス演算子(?.) | a?.b(aがnullならnull、非nullならbにアクセス) |
| null条件要素アクセス演算子?[] | a?[b](aがnullならnull、非nullならa[b]にアクセス) |
このうちnull条件メンバーアクセス演算子(?.)とnull条件要素アクセス演算子?[]は、代入演算子または複合代入演算子の左辺に現れる可能性があるにもかかわらず、使用できないという制限がありました。C# 14ではこれが緩和され、これらの演算子を代入演算子または複合代入演算子の左辺で利用できるようになりました。
class Customer
{
public string? Name { get; set; }
}
…略…
var customer = (Customer)null;
customer?.Name = "Yamauchi";
Console.WriteLine(customer?.Name); // null(何も出力されない)
customer = new Customer { Name = "Nao" };
customer?.Name = "Yamauchi";
Console.WriteLine(customer?.Name); // Yamauchi
customerがnullであれば何もせず、nullでなければ右辺の値をプロパティに代入します。なお、評価結果がnullの場合は、右辺自体が評価されないので、右辺が副作用のある式である場合には注意が必要です。また、if文で判定するときのような==演算子を使わないので、==演算子が副作用を持つ場合にも結果が異なってくる点にも注意が必要です。
null条件要素アクセス演算子?[]も、代入式の左辺で利用できます。以下のように、?.演算子との組み合わせも可能です。
class Customer
{
public int[] Items { get; set; }
}
…略…
customer?.Items?[0] = 100;
Console.WriteLine(customer?.Items?[0]); // null(何も出力されない)
customer?.Items = new int[3];
customer?.Items?[0] = 100;
Console.WriteLine(customer?.Items?[0]); // 100
この場合、null条件メンバーアクセス演算子(?.)がまず評価され、次にnull条件要素アクセス演算子(?[])が評価されます(左結合)。
null条件メンバーアクセス演算子とnull条件要素アクセス演算子は、複合代入演算子(+=、-=など)でも利用できます。これらと同じく値を書き換える演算子としてインクリメント・デクリメント演算子(++、--)がありますが、これらでは使用できません。
