3

I'm finding very little good documentation on Roslyn so forgive me if I missed something and this should be obvious. I'm parsing some code and my aim is to pull out every serializable field from a class.

What I have so far is this. First I parse the code file

            SyntaxTree tree = CSharpSyntaxTree.ParseText(programText);
            CSharpCompilation compilation = CSharpCompilation.Create("HelloWorld")
                                                             .AddReferences(MetadataReference
                                                                                .CreateFromFile(typeof(object)
                                                                                                .Assembly
                                                                                                .Location))
                                                             .AddSyntaxTrees(tree);

            CompilationUnitSyntax root = tree.GetCompilationUnitRoot();

Next, I get all the fields from each class

        foreach (MemberDeclarationSyntax member in root.Members)
        {
            if (member is ClassDeclarationSyntax classDeclarationSyntax)
            {

                foreach (MemberDeclarationSyntax rootGameMember in classDeclarationSyntax.Members)
                {
                    if (IsFieldSerializable(rootGameMember))
                    {
                    }
                }
            }
        }

aaaaand now I'm stuck. What I want to do with this field are a few things based on the Unity 3D rules for serialization

  • See if the type is public
  • See if the attribute [SerializeField] is applied
  • See if the type is int, float, string, double etc
  • See if the type, if its a class has the [Serializable] attribute

I think that's it. At the moment I'm trying to figure out the type of the field and getting lost in a rabbit warren of options of which none seem to give me what I want.

Real World
  • 1,593
  • 1
  • 21
  • 46

1 Answers1

2

The first thing you'll need to do is get the symbol representing your field, which you can do by using the SemanticModel class. I think this would help explain how you get the semantic model based on your example:

https://github.com/dotnet/roslyn/wiki/Getting-Started-C%23-Semantic-Analysis

TLDR: use compilation.GetSemanticModel()

This questions answers how to do this better than my original answer, so please look here:

How to get a Roslyn FieldSymbol from a FieldDeclarationSyntax node?

Now you have the semantic information about the field, let's look at each problem one at a time.

1) See if the type is public

This should be pretty simple, as the IFieldSymbol interface has a DeclaredAccessibility property which will tell you what you need to know!

if(fieldSymbol.DeclaredAccessibility != Accessibility.Public)
{
    // not interested in no-public members
}

2) See if the attribute [SerializeField] is applied

Use the GetAttributes() method of the IFieldSymbol interface. Then you can just query the attribute's AttributeClass like any other INamedTypeSymbol.

var attributes = fieldSymbol.GetAttributes();
if(!attributes.Any(attribute => attribute.AttributeClass.Name == "SerializeField"))
{
    // only interested in fields with the SerializeField attribute
}

3) See if the type is int, float, string, double etc

Luckily, Type is a property of the IFieldSymbol interface too, so we can just check that property of our field symbol. You can reference the Name property of the resulting ITypeSymbol, if you only care about simple types.

var fieldTypes = new List<string> { "int", "float", "string", "double" };

if(!fieldTypes.Contains(fieldSymbol.Type.Name))
{
    // only interested in certain types
}

(You may want to double check the type name strings in the debugger)

4) See if the type, if its a class has the [Serializable] attribute

From this I'm guessing you mean that the class has the [Serializable] attribute. For this you can use the same approach as 2, but you need the class symbol, so this will have to be done outside of your field checking function.

var classSymbol = model.GetSymbolInfo(classDeclarationSyntax).Symbol;
// remember to null check this
var attributes = classSymbol.GetAttributes();
if(attributes.Any(attribute => attribute.AttributeClass.Name == "Serializable"))
{
   // class is serializable
}

EDIT: This answer may also help you if you think checking attributes by name is a bit hacky!

Roslyn Check Type of an Attribute

IFieldSymbol:

https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.ifieldsymbol?view=roslyn-dotnet

INamedTypeSymbol:

https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.inamedtypesymbol?view=roslyn-dotnet

And I totally agree btw, good Roslyn documentation seems to be incredibly rare.

MattB
  • 159
  • 2
  • 8
  • Thanks so much for that detailed answer. That's a big help. Now I'm struggling getting the field symbol. Thats going to be one of two things I'm guessing. The type is `public List` so either the SemanticModel doesn't know about List or it doesn't know about MyClass. My codebase is parsed from numerous files so I guess I need to include them all in `CSharpSyntaxTree.ParseText` rather than just the first file. If the problem is List then I'm not sure how to resolve it. – Real World Aug 08 '19 at 10:27
  • You'll need the ```TypeArguments``` from the the list's symbol. Also ```GetDeclaredSymbol``` from the semantic model could be worth using.(https://learn.microsoft.com/en-us/dotnet/api/microsoft.codeanalysis.inamedtypesymbol.typearguments?view=roslyn-dotnet#Microsoft_CodeAnalysis_INamedTypeSymbol_TypeArguments) – MattB Aug 08 '19 at 10:37