SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

【C#で知っておくべき新機能】最新バージョンを徹底解説!

C#の新たなる進化──プライマリコンストラクタとコレクション式などバージョン12の注目機能を紹介!

【C#で知っておくべき新機能】最新バージョンを徹底解説! 第4回


  • X ポスト
  • このエントリーをはてなブックマークに追加

コレクション式の活用で、配列やリストを簡潔に記述する

 C# 12では、配列やリストなどのコレクションを、コレクション式(Collection expressions)により統一された簡潔な書式で記述可能になりました。

 従来の配列やリスト、スパンの初期化は、それぞれ以下のリストのように記述していました。それぞれに異なった初期化の書き方があり、使い分ける必要があったわけです。

collection_expressions/Program.cs
int[] intArray1 = new[] { 100, 200, 300 };	// 整数型配列
int[] intArray2 = { 400, 500, 600 };		// 同じく整数型配列
List<string> stringList = new() { "Jan", "Feb", "Mar" };	// 文字列型リスト
Span<char> charSpan = stackalloc[] { 'a', 'b', 'c' };	// 文字型スパン

 これでは煩雑で直感的でないということで、C# 12では大かっこ(ブラケット、[ ])を使って共通の書式で初期化できるようになりました。これをコレクション式(Collection Expression)といいます。

collection_expressions/Program.cs
int[] intArrayCollection = [100, 200, 300];
List<string> stringListCollection = ["Jan", "Feb", "Mar"];
Span<char> charSpanColllection = ['a', 'b', 'c'];

 配列なのかListやSpanなのかにかかわらず、同じ書き方ができるので、読み手にとっても書き手にとっても分かりやすくなりました(SpanについてはReadOnlySpanも同様)。このコレクション式は、以下の型に対して使用可能となっています。

  • 配列
  • Span<T>, ReadOnlySpan<T>
  • 配列が実装しているIEnumerable<T>、IReadOnlyList<T>、IList<T> などのインタフェースを実装している型
  • CollectionBuilder属性(後述)が付与されている型
  • コレクション初期化子の条件を満たす型({1, 2, 3}といった配列のような初期化が可能な型)

 これを踏まえると、ImmutableArray<T>のような型も、コレクション式で記述可能です。

collection_expressions/Program.cs
ImmutableArray<int> intImmutableArray = ImmutableArray.Create(100, 200, 300);	// 従来
ImmutableArray<int> intImmutableArrayColelction = [100, 200, 300];	// コレクション式

 コレクション式と同時に導入されたスプレッド式(Spread Expression)を使うと、初期化子の中に別のコレクションを埋め込むことができます。型が一致する限り、配列やListを問わず任意のコレクションを埋め込むことができます。スプレッド式は、「..式」というようにドット(.)を2つつなげて記述します。

collection_expressions/Program.cs
int[] intSpreadExpression = [0, ..intArray1, ..intArray2, 700];
foreach(var e in intSpreadExpression) {
    Console.Write($"{e} ");
}
Console.WriteLine();
// 実行結果:0 100 200 300 400 500 600 700

 コレクションを初期化するとき、どのような書式だったか?と迷わないで済むので、コーディング時の利便性が向上します。

 なお、C# 12におけるコレクション式の導入に合わせて、CollectionBuilder属性が導入されました。この属性を使って、独自のコレクション型にコレクション式を実装することができます。以下のリストに、IEnumerable<T>インタフェースを実装するだけのシンプルなコレクションの例を示します。基本的に、コレクションそのものの定義と、ビルダークラスの定義からなり、CollectionBuilder属性がそれらを結びつけるという動作になります。

collection_expressions/Program.cs
using System.Runtime.CompilerServices;	(1)

[CollectionBuilder(typeof(AnotherCollectionBuilder), nameof(AnotherCollectionBuilder.Create))]		(2)
public class AnotherCollection<T>: IEnumerable<T>	(3)
{
    private readonly T[] enums;
    public AnotherCollection(ReadOnlySpan<T> enums)
    {
        this.enums = new T[enums.Length];
        enums.CopyTo(this.enums);
    }
    public IEnumerator<T> GetEnumerator()
    {
        foreach (var e in enums) yield return e;
    }
    System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => this.GetEnumerator();
}

public static class AnotherCollectionBuilder	(4)
{
    public static AnotherCollection<T> Create<T>(ReadOnlySpan<T> values) => new(values);
}

 (1)はCollectionBuilder属性を使うための宣言です。(2)は属性の指定で、引数にBuilderType(コレクションの構築に使用するビルダーの型、(4)の定義から"AnotherCollectionBuilder"となる)とMethodName(コレクションの構築に使用するメソッド名、(4)の定義から"Create"となる)をそれぞれtypeof、nameofにて指定しています。

 (3)以降がコレクション型の定義になります。最低限のフィールド、コンストラクタ、メソッドを実装しているのみです。

 (4)はビルダークラスの定義です。ここに、(2)で指定する構築メソッドCreateが定義されます。

読み取り専用の参照を渡す「ref readonlyパラメータ」活用法を紹介

 C# 12では、ref readonlyパラメータ(ref readonly parameters)によって、左辺値(下記コラム参照)を読み出し専用で関数に渡したい場合の意図を明確にできるようになりました。

 C#には、関数の引数を参照渡しにするrefパラメータ、inパラメータというものがあります(受け取り専用のoutパラメータもありますが、本稿とは関係が薄いので省略します)。inパラメータは引き渡し専用で、参照先の値を変更できない他は基本的にrefパラメータと同じです。inパラメータには、メソッド定義時に引数にinパラメータ修飾子を指定します。

 このinパラメータは、参照渡しといいながら定数値などの右辺値(下記コラム参照)を渡すことが可能です。ですが定数値なので参照を持てないため、コンパイラは一時的な変数を生成して、参照として渡すように内部的に処理されています。これを確かめるのが以下のリストです。

collection_expressions/Program.cs
void func_in(in int a)	// inパラメータ修飾子を指定
{
    Console.WriteLine(a);
}

var x = 100;
func_in(x);		// 実行結果:100
func_in(200);		// 実行結果:200

 入力専用なので、変数であろうが定数であろうがin引数として渡せるのは便利なのですが、最近の.NETでこれが問題となるケースが出てきました。例えば、System.Runtime.InteropServices.Marshal.QueryInterface、System.ReadOnlySpan<T>.ReadOnlySpan<T>(T)、System.Runtime.CompilerServices.Unsafe.IsNullRefなどです。これらは、inパラメータを受け取って参照を返すメソッドを実装していることがあります。渡されたinパラメータが右辺値であった場合、返される参照はその一時的な変数に対するものなので、結果が予測できないものになります。

 これでは都合が悪いので、inパラメータに代わって読み取り専用の参照を渡すことを示すために、C# 12ではref readonlyパラメータを使えるようになりました。これは、メソッド内で変更されないが、右辺値は受け取らないという意図を明確にします。上記のリストをref readonlyパラメータで書き換えたのが以下のリストです。

collection_expressions/Program.cs
void func_rr(ref readonly int a)	// ref readonlyパラメータ修飾子を指定
{
    Console.WriteLine(a);
}

var y = 100;
func_rr(ref y);		// refがないと警告が発生する
func_rr(300);		// 警告となるが実行される

 発生する警告は以下の通りです(ソースのパスは省略)。変数は、refパラメータ修飾子かinパラメータ修飾子とともに渡す必要があり、渡した定数(右辺値)は変数である必要があると指摘されています。ただしあくまでも警告のみとなっており、プログラムは実行されます。

/…/Program.cs(18,9): warning CS9192: 引数 1 は 'ref' または 'in' キーワード (keyword)と共に渡す必要があります [/…/ref_readonly.csproj]
/…/Program.cs(19,9): warning CS9193: 引数 1 は 'ref readonly' パラメーターに渡されるため、変数である必要があります [/…/ref_readonly.csproj]

 参照を受け取ってその参照を返すとか、そういったケースで問題となっているようなので、日常的な開発ではあまり意識する必要のない機能と言えそうです。

左辺値と右辺値

 ここでは、左辺値と右辺値という用語を用いて解説しました。簡単に表現すると、左辺値とは代入式における「=」の左側に置く変数など、右辺値は右側に置く定数や式となります。ただし最近は意味が変化して、名前の付いたメモリ領域を持つものを左辺値、一時的な値で名前を持たないものを右辺値として区別しています。これを踏まえれば、変数は左辺値であり、定数や式、関数の戻り値などは右辺値です。元来、参照は左辺値に対するもので右辺値に対するものは存在しませんでしたが、現在は左辺値参照、右辺値参照として双方に参照が作成できるようになってきています。

readonly refとは異なる

 ref readonlyパラメータ修飾子と似ているreadonly refという記述もあります。これは、連載第2回で紹介した再代入不可のrefフィールドを意味します。クラスや構造体で参照先を変更できないフィールドの明示に使用しますが、用途は全く異なるので注意しましょう。ちなみに、再代入不可で参照先の変更もできないreadonly ref readonlyという記述もあります。

次のページ
ラムダ式の進化:C# 12でのパラメータ既定値の導入

この記事は参考になりましたか?

  • X ポスト
  • このエントリーをはてなブックマークに追加
【C#で知っておくべき新機能】最新バージョンを徹底解説!連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 山内 直(WINGSプロジェクト ヤマウチ ナオ)

WINGSプロジェクトについて> 有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS Twitter: @yyamada(公式)、@yyamada/wings(メンバーリスト) Facebook <個人紹介> WINGSプロジェクト所属のテクニカルライター。出版社を経てフリーランスとして独立。ライター、エディター、デベロッパー、講師業に従事。屋号は「たまデジ。」。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • X ポスト
  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/19041 2024/03/06 19:16

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング