Моему генератору исходного кода необходимо найти другие классы, производные от проверяемого класса, чтобы узнать, нужно ли добавлять специальный код. До сих пор я проверял только класс на предмет его расширения и следовал оттуда ссылкам, таким как тип свойств класса, но не любым другим классам, которые также существуют в проекте, который использует генератор исходного кода. Кажется, я не могу найти для этого никаких методов, интерфейсов или веб-документации. Возможно ли это вообще и как это будет работать?
Я ищу что-то вроде этого:
public static IEnumerable<string> GetDerivedTypes(this ITypeSymbol typeSymbol)
{
// TODO: Find all available classes,
// then I can proceed with inheritance checks and further tests
// Following is made-up code:
var derivedTypeNames = typeSymbol.ContainingAssembly.AllTypes
.Where(t => t.IsDerivedFrom(typeSymbol))
.Select(t => t.Name);
return derivedTypeNames;
}
Честно говоря, меня не волнует, можно ли это сделать эффективно. Теперь я знаю, что это можно сделать, и мне это нужно. Если вы можете предложить более эффективное решение, пожалуйста, сообщите нам об этом в ответе.
// Define your base class (e.g., Base)
var baseClassName = "Base";
// Create a SyntaxReceiver
var syntaxReceiver = new MySyntaxReceiver();
// Visit the syntax tree
syntaxReceiver.Visit(syntaxTree.GetRoot());
// Get the derived classes
var derivedClasses = syntaxReceiver.Classes
.Where(classDeclaration =>
classDeclaration.BaseList?.Types.Any(baseType =>
baseType.ToString() == baseClassName) ?? false)
.ToList();
или
// Create a semantic model
var semanticModel = compilation.GetSemanticModel(syntaxTree);
// Get the base class symbol
var baseClassSymbol = semanticModel.Compilation.GetTypeByMetadataName(baseClassName);
// Find derived types
var derivedTypes = semanticModel.Compilation.GetSymbolsWithName(
name => name == baseClassName,
SymbolFilter.Type)
.SelectMany(symbol => symbol.ContainingAssembly.GlobalNamespace.GetNamespaceMembers())
.SelectMany(namespaceSymbol => namespaceSymbol.GetTypeMembers())
.Where(typeSymbol => typeSymbol.BaseType?.Equals(baseClassSymbol) ?? false)
.ToList();
или Нахождение всех объявлений классов, а затем наследование от другого с помощью Рослин
Чтобы узнать подробности:
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
public class ClassSyntaxReceiver : ISyntaxReceiver
{
public List<ClassDeclarationSyntax> Classes { get; } = new List<ClassDeclarationSyntax>();
public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
{
if (syntaxNode is ClassDeclarationSyntax classDeclaration)
{
Classes.Add(classDeclaration);
}
}
}
public class MySourceGenerator : ISourceGenerator // to use in the source generator
{
public void Execute(GeneratorExecutionContext context)
{
var syntaxReceiver = (ClassSyntaxReceiver)context.SyntaxReceiver;
var classes = syntaxReceiver.Classes;
// Now you can process the list of classes as needed.
// For example, you can check if each class derives from a specific base class.
}
// Other methods and implementation details...
}
В методе Execute переберите собранные классы и проверьте, являются ли они производными от определенного базового класса (например, Base):
foreach (var classDeclaration in classes)
{
var semanticModel = context.Compilation.GetSemanticModel(classDeclaration.SyntaxTree);
var classSymbol = semanticModel.GetDeclaredSymbol(classDeclaration);
if (classSymbol.BaseType?.Name == "Base")
{
// This class derives from the specified base class.
// Add your custom logic here.
}
}
SyntaxReceiver Roslyn — получение классов, реализующих интерфейс
Откуда мне взять compilation
и syntaxTree
? Или MySyntaxReceiver
? Я в этом пейзаже: github.com/dotnet/roslyn/blob/main/docs/features/…
ISourceGenerator
кажется устаревшим, я использую IIncrementalGenerator
, у которого нет Execute
метода. В общем, к сожалению, ваш ответ не помог мне решить проблему.
Хорошо, я посмотрю на это завтра.
Извините, нет необходимости. Я попробовал еще кое-что и нашел рабочее решение для symbol.ContainingAssembly.GlobalNamespace.GetNamespaceMembers()
и .GetTypeMembers()
, которые вы упомянули. Требуется гораздо больше работы, чем просто вызвать им один раз, но здесь это правильная точка входа. Завтра опубликую свое решение.
Я узнал, как это работает. В ответе Робора где-то была спрятана подсказка. Я мог бы построить решение описанной проблемы вокруг этой линии, после большего количества проб и ошибок.
public static string [] GetDerivedTypes(this ITypeSymbol typeSymbol)
{
var derivedTypes = GetAllTypes(typeSymbol.ContainingAssembly.GlobalNamespace)
.Where(t => IsDerivedFrom(t, typeSymbol));
return derivedTypes
.Select(dt => dt.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat))
.ToArray();
static IEnumerable<INamedTypeSymbol> GetAllTypes(INamespaceSymbol nsSymbol) =>
nsSymbol.GetTypeMembers()
.Concat(nsSymbol.GetNamespaceMembers().SelectMany(GetAllTypes));
static bool IsDerivedFrom(ITypeSymbol typeSymbol, ITypeSymbol baseTypeSymbol) =>
typeSymbol.BaseType != null &&
(typeSymbol.BaseType.Equals(baseTypeSymbol, SymbolEqualityComparer.Default) ||
IsDerivedFrom(typeSymbol.BaseType, baseTypeSymbol));
}
Локальная вспомогательная функция GetAllTypes
рекурсивно находит все типы во всех пространствах имен сборки. Это не позволит найти производные типы в других сборках, я могу смириться с этим ограничением. Эффективно ли это? Я не могу сказать.
Другая локальная вспомогательная функция IsDerivedFrom
затем определяет, является ли тип производным от базового типа, возможно, транзитивно. Это также делается рекурсивным способом. Он используется для фильтрации набора всех типов в сборке, чтобы оставались только производные указанного типа.
Здесь я черпал вдохновение из функционального программирования и считаю, что оно отлично подходит.
Это невозможно сделать эффективно, и я думаю, что этого не следует делать.