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
-
5*Why* are you trying to get variables from a .cs file? This smells strongly of an XY problem. – Aug 09 '19 at 13:56
-
I imagine there's a way to do it using the [Roslyn Syntax API](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/get-started/syntax-analysis) – Diado Aug 09 '19 at 13:57
-
Thanks for suggestion! I will learn more about it. Any more ideas? – XazkerBoy Aug 09 '19 at 14:00
-
Just added walkthrough below. You may have to add a few more scenarios based on your requirements, but at least it should get you started with Roslyn. – Jason W Aug 09 '19 at 15:52
1 Answers
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;
}
}

- 13,026
- 3
- 31
- 62
-
This code works great, thanks a lot! I thought, nobody would help... But there is one problem: the code sees not all variables. E.g in this code void dosomething() { Random rand = new Random(); string pass = RandomString(rand.Next(5, 15)); byte[] app = File.ReadAllBytes(openFileDialog1.FileName); int cnt = 0; } it sees no variables at all – XazkerBoy Aug 09 '19 at 17:46
-
From the question, i wasn't sure if you wanted to see just fields/properties at class level or if you needed variables within methods. You can modify the collector to override other visit methods. There is one specific for local level variable declarations. – Jason W Aug 09 '19 at 18:04
-
Mr. Jason, im sorry to ask you that, but can you somehow help me to also get variables within methods? Or at least give a tip, how to do this. I have googled for a long time and did not find what i need. I would be very thankful! – XazkerBoy Aug 10 '19 at 07:01
-
I added "Variables" list to the `MemberCollector`, and then implemented the override of `VisitLocalDeclarationStatement` to get notified of all local variable declarations. Finally, I modified the `GetMembers` method to populate the collected local variables from the Variables list in the `MemberCollector`. – Jason W Aug 10 '19 at 14:45