C++ ときどき ごはん、わりとてぃーぶれいく☆

USAGI.NETWORKのなかのひとのブログ。主にC++。

C#-7.0: 滲み出すスコープ

Visual StudioC# プロジェクトでも ErrorWarning も1件も見逃さない綺麗なビルドを心掛けていますが、 Messages は見落としていました。何件か来ている。

  • f:id:USAGI-WRP:20181223081227p:plain

まず、一番上を見る。 IDE0018: Variable declaration can be inlined とやら。

C#-7.0 で何がどう変わるかを眺めた際に「うへー」って気持ちになった事があったのを思い出してしまいました。

// C# < 7.0
{
  var a = "1.23";
  double b; // <-- 事前に宣言が必要
  var c = double.TryParse( a, out b );
  Console.WriteLine( $"{a} {b} {c}" );
}
// C# >= 7.0
{
  var a = "2.34";
  //double b; <-- 要らんくなった
  var c = double.TryParse( a, out double b ); // <-- out の後で変数宣言していた事にできる言語機能
  Console.WriteLine( $"{a} {b} {c}" );
}

↑手間がかからず一見便利に見えますが、「スコープが滲み出す」とタイトルに書いたような現象が起こるようになります↓

var a = "3.45";
if ( double.TryParse( a, out double b ) )
  Console.WriteLine( $"X: {a} {b}" ); // <-- ここで b が生きているのは感覚的にもまあわかる
Console.WriteLine( $"Y: {a} {b}" ); // <-- コンパイル可能、実行可能、 b はここでも生きている

きもぃ。便利さと拮抗する程度には十分にきもぃ😅

この言語仕様上のスコープがプログラマーソースコード・リーディングにおける視認のスコープ感と明らかに異なるだろう挙動には注意が必要になる。

var p = "3.45";
var q = "4.56";

if ( double.TryParse( p, out double buffer ) )
  Console.WriteLine( buffer );

if ( double.TryParse( q, out double buffer ) ) // <-- コンパイル不能
  Console.WriteLine( buffer );

プログラマー、特に C 系に近い言語を主戦場とするプログラマーにとって、このコードはコンパイル可能な気がすると思います。しかし、 C#-7.0 言語仕様では "正しく" コンパイル不能なコードです。 p のパースの際に out に続けて宣言した double buffer のスコープは if と同じスコープになります。 if の内部でだけ使えるような見栄えのソースコードですが、 if と同じスコープへと double buffer 変数のスコープは "染み出し" しています。

同様の現象は is でも発生します。

var n = new List< int >(){ 1,2,3 };
var m = Enumerable.Range( 10, 20 );

if ( n is IEnumerable< int > integers )
  Console.WriteLine( integers.Count() );

if ( m is IEnumerable< int > integers ) // <-- コンパイル不能
  Console.WriteLine( integers.Count() );

染み出しスコープ言語機能…きもぃ😂 便利なのはワカルけど、どうして if スコープへ束縛とか考えなかったんだろう、言語仕様作った人。

おまけ

他にも Messages は出ているので一応おまけメモ。

  • IDE0033 Prefer explicitly provided tuple element name

↑は↓のようなタプルに名前が付けられる場合にもタプル標準の Item1 とか使っていると出る。

var vs = new List< string >(){ "aaa", "bbb", "ccc" };  
// ↓の t.Item1 に対して IDE0033 が発生する
var xs = ( from v in vs select ( v, v.GetHashCode() ) ).ToDictionary( t => t.Item1, t => t.Item2 );
Console.Write( string.Join( "\n", xs ) );

しかし、このしれっとタプルの変数名を勝手に変更してくれちゃう機能は C#-7.1 かららしい。 .net Framework 4.7.2 とかのプロジェクトでは使えない。 .net Core 2.0 ≃ C# 7.1 らしい。 IDE のおすすめメッセージは良い機能とは思うものの、プロジェクトが使用可能な言語バージョンを超えるサジェストはどうかと思う😂

  • IDE0042 Test C# Variable declaration can be deconstructed

↑は↓のようなタプルを構造化束縛せずに使っている場合に発生する。

var vs = new ( string uni, int tako )[]
{ ( "きたむらさき", 123 )
, ( "えぞばふん", 234 )
, ( "あか", 345 )
};

// ↓タプルを構造化束縛せずに使うと IDE0042
foreach ( var t in patterns )
  Console.WriteLine( $"uni={t.uni} tako={t.tako}" );

// ↓構造化束縛して使うと出ない
foreach ( var ( uni, tako ) in patterns )
  Console.WriteLine( $"uni={uni} tako={tako}" );