【C#】ショートコード・テクニック

C#開発における、ショートコーディングに役立つテクニックを紹介します。

# 動作環境

現在業務で使用している、以下の開発環境を想定しています。

* .NET Framework 4.6
* Visual Studio 2015

# 修正前サンプル

Studentクラス、Teacherクラス、テスト用メソッドのクラスを用意しました。
XMLコメントはコードが長くなるので省略。

```csharp:Student.cs
	class Student : Person
	{
		private string _Name;
		private int _Number;
		private int _Score;
		public string Name
		{
			get { return _Name; }
		}
		public int Number
		{
			get { return _Number; }
		}
		public int Score
		{
			get { return _Score; }
		}
		public Student(string name, int number)
		{
			_Name = name;
			_Number = number;
		}
		public void SetScore(int score)
		{
			_Score = score;
		}
		public bool HasName()
		{
			return string.IsNullOrEmpty(_Name);
		}
		public int CompareNumber(Student student)
		{
			int target = 0;
			if(student != null)
			{
				target = student.Number;
			}
			return _Number.CompareTo(target);
		}
	}
```

```csharp:Teacher.cs
	class Teacher : Person
	{
		private List<Student> _Students = new List<Student>();
		public List<Student> Students
		{
			get { return _Students; }
		}
		public Teacher(IEnumerable<Student> students)
		{
			_Students.AddRange(students);
		}
		public double AverageScore()
		{
			if (_Students.Count == 0)
			{
				return 0;
			}
			return _Students.Average(student => student.Score);
		}
	}
```

```csharp:Test.cs
	class Test
	{
		public void ResetScore(Student student)
		{
			if (student != null)
			{
				student.SetScore(0);
			}
		}
		public IEnumerable<Student> CreateSample(int count)
		{
			var students = new List<Student>();
			for (int number = 1; number <= count; number++)
			{
				students.Add(new Student($"生徒{number.ToString()}", number));
			}
			return students;
		}
		public IEnumerable<Student> GetAllStudents(IEnumerable<Teacher> teachers)
		{
			var students = new List<Student>();
			foreach (var teacher in teachers)
			{
				students.AddRange(teacher.Students);
			}
			return students;
		}
		public IEnumerable<String> GetStudentNames(IEnumerable<Person> people)
		{
			var studentNames = new List<string>();
			foreach (var person in people)
			{
				var student = person as Student;
				if (student != null)
				{
					studentNames.Add(student.Name);
				}
			}
			return studentNames;
		}
	}
```

# メソッド、プロパティのラムダ式記法

1行のメソッドやgetterのみのプロパティを記述する時に、ラムダ式のような``=>``を使用できます。

```csharp:Student.cs(修正前)
		public string Name
		{
			get { return _Name; }
		}
		public int Number
		{
			get { return _Number; }
		}
		public int Score
		{
			get { return _Score; }
		}
		public void SetScore(int score)
		{
			_Score = score;
		}
		public bool HasName()
		{
			return string.IsNullOrEmpty(_Name);
		}
```

``{ }``で行数が多くなっていますが、ラムダ式記法では以下のようにスッキリ書けます。
プロパティは意識することなく、setterを公開しない実装にできる点もポイント。

```csharp:Student.cs(修正後)
		public string Name => _Name;
		public int Number => _Number;
		public int Score => _Score;
		public void SetScore(int score) => _Score = score;    // 戻り値がないメソッド
		public bool HasName() => string.IsNullOrEmpty(_Name); // 戻り値があるメソッド
```

# null条件演算子＆null合体演算子

null条件演算子``?``とnull合体演算子``??``を使用することで、nullチェックの記述を簡略化できます。

## null条件演算子

```csharp:Test.cs(修正前)
		public void ResetScore(Student student)
		{
			if (student != null)
			{
				student.SetScore(0);
			}
		}
```

参照型の引数のメンバにアクセスする時、``NullReferenceException``を避けるためnullチェックを行うことがあります。
null条件演算子``?``を使用すれば、nullチェックが不要になります。

```csharp:Test.cs(修正後)
		public void ResetScore(Student student) => student?.SetScore(0);
```

## null条件演算子 + null合体演算子

```csharp:Student.cs(修正前)
		public int CompareNumber(Student student)
		{
			int target = 0;
			if(student != null)
			{
				target = student.Number;
			}
			return _Number.CompareTo(target);
		}
```

引数がnullの時に、既定値が必要となる場合があります。
null条件演算子``?``だけでは既定値0を指定できませんが、null合体演算子``??``を組み合わせれば指定できます。

```csharp:Student.cs(修正後)
		public int CompareNumber(Student student) => _Number.CompareTo(student?.Number ?? 0);
```

# Enumerableクラス

LINQには、ショートコーディングを実現できる様々なメソッドが用意されています。
ただしLINQの遅延評価により、完全に同じ動作にならないことには注意が必要です。

[Enumerable Class](https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable?view=netframework-4.6)
[LINQ と遅延評価](https://ufcpp.net/study/csharp/sp3_lazylist.html)

## Range

連続する数字のコレクションを作成します。

```csharp:Test.cs(修正前)
		public IEnumerable<Student> CreateSample(int count)
		{
			var students = new List<Student>();
			for (int number = 1; number <= count; number++)
			{
				students.Add(new Student($"生徒{number.ToString()}"));
			}
			return students;
		}
```

``for``文を用いた一般的なコードですが、ローカル変数の宣言やネストがやや冗長です。
``Range``を使用することで以下のように記述できます。

```csharp:Test.cs(修正後)
		public IEnumerable<Student> CreateSample(int count) => Enumerable.Range(1, count).Select(number => new Student($"生徒{number.ToString()}", number));
```

## DefaultIfEmpty

コレクションが空の場合にデフォルト値を返します。

```csharp:Teacher.cs(修正前)
		public double AverageScore()
		{
			if (_Students.Count == 0)
			{
				return 0;
			}
			return _Students.Average(student => student.Score);
		}
```

``Average``、``Sum``、``Max``などの集計演算子は、コレクションが空の場合にエラーとなるため事前チェックが必要です。
``DefaultIfEmpty``で既定値を設定すればこのチェックが不要になります。

```csharp:Teacher.cs(修正後)
		public double AverageScore() => _Students.Select(student => student.Score).DefaultIfEmpty(0).Average();
```

## SelectMany

コレクション同士を統合します。

```csharp:Test.cs(修正前)
		public IEnumerable<Student> GetAllStudents(IEnumerable<Teacher> teachers)
		{
			var students = new List<Student>();
			foreach (var teacher in teachers)
			{
				students.AddRange(teacher.Students);
			}
			return students;
		}
```

一時リストの作成とループを``SelectMany``で回避します。

```csharp:Test.cs(修正後)
		public IEnumerable<Student> GetAllStudents(IEnumerable<Teacher> teachers) => teachers.SelectMany(teacher => teacher.Students);
```

## OfType

型によるフィルタリングを行います。

```csharp:Test.cs(修正前)
		public IEnumerable<String> GetStudentNames(IEnumerable<Person> people)
		{
			var studentNames = new List<string>();
			foreach (var person in people)
			{
				var student = person as Student;
				if (student != null)
				{
					studentNames.Add(student.Name);
				}
			}
			return studentNames;
		}
```

コレクションの中から特定の型を選択したい場合は``OfType``が便利です。
``OfType``使用後はキャストも不要。

```csharp:Test.cs(修正後)
		public IEnumerable<String> GetStudentNames(IEnumerable<Person> people) => people.OfType<Student>().Select(student => student.Name);
```

# 修正後サンプル

上記修正をすべて行うことにより、コードの行数をかなり削減することができました。

```csharp:Student.cs
	// 48行 → 17行
	class Student : Person
	{
		private string _Name;
		private int _Number;
		private int _Score;
		public string Name => _Name;
		public int Number => _Number;
		public int Score => _Score;
		public Student(string name, int number)
		{
			_Name = name;
			_Number = number;
		}
		public void SetScore(int score) => _Score = score;
		public bool HasName() => string.IsNullOrEmpty(_Name);
		public int CompareNumber(Student student) => _Number.CompareTo(student?.Number ?? 0);
	}
```

```csharp:Teacher.cs
	// 20行 → 10行
	class Teacher : Person
	{
		private List<Student> _Students = new List<Student>();
		public List<Student> Students => _Students;
		public Teacher(IEnumerable<Student> students)
		{
			_Students.AddRange(students);
		}
		public double AverageScore() => _Students.Select(student => student.Score).DefaultIfEmpty(0).Average();
	}
```

```csharp:Test.cs
	// 41行 → 7行
	class Test
	{
		public void ResetScore(Student student) => student?.SetScore(0);
		public IEnumerable<Student> CreateSample(int count) => Enumerable.Range(1, count).Select(number => new Student($"生徒{number.ToString()}", number));
		public IEnumerable<Student> GetAllStudents(IEnumerable<Teacher> teachers) => teachers.SelectMany(teacher => teacher.Students);
		public IEnumerable<String> GetStudentNames(IEnumerable<Person> people) => people.OfType<Student>().Select(student => student.Name);
	}
```

# まとめ

C#の様々な機能を使って、簡潔で読みやすいコードを目指しましょう。
他にも便利な機能がありましたら教えてください。
ご意見、ご指摘もよろしくお願いします。

# 追記

## 変更のないメンバ

```csharp:Student.cs
		private string _Name;
		private int _Number;
		public string Name => _Name;
		public int Number => _Number;
		public Student(string name, int number)
		{
			_Name = name;
			_Number = number;
		}
```

``Name``、``Number``はコンストラクタで設定後、バッキングフィールドを変更していません。
``=>``の説明のため本文はそのままにしますが、変更のない場合は自動プロパティで十分です。

```csharp:Student.cs
		public string Name { get; }
		public int Number { get; }
		public Student(string name, int number)
		{
			Name = name;
			Number = number;
		}
```