Last active
October 19, 2015 08:23
-
-
Save m0sa/f086bb0e9f62e05995c6 to your computer and use it in GitHub Desktop.
Roslyn Adventures: Optimizing StringBuilder string interpolation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
new StringBuilder().Append(string.Format("foo {0}", 1)); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
new StringBuilder().Append($"foo {1}"); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
IL_0001: newobj System.Text.StringBuilder..ctor | |
IL_0006: ldstr "foo {0}" | |
IL_000B: ldc.i4.1 | |
IL_000C: box System.Int32 | |
IL_0011: call System.String.Format | |
IL_0016: call System.Text.StringBuilder.Append |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Foo | |
{ | |
public static void Bar(FormattableString str) | |
{ | |
Console.WriteLine("FORMAT: " + str.Format, str.GetArguments()); | |
} | |
public static void Bar(string str) | |
{ | |
Console.WriteLine("STRING: " + str); | |
} | |
public static void Main() | |
{ | |
Bar($"a test {42}"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
new StringBuilder().AppendFormat("foo {0}", 1); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
IL_0001: newobj System.Text.StringBuilder..ctor | |
IL_0006: ldstr "foo {0}" | |
IL_000B: ldc.i4.1 | |
IL_000C: box System.Int32 | |
IL_0011: call System.Text.StringBuilder.AppendFormat |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
CSharpCompilation compilation = ...; | |
foreach (var syntaxTree in compilation.SyntaxTrees) | |
{ | |
var model = compilation.GetSemanticModel (syntaxTree); | |
var rewriter = new StringBuilderInterpolationOptimizer(model); | |
var rootNode = syntaxTree.GetRoot(); | |
var rewritten = rewriter.Visit(rootNode); | |
if (rootNode != rewritten) | |
{ | |
compilation = compilation.ReplaceSyntaxTree( | |
syntaxTree, | |
syntaxTree.WithRootAndOptions(rewritten, syntaxTree.Options)); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class StringBuilderInterpolationOptimizer : CSharpSyntaxRewriter | |
{ | |
private readonly SemanticModel _model; | |
public StringBuilderInterpolationOptimizer(SemanticModel model) | |
{ | |
_model = model; | |
} | |
// we use the semantic model to get the type information of the method being called | |
private static bool CanRewriteSymbol(SymbolInfo symbolInfo, out bool appendNewLine) | |
{ | |
appendNewLine = false; | |
IMethodSymbol methodSymbol = symbolInfo.Symbol as IMethodSymbol; | |
if (methodSymbol == null) return false; | |
switch (methodSymbol.Name) | |
{ | |
case "AppendLine": | |
case "Append": | |
if (methodSymbol.ContainingType.ToString() == "System.Text.StringBuilder") | |
{ | |
appendNewLine = methodSymbol.Name == "AppendLine"; | |
return true; | |
} | |
break; | |
} | |
return false; | |
} | |
public override SyntaxNode VisitInvocationExpression(InvocationExpressionSyntax node) | |
{ | |
var memberAccess = node.Expression as MemberAccessExpressionSyntax; | |
if (memberAccess != null && node.ArgumentList.Arguments.Count == 1) | |
{ | |
// check if the single method argument is an interpolated string | |
var interpolatedStringSyntax = node.ArgumentList.Arguments[0].Expression as olatedStringExpressionSyntax; | |
if (interpolatedStringSyntax != null) | |
{ | |
bool appendNewLine; // this distinguishes Append and AppendLine calls | |
if (CanRewriteSymbol(_model.GetSymbolInfo(memberAccess), out appendNewLine)) | |
{ | |
var formatCount = 0; | |
var formatString = new StringBuilder(); | |
var formatArgs = new List<ArgumentSyntax>(); | |
// build the format string | |
foreach (var content in interpolatedStringSyntax.Contents) | |
{ | |
switch (content.Kind()) | |
{ | |
case SyntaxKind.InterpolatedStringText: | |
var text = (InterpolatedStringTextSyntax)content; | |
formatString.Append(text.TextToken.Text); | |
break; | |
case SyntaxKind.Interpolation: | |
var interpolation = (InterpolationSyntax)content; | |
formatString.Append("{"); | |
formatString.Append(formatCount++); | |
formatString.Append(interpolation.AlignmentClause); | |
formatString.Append(interpolation.FormatClause); | |
formatString.Append("}"); | |
// the interpolations become arguments for the AppendFormat call | |
formatArgs.Add(SyntaxFactory.Argument(interpolation.Expression)); | |
break; | |
} | |
} | |
if (appendNewLine) | |
{ | |
formatString.AppendLine(); | |
} | |
// the first parameter is the format string | |
formatArgs.Insert(0, | |
SyntaxFactory.Argument( | |
SyntaxFactory.LiteralExpression( | |
SyntaxKind.StringLiteralExpression, | |
SyntaxFactory.Literal(formatString.ToString())))); | |
return node | |
.WithExpression(memberAccess.WithName(SyntaxFactory.IdentifierName("AppendFormat"))) | |
.WithArgumentList(SyntaxFactory.ArgumentList(SyntaxFactory.SeparatedList(formatParams))); | |
} | |
} | |
} | |
return base.VisitInvocationExpression(node); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment