rksoftware

Visual Studio とか C# とかが好きです

C# 9.0 の確認「モジュール初期化子」

C# 9.0 の確認の目次はこちら

■ モジュール初期化子

ドキュメントはこちら

コード ジェネレーターのサポート という項目の 4 つ目の塊 「コード ジェネレーターの 2 つ目の新機能は」 から始まるブロックです。
簡単に言うとアセンブリ (.dll) が使われる際の初期化メソッドを実装できます。

これまでは、クラスライブラリなどで初期化が必要な場合は、初期化用のメソッドを用意して利用側で最初に一回呼び出すなどのルールでやっていたりしました。
ルールというのは危険で、簡単に守られない事態に遭遇します。守らなくてはならないものをルールにしておくのは悪手です。守らなければならないものは仕組みで守るべきです。そのための仕組みができました。

■ モジュール初期化

こんな感じです。

using System;
using System.Runtime.CompilerServices;

namespace ClassLibrary
{
    public class Class1
    {
        public static string Saitama => "Saitama! Saitama!";
    }

    class Class2
    {
        [ModuleInitializer]
        internal static void Initialize1() => Console.WriteLine("Initialize1");

        [ModuleInitializer]
        internal static void Initialize2() => Console.WriteLine("Initialize2");
    }
}

ModuleInitializer 属性のついているメソッドが初期化メソッドです。今回は 2 つ用意してみました。ほかにもいくつかルールがありますが、それはドキュメントを見てください。概ね何の前提もなくこのメソッドがただ呼ばれる、という前提で考えればいいでしょう。呼び出し元で何もしない、つまりパラメータも渡さない (型パラメータも渡さない)、クラスのインスタンスも生成しない、戻り値も使わない。そう考えておけばルールは覚えておかなくても大丈夫でしょう。

■ 検証コードと結果

■ クラスライブラリを使う側のコード

前述のコードで作ったクラスライブラリをコンソールアプリから使ってみます。

using System;

namespace ConsoleApp
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");
            Method();
        }

        static void Method()
        {
            Console.WriteLine(ClassLibrary.Class1.Saitama);
        }
    }
}

クラスライブラリの中の Initialize1Initialize2 の 2 つのメソッドは呼び出していません。

■ 実行結果

Hello World!
Initialize1
Initialize2
Saitama! Saitama!

計画通り! 初期化のメソッドが実行されています。

■ まとめ

この機能は、書く必要に出会わない人も少なくはないかもしれません。しかし、ある日レイヤー構造のアーキテクチャを採用したコードを書く日が突然やってくるかもしれません。やってきてほしいです。その時きっと役に立ちます。備えましょう。

C# 9.0 の確認「部分メソッドの新機能」

C# 9.0 の確認の目次はこちら

■ 部分メソッドの新機能

ドキュメントはこちら

コード ジェネレーターのサポート という項目の 3 つ目の塊 「コード ジェネレーターに対して追加された 2 つの機能は」 から始まるブロックです。
~の新機能、という寿葉はそれだけでは具体的な機能がわかりませんが、今回はそれ以前に 部分メソッド を知らないという方も多いのではないでしょうか? 通常自分で書くことはない、知らなくても当たり前の機能だと思うのでまず、部分メソッドのコードを書いてみましょう。

■ 部分メソッド

こんな感じです。

partial class MyClass
{
    partial void MyMethod(int arg);

    public void Main()
    {
        MyMethod(1);
    }
}

partial class MyClass
{
    partial void MyMethod(int arg)
    {
        ;
    }
}

partial class (部分クラス) の中に本体のないメソッドがあります。これが部分メソッドです。本体は部分クラスの別の方で書かれています。
そして重要なのは次のコードがエラーにならないということです。

partial class MyClass
{
    partial void MyMethod(int arg);

    public void Main()
    {
        MyMethod(1);
    }
}

partial class MyClass
{
    // メソッドの本体を書かない
    //partial void MyMethod(int arg)
    //{
    //    ;
    //}
}

部分メソッドでは本体を書かなくてもかまいません。もし本体がない場合はそのメソッドと、メソッド呼び出し (今回であれば MyMethod(1);) はコンパイル時に削除されます。

この機能がどんな時にうれしいかというと、コードが自動生成される場合です。コードの自動生成では 自動生成されたソースファイルにプログラマーが手を加える (コードを書き加える) のは最悪 の手法です。

・自動生成されたコードを書き換えてしまって、再自動生成でコードが失われてしまう ・自動生成したコードを書き換えてしまい動かなくなる

懸念があります。というかそういった事態はかつて起こっていました。皆さんは知らない太古の技術ですが、Windows フォームアプリケーションというものが、かつて存在していました。その技術では画面上のコントロールの定義は画面デザイナーにより自動生成され、自動生成されたソースコードにプログラマーがプログラムを書き足していました。不幸な事故はそう頻繁というわけではありませんが、一切見ないということもないレベルでは起きていたと思います。救世主、部分クラス・部分メソッドによってその懸念が大きく減りました。

■ 部分メソッドの制限

しかし、部分メソッドにはいくつもの制限がありました。private のみ。アクセス修飾子を書くことができない。return 値は void のみ。out 引数は不可。さして厳しい制限ではないですし、本体がなければ消える存在としては打倒な仕様です。

■ 制限解除

C# 9 でこれらの制限が解除されました。 今まで次のコードはエラーでしたが、これからは書けるようになります。

partial class MyClass
{
    private partial int MyMethod(out int arg);

    public void Main()
    {
        var ii = MyMethod(out var i);
    }
}

partial class MyClass
{
    private partial int MyMethod(out int arg)
    {
        arg = 1;
        return 2;
    }
}

■ 注意点

この部分メソッドの新機能を使う構文は先頭に private を書くことです。
C# ではアクセス修飾子がない場合のデフォルトは private ですが、この新機能を使うためには private を明示的に書く必要があります。

■ まとめ

この機能も、書きたくなった時に書いてみたらうまくいった、そういうやつです。しかし、 private と書かないといけない点は覚えておきましょう。でないと書きたくなって書いてみてもダメです。おこられます。
積極的に使いこなす機会は、多くの人にとってはあまりないのかもしれませんが、今期待のコード ジェネレーターを使いこなすためには必要でしょう。しっかり覚えておきましょう。

C# 9.0 の確認「ローカル関数の属性」

C# 9.0 の確認の目次はこちら

■ ローカル関数の属性

ドキュメントはこちら

適合性と完成度の機能 という項目の割と読み進めた所の 「ようやく、ローカル関数に属性を適用できるようになりました」 から始まるブロックです。
言葉の通り、ローカル関数で属性をつけられます。ローカル変数という狭い世界に閉じたものに属性をつけてどうこうする機会は少ない気もしますが、制約が少ないのはうれしいことですし、実際には技術的な負債を返済している途中のも多くのコードで大いに役に立つでしょう。

■ 検証コードと結果

■ 以前のバージョン

例えば次のようなコードはエラーになりました。

#nullable enable
static void Main(string[] args)
{
    var list = List(null);

    [System.Obsolete]
    static IEnumerable<int> List([System.Diagnostics.CodeAnalysis.NotNull] string text) => new int[0];
}

エラー箇所は 2 つの属性、System.ObsoleteSystem.Diagnostics.CodeAnalysis.NotNull です。どちらも次のエラーです。

'ローカル関数の属性' は C# 8.0 では使用できません。言語バージョン 9.0 以上を使用してください。

そのままのメッセージですね。わかりやすい

■ C# 9 では

C# 9 ではエラーとなりません。

#nullable enable
static void Main(string[] args)
{
    var list = List(null);

    [System.Obsolete]
    static IEnumerable<int> List([System.Diagnostics.CodeAnalysis.NotNull] string text) => new int[0];
}

実際にこのコードを書いてみると警告が出ますが、それは大丈夫です。今回例としてつけた属性が警告を表示する属性なので。
警告が出るのは

var list = List(null);

の部分で

'List(string)' は古い形式です
null リテラルを null 非許容参照型に変換できません。

の 2 つの警告です。どちらもソースコードをアップデートしている最中に有益な属性です。

■ まとめ

今回は細かいところまで強く記憶に残しておかなくても大丈夫でしょう。書きたくなった時に書いてみたらうまくいった、そういうやつです。ただ、他人が「書ける」といったときに古い知識で「書けない」と全力否定してしまうのは格好が悪いので、軽くで良いので覚えておきましょう。

C# 9.0 の確認「ラムダ ディスカード パラメーター」

C# 9.0 の確認の目次はこちら

■ ラムダ ディスカード パラメーター

ドキュメントはこちら

適合性と完成度の機能 という項目の割と読み進めた所の 「次に、ラムダ式に対するパラメーターと」 から始まるブロックです。
見出しのカタカナ「ラムダ ディスカード パラメーター」にとてつもないパワーを感じますが、これはとてもうれしい機能です。ぜひ使いこなしましょう!

■ 検証コードと結果

■ 以前のコード

例えば次のような Linq を使ったコードがあったとします。

new int[0].Select((element, index) => 0);

こんなコードを書くことはないかもしれませんが例なので我慢してください。ここで elementindex は使用しないのに名前をつけなければなりません。

名前をつけないので _ としてしまいたいところですが、残念ながら次のコードはこれまでエラーになっていました。

new int[0].Select((_, _) => 0);

エラーになる箇所は二つ目の _ エラーメッセージは

機能 'ラムダ ディスカード パラメーター' は C# 8.0 では使用できません。言語バージョン 9.0 以上を使用してください。

です。

余談ですが、_ は変数名として有効な名前なので次のコードは有効な C# コードでした。

new int[0].Select((_, index) => _);

■ C# 9 では

C# 9 では次のコードは有効な C# コードです。

new int[0].Select((_, _) => 0);

これにより使わない変数すべてを _ と書けるのでとてもすっきりします。うれしいですね。

■ 以前のコードは?

となると気になるのは、以前有効だった次のようなコードです。

new int[0].Select((_, index) => _);

これも大丈夫です。C# 9 でも問題なく書けています。

そうするとまた気になるのは _ を複数書いたときにそれは変数としてどうなるの? というところです。その答えはドキュメントのリンクを何個かたどると書いてありますが、変数として _ を使おうとするコードはエラーとなります。

次のコードはエラーになります。

new int[0].Select((_, _) => _);

エラーの箇所は、三つ目の _ でエラーメッセージは次のような CS0103 エラーが出て使えません。

現在のコンテキストに '_' という名前は存在しません  

ドキュメントによるとコンパイラ エラー CS0301 となっているので、そちらが出る書き方もあると思います。

■ まとめ

使わない変数をどんどん _ にすることができるようになっています。コードは簡潔でわかりやすくなります。完全に理解して積極的に使いこなしましょう。

C# 9.0 の確認「共変の戻り値の型」

C# 9.0 の確認の目次はこちら

■ 共変の戻り値の型

ドキュメントはこちら

適合性と完成度の機能 という項目の割と読み進めた所の 「共変の戻り値の型を使用すると、」 から始まるブロックです。
ドキュメントにちゃんと書いてはあるのですが、説明が少なすぎて一目で理解しづらいのでコードを書いてみましょう。

■ 検証コードと結果

■ 以前はこれはエラーになる

次のコードは以前の C# ではエラーになりました。

internal class A
{
    public virtual IEnumerable<int> Method() { return null; }
}

internal class B : A
{
    public override List<int> Method() { return new List<int>(); }
}

エラーとなるのはクラス B の Method メソッドの宣言です。
エラーの内容は次のようなものでクラス A とクラス B の Method メソッドの戻り値の型を合わせる必要があると言われます。

'B.Method()': ターゲットのランタイムはオーバーライドで戻り値の型 covariant をサポートしていません。戻り値の型は、オーバーライドされるメンバー 'A.Method()' と一致する 'IEnumerable<int>' にする必要があります

■ C# 9 では

C# 9 ではエラーになりません。エラーにならないということは次のようなコードが書けます。

internal class A
{
    public virtual IEnumerable<int> Method() { return null; }
}

internal class B : A
{
    public override List<int> Method() { return new List<int>(); }
}

internal class C
{
    public void Main()
    {
        A a = new B();
        IEnumerable<int> list = a.Method();
    }
}

a.Method()List<int> を返していますが、その値は IEnumerable<int> 型の変数に代入することが可能です。

IEnumerable<int> list = a.Method();  // return 値 は List<int>

地味ですがこういうことなのでしょう。

■ まとめ

C# が賢くなりました。積極的に狙って書くことがあるかどうかは人次第でしょう。しかしまあ他言語でできることですしできるだけ方は厳格に使っていきたいものです。完全に理解して使いこなしましょう。

C# 9.0 の確認「ターゲットにより型指定された条件式」

C# 9.0 の確認の目次はこちら

■ ターゲットにより型指定された条件式

ドキュメントはこちら

適合性と完成度の機能 という項目の割と読み進めた所の 「同様の機能により、条件式の対象となる型の解決が向上します。」 から始まるブロックです。
ちょっと何言っているかわからないので、試しにそれっぽいことを書いてみましょう。

■ 検証コードと結果

■ これはエラーになる

これはエラーになります。
型が List<int> なのか int[] なのか推論できませんからね。

var list = true ? new List<int>() : new int[0];

■ エラーを消してみる

こんな感じでキャストするとエラーがなくなります。

var list = true ? (IEnumerable<int>)new List<int>() : new int[0];

でも、これだったら宣言に型を書いても良い感じがしますね。しかし残念ながら次のコードはこれまでエラーになっていました。

IEnumerable<int> list = true ? new List<int>() : new int[0];

■ C# 9 では

C# 9 では先ほどのコードがエラーになりません。

IEnumerable<int> list = true ? new List<int>() : new int[0];

地味ですがこういうことなのでしょう。

■ まとめ

C# が賢くなりました。積極的に狙って書くことはドキュメントの通りにあまりないのかもしれませんが、古い環境で悩む日が来るかもしれません。完全に理解しておきましょう。

秋葉原 C# もくもく会 #147 勉強会を開催しました

■ C# もくもく会

C# もくもく会 #147 を開催しました。

C# もくもく会 は東京の秋葉原で毎週木曜日に開催している .NET 系の勉強会です。
もくもく自習を基本とし、分からないことを教えあったり情報共有したりしている会です。 定期開催していますので、お時間のある時に遊びに来ていただければと思います。
ちょっと詰まった時、ネット上で聞くのははずかしいなぁ、という課題のできた時などにも思い出していただけると嬉しいです。

f:id:rksoftware:20201215235009j:plain

写真は若者には食べ方がわからないと評判のお菓子などです。私も食べ方がわからない系でした。

■ 新タイプの会場

今週も、もくもくの日でした。

  • ブログ書きました。
  • CosmosDB。
  • 着いて PC 開いたら終わってました。

ちなみにこの勉強会ですが、実は公序良俗に反しなければどのような技術を扱っても大丈夫です。そもそも C# エンジニアが C# だけしか使わないというわけではありませんし。

■初心者歓迎

このもくもく会には、入門者の方も多くご参加いただいています。 突然 C# やらなければならなくなって途方に暮れている方、何となく C# をやってきたけど改めて見直してみたい方なども大歓迎です。
入門セミナー代わりでのご参加も歓迎です。プログラミング入門者の方も是非遊びに来てください。

特に C# で課題をお持ちでなくても是非遊びに来てください。

■ 目指す勉強会スタイル

世界一敷居の低い勉強会を目指しています。

何か聞きたいことがある場合は、聞く相手を決めずに独り言のようにつぶやくと誰かが拾ってくれる

何か共有したい情報を見つけた場合も、聞く相手を決めずに独り言のようにつぶやくと誰かが拾ってくれる

そんなスタイルでやっています。

■ 次回予定

最近の情勢により今後の開催は不透明ですが、とりあえず
次回は 2020/12/17 に開催予定です。

C# に関心のある方、是非遊びに来てください。