Operations and syntax

edit

Follow values

TryGetConstantValue returns the constant value of an operation, and GetActualType the type of the value it holds, which can be more precise than its declared type. Both unwrap the implicit conversions and, by default, follow the value of locals, fields and properties to the value assigned to them:

var syntaxTree = CSharpSyntaxTree.ParseText("""
    public class Sample
    {
        private readonly object _field = 3;

        public void M()
        {
            var value = 41;
            value = 42;
            object fromLocal = value;
            object fromField = _field;
        }
    }
    """);
var compilation = CSharpCompilation.Create("Sample", [syntaxTree], [MetadataReference.CreateFromFile(typeof(object).Assembly.Location)]);
var semanticModel = compilation.GetSemanticModel(syntaxTree);
var initializers = syntaxTree.GetRoot().DescendantNodes().OfType<VariableDeclaratorSyntax>()
    .ToDictionary(declarator => declarator.Identifier.ValueText, declarator => semanticModel.GetOperation(declarator.Initializer!.Value)!, StringComparer.Ordinal);

// "value" is a local whose last assignment before the read is 42
Assert.True(initializers["fromLocal"].TryGetConstantValue(out var localValue, CancellationToken.None));
Assert.Equal(42, localValue);
Assert.Equal(SpecialType.System_Int32, initializers["fromLocal"].GetActualType(CancellationToken.None)?.SpecialType);

// "_field" is a readonly field initialized to 3
Assert.True(initializers["fromField"].TryGetConstantValue(out var fieldValue, CancellationToken.None));
Assert.Equal(3, fieldValue);

The analysis stays local and conservative. It follows a local within the member that declares it, and the initializer of a private readonly field or a private get-only property, which no other code can change. When the value may come from several places, such as a write in a loop, a goto, a ref or out argument, or another member, it stops: TryGetConstantValue returns false, and GetActualType returns the declared type. Pass useDataFlowAnalysis: false to only unwrap the conversions.

Operations

MethodDescription
UnwrapImplicitConversions()The operand of the implicit conversions around an operation, such as the boxing of object o = 42.
UnwrapConversions()The operand of all the conversions, explicit casts included.
UnwrapLabels()The operation of a labeled statement.
Ancestors()The parents of an operation, from the closest.
IsInNameofOperation()The operation is inside a nameof(...), where nothing is evaluated.
GetContainingMethod()The method that contains the operation.
InParameterOrder()The arguments of an invocation or an object creation sorted by the ordinal of their parameter, as named arguments keep the source order.

Syntax

MethodDescription
Parenthesize()Wraps an expression in parentheses, to use it as an operand or as the target of a member access in generated code.
HasAutoGeneratedComment()The node starts with an <auto-generated> comment.
GetLine(), GetEndLine(), GetLineSpan(), SpansMultipleLines()The lines of a node, a token or a trivia.
Diagnostic.FindNode()The syntax node at the location of a diagnostic, for a suppressor or a code fix.

When the project references a Roslyn workspaces package, Parenthesize annotates the parentheses with Simplifier.Annotation, so a code fix removes the ones the final document does not need. The trivia of the expression stays inside the parentheses.