This is probably easy for someone more experienced with LINQ and the Lambda expressions in C#. I'm just starting out with them, so I must be missing something.
I have a custom object like this:
public class BusinessName {
public string Name { get; set; }
public string Type { get; set; }
}
Then, I have a list of these objects:
List<BusinessName> businessNames = new List<BusinessName>();
I want to convert this into a list of string
with the Name
property of the BusinessName
object:
List<string> names = new List<string>();
Obviously, I could do this:
foreach (BusinessName name in businessNames) {
names.Add(name.Name);
}
But, I want to try using the lambda expressions with LINQ so that I can put this into a one liner.
So, I tried:
names.AddRange(businessNames.Select(item => item.Name));
This works as expected, but is much slower than the foreach loop by 2x. I suspect this is because it's iterating the list twice, unlike the foreach loop.
So, I started looking for an alternative. I found Concat()
but couldn't figure out how it was any different than AddRange()
.
Does anyone know a way to do this in one pass with LINQ?
It's already clear that the behaviour is as per documented. But I think your main question is why is the behaviour different for ArrayList
and TreeSet
. Well, it has to do with how data is stored internally in both the collections.
An ArrayList
internally uses an array to store the data, which is re-sized as the size of ArrayList
dynamically increases. Now, when you create a subList
of your given list, original list with the specified indices is associated with the subList
. So, any structural changes (that screws the indexing of the original array), done in the original list, will make the index stored as a part of sublist meaningless. That is why any structural changes is not allowed in case of ArrayList#subList
method.
The subList method returns you an instance of an inner
class named SubList
inside the ArrayList
class, which looks like:
private class SubList extends AbstractList<E> implements RandomAccess {
private final AbstractList<E> parent;
private final int parentOffset;
private final int offset;
int size;
SubList(AbstractList<E> parent,
int offset, int fromIndex, int toIndex) {
this.parent = parent;
this.parentOffset = fromIndex;
this.offset = offset + fromIndex;
this.size = toIndex - fromIndex;
this.modCount = ArrayList.this.modCount;
}
As you see, the SubList
contains a reference to the original list. And the parentOffset
is nothing but the starting index of the subList
you are creating. Now modifying the original list
will possibly change the value at fromIndex
in original list, but not inside the SubList
. In that case, parentOffset
in SubList
class and fromIndex
in original list, will point to different array elements. It might also be possible that at some point the original array becomes shorter enough to invalidate index stored in the SubList
and make it OutOfRange
. This is certainly not desirable, and the semantics of the subList returned is considered undefined, on such structural changes to original list.
On the other hand, a TreeSet
stores it's data internally in a TreeMap
. Now as there is no such concept of indices in a Map
, there is no issue of indices breaking up. A Map
is nothing but a mapping of key-value pair. Creating a SubSet involves creating a SubMap which is backed by the original Map
. Modifying the original Set
will just require the corresponding key-value mapping being invalidated, thus propagating the changes to the subMap
created for the subSet
.