How and when to abandon the use of arrays in C#?

后端 未结 15 1736
盖世英雄少女心
盖世英雄少女心 2020-12-13 02:58

I\'ve always been told that adding an element to an array happens like this:

An empty copy of the array+1element is created and then the data from t

15条回答
  •  暗喜
    暗喜 (楼主)
    2020-12-13 03:43

    When to abandon the use of arrays

    1. First and foremost, when semantics of arrays dont match with your intent - Need a dynamically growing collection? A set which doesn't allow duplicates? A collection that has to remain immutable? Avoid arrays in all that cases. That's 99% of the cases. Just stating the obvious basic point.

    2. Secondly, when you are not coding for absolute performance criticalness - That's about 95% of the cases. Arrays perform better marginally, especially in iteration. It almost always never matter.

    3. When you're not forced by an argument with params keyword - I just wished params accepted any IEnumerable or even better a language construct itself to denote a sequence (and not a framework type).

    4. When you are not writing legacy code, or dealing with interop

    In short, its very rare that you would actually need an array. I will add as to why may one avoid it?

    1. The biggest reason to avoid arrays imo is conceptual. Arrays are closer to implementation and farther from abstraction. Arrays conveys more how it is done than what is done which is against the spirit of high level languages. That's not surprising, considering arrays are closer to the metal, they are straight out of a special type (though internally array is a class). Not to be pedagogical, but arrays really do translate to a semantic meaning very very rarely required. The most useful and frequent semantics are that of a collections with any entries, sets with distinct items, key value maps etc with any combination of addable, readonly, immutable, order-respecting variants. Think about this, you might want an addable collection, or readonly collection with predefined items with no further modification, but how often does your logic look like "I want a dynamically addable collection but only a fixed number of them and they should be modifiable too"? Very rare I would say.

    2. Array was designed during pre-generics era and it mimics genericity with lot of run time hacks and it will show its oddities here and there. Some of the catches I found:

      1. Broken covariance.

        string[] strings = ...
        object[] objects = strings;
        objects[0] = 1; //compiles, but gives a runtime exception.
        
      2. Arrays can give you reference to a struct!. That's unlike anywhere else. A sample:

        struct Value { public int mutable; }
        
        var array = new[] { new Value() };  
        array[0].mutable = 1; //<-- compiles !
        //a List[0].mutable = 1; doesnt compile since editing a copy makes no sense
        print array[0].mutable // 1, expected or unexpected? confusing surely
        
      3. Run time implemented methods like ICollection.Contains can be different for structs and classes. It's not a big deal, but if you forget to override non generic Equals correctly for reference types expecting generic collection to look for generic Equals, you will get incorrect results.

        public class Class : IEquatable
        {
            public bool Equals(Class other)
            {
                Console.WriteLine("generic");
                return true;
            }
            public override bool Equals(object obj)
            {
                Console.WriteLine("non generic");
                return true;
            } 
        }
        
        public struct Struct : IEquatable
        {
            public bool Equals(Struct other)
            {
                Console.WriteLine("generic");
                return true;
            }
            public override bool Equals(object obj)
            {
                Console.WriteLine("non generic");
                return true;
            } 
        }
        
        class[].Contains(test); //prints "non generic"
        struct[].Contains(test); //prints "generic"
        
      4. The Length property and [] indexer on T[] seem to be regular properties that you can access through reflection (which should involve some magic), but when it comes to expression trees you have to spit out the exact same code the compiler does. There are ArrayLength and ArrayIndex methods to do that separately. One such question here. Another example:

        Expression> e = () => new[] { "a" }[0];
        //e.Body.NodeType == ExpressionType.ArrayIndex
        
        Expression> e = () => new List() { "a" }[0];
        //e.Body.NodeType == ExpressionType.Call;
        

    How to abandon the use of arrays

    The most commonly used substitute is List which has a cleaner API. But it is a dynamically growing structure which means you can add to a List at the end or insert anywhere to any capacity. There is no substitute for the exact behaviour of an array, but people mostly use arrays as readonly collection where you can't add anything to its end. A substitute is ReadOnlyCollection. I carry this extension method:

    public ReadOnlyCollection ToReadOnlyCollection(IEnumerable source)
    {
        return source.ToList().AsReadOnly();
    }
    

提交回复
热议问题