7

大きな文字列constを持つクラスを生成する必要がある状況があります。私の制御外のコードにより、生成されたCodeDomツリーがC#ソースに発行され、後でより大きなアセンブリの一部としてコンパイルされます。

残念ながら、この文字列の長さがWin2K8 x64では335440文字(Win2K3 x86では926240文字)を超えると、C#コンパイラが致命的なエラーで終了するという状況に遭遇しました。

致命的なエラーCS1647:式が長すぎるか複雑すぎて、「int」の近くでコンパイルできません

MSDNによると、CS1647は「コンパイラのスタックオーバーフロー」です(しゃれは意図されていません!)。もっと詳しく見てみると、CodeDomは文字列constを80文字で「うまく」ラップしていると判断しました。これにより、コンパイラは4193文字列チャンクを連結します。これは明らかにx64 NetFxのC#コンパイラのスタック深度です。CSC.exeは、この式を内部的に再帰的に評価して、単一の文字列を「再水和」する必要があります。

私の最初の質問はこれです:「コードジェネレーターが文字列を出力する方法を変更するための回避策を知っている人はいますか?」外部システムがC#ソースを中間として使用するという事実を制御できず、これを定数にしたい(むしろ文字列の実行時の連結よりも)。

あるいは、特定の文字数の後でも定数を作成できるが、複数の大きなチャンクで構成されるように、この式をどのように定式化できますか?

完全な再現はここにあります:

// this string breaks CSC: 335440 is Win2K8 x64 max, 926240 is Win2K3 x86 max
string HugeString = new String('X', 926300);

CodeDomProvider provider = CodeDomProvider.CreateProvider("C#");
CodeCompileUnit code = new CodeCompileUnit();

// namespace Foo {}
CodeNamespace ns = new CodeNamespace("Foo");
code.Namespaces.Add(ns);

// public class Bar {}
CodeTypeDeclaration type = new CodeTypeDeclaration();
type.IsClass = true;
type.Name = "Bar";
type.Attributes = MemberAttributes.Public;
ns.Types.Add(type);

// public const string HugeString = "XXXX...";

CodeMemberField field = new CodeMemberField();
field.Name = "HugeString";
field.Type = new CodeTypeReference(typeof(String));
field.Attributes = MemberAttributes.Public|MemberAttributes.Const;
field.InitExpression = new CodePrimitiveExpression(HugeString);
type.Members.Add(field);

// generate class file
using (TextWriter writer = File.CreateText("FooBar.cs"))
{
    provider.GenerateCodeFromCompileUnit(code, writer, new CodeGeneratorOptions());
}

// compile class file
CompilerResults results = provider.CompileAssemblyFromFile(new CompilerParameters(), "FooBar.cs");

// output reults
foreach (string msg in results.Output)
{
    Console.WriteLine(msg);
}

// output errors
foreach (CompilerError error in results.Errors)
{
    Console.WriteLine(error);
}
4

5 に答える 5

4

CodeSnippetExpressionと手動で引用された文字列を使用して、Microsoft.CSharp.CSharpCodeGeneratorから見たかったソースを出力することができました。

したがって、上記の質問に答えるには、次の行を置き換えます。

field.InitExpression = new CodePrimitiveExpression(HugeString);

これとともに:

field.InitExpression = new CodeSnippetExpression(QuoteSnippetStringCStyle(HugeString));

最後に、Microsoft.CSharp.CSharpCodeGenerator.QuoteSnippetStringCStyleメソッドを引用するプライベート文字列を変更して、80文字以降でラップしないようにします。

private static string QuoteSnippetStringCStyle(string value)
{
    // CS1647: An expression is too long or complex to compile near '...'
    // happens if number of line wraps is too many (335440 is max for x64, 926240 is max for x86)

    // CS1034: Compiler limit exceeded: Line cannot exceed 16777214 characters
    // theoretically every character could be escaped unicode (6 chars), plus quotes, etc.

    const int LineWrapWidth = (16777214/6) - 4;
    StringBuilder b = new StringBuilder(value.Length+5);

    b.Append("\r\n\"");
    for (int i=0; i<value.Length; i++)
    {
        switch (value[i])
        {
            case '\u2028':
            case '\u2029':
            {
                int ch = (int)value[i];
                b.Append(@"\u");
                b.Append(ch.ToString("X4", CultureInfo.InvariantCulture));
                break;
            }
            case '\\':
            {
                b.Append(@"\\");
                break;
            }
            case '\'':
            {
                b.Append(@"\'");
                break;
            }
            case '\t':
            {
                b.Append(@"\t");
                break;
            }
            case '\n':
            {
                b.Append(@"\n");
                break;
            }
            case '\r':
            {
                b.Append(@"\r");
                break;
            }
            case '"':
            {
                b.Append("\\\"");
                break;
            }
            case '\0':
            {
                b.Append(@"\0");
                break;
            }
            default:
            {
                b.Append(value[i]);
                break;
            }
        }

        if ((i > 0) && ((i % LineWrapWidth) == 0))
        {
            if ((Char.IsHighSurrogate(value[i]) && (i < (value.Length - 1))) && Char.IsLowSurrogate(value[i + 1]))
            {
                b.Append(value[++i]);
            }
            b.Append("\"+\r\n");
            b.Append('"');
        }
    }
    b.Append("\"");
    return b.ToString();
}
于 2009-06-06T20:57:02.957 に答える
2

だから私はあなたが次のようなC#ソースファイルを持っていると言っているのは正しいですか?

public const HugeString = "xxxxxxxxxxxx...." +
    "yyyyy....." +
    "zzzzz.....";

そして、それをコンパイルしようとしますか?

もしそうなら、私はコンパイルする前にテキストファイルを(もちろんコードで)編集しようとします。おそらく、(人間が生成したソースコードと比較して)厳密に定義されたパターンに従うため、これは比較的簡単に実行できるはずです。定数ごとに1本の太い線になるように変換します。これを試すためのサンプルコードが必要な場合はお知らせください。

ちなみに、あなたの再現は私のボックスでエラーなしで成功します-どのバージョンのフレームワークを使用していますか?(私のボックスには4.0のベータ版があり、物事に影響を与える可能性があります。)

編集:文字列定数にならないように変更するのはどうですか?自分で分割して、次のようなパブリック静的読み取り専用フィールドとして出力する必要があります。

public static readonly HugeString = "xxxxxxxxxxxxxxxx" + string.Empty +
    "yyyyyyyyyyyyyyyyyyy" + string.Empty +
    "zzzzzzzzzzzzzzzzzzz";

重要なのstring.Emptyは、定数ではなくpublic static readonlyフィールドです。つまり、C#コンパイラは、問題のない呼び出しを発行するだけです。もちろん、実行時に1回だけ発生します。コンパイル時に実行するよりも遅くなりますが、他の何よりも簡単な回避策になる可能性があります。string.Concat

于 2009-06-06T19:28:16.953 に答える
2

文字列をconstとして宣言すると、コードでこの文字列を使用する各アセンブリにコピーされることに注意してください。

静的読み取り専用の方がよい場合があります。

別の方法は、文字列を返す読み取り専用プロパティを宣言することです。

于 2009-11-26T16:34:34.903 に答える
0

コードジェネレーターの動作を変更する方法がわかりませんが、コンパイラがEditBin.EXEの/stackオプションを使用して使用するスタックサイズを変更できます。

例:

editbin /stack:100000,1000 csc.exe <options>

以下はその使用例です。

class App 
{
    private static long _Depth = 0;

    // recursive function to blow stack
    private static void GoDeep() 
    {
        if ((++_Depth % 10000) == 0) System.Console.WriteLine("Depth is " +
            _Depth.ToString());
        GoDeep();
    return;
    }

    public static void Main() {
        try 
        {
            GoDeep();
        } 
        finally 
        {
        }

        return;
    }
}




editbin /stack:100000,1000 q.exe
Depth is 10000
Depth is 20000

Unhandled Exception: StackOverflowException.

editbin /stack:1000000,1000 q.exe
Depth is 10000
Depth is 20000
Depth is 30000
Depth is 40000
Depth is 50000
Depth is 60000
Depth is 70000
Depth is 80000

Unhandled Exception: StackOverflowException.
于 2009-06-06T19:30:50.990 に答える
-1

IISのアプリケーションプールで32ビットアプリケーションが有効になっていることを確認してください。Win764ビットで32ビットアプリをコンパイルしようとしてこの問題を解決するのにかかったのはこれだけです。奇妙なことに(またはそうではない)、Microsoftはこの答えを提供できませんでした。丸一日検索した後、IronSpeedDesignerフォーラムで修正へのリンクを見つけました。

http://darrell.mozingo.net/2009/01/17/running-iis-7-in-32-bit-mode/

于 2011-04-23T14:07:27.647 に答える