Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9691

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

$
0
0

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

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

使いたい理由1 : record

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

publicrecordPerson// classのかわりにrecord{publicstringName{get;}publicDateTimeBirthDate{get;}publicPerson(stringname,DateTimebirthDate){Name=name;BirthDate=birthDate;}}

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

publicclassPerson:IEquatable<Person>{protectedvirtualTypeEqualityContract{[System.Runtime.CompilerServices.NullableContext(1)][CompilerGenerated]get=>typeof(Person);}publicstringName{get;}publicDateTimeBirthDate{get;}publicPerson(stringname,DateTimebirthDate){Name=name;BirthDate=birthDate;}publicoverridestringToString(){StringBuilderstringBuilder=newStringBuilder();stringBuilder.Append("Person");stringBuilder.Append(" { ");if(PrintMembers(stringBuilder)){stringBuilder.Append(" ");}stringBuilder.Append("}");returnstringBuilder.ToString();}protectedvirtualboolPrintMembers(StringBuilderbuilder){builder.Append("Name");builder.Append(" = ");builder.Append((object?)Name);builder.Append(", ");builder.Append("BirthDate");builder.Append(" = ");builder.Append(BirthDate.ToString());returntrue;}[System.Runtime.CompilerServices.NullableContext(2)]publicstaticbooloperator!=(Person?r1,Person?r2)=>!(r1==r2);[System.Runtime.CompilerServices.NullableContext(2)]publicstaticbooloperator==(Person?r1,Person?r2)=>(object)r1==r2||(r1?.Equals(r2)??false);publicoverrideintGetHashCode()=>(EqualityComparer<Type>.Default.GetHashCode(EqualityContract)*-1521134295+EqualityComparer<string>.Default.GetHashCode(Name))*-1521134295+EqualityComparer<DateTime>.Default.GetHashCode(BirthDate);publicoverrideboolEquals(object?obj)=>Equals(objasPerson);publicvirtualboolEquals(Person?other)=>(object)other!=null&&EqualityContract==other!.EqualityContract&&EqualityComparer<string>.Default.Equals(Name,other!.Name)&&EqualityComparer<DateTime>.Default.Equals(BirthDate,other!.BirthDate);publicvirtualPerson<Clone>$()=>newPerson(this);// Clone系の何からしいが不明protectedPerson(Personoriginal){Name=original.Name;BirthDate=original.BirthDate;}}

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

使いたい理由2 : init

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

classProgram{staticvoidMain(string[]args){Personperson=newPerson(){Name="まだ名はない",BirthDate=DateTime.Today};Console.WriteLine(person);}}publicrecordPerson{publicstringName{get;init;}publicDateTimeBirthDate{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とこれのどちらを使うかは迷いどころです。

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

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

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

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

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

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

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

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

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

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

classFoo{staticFunc<double,double>Square=x=>x*x;}

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

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

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

classProgram{staticvoidMain(string[]args){foreach(varindexin1..10)Console.WriteLine(index);}}publicstaticclassRangeExtensions{publicstaticIEnumerable<int>GetEnumerable(thisRangerange){for(intidx=range.Start.Value;idx<=range.End.Value;idx++)yieldreturnidx;}publicstaticIEnumerator<int>GetEnumerator(thisRangerange)=>range.GetEnumerable().GetEnumerator();}

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

その他の強化

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

謝辞

ここで扱った情報は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にも触れる予定です。よろしければご覧ください。


Viewing all articles
Browse latest Browse all 9691

Trending Articles