Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using Roslyn for C#, how do I get a list of all properties that compose a return type?

Tags:

c#

roslyn

Let's say that I have queried a single method from a collection of methods:

var myMethod = someListofMethods.FirstOrDefault(m => m.Identifier.ValueText == myMethodName);

Now I want to take the method's return type. . .

var returnType = myMethod.ReturnType;

. . .and determine (if it is not a primitive) what properties are contained within that type.

So, for example let's say the return type is FooObject which is defined:

public class FooObject{
     public string Fizz {get; set; }
     public string Buzz {get; set; }
}

How do I properly interrogate FooObject for a list of it's properties?

Here is what I have already tried:

returnType.DescendantNodes().OfType<PropertyDeclarationSyntax>();

But this didn't work. Thanks in advance.

like image 966
Matt Cashatt Avatar asked Jan 23 '14 18:01

Matt Cashatt


Video Answer


2 Answers

You are looking at the abstract syntax tree (AST) level of code. Hence line:

returnType.DescendantNodes().OfType<PropertyDeclarationSyntax>();

returns nothing. returnType in this context is IdentifierNameSyntax node of the AST, just containing text FooObject. If you want to analyze return type, you should:

  • interpret syntax tree from returnType point of view to find full namespace of the return type
  • search trough code to find this type declaration
  • analyze type declaration syntax tree to find all its properties

But, it is in fact what compiler does so you can go level up with Roslyn usage to the compilation level, for example:

var workspace = Workspace.LoadSolution(solutionName);
var solution = workspace.CurrentSolution;

var createCommandList = new List<ISymbol>();
var @class = solution.Projects.Select(s => s.GetCompilation()
                                            .GetTypeByMetadataName(className))
                              .FirstOrDefault();
var method = @class.GetMembers(methodName)
                    .AsList()
                    .Where(s => s.Kind == CommonSymbolKind.Method)
                    .Cast<MethodSymbol>()
                    .FirstOrDefault();
var returnType = method.ReturnType as TypeSymbol;
var returnTypeProperties = returnType.GetMembers()
                                     .AsList()
                                     .Where(s => s.Kind == SymbolKind.Property)
                                     .Select(s => s.Name);
like image 99
Konrad Kokosa Avatar answered Oct 08 '22 23:10

Konrad Kokosa


Create the below class CsharpClass.cs:

public class CsharpClass
{
    public string Name { get; set; }

    public string Namespace { get; set; }

    public List<CsharpProperty> Properties { get; set; }

    public string PrimaryKeyType { get; set; }

    public class CsharpProperty
    {
        public string Name { get; set; }
        public string Type { get; set; }

        public CsharpProperty(string name, string type)
        {
            Name = name;
            Type = type;
        }
    }

    public CsharpClass()
    {
        Properties = new List<CsharpProperty>();
    }
}

Create the below helper class CsharpClassParser.cs:

public static class CsharpClassParser
{
    public static CsharpClass Parse(string content)
    {
        var cls = new CsharpClass();
        var tree = CSharpSyntaxTree.ParseText(content);
        var members = tree.GetRoot().DescendantNodes().OfType<MemberDeclarationSyntax>();

        foreach (var member in members)
        {
            if (member is PropertyDeclarationSyntax property)
            {
                cls.Properties.Add(new CsharpClass.CsharpProperty(
                     property.Identifier.ValueText,
                     property.Type.ToString())
                 );
            }

            if (member is NamespaceDeclarationSyntax namespaceDeclaration)
            {
                cls.Namespace = namespaceDeclaration.Name.ToString();
            }

            if (member is ClassDeclarationSyntax classDeclaration)
            {
                cls.Name = classDeclaration.Identifier.ValueText;

                cls.PrimaryKeyType = FindPrimaryKeyType(classDeclaration);
            }

            //if (member is MethodDeclarationSyntax method)
            //{
            //    Console.WriteLine("Method: " + method.Identifier.ValueText);
            //}
        }


        return cls;
    }

    private static string FindPrimaryKeyType(ClassDeclarationSyntax classDeclaration)
    {
        if (classDeclaration == null)
        {
            return null;
        }

        if (classDeclaration.BaseList == null)
        {
            return null;
        }

        foreach (var baseClass in classDeclaration.BaseList.Types)
        {
            var match = Regex.Match(baseClass.Type.ToString(), @"<(.*?)>");
            if (match.Success)
            {
                var primaryKey = match.Groups[1].Value;

                if (AppConsts.PrimaryKeyTypes.Any(x => x.Value == primaryKey))
                {
                    return primaryKey;
                }
            }
        }

        return null;
    }
}

How to use?

Read the content of the class as string and pass it to CsharpClassParser.Parse()

const string content = @"
namespace Acme.Airlines.AirCraft
{
    public class AirCraft
    {
        public virtual string Name { get; set; }

        public virtual int Code { get; set; }

        public AirCraft()
        {

        }
    }
}";

var csharpClass = CsharpClassParser.Parse(content);

Console.WriteLine(csharpClass.Name);
Console.WriteLine(csharpClass.Namespace);
Console.WriteLine(csharpClass.Properties.Count);
like image 37
Alper Ebicoglu Avatar answered Oct 08 '22 23:10

Alper Ebicoglu