Introduction
Arrays represent a fixed number of elements of a particular type and thus allow you to treat several items as a single collection. Arrays in .NET are mostly zero-based and all array types are implicitly derived from the System.Array abstract
class, which itself is derived from the System.Object
root class. This means that arrays are always reference types that are allocated on the managed heap (as opposed to inline on the thread stack as a sequence of bytes) and that your application’s variable or field contains a reference to the array and not the elements of the array itself. This article will focus on the use of non-zero-lower bound arrays. Prior to that subject, it is necessary to touch on a few basics of System.Array
. To declare and sort an array:
using System;
class Test {
static void Main() {
int[] ar = { 3, 1, 2 };
Array.Sort(ar);
Console.WriteLine("{0}, {1}, {2}", ar[0], ar[1], ar[2]);
}
}
OUTPUT: 3, 1, 2
The reader will note the simplicity of this code and many wonder how it can be used for practical purposes. That will come later. In C# ( and the CLR), an array is denoted with square brackets after the element type. For example: char[] vowels = new char[5];
Square brackets also index the array, accessing a particular element:
char[] vowels = new char [5];
vowels [0] = 'A';
vowels [1] = 'E';
vowels [2] = 'I';
vowels [3] = 'O';
vowels [4] = 'U';
Console.WriteLine([i]);
The for
loop is one control structure that steps over every element in an array and will therefore cycle the integer I
from 0
to 4
:
using System;
class Program {
static void Main() {
char[] vowels = new char [5];
vowels [0] = 'A';
vowels [1] = 'E';
vowels [2] = 'I';
vowels [3] = 'O';
vowels [4] = 'U';
for (int i = 0; i < vowels.Length; i++)
{
Console.Write( vowels [i]);
}
}
}
OUTPUT: A, E, I, O, U
The length
property of an array returns the number of elements in an array, but these arrays do not seem to do much instead print an output of a string
of alphanumeric characters in the form an array.
The CLR permits you to create and work with arrays that have non-zero-lower bounds. To create a dynamic array, you can call the System.Array
’s class static CreateInstance( )
method. Several overloads of this method exist, allowing you to specify the type of elements in the array, the number of dimensions in the array, the lower bounds of each dimension, and the number of elements in each dimension. Mathematically, a point on a line segment is one dimensional. The line segment is two dimensional, and the geometric circle or square are three dimensional. CreateInstance
allocates memory for the array, saves the parameter information in the overhead’s portion of the array’s memory block, and returns a reference to the array. Here is some code that illustrates how to dynamically create a 2-dimensional array of System.Decimal
values. The first dimension represents calendar years from 2008 to 2012. While we have not reached the year 2010 at the writing of this article, the use of the array is to show if the income is growing (ideally when the company is expanding: a hypothetical scenario). The second dimension represent fiscal quarters from 1 to 4 inclusive.
Non-Zero-Lower Bound Arrays
using System;
public sealed class Program {
public static void Main() {
Int32[] lowerBounds = { 2008, 1 };
Int32[] lengths = { 5, 4};
Decimal[,] quarterlyIncome = (Decimal[,])
Array.CreateInstance(typeof(Decimal), lengths, lowerBounds);
Console.WriteLine("{0,4} {1, 9} {2, 9} {3, 9}, {4, 9}",
"Year", "Q1", "Q2", "Q3" , "Q4");
Int32 firstYear = quarterlyIncome.GetLowerBound(0);
Int32 lastYear = quarterlyIncome.GetUpperBound(0);
Int32 firstQuarter = quarterlyIncome.GetLowerBound(1);
Int32 lastQuarter = quarterlyIncome.GetUpperBound(1);
for (Int32 year = firstYear; year <= lastYear; year++) {
Console.Write(year + " ");
for (Int32 quarter = firstQuarter; quarter <= lastQuarter; quarter++) {
Console.Write("{0,9:c} ", quarterlyIncome[year, quarter]);
}
Console.WriteLine();
}
}
}
Array Access Performance
As stated earlier, arrays are a way to treat several items (elements) as a single collection. Collections start off where arrays end and it is the hope of the writer of this paper that the beginner can move up to Collections and Generics. But recall that a reference to the array itself is stored on the managed heap, and not the elements. Therefore accessing items from an array would be a measure of performance. Internally, the CLR actually supports two different kinds of arrays:
- Single-dimensional arrays with a lower-bound of 0
- Single-dimensional and multi-dimensional arrays with unknown lower-bound
The code below will first demonstrate that when an array has two or more dimensions, you can cast the reference returned from CreateInstance( )
to an ElementType[ ]
variable (where ElementType
is some type name), making it easier for you to access the elements in an array. If the array has one dimension, in C#, you have to use the Array
class’s methods GetValue
and SetValue
to access the elements of an array.
using System;
public static class Program {
public static void Main() {
Array a;
a = new String[0];
Console.WriteLine(a.GetType());
a = Array.CreateInstance(typeof(String),
new Int32[] { 0 }, new Int32[] { 0 });
Console.WriteLine(a.GetType());
a = Array.CreateInstance(typeof(String),
new Int32[] { 0 }, new Int32[] { 1 });
Console.WriteLine(a.GetType());
Console.WriteLine();
a = new String[0, 0];
Console.WriteLine(a.GetType());
a = Array.CreateInstance(typeof(String), new Int32[] { 0, 0 }, new Int32[]
{ 0, 0 });
Console.WriteLine(a.GetType());
a = Array.CreateInstance(typeof(String),
new Int32[] { 0, 0 },
new Int32[] { 1, 1 });
Console.WriteLine(a.GetType());
}
}
Notice the comments that follow each Console.WriteLine
indicating an output. For a single-dimension zero-based array displays a type name of System.String[]
. The one-based array displays a type of System.String[*]
. The *
lets the CLR know that this is not a zero-based array. For multidimensional arrays, the Console.WriteLine
displays a type of System.String[,]
. It should be obvious that accessing the elements of a single-dimensional, zero-based array is quicker than accessing the elements of non-zero-based arrays. The reason for this can be found in the metadata and IL code that is emitted by the CSC.exe compiler. When typing the command ildasm /metadata=validate /out: Access.il Access.il
to the type ‘type Access.il
’,you can examine and research the IL code. Jeffrey Richter, an expert on the CLR (parts of this paper have been referenced from his book “The CLR via C#”), has insisted that the C# compiler can be deceiving. During compilation there are unnecessary objects created and the code must be optimized using the /optimize+
switch. Mr. Richter has further stated that his tool of choice is the ildasm.exe. Below is a snippet of the IL code (avoiding the metadata tables):
{
.entrypoint
// Code size 62 (0x3e)
.maxstack 5
.locals init (int32[] V_0)
IL_0000: nop
IL_0001: ldc.i4.3
IL_0002: newarr [mscorlib]System.Int32 //NOTE
IL_0007: dup
IL_0008: ldtoken field valuetype '<privateimplementationdetails>{AD4A1C2
5-8CEB-439C-88EC-E7E4AD0D78E3}'/'__StaticArrayInitTypeSize=12'
'<privateimplemen>{AD4A1C25-8CEB-439C-88EC-E7E4AD0D78E3}'::'$$method0x6000001-1'
IL_000d: call void [mscorlib]System.Runtime.CompilerServices.RuntimeH
elpers::InitializeArray(class [mscorlib]System.Array,
valuetype [mscorlib]System.RuntimeFieldHandle)
IL_0012: stloc.0
IL_0013: ldloc.0
IL_0014: call void [mscorlib]System.Array::Sort<int32>(!!0[])
IL_0019: nop
IL_001a: ldstr "{0}, {1}, {2}"
IL_001f: ldloc.0
IL_0020: ldc.i4.0
IL_0021: ldelem.i4 //NOTE
IL_0022: box [mscorlib]System.Int32
IL_0027: ldloc.0
IL_0028: ldc.i4.1
IL_0029: ldelem.i4 //NOTE
IL_002a: box [mscorlib]System.Int32
IL_002f: ldloc.0
IL_0030: ldc.i4.2
IL_0031: ldelem.i4
IL_0032: box [mscorlib]System.Int32
IL_0037: call void [mscorlib]System.Console::WriteLine(string,
object,
object,
object)
IL_003c: nop
IL_003d: ret
} // end of method Test::Main
There are specific IL instructions, like newarr
, ldelem
, ldelema
, ldlen
, and stelen
, which are used to manipulate single dimension, zero-based arrays. These special IL instructions cause the JIT compiler to emit optimized code.
Interfaces
Anyone who has ever practiced COM programming will be familiar with the interface. In COM, an interface is pointed at when there is an attempt to access a COM component. The interface is actually just that: an interface between groups of semantically-related functions that are pointed in the “virtual “table” by “virtual pointers”. But in C++, inheriting a class is not always the same as inheriting an interface. In the Microsoft .NET Framework, there is a class System.Object
that defines four instance public
methods: ToString()
, Equals()
, GetHashCode()
, and GetType()
. Consider the famous “Hello, World!” code.
using System
class App {
static void Main() {
Console.WriteLine(“Hello, World!”);
}
}
Hello, World!
Now we run a program that loads this managed module and uses the Reflection
API to emit what we discussed above:
using System;
using System.IO;
using System.Reflection;
public class Meta {
public static int Main() {
Assembly a = Assembly.LoadFrom("hello.exe");
Module[] m = a.GetModules();
Type[] types = m[0].GetTypes();
Type type = types[0];
Console.WriteLine("Type [{0}] has these methods:", type.Name);
MethodInfo[] mInfo = type.GetMethods();
foreach (MethodInfo mi in mInfo)
{
Console.WriteLine(" {0}", mi);
}
return 0;
}
}
We compile this code, having loaded the assembly in the Assembly
class’s LoadFrom
method:
C:\Windows\Microsoft.NET\Framework\v2.0.50727>csc /optimize+ meta.cs
c:\Windows\Microsoft.NET\Framework\v2.0.50727>meta
Type [App] has these methods:
System.String ToString()
Boolean Equals(System.Object)
Int32 GetHashCode()
System.Type GetType()
Since the System.Object
namespace is the root class, all other classes inherit those four public
instance methods. Actually, any class derived from Object
inherits the following:
- The method signatures
- The implementation of these methods
An interface is just another way to define a named set of method signatures. An interface can inherit other interfaces. For example, the ICollection<t>
interface must implement all of the methods defined by ICollection
<t>, IEnumerable
<t>, and IEnumberable
interfaces.
Define a Type that Implements an Interface
The System.IComparable<t>
interface is defined in Mscorlib.dll as follows:
public interface IComparable<t> {
Int32 CompareTo( T other);
}
The code shown below demonstrates how to define a type that implements this interface and also shows code that compares two object Point
s:
using System;
public sealed class Point : IComparable<point> {
private Int32 m_x, m_y;
public Point(Int32 x, Int32 y) {
m_x = x;
m_y = y;
}
public Int32 CompareTo(Point other) {
return Math.Sign(Math.Sqrt(m_x * m_x + m_y * m_y)
- Math.Sqrt(other.m_x * other.m_x + other.m_y * other.m_y));
}
public override String ToString() {
return String.Format("({0}, {1})", m_x, m_y);
}
}
public static class Program {
public static void Main() {
Point[] points = new Point[] {
new Point(3, 3),
new Point(1, 2),
};
if (points[0].CompareTo(points[1]) > 0) {
Point tempPoint = points[0];
points[0] = points[1];
points[1] = tempPoint;
}
Console.WriteLine("Points from closest to (0, 0) to farthest:");
foreach (Point p in points)
Console.WriteLine(p);
}
}
OUTPUT
Points from closest to (0, 0) to farthest:
(1, 2)
(3, 3)
References
- The CLR via C#, 2nd Edition, by Jeffrey Richter
History
- 16th March, 2009: Initial post