Почему этот тип не может быть выведен для этого общего метода Clamp?

Я пишу класс, представляющий светодиод. В основном 3 uint значения для r, g и b в диапазоне от 0 до 255.

Я новичок в С# и начал с uint 1 который больше 8 бит, который я хочу. Прежде чем писать свой собственный метод Clamp, я искал один онлайн-сайт и нашел этот замечательный ответ, предлагающий метод расширения. Проблема заключается в том, что он не может вывести тип uint. Почему это? Этот код имеет uint, написанный во всем этом. Я должен явно указать тип, чтобы он работал.

class Led
{
    private uint _r = 0, _g = 0, _b = 0;

    public uint R
    {
        get
        {
            return _r;
        }
        set
        {
            _r = value.Clamp(0, 255); // nope

            _r = value.Clamp<uint>(0, 255); // works
        }
    }
}

// /info/89561/where-can-i-find-the-clamp-function-in-net/581581#581581
static class Clamp
{
    public static T Clamp<T>(this T val, T min, T max) where T : IComparable<T>
    {
        if (val.CompareTo(min) < 0) return min;
        else if (val.CompareTo(max) > 0) return max;
        else return val;
    }
}

1 ошибка, используя byte - это путь, конечно. Но меня все еще интересует ответ на вопрос.

Ответы

Ответ 1

Это потому, что вы используете 0 и 255, которые являются значениями int, а не uint. Базовые целочисленные числа в С# всегда считаются значениями int (если они соответствуют диапазону int).

Вы вызываете Clamp с формой uint.Clamp(int, int) => uint. Это преобразуется компилятором в Clamp(unit, int, int) => uint. Компилятор действительно ожидает Clamp(T, T, T) => T, поэтому он сообщает об ошибке, поскольку смесь типов uint и int не позволяет решить, какой тип T должен принять.

Измените строку:

_r = value.Clamp(0, 255);

в

_r = value.Clamp(0U, 255U);

и код будет скомпилирован. Суффикс U сообщает компилятору, что это число uint.

Ответ 2

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

Обычно в С# тип целочисленного литерала int, но он может быть неявно преобразован в любой числовой тип, в котором константа находится в диапазоне. Таким образом, хотя int неявно конвертируется в uint, назначение myuint = 123; является законным, поскольку int подходит.

Из этого факта легко ошибиться в неверном убеждении, что int литералы могут использоваться везде, где ожидается uint, но вы обнаружили, почему это убеждение ложно.

Алгоритм вывода типа выглядит следующим образом. (Это, конечно, массовое упрощение, лямбды делают это значительно сложнее.)

  • Вычислить типы аргументов
  • Анализ отношений между аргументами и соответствующими формальными параметрами
  • Из этого анализа выведите ограничения типа на параметры типового типа
  • Проверяем границы для обеих полноты - каждый родовой параметр типа должен иметь границы границ границ и границы консистенции, не должен быть противоречивым. Если вывод неполный или несовместимый, метод неприменим.
  • Если выведенные типы нарушают их ограничения, метод неприменим.
  • В противном случае метод с выводимыми типами добавляется к набору методов, используемых для разрешения перегрузки.

Разрешение перегрузки затем переходит к методам сравнения в кандидате, установленном друг против друга, чтобы найти лучшее.

(Обратите внимание, что тип возврата, конечно, нигде не рассматривается, С# проверяет, может ли тип возврата быть назначен тому, на что он назначен, после того, как разрешение перегрузки выбрало метод, а не во время разрешения перегрузки.)

В вашем случае вывод типа не выполняется на шаге "Убедитесь, что существует согласованный набор границ". T ограничивается как int, так и uint. Это противоречие, поэтому метод никогда не добавляется к набору методов для разрешения перегрузки. Тот факт, что аргументы int преобразуются в uint, никогда не рассматривается; механизм вывода типа работает исключительно по типам.

Алгоритм вывода типа также не "отступает" каким-либо образом в вашем сценарии; он не говорит "ОК, я не могу вывести согласованный тип для T, но, возможно, работает один из отдельных типов. Что делать, если я пробовал обе границы int и uint? Мы можем видеть, на самом деле производят метод, который работает". (Он делает что-то подобное тому, что связано с lambdas, что может привести к тому, что в некоторых сценариях он может произвольно использовать множество возможных комбинаций типов.) Если бы алгоритм вывода работал таким образом, вы бы получили желаемый результат, но он не.

В основном философия здесь заключается в том, что алгоритм вывода типа не ищет способа заставить программу работать, а скорее находит цепочку рассуждений о типах, которая выводит уникальный логический вывод из информации, полученной из аргументов. С# пытается сделать то, что пользователь хочет сделать, но также пытается избежать угадывания; в этом случае, а не потенциально угадывать неправильно, это требует, чтобы вы были понятны о типах, которые вы намерены сделать.

Ответ 3

Вы вызываете Clamp<T>(T, T, T) с аргументами uint, int, int (как 0 и 255 являются int литералами).

Поскольку нет неявного преобразования от одного типа к другому, компилятор не может решить, нужно ли делать T int или uint.

Ответ 4

Когда целочисленный литерал не имеет суффикса, его тип является первым из этих типов, в котором его значение может быть представлено: int, uint, long, ulong. Вы используете 0 и 255, которые хорошо вписываются в int, чтобы выбрать один из них.

Вы можете сказать компилятору использовать uint просто путем суффикса литерального

_r = value.Clamp(0U, 255U);

Более подробную информацию можно найти в документации.