36
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 3 years have passed since last update.

弥生Advent Calendar 2020

Day 3

.NET 5 を使いたい理由6選(C#編)

Last updated at Posted at 2020-12-02

C#9.0の進歩が目覚ましいので使いたい

.NET5 がリリースされました。C#も9.0となりました。C#9.0は.NET5以降でしか使えません。
.NET5 を使えるプロジェクトではどんどん新しい文法を活用しましょう。
C#9.0の新機能から、素晴らしいと思った部分をまとめてみました。

使いたい理由1 : record

class, struct に加えて record を用いて型定義することができます。
変更不可能な参照型とのことです。

    public record Person // classのかわりにrecord
    {
        public string Name { get; }
        public DateTime BirthDate { get; }
        public Person(string name, DateTime birthDate)
        {
            Name = name;
            BirthDate = birthDate;
        }
    }

これをILSpyなどで覗いてみると以下のようなコードに展開されます。大方の予想通り class ですがリッチです。

    public class Person : IEquatable<Person>
    {
        protected virtual Type EqualityContract
        {
            [System.Runtime.CompilerServices.NullableContext(1)]
            [CompilerGenerated]
            get => typeof(Person);
        }
        public string Name { get; }
        public DateTime BirthDate { get; }
        public Person(string name, DateTime birthDate)
        {
            Name = name;
            BirthDate = birthDate;
        }

        public override string ToString()
        {
            StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("Person");
            stringBuilder.Append(" { ");
            if (PrintMembers(stringBuilder))
            {
                stringBuilder.Append(" ");
            }
            stringBuilder.Append("}");
            return stringBuilder.ToString();
        }

        protected virtual bool PrintMembers(StringBuilder builder)
        {
            builder.Append("Name");
            builder.Append(" = ");
            builder.Append((object?)Name);
            builder.Append(", ");
            builder.Append("BirthDate");
            builder.Append(" = ");
            builder.Append(BirthDate.ToString());
            return true;
        }

        [System.Runtime.CompilerServices.NullableContext(2)]
        public static bool operator !=(Person? r1, Person? r2) => !(r1 == r2);

        [System.Runtime.CompilerServices.NullableContext(2)]
        public static bool operator ==(Person? r1, Person? r2) => (object)r1 == r2 || (r1?.Equals(r2) ?? false);

        public override int GetHashCode() => (EqualityComparer<Type>.Default.GetHashCode(EqualityContract) * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Name)) * -1521134295 + EqualityComparer<DateTime>.Default.GetHashCode(BirthDate);
        public override bool Equals(object? obj) => Equals(obj as Person);
        public virtual bool Equals(Person? other) => (object)other != null && EqualityContract == other!.EqualityContract && EqualityComparer<string>.Default.Equals(Name, other!.Name) && EqualityComparer<DateTime>.Default.Equals(BirthDate, other!.BirthDate);
        public virtual Person<Clone>$() => new Person(this); // Clone系の何からしいが不明

        protected Person(Person original)
        {
            Name = original.Name;
            BirthDate = original.BirthDate;
        }
    }

ToStringGetHashCode、比較演算子やコピーコンストラクタなどが自動的に実装されます。
比較演算子も「クラスのインスタンス」ではなく「クラスの内容が」等しいかという実装は大変にありがたいものです。

使いたい理由2 : init

先ほどのPersonのコンストラクタを省略します。代わりに init という初期化中だけ使える Setter を用いてみますがこれが大変良い働きをします。

    class Program
    {
        static void Main(string[] args)
        {
            Person person = new Person() { Name = "まだ名はない", BirthDate = DateTime.Today };
            Console.WriteLine(person);
        }
    }

    public record Person
    {
        public string Name { get; init; }
        public DateTime BirthDate { get; init; }
    }

重要なのは、コンストラクタを抜けた後のwith式内まで使用可能であることです。
当然ながら record が不変であることも合わせて変更はできません。

   person.Name = "ねこ"; // コンパイルエラー

ところで、不変なのにSetterが使えるとはどういうことでしょうか。気になります。
ILを見てみましょう。
NameのSetterは内部的には set_Name となります。第1引数に value を受け取っています。

	.method public hidebysig specialname 
		instance void modreq([System.Runtime]System.Runtime.CompilerServices.IsExternalInit) set_Name (
			string 'value'
		) cil managed 
	{
        // 省略
	}

modreqというのがポイントのようです。これは呼び出し元が無視してはならない制約という意味です。
外部初期化までは有効、つまり「IsExternalInit」という属性が制約として機能します。

使いたい理由3 : 型省略可能なnew()

new は型が明確である場合に型の記述を省略できるようになりました。プロパティやフィールドの初期化などで同じ型を2回書く必要はもうなくなりました。
ローカル変数でvarとこれのどちらを使うかは迷いどころです。

    Person person = new() { Name = "まだ名はない", BirthDate = DateTime.Today };

使いたい理由4 : パターンマッチングの強化

is のあとに and or not などを記述できるようになりました。

    // c is のあとは c に対する条件式
    public static bool IsLetterOrSpace(this char c) => c is >= 'a' and <= 'z' or >= 'A' and <= 'Z' or ' ';
    // 従来の場合 c を何度も書く必要があった
    public static bool IsLetterOrSpace(this char c) => c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' || ' ';

同じものを記述する回数が減ることは基本的に良いことです。大いに活用したいと思います。

使いたい理由5 : staticラムダ式

メソッド内にこのように書くことができます。

    Func<double, double> square = static x => x * x;

意味が伝わりにくいかもしれませんが、ラムダ式はClosure(関数閉包)です。ラムダ式がローカル変数やthisを参照できるのは、関数オブジェクトとしてローカル変数やthisを内包しているからです。このキャプチャーと呼ばれる内包処理のために、通常のラムダ式はメソッド内で毎回 delegate を生成します。
しかし、thisもローカル変数も内包する必要のないラムダ式の場合、使用する都度生成する必要もなくなり、static で静的にラムダ式を保持するようになります。つまりその分はパフォーマンスが良いということになります。

しかしそれでは、このように書くのと何が違うのか?となります。

class Foo
{
    static Func<double, double> Square = x => x * x;
}

スコープを絞れるメリットがあります。ローカルメソッドのように、そのメソッド内部以外から見えないということは重要です。

使いたい理由6 : 拡張メソッドでもGetEnumeratorがあればforeachできる

GetEnumeratorを拡張メソッドで用意すれば、Rangeですらこのように拡張できてしまいます。これはとても強力です。

    class Program
    {
        static void Main(string[] args)
        {
            foreach (var index in 1..10)
                Console.WriteLine(index);
        }
    }
    public static class RangeExtensions
    {
        public static IEnumerable<int> GetEnumerable(this Range range)
        {
            for (int idx = range.Start.Value; idx <= range.End.Value; idx++)
                yield return idx;
        }
        public static IEnumerator<int> GetEnumerator(this Range range)
            => range.GetEnumerable().GetEnumerator();
    }

パターンマッチングと組み合わせて何かができそうな気がします。

その他の強化

unsafe unmanaged 関連の補強も興味深いですが、今回の記事では扱わないことにします。

謝辞

ここで扱った情報はMicrosoftのサイト( https://docs.microsoft.com/ja-jp/dotnet/csharp/whats-new/csharp-9 )の内容から、これはぜひ使いたいと思った機能を抽出し、自分なりに分析を加えたものです。
.NET と C# の発展に携わるすべてのエンジニアの皆様に感謝いたします。

ご案内

この記事は以下の連載記事の第2回目となります。
https://qiita.com/proprogrammer0/items/560ffaf99cdf828c8e52 「.NET 5 を使いたい理由6選」
第3回目記事はTwitterの同名アカウント( https://twitter.com/proprogrammer0 )上から案内予定です。ASP.NET CoreやEF Coreにも触れる予定です。よろしければご覧ください。

36
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
36
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?