Search code examples
c#stringvariablesreadfile

Get all variables from .cs file


I want to get all the variables from one CS file. The CS file will be opened by my app and its code will be read into string. After this i want to get all the variables (including names) into an array or list. This is needed to find these variables afterwards in the same code and replace their names with the MD5 hashed values. This article didnt really help me, because you cant get the variables from the "stringed" code. I know, that sounds strange, but i really need help


Solution

  • This should help you get started using Roslyn's Syntax API and C# built-in analyzer. You will need to add the "Microsoft.CodeAnalysis.CSharp" nuget package to your project.

    Suppose you have a source file you have parsed into a string that looks like this:

    public class Person
    {
        public string Name { get; set; }
        internal string Age;
        int _val;
        public void DoSomething(string x) { int y = 1; var z = 2; }
    }
    

    You can write some code like this:

    var parser = new ClassParser(stringOfCode);
    var members = parser.GetMembers();
    foreach (var m in members)
        Console.WriteLine(m);
    

    To output text like this showing you the text and locations (start and length) of all your defined properties and fields of the class so that you can replace the names as you want:

    Name: Name (36, 4), Type: string (29, 6), Declaration: public string Name { get; set; } (22, 32)
    Name: Age (71, 3), Type: string (64, 6), Declaration: internal string Age; (55, 20)
    Name: _val (80, 4), Type: int (76, 3), Declaration: int _val; (76, 9)
    

    To simplify the logic, you can start with defining some classes to model the results and override the ToString() for output:

    public class Member
    {
        public string KindText { get; set; }
        public MemberToken NameToken { get; set; }
        public MemberToken TypeToken { get; set; }
        public MemberToken DeclarationToken { get; set; }
        public override string ToString() => $"Name: {this.NameToken}, Type: {this.TypeToken}, Declaration: {this.DeclarationToken}";
    }
    
    public class MemberToken
    {
        public string Name { get; set; }
        public int Start { get; set; }
        public int Length { get; set; }
    
        public MemberToken(string code, TextSpan span)
        {
            this.Name = code.Substring(span.Start, span.Length);
            this.Start = span.Start;
            this.Length = span.Length;
        }
        public override string ToString() => $"{this.Name} ({this.Start}, {this.Length})";
    }
    

    Then you can write a CSharpSyntaxWalker to visit the nodes you care about (such as Field and Property declarations), and capture those into lists:

    public class MemberCollector : CSharpSyntaxWalker
    { 
        public List<FieldDeclarationSyntax> Fields { get; } = new List<FieldDeclarationSyntax>();
        public List<PropertyDeclarationSyntax> Properties { get; } = new List<PropertyDeclarationSyntax>();
        public List<LocalDeclarationStatementSyntax> Variables { get; } = new List<LocalDeclarationStatementSyntax>();
    
        public override void VisitFieldDeclaration(FieldDeclarationSyntax node) => this.Fields.Add(node);
        public override void VisitPropertyDeclaration(PropertyDeclarationSyntax node) => this.Properties.Add(node);
        public override void VisitLocalDeclarationStatement(LocalDeclarationStatementSyntax node) => this.Variables.Add(node);
    }
    

    Finally, you just need write your class parser to initialize the syntax tree, and then analyze the syntax tree to identify the members.

    public class ClassParser
    {
        public string Code { get; set; }
        public SyntaxNode Root { get; set; }
    
        public ClassParser(string code)
        {
            this.Code = code;
            var tree = CSharpSyntaxTree.ParseText(code);
            this.Root = tree.GetCompilationUnitRoot();
        }
    
        public List<Member> GetMembers()
        {
            var collector = new MemberCollector();
            collector.Visit(this.Root);
            var results = new List<Member>();
            results.AddRange(collector.Properties.Select(p => new Member()
            {
                KindText = p.Kind().ToString(),
                DeclarationToken = new MemberToken(this.Code, p.Span),
                NameToken = new MemberToken(this.Code, p.Identifier.Span),
                TypeToken = new MemberToken(this.Code, p.Type.Span),
            }));
            results.AddRange(collector.Fields.SelectMany(f => f.Declaration.Variables.Select(v => new Member()
            {
                KindText = f.Kind().ToString(),
                DeclarationToken = new MemberToken(this.Code, f.Span),
                TypeToken = new MemberToken(this.Code, f.Declaration.Type.Span),
                NameToken = new MemberToken(this.Code, v.Span),
            })));
            results.AddRange(collector.Variables.SelectMany(f => f.Declaration.Variables.Select(v => new Member()
            {
                KindText = f.Kind().ToString(),
                DeclarationToken = new MemberToken(this.Code, f.Span),
                TypeToken = new MemberToken(this.Code, f.Declaration.Type.Span),
                NameToken = new MemberToken(this.Code, v.Span),
            })));
            return results;
        }
    }