Иерархия наследования интерфейсов

213
12 декабря 2017, 16:56

Немного не понимаю, например есть

public interface IEnumerable<out T> : IEnumerable
public interface ICollection<T> : IEnumerable<T>, IEnumerable

Разве во втором случае обязательно указывать IEnumerable, если сам IEnumerable<T> уже наследует от него?

В чем выгода?

Или это для красоты так написали на msdn?

Даже сам поигрался, что так и что так класс, который реализует производный интерфейс должен реализовать методы всех интерфейсов.

Answer 1

Окей, как выяснилось в комментариях, базовые интерфейсы необязательно включать в определение. Но можно написать анализатор на Roslyn, который это будет уметь делать.

Вооружимся этими двумя статьями: C# and Visual Basic - Use Roslyn to Write a Live Code Analyzer for Your API и C# - Adding a Code Fix to Your Roslyn Analyzer, и напишем собственный анализатор.

Начнём с Visual Studio 2017, и установим нужные пакеты: VS extension development и .NET compiler platform SDK. Мне пришлось ещё почему-то установить .NET Core.

Теперь, создаём новый проект:

Мы получим два сгенерированных класса: InterfaceFlatteningAnalyzer и InterfaceFlatteningCodeFixProvider. Первый отвечает за появление зелёных предупреждений, а второй — за преобразование кода. У нас предупреждение будет появляться, когда базовые интерфейсы не упомянуты у класса (я возьму для примера только базовые интерфейсы интерфейсов нашего класса, но не его базового класса).

Я убрал из проекта локализацию, чтобы не усложнять себе жизнь. Заодно удалил .resx-файл.

В качестве анализатора пишем следующее:

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public class InterfaceFlatteningAnalyzer : DiagnosticAnalyzer
{
    // эту всю обвязку даёт шаблон Visual Studio, нам нужно только поправить строки
    // константы для определения правила (я убрал локализацию, она не нужна):
    // код правила
    public const string DiagnosticId = "InterfaceFlattening";
    // название правила
    static readonly string Title = "Interface flattening";
    // формат сообщения об ошибке
    static readonly string MessageFormat = "Base interfaces of {0} not listed";
    // Описание проблемы для пользователя
    static readonly string Description =
            "Base interfaces must be listed along with child interfaces";
    // категория
    const string Category = "Interface design";
    // создаём правило на основе этого всего
    private static DiagnosticDescriptor Rule =
        new DiagnosticDescriptor(
                DiagnosticId, Title, MessageFormat, Category,
                DiagnosticSeverity.Warning,
                isEnabledByDefault: true,
                description: Description);
    // поддерживаемые этим модулем правила
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics =>
        ImmutableArray.Create(Rule);
    // при инициализации говорим, что мы хотим анализировать все символы,
    // являющиеся именованными типами, при помощи метода AnalyzeSymbol
    public override void Initialize(AnalysisContext context) =>
        context.RegisterSymbolAction(AnalyzeSymbol, SymbolKind.NamedType);
    // а вот процедура анализа
    static void AnalyzeSymbol(SymbolAnalysisContext context)
    {
        // пусть у нас определение класса такое:
        //     class P : IEnumerable<string>
        // получим символ, который анализируется (P)
        var namedTypeSymbol = (INamedTypeSymbol)context.Symbol;
        // его интерфейсы (IEnumerable<string>)
        var declaredInterfaces = namedTypeSymbol.Interfaces;
        // теперь список их дочерних интерфейсов (IEnumerable)
        var childInterfaces = declaredInterfaces.SelectMany(i => i.AllInterfaces);
        // выбрасываем те, которые уже задекларированы
        var undeclaredInterfaces = childInterfaces.Except(declaredInterfaces).ToList();
        // если что-то недодекларировано,
        if (undeclaredInterfaces.Any())
        {
            // создаём диагностическое сообщение и выдаём его
            var diagnostic = Diagnostic.Create(
                Rule, namedTypeSymbol.Locations[0], namedTypeSymbol.Name);
            context.ReportDiagnostic(diagnostic);
        }
    }
}

Теперь нам нужно ещё сделать функцию добавления интерфейсов, а то покамест мы только ругаемся на юзера, но не помогаем ему.

Для этого у нас есть второй класс — InterfaceFlatteningCodeFixProvider.

Пишем.

[ExportCodeFixProvider(LanguageNames.CSharp,
     Name = nameof(InterfaceFlatteningCodeFixProvider)), Shared]
public class InterfaceFlatteningCodeFixProvider : CodeFixProvider
{
    // заголовок нашего исправления
    const string title = "Flatten child interfaces";
    // коды правил, для нарушений которых мы предлагаем исправления
    public sealed override ImmutableArray<string> FixableDiagnosticIds =>
        ImmutableArray.Create(InterfaceFlatteningAnalyzer.DiagnosticId);
    // сообщаем, что мы умеем исправить все проблемы в документе одним махом
    public sealed override FixAllProvider GetFixAllProvider() =>
        WellKnownFixAllProviders.BatchFixer;
    // это будет вызываться, когда Visual Studio будет собирать информацию
    // о доступных исправлениях
    public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        // получаем синтаксическое дерево
        var root = await context.Document
                .GetSyntaxRootAsync(context.CancellationToken)
                .ConfigureAwait(false);
        // тут наша диагностика. на каждый тип мы выдаём только одну, поэтому First
        var diagnostic = context.Diagnostics.First();
        // её положение в тексте
        var diagnosticSpan = diagnostic.Location.SourceSpan;
        // ищем объявление типа, которое лежит в том месте, где сработала диагностика
        var declaration =
                root.FindToken(diagnosticSpan.Start)
                    .Parent
                    .AncestorsAndSelf()
                    .OfType<TypeDeclarationSyntax>()
                    .First();
        // сообщаем, что у нас доступно исправление
        context.RegisterCodeFix(
            CodeAction.Create(
                title: title,
                createChangedDocument: c =>
                    FlattenInterfracesAsync(context.Document, declaration, c), 
                equivalenceKey: title),
            diagnostic);
    }
    // а это само исправление. оно сложное.
    async Task<Document> FlattenInterfracesAsync(
        Document document,
        TypeDeclarationSyntax typeDecl,
        CancellationToken ct)
    {
        // получим символ, представляющий провинившийся тип
        var semanticModel = await document.GetSemanticModelAsync(ct);
        var typeSymbol = semanticModel.GetDeclaredSymbol(typeDecl, ct);
        // уже известная логика: получаем недостающие интерфейсы
        var declaredInterfaces = typeSymbol.Interfaces;
        var undeclaredInterfaces =
                declaredInterfaces.SelectMany(i => i.AllInterfaces)
                                  .Except(declaredInterfaces)
                                  .ToList();
        // если их нет (как так может быть?), возвращаем нетронутый документ
        if (!undeclaredInterfaces.Any())
            return document;
        // окей, нам нужно добавить интерфейсы в список.
        // получим список интерфейсов:
        var baseListExpr = typeDecl.BaseList;
        // интерфейсы, которые надо добавить, сконвертируем в
        // синтаксические узлы определения базовых интерфейсов
        // и навесим флаг для упрощения выражения
        var additionalTypeNames =
            undeclaredInterfaces
                .Select(i => SyntaxFactory.ParseTypeName(i.ToDisplayString()))
                .Select(s => SyntaxFactory.SimpleBaseType(s)
                                  .WithAdditionalAnnotations(Simplifier.Annotation))
                .ToArray();
        // добавим типы в список базовых типов, получим новый список базовых типов
        // перенесём пробелы, концы строк и тому подобное со старого списка
        var newBaseListExpr = baseListExpr.AddTypes(additionalTypeNames)
                                          .WithTriviaFrom(baseListExpr);
        // теперь заменим в документе старый список на новый
        var root = await document.GetSyntaxRootAsync(ct);
        var modifiedDocument =
            document.WithSyntaxRoot(root.ReplaceNode(baseListExpr, newBaseListExpr));
        // упростим определения типов, чтобы было не System.Collections.IEnumerable,
        // а просто IEnumerable
        // упрощение пройдёт в тех местах, где навешен флаг
        var simplifiedDocument = await Simplifier.ReduceAsync(modifiedDocument, null, ct);
        // готово!
        return simplifiedDocument;
    }
}

Результат:

READ ALSO
Поворот коллайдера в CharacterConroller

Поворот коллайдера в CharacterConroller

Когда я пытаюсь изменить поворот объекта, к которому прикреплен CharacterController, то коллайдер, который находится внутри этого компонента, не поворачивается...

210
Не работает удаление SQLite

Не работает удаление SQLite

хотя запрос Builder генерирует вполне нормальный:

197
Определить id тега, на который нажали в WebBrowser (c#)

Определить id тега, на который нажали в WebBrowser (c#)

Создаю приложение, которое использует компонент WebBrower (Среда VS)Необходимо при нажатии на документ(страницу) в WebBrowser получить id тега, который...

291
Переопределение ToString в собственном классе

Переопределение ToString в собственном классе

Есть класс PolynomВ нем многочлен задается массивом коэффициентов

217