LoginSignup
56
47

More than 1 year has passed since last update.

C#の進化を垣間見てみよう - .NET 20周年を記念して

Last updated at Posted at 2022-03-06

はじめに

先月2月23日に.NETが20周年を迎えました。つまりC#も20周年ということになるのでしょうか? 僕はC#が発表されてすぐに使い始めたので、C#との付き合いももう20年近くということになるんですね。随分と時間は流れたものです。

今回は、.NET 20周年にかこつけて、古いC#のコードを最新のC#10に書き換えてみようと思います。これにより、C#がどう進化したかについて見ていければと思っています。

題材は、僕が2011年に出版した『C#プログラミング入門―「オブジェクト指向」の「プログラミング手法」を基礎から解説』に掲載したC#3.0のコードです。(一部コードを変更しています)

C# 3.0のコード

まずは、『C#プログラミング入門』の70ページに掲載したList2-21のコードをみてみましょう。
書籍ではプログラムコードの一部を抜粋したものでしたが、ここでは実際に動作する完全なコード(コンソールアプリケーション)として示します。

それほど難しいコードではないので、何をやっているかはソースをみていただければわかると思います。

Person.cs
using System;

namespace Gushwell.Sample
{
    public class Person
    {
        public string Name { get; set; }

        private double _weight;
        public double Weight
        {
            get { return _weight; }
            set
            {
                if (value > 0)
                    _weight = value;
            }
        }
        private double _height;
        public double Height
        {
            get { return _height; }
            set
            {
                if (value > 50)
                    _height = value;
            }
        }
        public DateTime Birthday { get; set; }

        public Person(string name, DateTime birthday)
        {
            Name = name;
            Birthday = birthday;
        }

        public double GetBmi()
        {
            double mh = Height / 100.0;
            return Weight / (mh * mh);
        }

        public int GetAge()
        {
            DateTime today = DateTime.Today;
            int age = today.Year - Birthday.Year;
            if (today.Month < Birthday.Month)
                age--;
            else if (today.Month == Birthday.Month && today.Day < Birthday.Day)
                age--;
            return age;
        }
    }
}
Program.cs
using System;

namespace Gushwell.Sample
{
    class Program
    {
        static void Main(string[] args)
        {
            Person p = new Person("檜山玄太郎", new DateTime(1979, 5, 25));
            p.Weight = 72;
            p.Height = 181;
            int age = p.GetAge();
            Console.WriteLine(age);
            Console.WriteLine("{0:f2}", p.GetBmi());  

            Console.ReadLine();
        }
    }
}

上記プログラムの実行結果を以下に示します。

42
21.98

では、上記コードを今のC#で書き換えるとどうなるかを順にみていきましょう。

varによる暗黙的な型宣言

最初はvarによる暗黙的な型宣言をしましょう。varはC#3.0で導入された機能ですが、上記コードでは、varを使っていません。
C#3.0が出た当初はvarの利用については賛否両論あって、頑なにvarの利用を拒否する人もいたようですが、今となっては否定派はほとんどいなくなったと思われます。

ここでは、GetAgeメソッドを書き換えてみます。

    public int GetAge()
    {
        var today = DateTime.Today;
        var age = today.Year - Birthday.Year;
        if (today.Month < Birthday.Month)
            age--;
        else if (today.Month == Birthday.Month && today.Day < Birthday.Day)
            age--;
        return age;
    }

get のみの自動プロパティ

プロパティBirthdayは、コンストラクタで値を設定し、その後は変化しないプロパティですから、getのみのプロパティに書き換えます。C# 6で導入された機能です。

    public DateTime Birthday { get; }

式形式のメソッド

次は、GetBmiを式形式のメソッドに書き換えます。ちょっと無理矢理感がありますが、以下のようなコードになります。これもC# 6で導入された機能です。

    public double GetBmi() => Weight / ((Height / 100.0) * (Height / 100.0));

式形式のプロパティ

GetBmiメソッドは、Bmiプロパティにすることもできますね。

    public double Bmi => Weight / ((Height / 100.0) * (Height / 100.0));

getアクセサ、setアクセサの式形式

ここでは、Weightプロパティのgetアクセサ、setアクセサを式形式の記述に変更します。この機能はC# 7で導入された機能ですね。

    public double Weight
    {
        get => _weight;
        set => _weight = value > 0 ? value : _weight;
    }

setアクセサも、式形式使うようにしました(ちょっと強引かな)。

最上位レベルのステートメント

Program.csにC# 9で導入された 最上位レベルのステートメントも導入しましょう。

using System;
using Gushwell.Sample;

var p = new Person("檜山玄太郎", new DateTime(1979, 5, 25));
p.Weight = 72;
p.Height = 181;
var age = p.GetAge();
Console.WriteLine(age);
Console.WriteLine("{0:f2}", p.Bmi); 

Console.ReadLine();

Programクラス、Mainメソッドがなくなりました。

文字列補間

Program.csの中の、

Console.WriteLine("{0:f2}", p.Bmi); 

の引数部分を文字列補完を使って書き換えます。

Console.WriteLine($"{p.Bmi:f2}");

これはC# 6で導入された機能です。

グローバル using

次に、C# 10で導入されたグローバルusingを適用します。Visual Studio 2022でコンソールアプリケーションを作成すると、自動で以下の名前空間がglobal usingされます。

System;
System.Collections.Generic;
System.IO;
System.Linq;
System.Net.Http;
System.Threading;
System.Threading.Tasks;

これにより、Person.csの以下の行を削除できます。

using System;

なお、作成するプロジェクトのタイプによって何がglobal usingされるのかは変わって来ます。一度アプリケーションをビルドすると、以下のファイルが作成されますので、このファイルの中身を見れば何がusingされているかを確認できます。

obj/Debug/net6.0/<ProjectName>.GlobalUsings.g.cs

以下は、今回のコンソールアプリケーションのGlobalUsings.g.csです。

MyConsoleApp.GlobalUsings.g.cs
global using global::System;
global using global::System.Collections.Generic;
global using global::System.IO;
global using global::System.Linq;
global using global::System.Net.Http;
global using global::System.Threading;
global using global::System.Threading.Tasks;

ファイル スコープ名前空間

さらに、ファイル スコープ名前空間を導入すれば、Person.csは、以下のように変更できます。これもC#10で導入された機能です。

namespace Gushwell.Sample;

public class Person
{
    ... 省略 ...
}

インデントが抑制されるのは、地味に嬉しい機能です。

レコード型を導入する

もし、PersonクラスのNameプロパティもreadonlyで良いのならば、C# 9で導入されたレコード型にすることも可能です。
これで、Name, Birthdayプロパティを明示的に記述する必要がなくなります。

ただし、== 演算子やEqualsメソッドがオーバーライドされて等値判定が変更されますので、そのクラスの特性に合わせてrecord型にするかどうかは判断する必要があると思います。

public record Person(string Name, DateTime Birthday)
{
    private double _weight;
    public double Weight
    {
        get => _weight;
        set => _weight = value > 0 ? value : _weight;
    }

    private double _height;
    public double Height
    {
        get => _height;
        set => _height = value > 0 ? value : _height;
    }
    ...

}

おまけ:Console.ReadLineの削除

.NET Coreあるいは.NET 5, .NET 6のコンソールアプリケーションならば、Visual Studio からデバッグ実行しても、コンソールウインドウは閉じることがないので、MainメソッドのConsole.ReadLine();も不要ですね。

using Gushwell.Sample;

var p = new Person("檜山玄太郎", new DateTime(1979, 5, 25));
p.Weight = 72;
p.Height = 181;
var age = p.GetAge();
Console.WriteLine(age);
Console.WriteLine($"{p.Bmi:f2}");

最終的なコード

最終的なコードを示します。

Person.cs
namespace Gushwell.Sample;

public record Person(string Name, DateTime Birthday)
{
    private double _weight;
    public double Weight
    {
        get => _weight;
        set => _weight = value > 0 ? value : _weight;
    }

    private double _height;
    public double Height
    {
        get => _height;
        set => _height = value > 0 ? value : _height;
    }

    public double Bmi => Weight / ((Height / 100.0) * (Height / 100.0));

    public int GetAge()
    {
        var today = DateTime.Today;
        var age = today.Year - Birthday.Year;
        if (today.Month < Birthday.Month)
            age--;
        else if (today.Month == Birthday.Month && today.Day < Birthday.Day)
            age--;
        return age;
    }
}

Person.csは、54行から33行になりました。

Program.cs
using Gushwell.Sample;

var p = new Person("檜山玄太郎", new DateTime(1979, 5, 25));
p.Weight = 72;
p.Height = 181;
var age = p.GetAge();
Console.WriteLine(age);
Console.WriteLine($"{p.Bmi:f2}"); 

Program.csは、19行から8行になりました。

最後に

選んだ題材があまり適切でなかったせいもあり、進化したC#の機能のほんのわずかな部分しか紹介できませんでした。
async/awaitによる非同期処置、タプル、パターンマッチング、インデックスと範囲、nullに関する様々な機能などなど、紹介できなかった機能がかなりあります。機会があれば第2弾を書ければと思います。

56
47
4

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
56
47