Dart:具有可为空属性的自定义 "copyWith" 方法
Dart: Custom "copyWith" method with nullable properties
我正在尝试为我的 class 创建一个“copyWith”方法,它适用于大多数情况。
问题是当我尝试将可为 null 的 属性 设置为 null 时,因为我的函数无法识别它是否是有意的。
例如:
class Person {
final String? name;
Person(this.name);
Person copyWith({String? name}) => Person(name ?? this.name);
}
void main() {
final person = Person("Gustavo");
print(person.name); // prints Gustavo
// I want the name property to be nul
final person2 = person.copyWith(name: null);
print(person2.name); // Prints Gustavo
}
有人知道这种情况的解决方法吗?这真的很困扰我,我不知道如何避免这种情况。
Person.name
被声明为不可空的,所以copyWith
不可能给它赋空值。如果您希望 Person.name
可以为空,您应该问问自己是否真的想要区分 null
和空字符串。通常你不会。
如果您确实希望 null
和 都允许空字符串,那么您将需要使用其他标记值:
class Person {
static const _invalid_name = '_invalid_name_';
final String? name;
Person(this.name);
Person copyWith({String? name = _invalid_name}) =>
Person(name != _invalid_name ? name : this.name);
}
或者您需要将其包装在另一个 class 中,例如:
class Optional<T> {
final bool isValid;
final T? _value;
// Cast away nullability if T is non-nullable.
T get value => _value as T;
const Optional()
: isValid = false,
_value = null;
const Optional.value(this._value) : isValid = true;
}
class Person {
final String? name;
Person(this.name);
Person copyWith({Optional<String?> name = const Optional()}) =>
Person(name.isValid ? name.value : this.name);
}
void main() {
final person = Person("Gustavo");
print(person.name);
final person2 = person.copyWith(name: Optional.value(null));
print(person2.name);
}
现有的包实现了 Optional
-like classes,可能可以帮助您。
我正在使用 Optional package 来解决这个问题,所以代码看起来像这样:
final TZDateTime dateTime;
final double value;
final Duration? duration;
...
DataPoint _copyWith({
TZDateTime? dateTime,
double? value,
Optional<Duration?>? duration}) {
return DataPoint(
dateTime ?? this.dateTime,
value ?? this.value,
duration: duration != null ?
duration.orElseNull :
this.duration,
);
}
在此示例中,duration 是一个可为空的字段,copyWith 模式正常工作。唯一需要做的不同的是,如果您设置 duration
,将其包装在 Optional 中,如下所示:
Duration? newDuration = Duration(minutes: 60);
_copyWith(duration: Optional.ofNullable(newDuration));
或者,如果您想将持续时间设置为空:
_copyWith(duration: Optional.empty());
以 copyWith
的实现两倍大为代价,您实际上可以使用标志来允许 null
-ing 字段而不使用任何“默认空对象”或选项class:
class Person {
final String? name;
final int? age;
Person(this.name, this.age);
Person copyWith({
String? name,
bool noName = false,
int? age,
bool noAge = false,
// ...
}) =>
Person(
name ?? (noName ? null : this.name),
age ?? (noAge ? null : this.age),
// ...
);
}
void main() {
final person = Person('Gustavo', 42);
print(person.name); // prints Gustavo
print(person.age); // Prints 42
final person2 = person.copyWith(noName: true, age: 84);
print(person2.name); // Prints null
print(person2.age); // Prints 84
final person3 = person2.copyWith(age: 21);
print(person3.name); // Prints null
print(person3.age); // Prints 21
final person4 = person3.copyWith(name: 'Bob', noAge: true);
print(person4.name); // Prints Bob
print(person4.age); // Prints null
runApp(MyApp());
}
确实有无意义的情况:
final person = otherPerson.copyWith(name: 'John', noName: true);
但我想如果你真的想禁止它,你可以为此做出断言。
另一种解决方案是使用函数来设置值。这样,您可以确定未提供的函数 (null
)、提供的函数和 returns () => null
以及 returns 名称的函数() => 'Gustavo'
)
class Person {
final String? name;
Person(this.name);
Person copyWith({String? Function()? name}) =>
Person(name != null ? name() : this.name);
}
void main() {
final person = Person('Gustavo');
print(person.name); // prints Gustavo
// I want the name property to be nul
final person2 = person.copyWith(name: () => null);
print(person2.name); // Prints null
final person3 = person.copyWith(name: () => 'new name');
print(person3.name); // Prints new name
final person4 = person.copyWith();
print(person4.name); // Prints Gustavo
}
这使得设置名称稍微麻烦一些,但好的一面是,如果您尝试直接传递字符串,编译器会告诉您提供了错误的类型,因此会提醒您添加 () =>
到它。
灵感来自答案:
您需要做的就是提供一个包装器。考虑这个例子:
class Person {
final String? name;
Person(this.name);
Person copyWith({Wrapped<String?>? name}) =>
Person(name != null ? name.value : this.name);
}
// This is all you need:
class Wrapped<T> {
final T value;
const Wrapped.value(this.value);
}
void main() {
final person = Person('John');
print(person.name); // Prints John
final person2 = person.copyWith();
print(person2.name); // Prints John
final person3 = person.copyWith(name: Wrapped.value('Cena'));
print(person3.name); // Prints Cena
final person4 = person.copyWith(name: Wrapped.value(null));
print(person4.name); // Prints null
}
有多个选项:
1. ValueGetter
class B {
const B();
}
class A {
const A({
this.nonNullable = const B(),
this.nullable,
});
final B nonNullable;
final B? nullable;
A copyWith({
B? nonNullable,
ValueGetter<B?>? nullable,
}) {
return A(
nonNullable: nonNullable ?? this.nonNullable,
nullable: nullable != null ? nullable() : this.nullable,
);
}
}
const A().copyWith(nullable: () => null);
const A().copyWith(nullable: () => const B());
2。从 Quiver package
中可选
class B {
const B();
}
class A {
const A({
this.nonNullable = const B(),
this.nullable,
});
final B nonNullable;
final B? nullable;
A copyWith({
B? nonNullable,
Optional<B>? nullable,
}) {
return A(
nonNullable: nonNullable ?? this.nonNullable,
nullable: nullable != null ? nullable.value : this.nullable,
);
}
}
const A().copyWith(nullable: const Optional.fromNullable(null));
const A().copyWith(nullable: const Optional.fromNullable(B()));
3。 copyWith 作为字段
class _Undefined {}
class B {
const B();
}
class A {
A({
this.nonNullable = const B(),
this.nullable,
});
final B nonNullable;
final B? nullable;
// const constructor no more avaible
late A Function({
B? nonNullable,
B? nullable,
}) copyWith = _copyWith;
A _copyWith({
B? nonNullable,
Object? nullable = _Undefined,
}) {
return A(
nonNullable: nonNullable ?? this.nonNullable,
nullable: nullable == _Undefined ? this.nullable : nullable as B?,
);
}
}
A().copyWith(nullable: null);
A().copyWith(nullable: const B());
4. copyWith 重定向构造函数
class _Undefined {}
class B {
const B();
}
abstract class A {
const factory A({
B nonNullable,
B? nullable,
}) = _A;
const A._({
required this.nonNullable,
this.nullable,
});
final B nonNullable;
final B? nullable;
A copyWith({B? nonNullable, B? nullable});
}
class _A extends A {
const _A({
B nonNullable = const B(),
B? nullable,
}) : super._(nonNullable: nonNullable, nullable: nullable);
@override
A copyWith({B? nonNullable, Object? nullable = _Undefined}) {
return _A(
nonNullable: nonNullable ?? this.nonNullable,
nullable: nullable == _Undefined ? this.nullable : nullable as B?,
);
}
}
const A().copyWith(nullable: null);
const A().copyWith(nullable: const B());
5. copyWith 重定向构造函数 2
class _Undefined {}
class B {
const B();
}
abstract class A {
const factory A({
B nonNullable,
B? nullable,
}) = _A;
const A._();
B get nonNullable;
B? get nullable;
A copyWith({B? nonNullable, B? nullable});
}
class _A extends A {
const _A({
this.nonNullable = const B(),
this.nullable,
}) : super._();
@override
final B nonNullable;
@override
final B? nullable;
@override
A copyWith({B? nonNullable, Object? nullable = _Undefined}) {
return _A(
nonNullable: nonNullable ?? this.nonNullable,
nullable: nullable == _Undefined ? this.nullable : nullable as B?,
);
}
}
const A().copyWith(nullable: null);
const A().copyWith(nullable: const B());
我正在尝试为我的 class 创建一个“copyWith”方法,它适用于大多数情况。
问题是当我尝试将可为 null 的 属性 设置为 null 时,因为我的函数无法识别它是否是有意的。
例如:
class Person {
final String? name;
Person(this.name);
Person copyWith({String? name}) => Person(name ?? this.name);
}
void main() {
final person = Person("Gustavo");
print(person.name); // prints Gustavo
// I want the name property to be nul
final person2 = person.copyWith(name: null);
print(person2.name); // Prints Gustavo
}
有人知道这种情况的解决方法吗?这真的很困扰我,我不知道如何避免这种情况。
Person.name
被声明为不可空的,所以copyWith
不可能给它赋空值。如果您希望 Person.name
可以为空,您应该问问自己是否真的想要区分 null
和空字符串。通常你不会。
如果您确实希望 null
和 都允许空字符串,那么您将需要使用其他标记值:
class Person {
static const _invalid_name = '_invalid_name_';
final String? name;
Person(this.name);
Person copyWith({String? name = _invalid_name}) =>
Person(name != _invalid_name ? name : this.name);
}
或者您需要将其包装在另一个 class 中,例如:
class Optional<T> {
final bool isValid;
final T? _value;
// Cast away nullability if T is non-nullable.
T get value => _value as T;
const Optional()
: isValid = false,
_value = null;
const Optional.value(this._value) : isValid = true;
}
class Person {
final String? name;
Person(this.name);
Person copyWith({Optional<String?> name = const Optional()}) =>
Person(name.isValid ? name.value : this.name);
}
void main() {
final person = Person("Gustavo");
print(person.name);
final person2 = person.copyWith(name: Optional.value(null));
print(person2.name);
}
现有的包实现了 Optional
-like classes,可能可以帮助您。
我正在使用 Optional package 来解决这个问题,所以代码看起来像这样:
final TZDateTime dateTime;
final double value;
final Duration? duration;
...
DataPoint _copyWith({
TZDateTime? dateTime,
double? value,
Optional<Duration?>? duration}) {
return DataPoint(
dateTime ?? this.dateTime,
value ?? this.value,
duration: duration != null ?
duration.orElseNull :
this.duration,
);
}
在此示例中,duration 是一个可为空的字段,copyWith 模式正常工作。唯一需要做的不同的是,如果您设置 duration
,将其包装在 Optional 中,如下所示:
Duration? newDuration = Duration(minutes: 60);
_copyWith(duration: Optional.ofNullable(newDuration));
或者,如果您想将持续时间设置为空:
_copyWith(duration: Optional.empty());
以 copyWith
的实现两倍大为代价,您实际上可以使用标志来允许 null
-ing 字段而不使用任何“默认空对象”或选项class:
class Person {
final String? name;
final int? age;
Person(this.name, this.age);
Person copyWith({
String? name,
bool noName = false,
int? age,
bool noAge = false,
// ...
}) =>
Person(
name ?? (noName ? null : this.name),
age ?? (noAge ? null : this.age),
// ...
);
}
void main() {
final person = Person('Gustavo', 42);
print(person.name); // prints Gustavo
print(person.age); // Prints 42
final person2 = person.copyWith(noName: true, age: 84);
print(person2.name); // Prints null
print(person2.age); // Prints 84
final person3 = person2.copyWith(age: 21);
print(person3.name); // Prints null
print(person3.age); // Prints 21
final person4 = person3.copyWith(name: 'Bob', noAge: true);
print(person4.name); // Prints Bob
print(person4.age); // Prints null
runApp(MyApp());
}
确实有无意义的情况:
final person = otherPerson.copyWith(name: 'John', noName: true);
但我想如果你真的想禁止它,你可以为此做出断言。
另一种解决方案是使用函数来设置值。这样,您可以确定未提供的函数 (null
)、提供的函数和 returns () => null
以及 returns 名称的函数() => 'Gustavo'
)
class Person {
final String? name;
Person(this.name);
Person copyWith({String? Function()? name}) =>
Person(name != null ? name() : this.name);
}
void main() {
final person = Person('Gustavo');
print(person.name); // prints Gustavo
// I want the name property to be nul
final person2 = person.copyWith(name: () => null);
print(person2.name); // Prints null
final person3 = person.copyWith(name: () => 'new name');
print(person3.name); // Prints new name
final person4 = person.copyWith();
print(person4.name); // Prints Gustavo
}
这使得设置名称稍微麻烦一些,但好的一面是,如果您尝试直接传递字符串,编译器会告诉您提供了错误的类型,因此会提醒您添加 () =>
到它。
灵感来自
您需要做的就是提供一个包装器。考虑这个例子:
class Person {
final String? name;
Person(this.name);
Person copyWith({Wrapped<String?>? name}) =>
Person(name != null ? name.value : this.name);
}
// This is all you need:
class Wrapped<T> {
final T value;
const Wrapped.value(this.value);
}
void main() {
final person = Person('John');
print(person.name); // Prints John
final person2 = person.copyWith();
print(person2.name); // Prints John
final person3 = person.copyWith(name: Wrapped.value('Cena'));
print(person3.name); // Prints Cena
final person4 = person.copyWith(name: Wrapped.value(null));
print(person4.name); // Prints null
}
有多个选项:
1. ValueGetter
class B {
const B();
}
class A {
const A({
this.nonNullable = const B(),
this.nullable,
});
final B nonNullable;
final B? nullable;
A copyWith({
B? nonNullable,
ValueGetter<B?>? nullable,
}) {
return A(
nonNullable: nonNullable ?? this.nonNullable,
nullable: nullable != null ? nullable() : this.nullable,
);
}
}
const A().copyWith(nullable: () => null);
const A().copyWith(nullable: () => const B());
2。从 Quiver package
中可选class B {
const B();
}
class A {
const A({
this.nonNullable = const B(),
this.nullable,
});
final B nonNullable;
final B? nullable;
A copyWith({
B? nonNullable,
Optional<B>? nullable,
}) {
return A(
nonNullable: nonNullable ?? this.nonNullable,
nullable: nullable != null ? nullable.value : this.nullable,
);
}
}
const A().copyWith(nullable: const Optional.fromNullable(null));
const A().copyWith(nullable: const Optional.fromNullable(B()));
3。 copyWith 作为字段
class _Undefined {}
class B {
const B();
}
class A {
A({
this.nonNullable = const B(),
this.nullable,
});
final B nonNullable;
final B? nullable;
// const constructor no more avaible
late A Function({
B? nonNullable,
B? nullable,
}) copyWith = _copyWith;
A _copyWith({
B? nonNullable,
Object? nullable = _Undefined,
}) {
return A(
nonNullable: nonNullable ?? this.nonNullable,
nullable: nullable == _Undefined ? this.nullable : nullable as B?,
);
}
}
A().copyWith(nullable: null);
A().copyWith(nullable: const B());
4. copyWith 重定向构造函数
class _Undefined {}
class B {
const B();
}
abstract class A {
const factory A({
B nonNullable,
B? nullable,
}) = _A;
const A._({
required this.nonNullable,
this.nullable,
});
final B nonNullable;
final B? nullable;
A copyWith({B? nonNullable, B? nullable});
}
class _A extends A {
const _A({
B nonNullable = const B(),
B? nullable,
}) : super._(nonNullable: nonNullable, nullable: nullable);
@override
A copyWith({B? nonNullable, Object? nullable = _Undefined}) {
return _A(
nonNullable: nonNullable ?? this.nonNullable,
nullable: nullable == _Undefined ? this.nullable : nullable as B?,
);
}
}
const A().copyWith(nullable: null);
const A().copyWith(nullable: const B());
5. copyWith 重定向构造函数 2
class _Undefined {}
class B {
const B();
}
abstract class A {
const factory A({
B nonNullable,
B? nullable,
}) = _A;
const A._();
B get nonNullable;
B? get nullable;
A copyWith({B? nonNullable, B? nullable});
}
class _A extends A {
const _A({
this.nonNullable = const B(),
this.nullable,
}) : super._();
@override
final B nonNullable;
@override
final B? nullable;
@override
A copyWith({B? nonNullable, Object? nullable = _Undefined}) {
return _A(
nonNullable: nonNullable ?? this.nonNullable,
nullable: nullable == _Undefined ? this.nullable : nullable as B?,
);
}
}
const A().copyWith(nullable: null);
const A().copyWith(nullable: const B());