CA1036: переопределяйте методы в сравнимых типах

Свойство Значение
Идентификатор правила CA1036
Заголовок Переопределите методы в сопоставимых типах
Категория Проектирование
Исправление является критическим или не критическим Не критическое
Включен по умолчанию в .NET 8 No

Причина

Тип реализует интерфейс System.IComparable. Он не переопределяет System.Object.Equals и не перегружает языковые операторы "равно", "не равно", "меньше" и "больше". Правило не сообщает о нарушении, если тип наследует только реализацию интерфейса.

По умолчанию это правило проверяет только видимые извне типы, но это поведение можно настроить.

Описание правила

Типы, определяющие пользовательский порядок сортировки, реализуют интерфейс IComparable. Метод CompareTo возвращает целочисленное значение, указывающее правильный порядок сортировки для двух экземпляров типа. Это правило обнаруживает типы, которые задают порядок сортировки. Установка порядка сортировки подразумевает, что обычные значения "равно", "не равно", "меньше" и "больше" не применяются. Когда вы предоставляете реализацию IComparable, обычно также необходимо переопределять Equals, чтобы возвращались значения, соответствующие CompareTo. Если вы переопределяете Equals и пишете код на языке, поддерживающем перегрузки операторов, следует также предоставить операторы, которые соответствуют Equals.

Устранение нарушений

Чтобы устранить нарушение этого правила, переопределите метод Equals. Если ваш язык программирования поддерживает перегрузку операторов, укажите следующие операторы:

  • op_Equality
  • op_Inequality
  • op_LessThan
  • op_GreaterThan

В C# для представления этих операторов используются следующие токены:

==
!=
<
>

Когда лучше отключить предупреждения

Можно спокойно отключить предупреждение из правила CA1036, если нарушение вызвано отсутствующими операторами, а язык программирования не поддерживает перегрузку операторов, как в случае с Visual Basic. Если вы определите, что реализация операторов не имеет смысла в контексте вашего приложения, можно также безопасно отключить предупреждение из этого правила, когда оно срабатывает для операторов равенства, отличных от op_Equality. Однако следует всегда переопределять op_Equality и оператор ==, если вы переопределяете Object.Equals.

Отключение предупреждений

Если вы просто хотите отключить одно нарушение, добавьте директивы препроцессора в исходный файл, чтобы отключить и повторно включить правило.

#pragma warning disable CA1036
// The code that's violating the rule is on this line.
#pragma warning restore CA1036

Чтобы отключить правило для файла, папки или проекта, задайте его серьезность none в файле конфигурации.

[*.{cs,vb}]
dotnet_diagnostic.CA1036.severity = none

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

Настройка кода для анализа

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

Этот параметр можно настроить только для этого правила, для всех правил, к которым он применяется, или для всех правил в этой категории (конструкторе), к которым она применяется. Дополнительные сведения см. в статье Параметры конфигурации правила качества кода.

Включение определенных контактных зон API

Вы можете настроить, для каких частей базы кода следует выполнять это правило в зависимости от их доступности. Например, чтобы указать, что правило должно выполняться только для закрытой контактной зоны API, добавьте следующую пару "ключ-значение" в файл EDITORCONFIG в своем проекте:

dotnet_code_quality.CAXXXX.api_surface = private, internal

Примеры

Следующий код содержит тип, который правильно реализует IComparable. В комментариях к коду определены методы, которые соответствуют различным правилам, связанным с Equals и интерфейсом IComparable.

// Valid ratings are between A and C.
// A is the highest rating; it is greater than any other valid rating.
// C is the lowest rating; it is less than any other valid rating.

public class RatingInformation : IComparable, IComparable<RatingInformation>
{
    public string Rating
    {
        get;
        private set;
    }

    public RatingInformation(string rating)
    {
        if (rating == null)
        {
            throw new ArgumentNullException("rating");
        }

        string v = rating.ToUpper(CultureInfo.InvariantCulture);
        if (v.Length != 1 || string.Compare(v, "C", StringComparison.Ordinal) > 0 || string.Compare(v, "A", StringComparison.Ordinal) < 0)
        {
            throw new ArgumentException("Invalid rating value was specified.", "rating");
        }

        Rating = v;
    }

    public int CompareTo(object? obj)
    {
        if (obj == null)
        {
            return 1;
        }

        if (obj is RatingInformation other)
        {
            return CompareTo(other);
        }

        throw new ArgumentException("A RatingInformation object is required for comparison.", "obj");
    }

    public int CompareTo(RatingInformation? other)
    {
        if (other is null)
        {
            return 1;
        }

        // Ratings compare opposite to normal string order, 
        // so reverse the value returned by String.CompareTo.
        return -string.Compare(this.Rating, other.Rating, StringComparison.OrdinalIgnoreCase);
    }

    public static int Compare(RatingInformation left, RatingInformation right)
    {
        if (object.ReferenceEquals(left, right))
        {
            return 0;
        }
        if (left is null)
        {
            return -1;
        }
        return left.CompareTo(right);
    }

    // Omitting Equals violates rule: OverrideMethodsOnComparableTypes.
    public override bool Equals(object? obj)
    {
        if (obj is RatingInformation other)
        {
            return this.CompareTo(other) == 0;
        }

        return false;
    }

    // Omitting getHashCode violates rule: OverrideGetHashCodeOnOverridingEquals.
    public override int GetHashCode()
    {
        char[] c = this.Rating.ToCharArray();
        return (int)c[0];
    }

    // Omitting any of the following operator overloads 
    // violates rule: OverrideMethodsOnComparableTypes.
    public static bool operator ==(RatingInformation left, RatingInformation right)
    {
        if (left is null)
        {
            return right is null;
        }
        return left.Equals(right);
    }
    public static bool operator !=(RatingInformation left, RatingInformation right)
    {
        return !(left == right);
    }
    public static bool operator <(RatingInformation left, RatingInformation right)
    {
        return (Compare(left, right) < 0);
    }
    public static bool operator >(RatingInformation left, RatingInformation right)
    {
        return (Compare(left, right) > 0);
    }
}

Следующий код приложения проверяет поведение реализации IComparable, показанной ранее.

public class Test
{
    public static void Main1036(string[] args)
    {
        if (args.Length < 2)
        {
            Console.WriteLine("usage - TestRatings  string 1 string2");
            return;
        }
        RatingInformation r1 = new RatingInformation(args[0]);
        RatingInformation r2 = new RatingInformation(args[1]);
        string answer;

        if (r1.CompareTo(r2) > 0)
            answer = "greater than";
        else if (r1.CompareTo(r2) < 0)
            answer = "less than";
        else
            answer = "equal to";

        Console.WriteLine("{0} is {1} {2}", r1.Rating, answer, r2.Rating);
    }
}

См. также