Немного не понимаю, например есть
public interface IEnumerable<out T> : IEnumerable
public interface ICollection<T> : IEnumerable<T>, IEnumerable
Разве во втором случае обязательно указывать IEnumerable
, если сам IEnumerable<T>
уже наследует от него?
В чем выгода?
Или это для красоты так написали на msdn?
Даже сам поигрался, что так и что так класс, который реализует производный интерфейс должен реализовать методы всех интерфейсов.
Окей, как выяснилось в комментариях, базовые интерфейсы необязательно включать в определение. Но можно написать анализатор на 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;
}
}
Результат:
Виртуальный выделенный сервер (VDS) становится отличным выбором
Когда я пытаюсь изменить поворот объекта, к которому прикреплен CharacterController, то коллайдер, который находится внутри этого компонента, не поворачивается...
Создаю приложение, которое использует компонент WebBrower (Среда VS)Необходимо при нажатии на документ(страницу) в WebBrowser получить id тега, который...
Есть класс PolynomВ нем многочлен задается массивом коэффициентов