Question is: what is the best (best readable, easiest to write, without performance loss) way to avoid that?
In general, I don't try to avoid it. The consumer of my API should use the type I expose, and if they don't, any bugs resulting are their fault, not mine. As such, I don't really care if they cast the data that way - when I change my internal representation, and they get cast exceptions, that's their issue.
That being said, if there is a security concern, I would likely just use AsReadOnly
. This is effectively self-documenting, and has no real downsides (apart from a small allocation for the wrapper, as there is no copy of the data, you do get meaningful exceptions on modification, etc). There is no real disadvantage to this vs. making your own custom wrapper, and a custom wrapper means more code to test and maintain.
In general, I personally try to avoid copying without reason. That would eliminate ToList()
as an option in general. Using an iterator (your first option) is not as bad, though it does not really provide many advantages over a ReadOnlyCollection<T>
.