@azyobuzinの技術ブログ

このプログラム

using System;

class Program
{
    static void Main(string[] args)
    {
        var (foo, bar) = F(() =>
        {
            var foo = 0;
            var bar = 1;
            return (foo, bar);
        });
    }

    static T F<T>(Func<T> x) => x();
}

は、残念ながら現在の C# コンパイラではエラーになる。

Program.cs(9,17): error CS0136: ローカルまたはパラメーター 'foo' は、その名前が外側のロ
ーカルのスコープでローカルやパラメーターの定義に使用されているため、このスコープでは宣言できません。
Program.cs(10,17): error CS0136: ローカルまたはパラメーター 'bar' は、その名前が外側のローカルのスコープでローカルやパラメーターの定義に使用されているため、このスコープでは宣言できません。

しかし、ちょっと改変した次のプログラムを RoslynPad で実行すると、何もエラーにならずに実行される。

T F<T>(Func<T> x) => x();

var (foo, bar) = F(() =>
{
    var foo = 0;
    var bar = 1;
    return (foo, bar);
});

これは、 RoslynPad がスクリプトモードでコンパイルを行っているためである。 CS0136 のチェックは、ローカル変数とパラメータに対してのみ重複チェックを行う。しかし、スクリプトで最上位に変数を定義した場合、扱いとしてはフィールドとなる。したがって、このチェックを潜り抜けるので、なぜかスクリプトモードだけ挙動が違うように見えてしまう。

通常コンパイルとスクリプトモードでのエラーの違いを見る検証プログラム

using System;
using System.Globalization;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;

class Program
{
    static void Main(string[] args)
    {
        // メッセージの英語化
        Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;

        Console.WriteLine("Roslyn v" + typeof(CSharpCompilation).Assembly.GetName().Version);

        var testSource = @"using System;
class Program
{
    static void Main(string[] args)
    {
        var (foo, bar) = F(() =>
        {
            var foo = 0;
            var bar = 1;
            return (foo, bar);
        });
    }
    static T F<T>(Func<T> x) => x();
}";

        var compilation = CSharpCompilation.Create(
            "CS0136Test",
            new SyntaxTree[] { CSharpSyntaxTree.ParseText(testSource) },
            new MetadataReference[] { MetadataReference.CreateFromFile(typeof(string).Assembly.Location) }
        );

        Console.WriteLine("Library");
        foreach (var diag in compilation.GetDiagnostics())
        {
            Console.WriteLine(diag);
        }

        var testScript = @"using System;
T F<T>(Func<T> x) => x();
var (foo, bar) = F(() =>
{
    var foo = 0;
    var bar = 1;
    return (foo, bar);
});";

        var scriptCompilation = CSharpCompilation.CreateScriptCompilation(
            "CS0136Test",
            CSharpSyntaxTree.ParseText(testScript, new CSharpParseOptions(kind: SourceCodeKind.Script)),
            new MetadataReference[] { MetadataReference.CreateFromFile(typeof(string).Assembly.Location) }
        );

        Console.WriteLine();
        Console.WriteLine("Script");
        foreach (var diag in scriptCompilation.GetDiagnostics())
        {
            Console.WriteLine(diag);
        }
    }
}

/*
Roslyn v2.9.0.0
Library
(8,17): error CS0136: A local or parameter named 'foo' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter
(9,17): error CS0136: A local or parameter named 'bar' cannot be declared in this scope because that name is used in an enclosing local scope to define a local or parameter

Script
*/

スクリプトモードで定義した変数がフィールド扱いになっていることを確認するプログラム

using System;
using System.Globalization;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

class Program
{
    static void Main(string[] args)
    {
        Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
        Console.WriteLine("Roslyn v" + typeof(CSharpCompilation).Assembly.GetName().Version);

        var testScript = @"var x = 0;";
        var tree = CSharpSyntaxTree.ParseText(testScript, new CSharpParseOptions(kind: SourceCodeKind.Script));

        var scriptCompilation = CSharpCompilation.CreateScriptCompilation(
            "TestAssembly",
            tree,
            new MetadataReference[] { MetadataReference.CreateFromFile(typeof(string).Assembly.Location) }
        );

        var semanticModel = scriptCompilation.GetSemanticModel(tree, false);
        var varDecl = tree.GetRoot().DescendantNodes()
            .OfType<VariableDeclaratorSyntax>()
            .First();
        var symbol = semanticModel.GetDeclaredSymbol(varDecl);
        Console.WriteLine("Kind: " + symbol.Kind);
    }
}

/*
Roslyn v2.9.0.0
Kind: Field
*/

このログへのコメント

コメントはありません