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

请注意,如果您将字符串声明为 const,它将被复制到在其代码中使用此字符串的每个程序集中。

使用静态只读可能会更好。

另一种方法是声明一个返回字符串的只读属性。

于 2009-11-26T16:34:34.903 回答
2

所以我说你有类似的 C# 源文件是对的:

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

然后你尝试编译它?

如果是这样,我会在编译之前尝试编辑文本文件(当然是在代码中)。这应该相对简单,因为他们可能会遵循严格定义的模式(与人工生成的源代码相比)。将其转换为每个常数都有一条大线。如果您想要一些示例代码来试试这个,请告诉我。

顺便说一句,您的复制成功,我的盒子上没有错误 - 您使用的是哪个版本的框架?(我的盒子有 4.0 的测试版,这可能会影响事情。)

编辑:如何将其更改为不是字符串常量?您需要自己分解它,并将其作为公共静态只读字段发出,如下所示:

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

至关重要的是,string.Empty是一个public static readonly场,而不是一个常数。这意味着 C# 编译器只会发出一个调用,string.Concat这很可能是好的。当然,它只会在执行时发生一次 - 比在编译时执行它要慢,但它可能比其他任何事情都更容易解决。

于 2009-06-06T19:28:16.953 回答
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 位应用程序。这就是我试图在 Win7 64 位中编译一个 32 位应用程序时解决这个问题的全部内容。奇怪的是(或不是),微软无法提供这个答案。经过一整天的搜索,我在 Iron Speed Designer 论坛上找到了此修复链接:

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

于 2011-04-23T14:07:27.647 回答