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

この記事は最終更新日から1年以上が経過しています。

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

動作環境

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

  • .NET Framework 4.6
  • Visual Studio 2015

修正前サンプル

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

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);
        }
    }
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);
        }
    }
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のみのプロパティを記述する時に、ラムダ式のような=>を使用できます。

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を公開しない実装にできる点もポイント。

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条件演算子

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

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

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

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

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

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

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

Enumerableクラス

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

Enumerable Class
LINQ と遅延評価

Range

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

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を使用することで以下のように記述できます。

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

DefaultIfEmpty

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

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

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

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

SelectMany

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

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で回避します。

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

OfType

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

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使用後はキャストも不要。

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

修正後サンプル

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

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

追記

変更のないメンバ

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

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

Student.cs
        public string Name { get; }
        public int Number { get; }
        public Student(string name, int number)
        {
            Name = name;
            Number = number;
        }
ユーザー登録して、Qiitaをもっと便利に使ってみませんか。
  1. あなたにマッチした記事をお届けします
    ユーザーやタグをフォローすることで、あなたが興味を持つ技術分野の情報をまとめてキャッチアップできます
  2. 便利な情報をあとで効率的に読み返せます
    気に入った記事を「ストック」することで、あとからすぐに検索できます
コメント
この記事にコメントはありません。
あなたもコメントしてみませんか :)
すでにアカウントを持っている方は
ユーザーは見つかりませんでした