CA1036. Переопределите методы в сопоставимых типах

Элемент Значение
Идентификатор правила CA1036
Категория Оформление
Исправление является критическим или не критическим Не критическое

Причина

Тип реализует интерфейс 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.

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

Существует несколько способов скрыть предупреждения анализа кода, в том числе отключение правила для проекта, использование директивы препроцессора, чтобы отключить его для конкретной строки кода, или применение атрибута SuppressMessageAttribute. Дополнительные сведения см. в разделе Практическое руководство. Скрытие предупреждений анализа кода.

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

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

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

Включение определенных контактных зон 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;
        }

        RatingInformation other = obj as RatingInformation; // avoid double casting
        if (other == null)
        {
            throw new ArgumentException("A RatingInformation object is required for comparison.", "obj");
        }

        return CompareTo(other);
    }

    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)
    {
        RatingInformation other = obj as RatingInformation; //avoid double casting
        if (other is null)
        {
            return false;
        }
        return this.CompareTo(other) == 0;
    }

    // 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);
    }
}

См. также раздел