第2回 簡潔なコーディングのために:特集:C# 7の新機能詳説(1/2 ページ)
ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている。
Visual Studio 2017とともにリリースされたC# 7には多くの新機能がある。それらの新機能はどのような場面で役立つのだろうか? 3回にわたって紹介していく。
- 第1回:明瞭なコーディングのために
- 第2回:簡潔なコーディングのために(本稿)
- 第3回:型による分岐の改良
簡潔なコーディングのために
コードを短く要領よく書きたいとは、プログラマーなら誰しもが思うことだろう。今回は、簡潔なコーディングに役立つC# 7の新機能を紹介する。
- 本体が式のメンバの拡大
- throw式
- out変数
- タブルの生成と分解
本体が式のメンバの拡大
クラスメンバの本体をラムダ式の形で簡潔に記述できる機能である。
C# 6では、次に示すメンバが対応していた。
- 通常のメソッド
- 演算子
- 読み取り専用のプロパティ/インデクサー
C# 7では、以下のメンバにも拡張された。およそあらゆるメンバがラムダ式で記述可能になったといえるだろう。
- コンストラクタ/デストラクタ
- プロパティ/インデクサーのget/set
- イベントのadd/remove
プロパティをラムダ式で記述する
プロパティやインデクサーをラムダ式で記述する場合、読み取り専用ならばC# 6の形式を、読み書き可能なときはget/setを個別にラムダ式で書くC# 7の形式を使うとよい。
プロパティをラムダ式で記述する例を次のコードに示す。
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
public abstract class BindableBase : INotifyPropertyChanged
{
// INotifyPropertyChangedインタフェースの実装と、ヘルパーメソッド
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
=> PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
protected void SetProperty<T>(ref T storage, T value,
[CallerMemberName] string propertyName = null)
{
if (object.Equals(storage, value))
return;
storage = value;
OnPropertyChanged(propertyName);
}
}
public class ABindableClass : BindableBase
{
// 読み書きするプロパティは、get/setそれぞれをラムダ式で記述できる
private DateTimeOffset _date;
public DateTimeOffset Date
{
get => _date;
set => SetProperty(ref _date, value);
}
// 読み取り専用プロパティは、次のような形でラムダ式を使える
public string NowTime => $"{DateTimeOffset.Now:HH:mm:ss}";
// これはC# 7では次のように書くこともできるが、かえって長くなる
//public string NowTime
//{
// get => $"{DateTimeOffset.Now:HH:mm:ss}";
//}
}
C# 7では、プロパティのget/setを別々にラムダ式で記述できるようになった。
読み取り専用プロパティの場合は、C# 6の形式(getキーワードを書かない)で書いた方が簡潔になる。
なお、このサンプルコードはINotifyPropertyChangedインタフェース(System.ComponentModel名前空間)の実装例になっている。読み書き可能なDateプロパティはデータバインディングに対応している。INotifyPropertyChangedインタフェース登場時にその実装をした経験のある開発者は、このDateプロパティの簡潔さに驚くかもしれない。また、INotifyPropertyChangedインタフェースのヘルパーメソッドの1つ、OnPropertyChangedメソッドもラムダ式で実装している。
throw式
これは、throwステートメントを特別な場所でだけ式として扱うものだ。何だか難しそうだが、以下に示す3箇所にもthrowが書けるようになったのだ。
- 式形式のラムダ式の中(「=>」の後ろ)
- null合体演算子「??」の後ろ
- 条件演算子(三項演算子)「?:」の後ろ
式形式のラムダ式の中で例外を投げる
今までは式形式のラムダ式の中で例外を投げられなかった。そのため、取りあえずコンパイルを通すためだけにメンバを書いておいて後からちゃんと実装するような場合には、次のコードに示すような手順を踏んでいた。
public string SampleMethod()
{
// コンパイルを通すためだけの仮実装
// 後で実装するのを忘れないよう、例外を投げるようにしておく
throw new NotImplementedException();
}
// ↓ 後からちゃんと実装する
public string SampleMethod()
{
return "Hello, world!";
}
// ↓ ラムダ式にして簡潔に
public string SampleMethod() => "Hello, world!";
最初に、コンパイルを通すためだけの仮実装を行う。このとき、後で実装するのを忘れないように、メソッド本体はNotImplementedException例外を投げるようにしておきたいとする。すると、C# 6までは式形式のラムダ式の中にthrowステートメントは書けなかったので、通常の形式でメソッドを書かねばならない。
次に、メソッドの中身をちゃんと実装し、それからラムダ式に書き直して簡潔にする。この2ステップは同時にやってもよいが、いずれにせよ、最初に普通の形式でメソッドを書かねばならないのは面倒だ。
C# 7では、最初からラムダ式で書ける(次のコード)。
// C# 7では、最初からラムダ式で書ける
public string SampleMethod() => throw new NotImplementedException();
// ↓ 後からちゃんと実装する
public string SampleMethod() => "Hello, world!";
C# 7では、式形式のラムダ式にthrowを置けるので、仮実装のときからラムダ式で書き始められる。
null合体演算子や条件演算子の中で例外を投げる
例として、引数がnullだったときに、ArgumentNullException例外(System名前空間)に代えて独自の例外(例えばSystem名前空間のApplicationException例外)を出したいとしよう。
これまでであれば、次のコードのようにnull判定を行ってthrowステートメントを書いていた。
public static string Reverse(object o)
{
// nullのとき、ArgumentNullException例外ではなく、独自の例外を出したい
var s = o as string;
if (s == null)
throw new ApplicationException();
return new string(s.Reverse().ToArray());
}
C# 7では、上のコードはnull合体演算子を使って簡潔に書ける(次のコード)。
public static string Reverse(object o)
{
var s = o as string ?? throw new ApplicationException();
return new string(s.Reverse().ToArray());
}
あるいは、キャストする必要がなくてただnull判定をする場合は、次のコードのように条件演算子(三項演算子)の中でthrowできる。
public static string Reverse2(string s)
=> string.IsNullOrWhiteSpace(s)
? throw new ApplicationException()
: new string(s.Reverse().ToArray());
Copyright© Digital Advantage Corp. All Rights Reserved.