C#開発における、ショートコーディングに役立つテクニックを紹介します。
動作環境
現在業務で使用している、以下の開発環境を想定しています。
- .NET Framework 4.6
- Visual Studio 2015
修正前サンプル
Studentクラス、Teacherクラス、テスト用メソッドのクラスを用意しました。
XMLコメントはコードが長くなるので省略。
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);
}
}
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);
}
}
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のみのプロパティを記述する時に、ラムダ式のような=>
を使用できます。
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を公開しない実装にできる点もポイント。
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条件演算子
public void ResetScore(Student student)
{
if (student != null)
{
student.SetScore(0);
}
}
参照型の引数のメンバにアクセスする時、NullReferenceException
を避けるためnullチェックを行うことがあります。
null条件演算子?
を使用すれば、nullチェックが不要になります。
public void ResetScore(Student student) => student?.SetScore(0);
null条件演算子 + null合体演算子
public int CompareNumber(Student student)
{
int target = 0;
if(student != null)
{
target = student.Number;
}
return _Number.CompareTo(target);
}
引数がnullの時に、既定値が必要となる場合があります。
null条件演算子?
だけでは既定値0を指定できませんが、null合体演算子??
を組み合わせれば指定できます。
public int CompareNumber(Student student) => _Number.CompareTo(student?.Number ?? 0);
Enumerableクラス
LINQには、ショートコーディングを実現できる様々なメソッドが用意されています。
ただしLINQの遅延評価により、完全に同じ動作にならないことには注意が必要です。
Range
連続する数字のコレクションを作成します。
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
を使用することで以下のように記述できます。
public IEnumerable<Student> CreateSample(int count) => Enumerable.Range(1, count).Select(number => new Student($"生徒{number.ToString()}", number));
DefaultIfEmpty
コレクションが空の場合にデフォルト値を返します。
public double AverageScore()
{
if (_Students.Count == 0)
{
return 0;
}
return _Students.Average(student => student.Score);
}
Average
、Sum
、Max
などの集計演算子は、コレクションが空の場合にエラーとなるため事前チェックが必要です。
DefaultIfEmpty
で既定値を設定すればこのチェックが不要になります。
public double AverageScore() => _Students.Select(student => student.Score).DefaultIfEmpty(0).Average();
SelectMany
コレクション同士を統合します。
public IEnumerable<Student> GetAllStudents(IEnumerable<Teacher> teachers)
{
var students = new List<Student>();
foreach (var teacher in teachers)
{
students.AddRange(teacher.Students);
}
return students;
}
一時リストの作成とループをSelectMany
で回避します。
public IEnumerable<Student> GetAllStudents(IEnumerable<Teacher> teachers) => teachers.SelectMany(teacher => teacher.Students);
OfType
型によるフィルタリングを行います。
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
使用後はキャストも不要。
public IEnumerable<String> GetStudentNames(IEnumerable<Person> people) => people.OfType<Student>().Select(student => student.Name);
修正後サンプル
上記修正をすべて行うことにより、コードの行数をかなり削減することができました。
// 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);
}
// 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();
}
// 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#の様々な機能を使って、簡潔で読みやすいコードを目指しましょう。
他にも便利な機能がありましたら教えてください。
ご意見、ご指摘もよろしくお願いします。
追記
変更のないメンバ
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
はコンストラクタで設定後、バッキングフィールドを変更していません。
=>
の説明のため本文はそのままにしますが、変更のない場合は自動プロパティで十分です。
public string Name { get; }
public int Number { get; }
public Student(string name, int number)
{
Name = name;
Number = number;
}