Почему этот тип не может быть выведен для этого общего метода 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);
Более подробную информацию можно найти в документации.