2009-06-06 30 views
7

私は大きな文字列constを持つクラスを生成する必要がある状況があります。私のコントロール外のコードは、生成されたCodeDomツリーをC#ソースに出力し、後に大きなアセンブリの一部としてコンパイルします。csc.exeのスタックオーバーフロー(CS1647)を引き起こすC#CodeDomの回避策?

fatal error CS1647: An expression is too long or complex to compile near 'int'

MSDN:

残念ながら、私はこの文字列の長さはWin2K8 x64版で335440文字(WIN2K3のx86で926240)、致命的なエラーとC#コンパイラの終了を超えたことにより、場合、状況に遭遇しましたCS1647は「コンパイラでのスタックオーバーフロー」です(何も意図していません)。より密接に見ると、CodeDomが文字列constを80文字で "うまく"ラップすることが判明しました。これは、x64 NetFxのC#コンパイラのスタック深度である4193文字列チャンクを連結します。 CSC.exeは、この式を内部的に再帰的に評価して、単一の文字列を再水和する必要があります。 "外部システムがC#ソースを中間体として使用しているという事実を制御することはできません。私はこれを欲しがっています。 (文字列の実行時連結ではなく)定数にする。

また、はどのように私はこの表現は、文字の特定の番号の後、私はまだ定数を作成することができると思っているように定式化することができますが、それはチャンク大多数ので構成されていますか?

全REPROはここにある:

// 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); 
} 
+0

これは、.NET 3.5をターゲットにもかかわらず、2.0のように見えるの下で実行さcsc.exeバージョン:検索の完全な一日を過ごした後、私は鉄のスピードデザイナーフォーラムで解決するには、このリンクを見つけました。 – mckamey

答えて

4

、私は私が好きだろうソースを放出することができました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(); 
} 
+0

Jon Skeetのおかげでこの解決策が思い浮かびました。また、箱の外で考えてくれたRobert Harveyに感謝します。 – mckamey

+0

文字列定数をラップしない場合のcsc.exeの別の制限事項は次のとおりです。「エラーCS1034:コンパイラの制限を超えました:行は16777214文字を超えることはできません」明らかに必要なものはハイブリッドです。 – mckamey

+0

この答えでは、より長い文字列の長さの*多くの*オーダーが可能です(数百万の文字が読み込まれます)。ストレステストでは、マシンのメモリ制限が新しいバウンディングサイズになることが示されています。 – mckamey

2

だから私は右のあなたのようなものを持つC#のソースファイル持っていると言っで午前:

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

を、あなたその後、コンパイルしようそれ?

もしそうなら、私はコンパイルする前にテキストファイルを(コード内で)編集しようとします。人間が生成したソースコードと比較して、厳密に定義されたパターンに従うと考えられるので、これは比較的簡単に行うべきです。各定数に対して1つの大量の行を持つように変換します。いくつかのサンプルコードでこれを試してみたいと思ったらお知らせください。

ところで、あなたのreproは私の箱に何のエラーもなく成功しています - どのバージョンのフレームワークを使用していますか? (私のボックスは、4.0のベータ版を持っていますので、影響を受ける可能性があります)

EDIT:文字列定数に変更しないでください。あなたは自分でそれを分割する必要があり、このような公共の静的な読み取り専用フィールドとしてそれを放出したい:

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

極めて重要な、string.Emptyは、ない定数public static readonlyフィールドです。つまり、C#コンパイラはstring.Concatへの呼び出しを出すだけで、大丈夫かもしれません。それは、実行時に一度だけ実行されます - コンパイル時に実行するよりも遅いですが、他のものより簡単な回避策かもしれません。

+0

ランタイムは.NET 3.5ですが、実際にコードをコンパイルするときに2.0 csc.exeまたはそれ以上を実行するかどうかはわかりません。私は、より多くの状況で失敗するように、文字列のサイズをreproにぶつけた。それでも成功すれば、4.0のいずれかがスタックの深さを増やしたか、または私が疑うよりも値がマシンに依存しています。 ファイルを編集することはできますが、残念ながら私のコードはCodeDomツリーを返すためだけに呼び出されています。外部コードは、中間ファイルがいつどこで放出/コンパイルされるかを決定します。 – mckamey

+0

Ah。さて、奇妙なアイデアで編集。 –

+0

私はあなたに投票しますが、明らかに私は十分にSOに参加していません。 興味深い。 CodeDomは完全なC#ではないので、実際には読み取り専用では発行できませんが、連結を解除するとコンパイルできます。今私はこれが単にランタイムにオーバーフローをプッシュするかどうかを確認する必要があります。 – mckamey

0

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

例:

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

後は、その使用の例である:

CodeSnippetExpression、手動で引用符で囲まれた文字列を使用して
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. 
+0

興味深い提案。残念ながら私が呼び出されている場所では、csc.exeに直接アクセスすることはできません。理想的には、文字列が長すぎるかどうか再度質問する必要はありません。この問題を回避するには、文字列のサイズが大きくなるにつれてスタックサイズを維持する必要があります。 – mckamey

2

文字列をconstとして指定すると、がコード内でこの文字列を使用する各アセンブリににコピーされます。

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

もう1つの方法は、文字列を返すreadonlyプロパティを宣言することです。

+0

これは興味深いです。私はこれについて聞いていない。 "この文字列を使用する"とは何ですか?他のアセンブリが生成されたクラスの定数メンバを参照していることを意味していますか? Reflectorで他のアセンブリのコピーされた定数を見ることができないでしょうか?私のコードで実際に行っていることは、プロパティゲッターでこの定数を返すことで実装されているインターフェイスを満足させることです。私は、コンパイラが常に定数を返すことを知ることができないと確信しています。詳しい情報はどこにありますか? – mckamey

+0

Console.WriteLine(MyClass.HugeString)を呼び出してリフレクタを調べると、Console.WriteLine( "blah blah blubb ..")だけが表示され、参照はなくなります。 constは、コンパイル時定数であり、C++で定義するのと似ています(しかし異なる)。本当にそうではありません。 google for "const vs readonly"を使用して詳細な情報を見つけたり、C#言語仕様を読んでください。 – codymanix

+0

ヘッドアップと説明をありがとう。私は、コンパイラの定数フォールディングがアセンブラの境界を越えることができないように見えます。 ここのサンプルコードは私が実際にやっていることから単純化されているので、私はこの場合大丈夫だと思います。私は実際にgetterだけでプロパティから返す文字列リテラルを構築しています: 'property.GetStatements.Add(new CodeMethodReturnStatement(new CodeSnippetExpression(QuoteSnippetStringCStyle(str)))); ' – mckamey

-1

IISのアプリケーションプールに32ビットアプリケーションが有効になっていることを確認してください。 Win7 64ビット版で32ビット版のアプリケーションをコンパイルしようとすると、この問題を解決するために必要なことはすべてです。奇妙なことに、マイクロソフトはこの回答を提供できませんでした。

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

+1

-1、失敗します。この回答がどのように問題に関連しているかを確認してください。なぜC#コンパイラはIISアプリケーションプールを気にする必要がありますか? – stakx