SHOEISHA iD

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

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

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

絶え間ない進化を続けるC#──バージョン11を理解しよう! 参照の強化と文字列関連の新機能を紹介

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

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

参照関連の新機能

 C# 11では、refフィールドとscoped refの利用が可能になりました。refフィールドは、構造体に置くことのできる参照型のフィールドで、Span<T>という構造体を実装するための機能の一つです。また、scoped refは、参照の安全な利用のための機能の一つです。単体で見ると用途が分かりにくい機能ですが、これらを理解するためにC# 7.0以降の参照関連の機能強化を絡めながら紹介していきます。

[NOTE]参照型

 参照型は、値そのものではなく値の場所を保持する型です。これに対し値型は、値そのものを保持する型です。例えば、クラスは参照型で構造体やプリミティブ型は値型です。関数の引数にrefキーワードやoutキーワードを付与することによって、値ではなく参照を渡すこともできます(参照引数)。参照引数によって、大きな構造体を効率よく渡したり、関数から引数経由で値を受け取ることができます。

Span<T>構造体について

 まず、Span<T>構造体について触れておきます。Span<T>構造体は、その名の通りメモリ上の連続した領域を指し、配列やマネージドメモリなどの一部あるいは全部を表現するために利用されます。このような用途の構造体あるいはクラスはほかにもありますが、Span<T>は効率よく高速な処理が要求される場面で利用されることを想定しています。そのため、やや低レベルな処理においてその真価を発揮します。C#は高水準なオブジェクト指向言語ですが、パフォーマンスが重視される場面にも適用できるように、このような機能が用意されています。

 C# 11のSpan<T>構造体の実装は、大まかに以下のようになっています(本質的ではないのでアクセス修飾子などは省いています)。シンプルにいうと、T型の参照と、長さをフィールドとして持つ構造体です。

readonly ref struct Span<T>	(1)
{
    readonly ref T _reference;		(2)
    readonly int _length;
}

 ここには、C# 7.2以降で実装された機能が幾つか見られます。(1)の「readonly ref struct」は変更不可のref構造体の定義、(2)の「readonly ref」は再代入不可のrefフィールドの宣言です。それぞれ、以降で見ていきます。

ref構造体(ref struct)[7.2]

 C# 7.2では、配置場所がスタックに限定される、ref構造体を使えるようになりました。

 構造体は値型であるので、通常はスタック上に配置されます。しかしながら、クラスのメンバになったりすると、クラスのインスタンスの一部として通常はヒープへ配置されてしまいます。上記のSpan<T>構造体は、その性質上、スタックへの配置が要求されることから、これでは具合が悪いということでref構造体が追加されました。ref構造体は、いかなるときにもスタックに配置されることが保証された構造体です。そのため、ヒープへ配置されないようにするための、以下に挙げるようなさまざまな制限を持っています。

  • Box化できない
  • 配列にできない
  • クラスのメンバになれない
  • 型引数になることができない

 ref構造体は、ref構造体自身(C# 11ではrefフィールドに拡大)をフィールドに持つことができます。構造体の定義にrefキーワードを付与してref構造体であることを宣言します。Span<T>構造体はref構造体なので、ref構造体ではSpan<T>構造体のフィールドを持つことができます。逆をいうと、Span<T>構造体のフィールドを持てるのはref構造体のみです。通常の構造体では持つことができません。

 以下は、Span<T>構造体のフィールドspanを持つref構造体Sの使用例です。

リスト reference72/Program.cs
ref struct S {			// ref構造体
    public Span<string> span;	// ref構造体Spanのフィールド

    public S() {
        span = new string[10];	// 10個のstring型要素で初期化
    }
}

var s = new S();
s.span[0] = "Hello";			// 配列と同じように使用できる
Console.WriteLine($"{s.span[0]}");
  // 実行結果:Hello

 Span<T>構造体については、次回でパターンマッチングの新機能とともに改めて紹介します。

refフィールド(ref fields)[11.0]

 C# 11では、ref構造体のみに限定されますが、参照型のフィールドを使えるようになりました。

 C# 10までは、ref構造体に置ける参照はref構造体のみでした。C# 11では、これが拡張されてref構造体に限らない参照フィールドを置くことができるようになりました。refフィールドは、通常のフィールドの宣言にrefキーワードを付与して宣言します。上記のSpan<T>構造体の定義イメージでは、このrefフィールドによってT型の参照をフィールドとして配置しています。

 以下は、refフィールドの使用例です。構造体Rのインスタンス生成でscopedキーワードをエラー回避のために付与しています。scopedキーワードについては後述します。

リスト reference11/Program.cs
ref struct R {
    public ref int iref;	// refフィールド
    public ref string? sref;
}

var ivalue = 100;
var svalue = "Hello";
scoped var r = new R();		// scopedキーワードについては後述
r.iref = ref ivalue;
r.sref = ref svalue;

 refフィールドが導入されていないC# 10までのSpan<T>の実装は、参照としてのフィールドを内部で特別扱いすることで目的の機能を達成していました。C# 11では、refフィールドにより参照型フィールドを自然な形で構造体に置くことができるようになったというわけです。

参照戻しとrefローカル変数[7.0]

 C# 7.0では、関数の戻り値とローカル変数を、参照型とすることができるようになりました。

 ここで、C# 7.0で利用可能になった、参照戻しとrefローカル変数について紹介しておきます。これらは、関数の戻り値とローカル変数を、参照型とします。これにより、参照引数を参照のまま取り扱い、関数の実行結果として返すことができます。関数内部でも値のコピーが発生しにくくなるので、パフォーマンス的に有利となります。

 以下は、参照引数をrefローカル変数で受け取り、値を書き換えて参照戻しとして返す関数の例です。呼び出し側の変数aが書き換わる点に注目です。

リスト reference70/Program.cs
static ref int func(ref int x) {
    ref var y = ref x;	// x, yともにaへの参照
    y = 200;		// yはaへの参照なのでaの値が書き換わる
    return ref y;	// aへの参照が返る
}

var a = 100;
Console.WriteLine($"a={a},ret={func(ref a)},a={a}");
  // 実行結果:a=100,ret=200,a=200

 このように、くどいほどにrefキーワードの付与が必要になります。呼び出し側の実引数にrefキーワードが必要なのは従来どおりですが、参照のままの受け渡しを明示的に記述することで、意識しない代入などによる不具合をあらかじめ防止するためと言えます。

[NOTE]エスケープ解析

 例えば関数から参照戻しで結果を受け取る場合、その参照先は関数終了後も存在している必要があります。これを含めて、参照(ポインタ)を追跡してその有効性をコンパイル段階で調べることをエスケープ解析といいます。これにより、あるスコープにおいて参照先が有効であるかどうか(エスケープ=逃げ出されていないか)コンパイル段階で確認することができます。参照戻しやrefローカル変数、refフィールドなどを安全に使うには必須の機能です。

スコープされるref(scoped ref)[11.0]

 C# 11では、参照の有効なスコープを明示するscoped refを使えるようになりました。

 参照は、エスケープ解析によって、あるスコープで有効か調べられますが、このとき参照が有効なスコープを明示するのがscopedキーワードです。参照引数、refローカル変数ともに、scopedキーワードが付与されると、それはその関数外に値を漏らさない(エスケープしない)参照(scoped ref)であることを明示的に指定したことになります。

 エスケープ解析では可能な限り参照を追跡しようとするので、上記のrefフィールドのサンプルのように、インスタンスが別のスコープにまで渡される可能性がある場合にはエラーとなってしまいます。このようなときに、インスタンスにscopedキーワードを付与することで、そのスコープの外には持ち出されないことを明示し、エラーを回避するのです。

 以下は、scopedキーワードを付与された参照引数とrefローカル変数を関数が戻そうとしたときにエラーとなることを確かめる例です。

リスト reference11/Program.cs
static ref int func1(scoped ref int x) {
  return ref x;	// error CS9075: 現在のメソッドに範囲指定されているため、
		// 参照渡し 'x' でパラメーターを返すことはできません
}

static ref int func2(ref int x) {
  scoped ref int y = ref x;
  return ref y;	// error CS8157: 'y' は参照渡しで返せない値に初期化されたため、
		// 参照渡しで返すことができません
}

再代入不可のrefフィールド[11.0]

 C# 11では、再代入のできないrefフィールドを指定できるようになりました。

 refフィールドでは、再代入不可をreadonlyキーワードで指定することができます。これにより、上記のSpan<T>構造体の定義イメージのように、参照先の変更ができないrefフィールドを置くことができます。参照先の変更ができないので、エスケープ解析による追跡の精度が向上するというメリットがあります。なお、参照先の変更ができず、参照先の値の書き換えもできないrefフィールドを、readonly ref readonlyとして記述可能です(readonly ref+readonly)。

 以下は、再代入不可のrefフィールドと値の書き換えもできないrefフィールドへの代入でエラーとなる例です。

リスト reference11/Program.cs
ref struct S {
    public readonly ref int x;			// 参照が変更不可
    public readonly ref readonly int y;		// 値も参照も変更不可
    public S(ref int _x, ref int _y) {
        x = ref _x;
        y = ref _y;
    }
}

var a = 100;
var b = 200;
var c = 300;
var s = new S(ref a, ref b);
s.x = ref c;	// error CS0191: 読み取り専用フィールドに割り当てることはできません
s.y = c;	// error CS8331: フィールド に割り当てることができません。

次のページ
文字列関連の新機能

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

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

もっと読む

この記事の著者

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

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/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/17486 2023/03/30 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

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

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

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

メールバックナンバー

アクセスランキング

アクセスランキング