はじめに
C#6.0では、細かな文法の拡張や演算子の追加などが多く行われました。
その多くは、頻繁に使用するにもかかわらず、C#5.0まででは記述が面倒だった処理を簡単に記述できるような機能です。
本記事では、C#6.0から簡単にかけるようになった処理を、以前の記述方法と比較しながら紹介します。
なお、本記事で紹介されていない処理があればコメント等で指摘をいただければ、随時追加します。
INotifyPropertyChanged
概要
最初に紹介するのは、INotifyPropertyChangedの実装です。
このインターフェースを実装したクラスは、PropertyChanged
イベントを発生させて、プロパティが変化したことを通知します。
WPFでViewModelを実装する際に使用します。
BeforeAfter
using System.ComponentModel
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if(PropertyChanged != null)
PropertyChanged(this, e);
}
private string _name;
public string Name
{
get { return _name; }
set
{
if(value == _name)
return;
_name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name"));
}
}
public Person(string name)
{
_name = name;
}
}
using System.ComponentModel
public class Person : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
PropertyChanged?.Invoke(this, e);
}
private string _name;
public string Name
{
get { return _name; }
set
{
if(value == _name)
return;
_name = value;
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Name)));
}
}
public Person(string name)
{
_name = name;
}
}
解説
改善された点は2点あります。OnPropertyChanged
のnullチェックの簡略化と、プロパティ名の記述方法です。
null条件演算子の導入によって、nullでなければ呼び出すという処理が一行でかけるようになりました。
また、nameof
演算子の導入によって、プロパティ等の名前が得られるようになりました。
nameof
演算子の真価は、コンパイル時に検証できるということにあります。プロパティ名を変更した時、文字列で記述されていると、new PropertyChangedEventArgs
の引数を変え忘れていてもコンパイルが成功してしまいますが、nameof
演算子を使用することによって、コンパイルエラーを発生させることができます。
// NameをFullNameに変更したとする
private string _fullName;
public string FullName
{
get { return _fullName; }
set
{
if(value == _fullName)
return;
_full_name = value;
OnPropertyChanged(new PropertyChangedEventArgs("Name")); // コンパイルが通る
OnPropertyChanged(new PropertyChangedEventArgs(nameof(Name))); // コンパイルエラー
}
}
また、nameof
演算子を使用すると、VisualStudioのリファクタリング機能の対象にすることができます。
Argument(Null)Exception
概要
先ほどのINotifyPropertyChanged
でも登場した、nameof
演算子は、Argument(Null)Exception
を使用する際にも活躍します。
説明不要だとは思いますが、ArgumentNullException
とはnull
になってはいけない引数にnull
が渡された場合にスローされる例外であり、ArgumentException
を継承しています。また、ArgumentException
とは、誤った引数が渡された場合にスローされる例外です。
BeforeAfter
using System;
public static class MyClass
{
public static double Sqrt(double x)
{
if(x < 0)
throw new ArgumentException("xは0以上でなければなりません。");
return Math.Sqrt(x);
}
public static string Hello(string name)
{
if(name == null)
throw new ArgumentNullException("nameにnullは使用できません。"});
return string.Format("{0} Hello", name);
}
}
using System;
public static class MyClass
{
public static double Sqrt(double x)
{
if(x < 0)
throw new ArgumentException($"{nameof(x)}は0以上でなければなりません。");
return Math.Sqrt(x);
}
public static string Hello(string name)
{
if(name == null)
throw new ArgumentNullException($"{nameof(name)}はnullは使用できません。"});
return $"{name} Hello";
}
}
(QiitaのシンタックスハイライトがC#6.0に対応していないため若干見づらくなっています)
解説
nameof演算子に加え、文字列挿入(string interpolation)と呼ばれる機能が使用されています。これは、ruby等では式展開と呼ばれており、文字列中で式を評価して埋め込む機能です。
なお、文字列挿入は単純にコンパイル時にstring.Format
に置き換えられます。以下の2つの式は等価です。
var strA = string.Format("{0:e} {1:d}", x, y))
var strB = $"{x:e} {y:d}"
strA == strB // true
C#5.0以前でも、string.Format
は存在していますが、番号の順番を間違えたり、誤った書式指定文字列を使用することでエラーになることがありました。文字列挿入ではコンパイル時に検査されるため、直感的かつより安全に記述することができます。
また、単純に短くなるというメリットもあります。
Console.WriteLine(string.Format("{0:e} {1,d}", x, y); // 実行時エラー
Console.WriteLine(string.Format("{0:e} {0:d}", x, y); // 番号の指定ミス
Console.WriteLine($"{x:e} {y,d}"); // コンパイルエラー
プロパティ
概要
プロパティはC#の強力な機能の1つで、フィールドをラップして公開することができます。プロパティば、外側から見ればフィールド、内側からみればメソッドのようにふるまいます。
BeforAfter
public class Person
{
private readonly string _firstName, _lastName;
private int _age = 0;
public string FirstName { get { return _firstName; } }
public string LastName { get { return _lastName; } }
public string Name { get { return string.Format("{0} {1}", FirstName, LastName); } }
public string Age
{
get { return _age; }
private set { _age = value; }
}
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
public class Person
{
public string FirstName { get; }
public string LastName { get; }
public string Name => $"{FirstName} {LastName}"
public string Age { get; private set; } = 0
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
}
解説
プロパティについては、C#3.0のときに自動実装プロパティが導入されるという大きな変化がありました。C#6.0での変更では、プロパティが更に協力になっています。
まず、自動実装プロパティに初期値を与えることが可能となりました。プロパティに初期値を与えるためには、自動実装プロパティを使用せずにフィールドに初期値を与えるか、コンストラクタで初期化する方法しかありませんでした。
ただし、後者の方法ではset
アクセサが必須になり、readonly
にすることができません。そのため、readonly
かつプロパティを使用したい場合、自動実装プロパティは使用することができませんでした。
C#6.0からは、set
アクセサを定義しない自動実装プロパティを作成すると、そのプロパティはreadonly
なフィールドと同様、コンストラクタ内または初期化子以外では値を代入できなくなります。
また、C#6.0からは、get-onlyなプロパティまたはインデクサの本体をラムダで記述することができるようになりました。なお、get-onlyでないものについては、ラムダによる省略記法はありません。
イベント
番外篇 未来のC#: メンバの初期化
概要
コンストラクタの引数でのメンバの初期化は、言語を問わず頻繁に書くことになる処理でしょう。しかし、C#6.0でも、同じような単語を何度もタイプする必要があり、うんざりします。
C#6.0で採用される予定だったPrimaryConstructorが採用されると、そんなメンバの初期化処理が非常に簡単に書くことができるようになります。
BeforAfter
BeforeはC#6.0, Afterは採用を見送られた機能が採用された場合にかけるようになるコードです。
public class Person
{
public string Name { get; }
public Person(string name)
{
Name = name;
// do something
}
}
public class Person(string name)
{
{
// do something
}
public string Name { get; } = name;
}
解説
個人的には変態構文にしか見えないのですが、F#やScalaには既に存在する機能らしいです。
クラス宣言に引数リストを追加することで、メンバの初期化子などで引数を使用することができるようになります。
また、クラス直下に{}
を記述することで、メンバの初期化以外の処理も記述することができます。
まとめ
本記事で紹介したもの以外でも、C#6.0では多くの機能が追加されています。また、コンパイラが再実装された関係で、小さな改良も多数加えられています。
C#6.0によって、C#はより良い言語へと進化しました。しかし、言語が良くなってもプログラマがその機能を使いこなせなければ意味がありません。C#6.0をより良く知ることで、C#の機能を最大限使いこなしましょう。