Минимальный воспроизводимый код:
void main() {
print(foo<int>('1'));
print(foo<double>('1.0'));
print(foo<double>('1'));
print(foo<int>('1.0'));
}
T foo<T extends num>(String s) {
final res = num.parse(s);
return (T == double ? res.toDouble() : res.toInt()) as T;
}
Как видите, я вручную обрабатываю типы. Есть ли лучший способ сделать это?
Ну, я недостаточно умен. Я вернусь в библиотеку.
Интересно, в чем смысл этого упражнения? Вам все равно нужно включить параметр типа T
из foo<T>()
в исходный код, так почему бы не иметь отдельные функции для int
и double
?
К сожалению, если вам нужен класс, который обрабатывает как int
, так и double
, я думаю, вам придется прибегнуть к этому. Однако вместо этого я бы использовал return (T == double ? double.parse(s) : int.parse(s)) as T;
, чтобы foo<int>('3.9')
генерировал ошибку, а не усекался молча (если только это поведение вам не нужно).
@Ber Да, было бы лучше, если бы это был вариант. Однако вы можете себе представить, что аналогичная проблема может возникнуть в нетривиальном универсальном классе.
@jamesdlin Ну, в таком случае я бы посчитал это действительно плохим дизайном. Весь смысл дженериков и наследования классов заключается в том, чтобы избежать проверки типов и особых случаев.
@jamesdlin Спасибо за подтверждение. И да, мне нужно было заставить foo<int>('1.0')
работать, поэтому я использовал toInt()
там.
@Ber В общем, я бы согласился, но num
немного раздражает, и есть только два возможных конкретных класса, поэтому я думаю, что частный случай приемлем.
@Ber Я не хочу создавать две функции, потому что у меня есть виджет, который принимает тип T extends num
и возвращает значение T
после вызова foo
.
@iDecode Вы можете получить два класса виджетов, по одному для каждого типа. Или вы передаете соответствующую версию foo в качестве аргумента.
@Ber, если я получу два класса виджетов и помещу логику в суперкласс, тогда я все равно буду иметь дело с num
. Извините, я не получил вас, передав соответствующую версию foo
.
@iDecode Возможно, это вопрос, который слишком сосредоточен на конкретном решении более абстрактной проблемы. Если вы зададите вопрос о дизайне своих виджетов, возможно, вы получите гораздо лучший ответ.
Я не вижу лучшего решения. У вас есть функция, которая полностью меняет поведение на основе аргумента типа. Поскольку все, что вы можете сделать с аргументом типа, — это проверить подтип или сравнить его с литералом постоянного типа, вам нужно сделать что-то подобное.
Я бы предпочел проверку подтипа, потому что это также позволяет продвигать, но для чего-то ограниченного, такого как это, где есть только четыре возможных типа для T
, проверка Type
на равенство объектов также может работать. Перед возвращением должен быть хотя бы один as T
.
Любой подход также работает только для иерархий типов, которые являются конечными, и вы учитываете все возможные типы. Даже здесь текущий код не охватывает <num>
и <Never>
, которые также являются допустимыми аргументами типа для связанного num
. Итак, будьте бдительны.
Возможно, используя проверки подтипа для продвижения:
T parse<T extends num>(String source) {
var value = num.parse(source);
if (value is T) return value;
// T is not `num`.
num d = value.toDouble();
if (d is T) return d; // T was double.
try {
num n = value.toInt(); // Can fail for Infinity/NaN
if (n is T) return n; // T was int
} catch (_) {
// T was `int` after all, so throwing was correct.
if (1 is T) rethrow; // T was int, but the input was not valid.
}
// T was neither num, double, nor int, so must be `Never`.
throw ArgumentError.value(T, "T", "Must not be Never");
}
Или. используя Type
равенство объектов:
T parse<T extends num>(String source) {
var value = num.parse(source);
switch (T) {
case num: return value as T;
case int: return value.toInt() as T;
case double: return value.toDouble() as T;
default:
throw ArgumentError.value(T, "T", "Must not be Never");
}
}
@BrunoPeixoto Я не вижу ничего плохого в этом вопросе.