17
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

C#6.0での実装パターンまとめ

Last updated at Posted at 2015-08-12

はじめに

C#6.0では、細かな文法の拡張や演算子の追加などが多く行われました。

その多くは、頻繁に使用するにもかかわらず、C#5.0まででは記述が面倒だった処理を簡単に記述できるような機能です。

本記事では、C#6.0から簡単にかけるようになった処理を、以前の記述方法と比較しながら紹介します。

なお、本記事で紹介されていない処理があればコメント等で指摘をいただければ、随時追加します。

INotifyPropertyChanged

概要

最初に紹介するのは、INotifyPropertyChangedの実装です。

このインターフェースを実装したクラスは、PropertyChangedイベントを発生させて、プロパティが変化したことを通知します。

WPFでViewModelを実装する際に使用します。

BeforeAfter

before
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;
  }
}
after
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

before
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);
  }
}
after
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

before
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;
  }
}
after
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#6.0時代のイベント実装方法 - Qiitaを参照

番外篇 未来のC#: メンバの初期化

概要

コンストラクタの引数でのメンバの初期化は、言語を問わず頻繁に書くことになる処理でしょう。しかし、C#6.0でも、同じような単語を何度もタイプする必要があり、うんざりします。

C#6.0で採用される予定だったPrimaryConstructorが採用されると、そんなメンバの初期化処理が非常に簡単に書くことができるようになります。

BeforAfter

BeforeはC#6.0, Afterは採用を見送られた機能が採用された場合にかけるようになるコードです。

before
public class Person
{
  public string Name { get; }
  public Person(string name)
  {
    Name = name;
    // do something
  }
}
after
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#の機能を最大限使いこなしましょう。

References

17
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?