Hacker News new | ask | show | jobs
by CharlieDigital 1030 days ago
Wouldn't this just be ImmutableArray or ReadOnlyCollection?

    > “Frozen collections” and ImmutableArray<T> can solve this issue, but the latter is essentially just a defensive copy of the array, but in a special type
OK. Or just declare your parameter as IEnumerable<>? This has the effect of restricting the operations on the incoming collection in the same way.
2 comments

Obviously, if this is a processing function that iterates over the array and forgets about it, IEnumerable<T> or IList<T> (IReadOnlyList<T> to communicate intent) would be the better option.

My thoughts involved constructors. In those, sure, I could take IEnumerable<T> or whatever else there is, but if I want to store an array (or list) as a field in my class, I'd have to make a copy with ToArray() (and friends). Being able to "move" an array from the caller into the constructor (callee) would be nice.

ReadOnlyCollection<T> isn't actually read only, but just a wrapper around an array/list. IReadOnlyList<T> also isn't read only, but just restricts me from editing it. I can't edit the parameters, but the caller possibly could, and that's my issue.

    > I can't edit the parameters, but the caller possibly could, and that's my issue.
This seems more like a concurrency issue, then. If that's the case, it seems like the right answer is some synchronization primitive or perhaps using a `Channel<T>` to serialize the flow.
Sometimes it's concurrency, but most of the time it's that I want to know I own the array. I want to know that `_array[5]` will be the same, no matter when I access it (excluding intentional modifications by callee). For example, if I take `T[]` as a parameter to my constructor, I might think `_array[5]` will be the same, but nothing stops the caller from doing `Array.Sort(theParameter)` sometime later.

    // in one function of the caller's type
    _data = new int[] { 1, 2, 3, 4, 5, 6, 7 }; // could come from some data collection engine
    _dataTable = new(_data);
 
    // then, sometime later, in another function of the caller's type
    Array.Clear(_data); // reset for next iteration or something
    // data table now has an array of zeros if it didn't copy the data
This is just an example, and the problem is not only limited to arrays; Mutable classes can be mutated by the caller after the callee is given them. Basically, the issue is multiple mutable references. The only solution to the above problem is a defensive copy by the "data table" constructor. I would like the ability to say, "the caller cannot modify this reference anymore".

This is something Rust gets right, IMO. If I want the callee and the caller to share mutable ownership, RefCell<T> or Mutex<T> can be used, and the usage of such a type makes that clear. If I want thread safety, I can wrap the mutex in an Arc<T>. And, if I don't want shared ownership, a plain T can be used, and ownership passes into the callee. The issue is: in C#, without defensive measures, all objects are just T* with no ownership or thread-safety.

> OK. Or just declare your parameter as IEnumerable<>? This has the effect of restricting the operations on the incoming collection in the same way.

It does not work that way, because it is an `in` parameter, thus you can still accept any mutable type (including an array) which implements the essentially immutable IEnumerable interface you require.

Now if it was about a return value, it would be a different story.

edit:

Still what you suggest has its merits in the other direction, as it gives the caller a guarantee that the passed in collection will not be modified inside. Had to untangle broken code (without unit test when i got it) that was called like something `CalculateCost(SomegraphData input)` and while its name (and xmldoc) did not suggest, it did subtle modification to the data it got handed in... I was very upset about that legacy codebase I just inherited when I found that...