22
9

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.

C# その2Advent Calendar 2020

Day 25

.NET 5 を使いたい理由6選(C# unsafe/制約緩和編)

Last updated at Posted at 2020-12-24

はじめに

第1回目 .NET 5 を使いたい理由6選
第2回目 .NET 5 を使いたい理由6選(C#編)

この記事は第3回目となります。
unsafeを使いこなせているでしょうか、みなさん。
私は使っていません。(←?!)
でも、C#9.0 のunsafeは久しぶりに少し刺激的な内容になっています。パフォーマンスの欲求に従ってよいと言われたら使ってしまうかもしれませんね。

使いたい理由1 : delegate* (関数ポインタ)が使える

https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/proposals/csharp-9.0/function-pointers
素直な関数ポインタの実装がとうとう使えるようになりました。

    unsafe class Program
    {
        public static void Error(string text) => Console.WriteLine($"ERROR : {text}");
        public static void Warning(string text) => Console.WriteLine($"WARNING : {text}");
        public static void Information(string text) => Console.WriteLine(text);

        static void Main(string[] args)
        {
            var funcs = stackalloc delegate* managed<string, void>[3] { &Error, &Warning, &Information };

            funcs[1]("test"); // WARNING : test
        }
    }

このように配列も可能ですし、ポインタなのでインクリメント/デクリメントや加減算も可能です。これはC/C++のように動作します。

delegate というものはこれまでは MulticastDelegate クラスが実体で、ラムダ式も突き詰めるとこれになるという状態でした。staticメソッドのActionやFuncのパフォーマンスが悪いと言われた理由もすべてこれです。
これは delegate の特性として、+= / -= (つまり add/remove)を受け付けなければならないという言語上大切な理由があってのことでした。
それに対して、delegate* は素直な関数ポインタです。なので staticメソッドにしか使えません。また、リンク先の規約を見ていただければわかりますが、unmanaged で呼び出し規約指定までできるようになっています。ありがたいことです。

managed Thiscallは無いのか…と少し思いました。(unmanaged Thiscallはあるのですが)

使いたい理由2 : スタックメモリを初期化しない[SkipLocalsInit]

https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/proposals/csharp-9.0/skip-localsinit
ローカル変数は初期化しないとそもそもエラーになりますが、stackalloc で確保された領域はゼロfillを行います。
これを省略してくれるのが[SkipLocalsInit]です。

using System.Runtime.CompilerServices;

[SkipLocalsInit]
void Func()
{
    var values = stackalloc int [3]; // 初期化が抑止されるので中身が不定になる
}

真にパフォーマンスが必要なコアコードでのみ役立つようなところですが、ループの都度呼ばれるようなメソッドでは強い威力を発揮します。
使う際には、すべてのスタックメモリの初期化に責任を持つことが肝要です。

使いたい理由3 : ネイティブ整数型のenumを定義できる

これは制約の緩和です。
https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/proposals/csharp-9.0/native-integers
nint nuintが定義されました。intが内部的にはInt32であるように、nintIntPtrであり、nuintUIntPtrです。拡張のポイントは enum で定義できるようになったことです。
便利と言えば便利、そうでもないといえばそうでもないですね。ただ、ネイティブなライブラリを使う際に、ネイティブな値をそのまま enum にできることは安全性を高めるに違いありません。C#の実装がない場合、C/C++で書かれたものを使う必要はいまだに無くなっていないため助かります。
それと同時に、案外とネイティブライブラリをC#に移植するのを促進する一助とはなるかもしれません。

使いたい理由4 : partialメソッドが大幅に拡張された

これも規制の緩和です。もはや別物ですね。
https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/proposals/csharp-9.0/extending-partial-methods

上記リンクからの引用となりますが、C#9より前のpartialメソッドの仕様はトリッキーな仕様を持っていました。

partial class D
{
    partial void M(string message);

    void Example()
    {
        M(GetIt()); // Call to M and GetIt erased at compile time
    }

    string GetIt() => "Hello World";
}

定義されていないのはM()なのに、GetIt()まで消されてしまいます。
これは特定の処理をフォームのデザイン時と実行時でON/OFFするようなシナリオで重宝されたようです。
そのため partial メソッドは「戻り値はvoidのみ」「アクセシビリティを持てず常にprivateメソッドとなる」「outパラメーター不可」などの制約も持っていました。

今回は細部に触れませんが、C#9から使えるようになったソースジェネレーターの拡張に合わせて partial メソッドは大いに拡張されました。

  1. 戻り値を持つことができる
  2. アクセシビリティを持つことができる
  3. outパラメーター可
    これだけの変更でできることはかなり広がります。クラス内には宣言のみ記述し、ソースジェネレーターに実装を任せることなどが可能です。

使いたい理由5 : 共変戻り値が採用された

これも規制の緩和です。
https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/proposals/csharp-9.0/covariant-returns

    class A { public virtual A Create() => new A(); }

    class B : A { public override B Create() => new B(); }  // C#9より前はエラーだった

基幹コードを作る側には少しありがたい。使う側にはさほどメリットは無い。そんな立場の違いが分かる変更と言えます。戻り値の型が固定されていたために内部にXxxxInternalとかXxxxxImplのようなものを作っていたことがあったなら、それらはある程度削除できそうです。

使いたい理由6 : [ModuleInitializer]属性が使える

https://docs.microsoft.com/ja-jp/dotnet/csharp/language-reference/proposals/csharp-9.0/module-initializers
静的コンストラクタのコストについて考えたことがあるでしょうか。
クラスAの静的コンストラクタがクラスBを参照しており、そこにも静的コンストラクタがあった場合、クラスB、クラスAの順でコンストラクタが動かなければなりません。
こういった判定はランタイムで実行されるのでコストがあります。
実行順序が決まっている初期化ルーチンをこの[ModuleInitializer]属性を使って書けば、ロードにかかるコストを抑えることが可能です。
まさに起動時間を縮めるための処方箋と言えます。

まとめ

.NET 5を使いたい理由(C#編)の続きでした。
私は最近 C#9.0 を使うようになってから .NET 5 より前に戻りたくなくなってしまいました。
レガシーなものを触るときを除いて、 .NET 5 を使うべき理由は沢山あると思います。
unsafeを使う現場は少ないと思いますが、制約緩和系の拡張は魅力的なものが多いと感じています。

感謝

.NET と C# の発展に携わるすべてのエンジニアの皆様に感謝いたします。

22
9
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
22
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?