The SLAR on System.Array


The SLAR on System.Array


Continuing in the series on sharing some of the information in the .NET Framework Standard Library Annotated Reference Vol 1 here are some of the annotations and a sample on the System.List class.


 


BA  Notice that we used signed values for the indices in the Array class. Some people may feel this is an odd choice as the negative values are always invalid for indices. There are two reasons for this. First, the Common Language Specification lacks support for unsigned 32-bit integrals, which means some languages would not be able to consume these members. Second, unsigned numbers are just harder to work with. Many domains naturally return values as signed numbers, so using unsigned indices here would lead to uglier code with more casts.


 


BA  Notice that the Array class implements the IList interface. We did that to support databinding and other generic mechanisms such as the “foreach” language construct in C#. Accessing elements through the IList interface on an array is significantly more expensive than accessing elements directly, because the JIT can, for example, recognize code patterns and hoist bounds checks. Luckily, the C# compiler generates special case code for arrays that the JIT is able to recognize and optimize. For example, look at the code the C# compiler generates for this:


int [] arr = new int[] {1,2,3,4};


foreach (int i in arr)


{


      Console.WriteLine (i);


}


Rather than going through the enumerator the C# compiler generates code as if you


had written:


int [] arr = new int[] {1,2,3,4};


for (int i = 0; i < arr.Length; i++)


{


      Console.WriteLine (arr[i]);


}


 


JM In the original submission of this class for standardization, the Clear() method was not specified as precisely as it is now. Before it was specified as just zeroing out an array. But for Booleans and reference types it needs to be false and null, respectively. The change was indeed made during the standardization process.


 


BG  Array::Copy is a very interesting method, supporting all the conversions that we can prove will always work (i.e., upcasting, downcasting particular instances, boxing, unboxing, and primitive widening). Most people do not realize how useful this method can be when dealing with arrays of various primitive types.


 


——


 


I also wanted to highlight the fact that there is lots of good info on the CD in the back of the book.  One of my favorite features is the ILASM syntax for every member.  I almost cut this originally but was encouraged by two of the smartest folks I know (Don Box and Jim Miller) to include it… so I did.  Here is an example, hope you find it valuable.


 


Array.Sort(System.Array, System.Collections.IComparer) Method


[ILASM]


.method public hidebysig static void Sort(class System.Array array, class


System.Collections.IComparer comparer)


[C#]


public static void Sort(Array array, IComparer comparer)


 


Summary


Sorts the elements in the specified one-dimensional System.Array using the specified


System.Collections.IComparer implementation.


 


—-


And here is a sample application:


using System;


using System.Collections;


namespace Samples


{


      public class ArraySample


      {


            public class MyReverseComparer: IComparer


            {


                  public int Compare(Object a, Object b)


                  {


                        return -((IComparable)a).CompareTo(b);


                  }


            }


            public static void Main()


            {


                  string[] strings = {“one”, “two”, “three”};


                  Console.WriteLine(“Array elements: “);


                  Display(strings);


                  Array.Reverse(strings);


                  Display(strings);


                  Array.Sort(strings);


                  Display(strings);


                  Array.Sort(strings, new MyReverseComparer());


                  Display(strings);


            }


            public static void Display(Array a)


            {


                  foreach(object o in a)


                        Console.Write(“{0} “, o);


                  Console.WriteLine();


            }


      }


}


 


The output is


 


Array elements:


one two three


three two one


one three two


two three one


 


——


Amazon.com Sales Rank: 4,316 (up 10079 from last post)

Comments (16)

  1. Ken says:

    In para 2, did you mean "hoist" instead of "host"?

  2. I’d like to point out a mistake in the foreach statement for the array. It is NOT equivalent to the for loop you provided. The difference between the statements is the number of stack locals created. I swore I had a link to something like this on my blog, but I can’t find it. I’m guessing I only hosted this informaton in the msnews newsgroups.

    The biggest difference is that with a for loop over an array, the array reference is not copied. That means you can change the array’s length and truncate the loop. With foreach the original array is stored in a private scoped variable (not for the purposes of IL, but there is no way to access this within C#) so that modifications to the original array reference don’t affect the loop process.

    While the semantical differences are slight, they add a level of protection that isn’t apparent in the for loop version.

  3. Ah, the differences are buried in another post and not easy to find. I talk about all of the various stack locals and what they do in this post:

    http://weblogs.asp.net/justin_rogers/archive/2004/03/26/97230.aspx

    Again, the posting to the newsgroups was more focused on the IL generated by similar methods and how that IL was different. That examination was probably more appropriate to this conversation than the performance debate I hold in my blog article.

  4. Ken says:

    You can’t change the length of an array, it’s a read only property, and for good reason – .net arrays are not dynamic. Even if you could change the length of the array, changing the length of a collection while it is being enumerated invalidates the enumerator. Even modifying elements of the collection invalidates the enumerator. So I fail to see how you could detect any difference between the two versions of the loop without invoking undefined behavior.

  5. Ken, that wasn’t the point. The array reference can be changed, hence the collection you are operating over in a for loop can be swapped out for a different collection of a different length. This can not and does not happen when using the foreach statement for reasons mentioned above and in my entry.

  6. Nat Luengnaruemitchai says:

    int [] arr = new int[] {1,2,3,4};

    for (int i = 0; i < arr.Length; i++)

    {

    Console.WriteLine (i);

    }

    should be

    int [] arr = new int[] {1,2,3,4};

    for (int i = 0; i < arr.Length; i++)

    {

    Console.WriteLine (arr[i]);

    }

  7. Brad Abrams says:

    Ken, Nat, thanks for pointing out my typeos… fixed.

  8. "Some people may feel this is an odd choice as the negative values are always invalid for indices."

    Not always. Array.CreateInstance lets you create arrays with arbitrary lower bounds, including negative values.

  9. Brad Abrams says:

    Fair point, but really who is using this funcationality in the real world… we added it for compat with VB, but I think it is rarely used…

  10. Strangely enough it works well for purposes of test cases. Say I have to test the parsing of SByte values. I need an array of size 256 right? Yep, so I can go from 0 to 256 that is fine. Now when I iterate over the loop, my indexer is in terms of Byte and not SByte, so I have to remap the value to it’s appropriate place (i + SByte.MinValue). You see the test case is storing strings in an array (the string identify of the value to test). After the string is grabbed from the array and parsed I have to test the resulting value against the numeric value that it should represent. Makes a lot more sense to write loops like:

    for(SByte indexer = SByte.MinValue; indexer <= SByte.MaxValue; indexer++) {

    strings[indexer];

    SByte check = ParseSByte(indexedString);

    if ( check != indexer ) { /* We Broke */ }

    }

    instead of:

    for(SByte indexer = SByte.MinValue; indexer <= SByte.MaxValue; indexer++) {

    strings[indexer + Math.Abs(SByte.MinValue)]; // This is ugly

    }

  11. <p>In his blog, <span style="font-style: italic;">Better Living Through Software</span>, Joshua Allen looks at three different ways to loop in C# from an optimizing-for-performance point of view:</p><p><code>foreach (int i in foo) {}<br>

    <br>

    for (int i = 0; i &lt; foo.Length; i++) {}<br>

    <br>

    int len = foo.Length; for (int i = 0; …

  12. Doug McClean says:

    The SLAR on System.Array includes an example of casting a two-dimensional array of System.Decimal created with Array.CreateInstance with non-zero lower bounds to decimal[,]. This is allowed by the compiler but does not actually execute. What exactly are the rules on this? If I have a public method that takes int[] as a parameter, can I rely on the lower bound being 0? Or not?