手続き型音楽の日常

関数型音楽に乗り換えたい

.net standard プロジェクトでnull許容構造体の循環参照が検出されない

先日、ウェーブレット木の記事を書くときに C# 書いていて、あれおかしいな?と感じたところを突き詰めた結果、こんなことが分かった次第です。

Visual Studio 2017 で現象を確認しましたが、 Visual Studio 2019 Preview 2 でも同じく確認したので。

現象詳細

構造体定義の中に、自分自身のnull許容構造体のフィールドを入れたとき (struct Test { Test? test; } みたいな感じ)、通常であればコンパイルエラーになるが、 .net standard プロジェクトでのみコンパイルエラーにならず実行時エラーが発生する。

再現手順

1. プロジェクトの作成

.net standard プロジェクトと、 .net core コンソールプロジェクトを作ります。そして .net core プロジェクトから .net standard プロジェクトを参照します。 .net core のほうは、 .net standard でも OK です。

.net standard プロジェクトは移植可能な DLL を作成するのみで、ビルドしても実行可能なバイナリは生成できません。そのため、別のプロジェクトから参照する形で実行します。

f:id:yuzutan_hnk:20190203155459p:plain
.net standard プロジェクトのプロパティ

2. コードの記述

検証用のコードは至ってシンプルです。 .net standard 側で構造体を定義し、 .net core 側でそれをインスタンス化します。

まずは .net standard プロジェクトの構造体定義。

namespace netstandard_structtest
{
    public struct TestData
    {
        TestData? test;
    }
}

そして、 .net core プロジェクトのエントリポイント。

using System;

namespace netcore_structtest
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = new netstandard_structtest.TestData();

            Console.WriteLine("Press key to continue...");
            Console.Read();
        }
    }
}

これで準備は完了です。

3. 実行

現状で、エラーは無くビルドが通ります。なので、そのまま実行します。

実行時エラーで起動できません。

f:id:yuzutan_hnk:20190203160537p:plain
型が読み込めない

4. 別パターン

インスタンスを作らない

エントリポイント内でインスタンスを作らないことにしてみます。つまり、定義だけで利用しません。この場合は、実行できます。

using System;

namespace netcore_structtest
{
    class Program
    {
        static void Main(string[] args)
        {
            //var x = new netstandard_structtest.TestData();

            Console.WriteLine("Press key to continue...");
            Console.Read();
        }
    }
}

f:id:yuzutan_hnk:20190203160807p:plain
動くのかい

null許容にしない

構造体の定義で null許容を外して完全に自分自身を参照します。結果は、たぶん定義通りの動きだと思います。

namespace netstandard_structtest
{
    public struct TestData
    {
        TestData test;
    }
}

f:id:yuzutan_hnk:20190203161352p:plain
わかりみが深い

.net core プロジェクト側で定義する

構造体の定義を .net core プロジェクトでやってみます。コンパイルエラーになります。

using System;

namespace netcore_structtest
{
    class Program
    {
        static void Main(string[] args)
        {
            var x = new netstandard_structtest.TestData();

            Console.WriteLine("Press key to continue...");
            Console.Read();
        }
    }

    struct TestData
    {
        TestData? test;
    }
}

f:id:yuzutan_hnk:20190203161624p:plain
コンパイルエラー

null許容じゃなくてクラスにする

null許容にしたいのであれば、クラスでラップすれば事足りるのではないか?という発想。

namespace netstandard_structtest
{
    public struct TestData
    {
        NullableTestData test;
    }

    public class NullableTestData
    {
        TestData value;
    }
}

f:id:yuzutan_hnk:20190203162039p:plain
まあ、動くよね。

所感

単純に考えて、自分自身を構造体で参照したらレイアウトが循環するのは正しいと思います。

いくら null許容と言っても、実際には Nullable<T> 構造体の中で自分自身の変数が定義されるので、結果的に循環参照になるはず。

.net core 側では事前にチェックされてコンパイルエラーになるのに、 .net standard ではならないということは、何か理由があるのか?それともコンパイラの単なる実装バグ?

一体、これはどこに報告すれば回答が得られるのでしょうか?詳しい方、ぜひ教えてください。

現在 0000/00/00 00:00 を生きています。