0

I have some wish to be able to guarante that array will not be changed if I provide it somewhere. Does C# have some interface for arrays that allows you to read elements, but doesn't allow to change? Or is it possible to implement my hand made interface in the existing Array-class?

I know that I can make wrappers, adapters and something like this with all implemented interfaces I need. But I would be glad to leave all arrays in my code, just return the interface somewhere.

  • You can create an array that only provides `get` accessors to the elements, but if those elements are complex objects you will still have full use of the objects, including setting properties. – Joel Coehoorn Mar 07 '23 at 14:58
  • 1
    Consider using `ImmutableArray` - but passing `IReadOnlyList` is also an option. Personally I use ``IReadOnlyList` (because switching to `ImmutableArray` is painful due to it using `.Length` instead of `.Count`, grrr). – Dai Mar 07 '23 at 15:01
  • @JoelCoehoorn _"You can create an array that only provides get accessors to the elements"_ - can you elaborate on that? – Dai Mar 07 '23 at 15:03
  • @Dai Array was the wrong word, but there is IReadOnlyList and IReadOnlyCollection. A list you can't add to or remove from is functionally equivalent to the array. – Joel Coehoorn Mar 07 '23 at 15:05

4 Answers4

3

The most common interfaces are

  1. IEnumerable<T> - When you just need some sequence of items to iterate over. May even be of infinite length.
  2. IReadOnlyCollection<T> - When you also need to know how many items there are
  3. IReadOnlyList<T> - When you also need to access an item by index

Note that all of these allow the collection to be cast to the actual type, and modified. But this is obviously bad practice, and anyone doing that have to suffer the consequences. These interfaces are implemented by the most common .net collections, like List<T>, T[] etc.

Then there is ReadOnlySpan<T> and ReadOnlyMemory<T>, but these are more intended for low level code. I would view these as a kind of type safe pointer rather than an interface for a collection.

Old code may use ICollection<T> or IList<T>, I dislike these interfaces, since they are not read only, and I consider them rather bloated. There is also a ReadOnlyCollection<T>, but this implements ICollection<T> and IList<T>, and therefore violates Liskov substitution principle, so I would not use it for new code.

JonasH
  • 28,608
  • 2
  • 10
  • 23
  • 1
    If you do not want to use `ReadOnlyCollection` class because it violates Liskov, then maybe you also do not want to use `T[]` since it implements `ICollection` which has methods like `Add(T item)`, `Remove(T item)`, and `Clear()` all of which just throw if invoked. And `T[]` has "crazy covariance" where you can assign it to a variable of type `S[]` provided that `T` is a reference type that inherits or implements `S`, and this completely ruins Liskov and type-safety sinces arrays are read/write, hence semantically cannot allow covariance. – Jeppe Stig Nielsen Mar 07 '23 at 15:18
  • 1
    I think IReadOnlyList is the interface I look for! Because, as I see in @Theodor Zoulias's answer, I don't need to cast or wrap it. This interface is already implemented by Array-class! – Євген Діулін Mar 07 '23 at 15:20
  • @JeppeStigNielsen That is one of the reasons I dislike `ICollection`. I have to use arrays, since it is the foundational collection data type. But as long as I can pretend that `ICollection` does not exist I don't have to worry about LSP vilolations. I guess the same could be said of `ReadOnlyCollection`, but I see little reason to use it over `IReadOnlyList`. – JonasH Mar 07 '23 at 15:28
  • 1
    As I said, arrays have other problems. For example, `string[] arrX = { "ab", "cd", }; object[] arrY = arrX; arrY[0] = 3.14;` is a horrible LSP violation which will not go away if you pretend some interfaces do not exist! – Jeppe Stig Nielsen Mar 07 '23 at 15:39
  • @JeppeStigNielsen, I have honestly almost forgotten array covariance even is a thing. So yea, I completely agree that is a problem. *But I still have to use arrays*, and I can avoid the problem by just not using arrays in that way, something the IDE tend to help with. The same does not apply to `ReadOnlyCollection` since there are better alternatives. Not saying it is objectively bad, just that it is not something **I** prefer not to use. – JonasH Mar 07 '23 at 15:43
2

You can look into simply Array.AsReadOnly; example:

int[] myReadWriteArray = { 7, 9, 13, };
ReadOnlyCollection<int> myReadOnlyArray = Array.AsReadOnly(myReadWriteArray);

But also note that both types (int[] and ReadOnlyCollection<int>) implement the interface IReadOnlyList<out T>, so in some situations rather than wrapping in a wrapper class with AsReadOnly, it may be sufficient for you to just return the same instance typed as this interface:

int[] myReadWriteArray = { 7, 9, 13, };
IReadOnlyList<int> myReadOnlyReference = myReadWriteArray;
Jeppe Stig Nielsen
  • 60,409
  • 11
  • 110
  • 181
1

All one-dimensional arrays implement the IReadOnlyList<T> interface. So you could expose an array that is internal to your class as a public property like this:

class SomeClass
{
    private readonly int[] _numbers;

    public IReadOnlyList<int> Numbers => _numbers;
}

Users of your class won't be able to mutate the internal array, without resorting to tricks like casting.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
0

Please check out ReadOnlyMemory. You can return this instead of your array.

Nick
  • 4,787
  • 2
  • 18
  • 24