I tried writing a simple generic method that would iteratively copy a nested List
, for example a List<List<int>>
. But unfortunately, the recursive call seems to always return List<dynamic>
, so I get the following error
The argument type List<dynamic> can't be assigned to the parameter type T
List<T> listDeepCopy<T>(List<T> list){
List<T> newList = List<T>();
list.forEach((value) {
if( value is List ){
newList.add(listDeepCopy(value)); // <-- listDeepCopy() always returns List<dynamic>
}
else{
newList.add(value);
}
});
return newList;
}
So if I call
List<List<int>> list = [[1,2],[3,4]];
List<List<int>> copy = listDeepCopy(list);
T
is List<int>
value
is T
- i.e. List<int>
listDeepCopy(value)
should equal listDeepCopy<List<int>>
, which would return a List<int>
, which should be possible to add to newList
, which is a List<List<int>>
Where am I going wrong here, and how can I make something like this work?
CodePudding user response:
I probably would implement it as:
List<T> listDeepCopy<T>(List<T> list) {
var copy = list.toList();
for (var i = 0; i < copy.length; i = 1) {
var element = copy[i];
if (element is List) {
copy[i] = listDeepCopy(element) as T;
}
}
return copy;
}
void main() {
List<List<int>> list = [
[1, 2],
[3, 4]
];
List<List<int>> copy = listDeepCopy(list);
list[0][0] = 99;
print(copy); // Prints: [[1, 2], [3, 4]]
}
A problem with your approach is that Dart cannot properly infer the generic type parameter for that recursive listDeepCopy(value)
call. value
is of type T
that is known to be a List
(which is shorthand for List<dynamic>
), and I am not aware of a way to extract the static element type. (Maybe @lrn will see this and provide a better, more complete explanation.)
In such a case, it's better to rely on polymorphism by calling a method on the List
that returns a copy of itself: .toList()
.
(As an example where this matters, consider a shallow copy scenario:
List<T> shallowCopy1<T>(List<T> list) => <T>[...list];
List<T> shallowCopy2<T>(List<T> list) => list.toList();
extension StaticType<T> on T {
Type get staticType => T;
}
void main() {
List<num> list = <int>[1, 2, 3];
var copy1 = shallowCopy1(list);
var copy2 = shallowCopy2(list);
print('original: staticType: ${list.staticType}, runtimeType: ${list.runtimeType}');
print('copy1: staticType: ${copy1.staticType}, runtimeType: ${copy1.runtimeType}');
print('copy2: staticType: ${copy2.staticType}, runtimeType: ${copy2.runtimeType}');
}
Although both copies preserve the static type of the original List
, only copy2
preserves the object's actual (runtime) type. A proper copy depends on the runtime type of the object being copied, and the only robust way to do that is for the object to create a copy of itself.)
CodePudding user response:
The dynamic version will do deep copy but the generic one is no better than list4 = [...list1];
and the dynamic one no better than list5 = jsonDecode(jsonEncode(list1));
import 'dart:convert';
List<T> listDeepCopy<T>(List<T> list) {
var newList = <T>[];
for (final value in list) {
if (value is List<T>) {
newList.add(listDeepCopy(value) as T);
} else {
newList.add(value);
}
}
return newList;
}
// Dynamic version
listDeepCopy3(List list) {
var newList = [];
for (final value in list) {
if (value is List) {
newList.add(listDeepCopy(value));
} else {
newList.add(value);
}
}
return newList;
}
void main() {
List<List<int>> list1 = [
[1, 2],
[3, 4]
];
var list2 = listDeepCopy(list1);
var list3 = listDeepCopy3(list1);
var list4 = [...list1]; // shallow copy
var list5 = jsonDecode(jsonEncode(list1)); // deep copy
list2[0][1] = 9;
list3[0][1] = 8;
list4[0][1] = 7;
list5[0][1] = 6;
print(list1);
print(list2); // shallow copy
print(list3); // deep copy but type is dynamic
print(list4); // shallow copy
print(list5); // deep copy but type is dynamic
}