Edited at

C#7.2 コンパイラのバグぽいもの

More than 1 year has passed since last update.


デフォルト引数にin で死ぬ

using System;

namespace ConsoleApp1 {
class Program {
static void Test( in double x = 1 , in string y = "" ) => Console.WriteLine( y );
static void Main() {
Test( 10 );
Console.ReadLine();
}
}
}

私の環境で実行するとこんな感じにバグる。

AccessViolationException吐く場合もある。(触っちゃいけないメモリにアクセスしようとする可能性がある)

image.png


これなら問題ない

static void Test( double x = 1 , in string y = "" ) => Console.WriteLine( y );


string以外、例えば、int(値型)にすると、値型なのにNullReferenceExceptionだと言われる。

image.png


何が起きているのか?

Test( 10 );というのは、実際には、こんなふうにコンパイラによって展開される。(…はずである)

inは、結局のところref扱いされている。refというのは、ローカル変数の参照を渡すもの。


正常ならこんな感じに展開される

static void Main() {

int a = 1; // x
string b = ""; // y
Test( ref a , ref b );
}


問題が発生してあるケース

static void Main() {

int a = 1; // x
Test( ref a , "" );
}

即値を渡すとまずいところに即値を渡しているため、参照先が不正、どこにも実体が無い状態となってしまう。

そもそも、次のコードは、コンパイラエラーになるわけでin=refである以上、コンパイル通るのがおかしい。

Test( ref int x = 100 );

まあでもinなんだから通っても構わないという意見もある。


名前付き引数でもバグる。

class Program {

static void Test( in int x = 646346 , in int y = 100 ) => Console.WriteLine( x );
static void Main() {
Test( y : 10 );
Console.ReadLine();
}
}

x = 646346の部分を即値として渡してしまい、すなわちアドレス(646346)を読みに行こうとする。


参照型のthisinに渡してフィールドを読み取ると死ぬ。

いろいろ検証した結果...別にvirtualメンバーと関係なかった

image.png


問題のコード

using System;

namespace ConsoleApp1 {
sealed class A {
public string Value = "";
public void Run() => show( this );

static void show( in A a ) => Console.WriteLine( a.Value );
// ↑inでthisを受けることでメモリ破壊されている?
// * thisを一時変数にコピーするなどした場合は、発生しない。
}

class Program {
static void Main() {
var a = new A();
a.Run();
Console.ReadLine();
}
}
}


参照型の this を in で受けるメソッドに渡した上で、フィールドにアクセスするとAccessViolationExceptionExecutionEngineExceptionで死にます。

値型の場合は、フィールドの値がバグってたりする。


1234と出力されるはずが278920と出力される(実行環境によって異なるはず)

using System;

namespace ConsoleApp1 {
sealed class A {
public int Value = 1234;
public void Run() => show( this );
static void show( in A a ) => Console.WriteLine( a.Value );
}

class Program {
static void Main() {
new A().Run();
Console.ReadLine();
}
}
}


これも実際のところ、こんなふうにrefで受け取るわけで...

void show( ref A a ) => Console.WriteLine( a.Value );

show( ref this );

のように呼ぶわけで、これはコンパイルエラーになるわけだから、in thisで通るのがおかしい。


外部アセンブリで演算子のオーバーロードにinを使うとコンパイルエラーになる。

using System;

namespace ConsoleApp1 {
public struct Hoge {
public Hoge( int value ) => this.Value = value;
public int Value;
public static implicit operator int( in Hoge source ) => source.Value;
}

class Program {
static void Main() {
Hoge x = new Hoge( 100 );
if( x == new Hoge(200 ) )
Console.WriteLine();
Console.ReadLine();
}
}
}

同一アセンブリ内であれば、問題ないがHogeを外部アセンブリに移動するとコンパイルエラーになる。


エラー一覧には出ず、出力に出てくる。

error CS0019: 演算子 '==' を 'Hoge' と 'Hoge' 型のオペランドに適用することはできません


キャスト演算子だけではなく、どのオペレータでも発生する。

恐らく近々修正が入るだろうけど、修正されるまで気をつけるべし...