
У JetBrains есть фреймворк JetBrains.Annotations для .NET, который предоставляет набор полезных атрибутов. Они выступают дополнительными метаданными как для самих разработчиков, так и для статического анализатора JB, который включён в их IDE и ReSharper.
JetBrains.Annotations доступен в nuget, но может ограниченно работать вне продуктов JetBrains. Тем не менее в System.Diagnostics.CodeAnalysis тоже есть набор стандартных полезных и похожих атрибутов.
В первую очередь, атрибуты позволяют лучше понимать как намерения автора, так логику и семантику кода, а не только его синтаксис. Это делает листинг проще (если не переборщить с обилием атрибутов) и позволяет анализатору максимально своевременно предупредить разработчика или дать ему подсказку.
В текущих реалиях у этих атрибутов появилось ещё одно полезное свойство – они учитываются нейросетью и помогают ей быстрее ориентироваться в проекте. И, как мне показалось, автокомплит тоже становится более точным (про инструменты, доступные в Rider, делал отдельную, ещё не сильно устаревшую, подборку).

Важно отметить, что атрибуты — это лишь мета-информация для анализатора, которая не влияет на runtime.
Например, использование атрибута [NotNull] для аргумента метода не добавляет автоматической проверки. В метод всё ещё можно будет передать null. И если в методе этот кейс не будет обработан, то случится NullReferenceException. Атрибут лишь подскажет IDE, что в этот метод не нужно позволять передавать null на этапе разработки.

Также важно отметить, что этот фреймворк развивается давно, а языки программирования не стоят на месте. Поэтому какие-то атрибуты для определённых версий языка могут оказаться избыточными. Так, например, появление nullable reference type в C# 8 позволяет выразить намерения по null-спецификациям средствами самого языка.
Про этот кейс, атрибуты от JB и MS, операторы ? и !, когда что и для чего использовать, можно подробнее почитать в этом лонгриде с Хабра.
Ещё один нюанс связан с тем, что для Unity-проектов атрибуты Jetbrains поддерживаются по умолчанию, но не в полном объёме. Так, например, не удастся применить [NonNegativeValue] или [ValueRange]. В теории, можно выкачать более актуальный Jetbrains.Annotations из nuget и добавить в проект. Но лично у меня не было необходимости этим заниматься.
Примеры атрибутов
Всем, кто работает с инструментами JB, рекомендую ознакомиться с полным списком атрибутов. А здесь оставлю наиболее часто встречаемые и работающие в Unity.
⚠️ Дисклеймер ⚠️
Далее будет сгенерированный код.
Особо чувствительным лучше остановиться здесь.
Код проверен, исправлен, дополнен комментариями.
// -------------------------------------------------------------------
// 1. Анализ на Null
// -------------------------------------------------------------------
// Возвращаемое значение может быть null.
[CanBeNull]
public User FindUser(int id)
{
return _users.TryGetValue(id, out var user) ? user : null;
}
// Параметр не может быть null.
public void LogMessage([NotNull] string message)
{
// Полезно для FailFast в runtime.
if (message == null) throw new ArgumentNullException(nameof(message));
Console.WriteLine(message);
}
// Коллекция не может быть null, но могут быть null её элементы.
[NotNull, ItemCanBeNull]
public List<string> GetUserNamesWithNulls()
{
return new List<string> { "Alice", null, "Bob" };
}
// Коллекция и её элементы не могут быть null.
public void ProcessUserNames([NotNull, ItemNotNull] IEnumerable<string> names)
{
foreach (var name in names)
Console.WriteLine(name.ToUpper());
}
// -------------------------------------------------------------------
// 2. Контракты Параметров
// -------------------------------------------------------------------
// Если 'obj' равен null, метод останавливает выполнение.
[ContractAnnotation("obj:null => halt")]
public void GuardNotNull(object obj)
{
if (obj == null)
throw new ArgumentNullException(nameof(obj));
}
// Если 's' равен null, метод вернет true.
[ContractAnnotation("s:null => true")]
public bool IsNullOrEmpty(string s)
{
return s == null || s.Length == 0;
}
// Если 'input' не null, результат тоже не null.
[ContractAnnotation("input:notnull => notnull")]
public string Decorate(string input)
{
return input == null ? null : $"--{input}--";
}
// IDE предлагает значения из списка констант.
public void FindControl([ValueProvider("UiConstants")] string id)
{
// ...
}
public static class UiConstants
{
public const string PanelId = "mainPanel";
public const string ButtonId = "okButton";
}
// -------------------------------------------------------------------
// 3. Поведение Методов
// -------------------------------------------------------------------
// Метод не имеет побочных эффектов.
// Его вызов без использования результата бессмыслен.
[Pure]
public int CalculateSum(int a, int b)
{
return a + b;
}
// Метод может иметь побочные эффекты.
// Но его вызов без использования результата бессмыслен.
[MustUseReturnValue]
public string GetAndRemoveFirst(Queue<string> queue)
{
return queue.Dequeue(); // побочный эффект: меняет очередь
}
// Помечают метод как Assert-метод для параметров.
[AssertionMethod]
public void MyAssert(
[AssertionCondition(AssertionConditionType.IS_TRUE)] bool condition)
{
if (!condition)
throw new InvalidOperationException("Assertion failed");
}
// Метод безусловно завершает программу.
// Устаревший. Аналог: [ContractAnnotation("=> halt")]
[TerminatesProgram]
public void FatalError(string msg)
{
Console.Error.WriteLine(msg);
Environment.Exit(1);
}
// Вызывающий код обязан вызвать Dispose.
[MustDisposeResource]
public System.IO.FileStream OpenFile(string path)
{
return new System.IO.FileStream(path, System.IO.FileMode.Open);
}
// -------------------------------------------------------------------
// 4. Работа со Строками
// -------------------------------------------------------------------
// 'format' - это строка формата.
[StringFormatMethod("format")]
public void Log(string format, params object[] args)
{
Console.WriteLine(string.Format(format, args));
}
// Строка является паттерном регулярного выражения.
public void FindMatches([RegexPattern] string regexPattern, string text)
{
var matches = Regex.Matches(text, regexPattern);
Console.WriteLine($"Found {matches.Count}");
}
// Строка является ссылкой на путь (файл/папка).
// IDE включит проверку пути и автодополнение.
public void LoadConfig([PathReference] string configPath)
{
// ...
}
// Строка должна быть локализована.
public void SetWindowTitle([LocalizationRequired] string title)
{
Console.Title = title;
}
// Параметр должен быть именем одного из параметров вызывающего метода.
public void GuardNotNull(object arg, [InvokerParameterName] string paramName)
{
if (arg == null)
throw new ArgumentNullException(nameof(paramName));
}
// -------------------------------------------------------------------
// 5. Управление Коллекциями
// -------------------------------------------------------------------
// Как метод, конструктор или свойство влияет на коллекцию внутри.
// - None: никак не влияет;
// - Read: только читает;
// - ModifyExistingContent: изменяет элементы коллекции;
// - UpdatedContent: изменяет состав коллекции.
[CollectionAccess(CollectionAccessType.Read)]
public void ReadOnlyAccess(IReadOnlyList<int> list)
{
foreach (var item in list)
Console.WriteLine(item);
}
// IEnumerable не будет перечислен (MoveNext, foreach, Linq, ...)
public void CheckCountFast([NoEnumeration] IEnumerable items)
{
if (items is ICollection collection) // без перечисления
Console.WriteLine(collection.Count);
}
// IEnumerable будет обработан немедленно (NoLazy).
public void ProcessItemsImmediately([InstantHandle] IEnumerable items)
{
foreach (var item in items)
{
// ...
}
}
// Метод является частью цеопчки Linq-вызовов.
[LinqTunnel]
public static T AndLog<T>(this T obj)
{
Console.WriteLine(obj);
return obj;
}
// -------------------------------------------------------------------
// 6. Контроль использования
// -------------------------------------------------------------------
// Подавляет "unused" при неявном использовании
// (рефлексия, DI, сериализация, ...)
[UsedImplicitly]
public class MyApiController
{
// ...
}
// Помечает кастомный атрибут как [UsedImplicitly].
[MeansImplicitUse(ImplicitUseTargetFlags.WithMembers)]
[AttributeUsage(AttributeTargets.Class)]
public class CustomAttribute : Attribute
{
// ...
}
// Класс или его методы являются публичным API.
// Подавляет "unsed".
[PublicAPI]
public class MyLibraryFacade
{
// ...
}
DmitrySharov
Спасибо за список атрибутов.
Может быть есть ещё атрибут, который будет работать с JB библиотекой lifetimes.
А именно интересует как можно пометить ISource<T?> Чтобы ide подсказала, что может прийти null?