16

I encountered this FxCop rule before and wasn't really content with how to solve violations (thread1, thread2). I now have another case where I need to correct violations of the CA1819 kind.

Specifically, I have an algorithm-library that performs some analytic calculations on a curve (x,y), with a public "input object" like this:

public class InputObject
{
        public double[] X { get; set; }
        public double[] Y { get; set; }
        // + lots of other things well
}

This object's X and Y properties are used in hundreds of locations within library, typically using indexes. The input object is never altered by the algorithms, but actually it shouldn't matter if so. Also, .Length is called pretty frequently. It's a mathematical library, and double[] is kind of the standard data type in there. In any case, fixing CA1819 will require quite some work.

I thought about using List<double>, since Lists support indexing and are quite similar to arrays but I'm not sure whether this may slow down the algorithms or whether FxCop will be happy with those Lists.

What is the best option to replace these double[] properties?

Community
  • 1
  • 1
Efrain
  • 3,248
  • 4
  • 34
  • 61

5 Answers5

13

If it is read only to external consumer and consumer does not want to access it by index then the best is to have a public read only property of type IEnumerable<> with method accessors to add and remove, this way you will not have to expose your array to someone to mess with.

If you need to access the indexers then expose it as read only property of type IList<> and probably return a ReadOnly instance, with methods to add and remove.

This way you keep encapsulation of the internal list and allow consumer to access it in a read only way

Mohamed Abed
  • 5,025
  • 22
  • 31
  • 1
    Thanks for that input, that's what I did.. especially because there is another FxCop rule (CA2227, http://msdn.microsoft.com/de-de/library/ms182327.aspx) , that forces me to do so. ;) – Efrain Oct 19 '11 at 13:12
  • 1
    Personally I would only use IEnumerable if I wanted to imply to the caller that a property may be lazily-evaluated. Probably not the case in the OP's example, so I'd prefer IList, backed by a ReadOnlyCollection field if the list should be readonly. – Joe Oct 19 '11 at 17:27
  • Exactly @Joe that what i meant exactly :) – Mohamed Abed Oct 19 '11 at 18:46
2

Sometime FxCop from my point of view exagerates.

It all depends on what you have to do, if you are writing a complex system where security and very clean code is required, you should returns a readonly version of that array. That is, cast the array as IEnumerable as suggests devdigital or use the good idea ImmutableArray of Mohamed Abed, that i prefer.

If your are writing software that require high performance... there is nothing better than an array for performances in C#. Arrays can be a lot more performant for iterating and reading.

If performances are really important I suggest you to ignore that warning. Is still legal, also if not too much clean, to return a readonly array.

for (int i = 0; i < array.Length; ++i) { k = array[i] + 1; }

This is very fast for big arrays in C#: it avoids array bounds check. It will perform very much as a C compiled code would do.

I always wished a "readonly array" type in C# :) but there is no hope to see it.

Salvatore Previti
  • 8,956
  • 31
  • 37
  • "I always wished a "readonly array" type in C#" - a ReadOnlyCollection is functionally equivalent to a readonly array. – Joe Oct 19 '11 at 19:46
  • 1
    Absolutely true but not as much in performances, arrays have special "shortcusts" in JIT. – Salvatore Previti Oct 20 '11 at 18:23
  • 2
    Many years passed but finally we have it - ReadOnlySpan - https://learn.microsoft.com/en-us/dotnet/api/system.readonlyspan-1?view=netcore-3.1 – apocalypse Apr 01 '20 at 03:01
1

As your link suggests:

To fix a violation of this rule, either make the property a method or change the property to return a collection.

Using a collection such as a List should not have a significant impact on performance.

devdigital
  • 34,151
  • 9
  • 98
  • 120
  • 3
    FxCop won't like `List` either - prefer `IList`. – Joe Oct 19 '11 at 12:10
  • Thanks Joe, we don't use FxCop, but this makes sense – devdigital Oct 19 '11 at 12:11
  • As per @Joe I think changing the return value of your properties to IList should not have any impact on other code to be recompiled/changed, and resolve the FxCop issue.. – S2S2 Oct 19 '11 at 12:13
0

Change array [] to IEnumerable:

public class InputObject
{
    public IEnumerable<double> X { get; set; }
    public IEnumerable<double> Y { get; set; }
    // + lots of other things well
}
0

The big problem here isn't really what your library does with the values (which is a potential problem, albeit a much more manageable one), but rather what callers might do with the values. If you need to treat them as immutable, then you need to ensure that a library consumer cannot change the contents after their original assignment. The easy fix here would be to create an interface that exposes all the array members that your library uses, then create an immutable wrapper class for an array that implements this interface to use in your InputObject class. e.g.:

public interface IArray<T>
{
    int Length { get; }

    T this[int index] { get; }
}

internal sealed class ImmutableArray<T> : IArray<T>
    where T : struct
{
    private readonly T[] _wrappedArray;

    internal ImmutableArray(IEnumerable<T> data)
    {
        this._wrappedArray = data.ToArray();
    }

    public int Length
    {
        get { return this._wrappedArray.Length; }
    }

    public T this[int index]
    {
        get { return this._wrappedArray[index]; }
    }
}

public class InputObject
{
    private readonly IArray<double> _x;
    private readonly IArray<double> _y;

    public InputObject(double[] x, double[] y)
    {
        this._x = new ImmutableArray<double>(x);
        this._y = new ImmutableArray<double>(y);
    }

    public IArray<double> X
    {
        get { return this._x; }
    }

    public IArray<double> Y
    {
        get { return this._y; }
    }

    //...
}

The elements in your "immutable" array contents would still be mutable if T is mutable, but at least you're safe for the double type.

Nicole Calinoiu
  • 20,843
  • 2
  • 44
  • 49
  • Seems overkill to create your own interface (IArray) rather than using the similar IList that already exists in the framework. – Joe Oct 19 '11 at 17:11
  • 1
    @Joe: IList exposes operations for modifying the list. Items can be added, removed, and swapped out. Of course, one could throw a NotSupportedException for each of the mutation points, but that's not a particularly polite way to treat API consumers. And seriously, how much trouble is it to add an interface? – Nicole Calinoiu Oct 19 '11 at 18:35
  • it also a property IsReadOnly, which allows a caller to avoid the NotSupportedException. The idiomatic way to handle this in .NET is to expose a readonly IList, probably backed by a ReadOnlyCollection. Your current implementation has a number of weaknesses, including failing to implement IEnumerable, ICollection and IList, which would be expected of an array class. You could spend time implementing these, but why would you do so when the Framework already contains all you need? – Joe Oct 19 '11 at 19:29
  • @Joe: That's a terrible thing to do to one's API consumers. Sometimes it's necessary, but this isn't really one of those cases. It's a pity that the BCL doesn't include a read-only collection interface, but that doesn't mean that we have to use the mutable interfaces it offers when we want to offer immutability. (BTW, my sample is incomplete because it's a *sample*. It's meant to show the core approach, not the complete implementation.) – Nicole Calinoiu Oct 20 '11 at 12:07
  • all design is trade-off, including when deciding whether it's better to have a single interface with boolean "capability" properties, or multiple interfaces. In this case I think the BCL is absolutely right not to have a read-only collection interface, for reasons which won't fit into a comment. Both choices can have negative consequences in some situations so it's wrong to say one is "terrible" without carefully evaluating all the pros and cons - which I believe the BCL designers did when designing their API. – Joe Oct 20 '11 at 13:30