如何映射InvocationExpressionSyntax实参至形参?Roslyn方法问询
Great question! Mapping invocation arguments to method parameters in Roslyn—especially when dealing with optional parameters, named arguments, or params arrays—requires accounting for all the nuanced logic the C# compiler uses. Let's cover both manual mapping (if you need custom logic) and the built-in Roslyn API that matches the compiler's behavior exactly.
Manual Argument-to-Parameter Mapping (For Custom Scenarios)
If you need to implement custom matching logic, follow these steps to map InvocationExpressionSyntax arguments to a method's parameters:
Get the method's semantic symbol
First, bind the syntax node to its semantic model to get theIMethodSymbol(which contains the method's parameter metadata):var semanticModel = // Retrieve from your compilation context (e.g., AnalysisContext) var invocationExpr = // Your target InvocationExpressionSyntax instance var methodSymbol = semanticModel.GetSymbolInfo(invocationExpr).Symbol as IMethodSymbol; if (methodSymbol == null) return; // Handle cases where the symbol couldn't be resolvedSeparate named and positional arguments
Split the invocation's arguments into named (those with aNameColon) and positional (those without) groups:var arguments = invocationExpr.ArgumentList.Arguments; var namedArgs = arguments .Where(arg => arg.NameColon != null) .ToDictionary( arg => arg.NameColon.Name.Identifier.Text, arg => arg ); var positionalArgs = arguments.Where(arg => arg.NameColon == null).ToList();Map parameters to arguments
First match named arguments directly by parameter name, then fill remaining parameters with positional arguments in order. For optional parameters without an explicit argument, mark them as using the default value:var parameterMap = new Dictionary<IParameterSymbol, ArgumentSyntax?>(); int positionalIndex = 0; foreach (var param in methodSymbol.Parameters) { // Check for a matching named argument first if (namedArgs.TryGetValue(param.Name, out var namedArg)) { parameterMap[param] = namedArg; namedArgs.Remove(param.Name); // Avoid duplicate matches } // Fall back to positional arguments if available else if (positionalIndex < positionalArgs.Count) { parameterMap[param] = positionalArgs[positionalIndex]; positionalIndex++; } // No explicit argument—use the optional parameter's default value else { parameterMap[param] = null; } }Now
parameterMapcontains each parameter paired with its corresponding argument (ornullif the optional parameter's default value is used).
Using Roslyn's Built-in Compiler-Consistent Mapping (Recommended)
Roslyn provides an API that replicates the C# compiler's exact argument-binding logic, so you don't have to handle edge cases like reordered named arguments, params array expansion, or optional parameter defaults manually.
Use SemanticModel.GetInvocationExpressionInfo() to get a structured breakdown of the argument-parameter mappings:
var semanticModel = // Retrieve from your compilation context var invocationExpr = // Your target InvocationExpressionSyntax instance var invocationInfo = semanticModel.GetInvocationExpressionInfo(invocationExpr); if (invocationInfo.Symbol == null) return; foreach (var argInfo in invocationInfo.Arguments) { var parameter = argInfo.Parameter; var argumentSyntax = argInfo.Argument; if (argumentSyntax != null) { Console.WriteLine($"Parameter '{parameter.Name}' matched with explicit argument: {argumentSyntax}"); } else { Console.WriteLine($"Parameter '{parameter.Name}' uses its default value: {parameter.DefaultValue}"); } }
Each ArgumentInfo in invocationInfo.Arguments includes:
Parameter: TheIParameterSymbolthe argument is bound toArgument: TheArgumentSyntaxfor the explicit argument (ornullif using an optional parameter's default)IsParamsArray: Whether the argument is part of a params array expansionIsOptional: Whether the argument is using an optional parameter's default value
Key Notes
- Always use
SemanticModelinstead of relying solely on syntax nodes—semantic analysis gives you access to the actual method signature and parameter metadata, which is critical for accurate mapping. - The built-in
GetInvocationExpressionInfomethod handles all edge cases the C# compiler does, so it's the recommended approach unless you need custom matching logic.
内容的提问来源于stack exchange,提问作者user3284063

