C# Tutorial Lesson 15: Constants, Fields, Properties and Indexers

Fields

Fields are variables associated with either classes or instances of classes. There are seven modifiers which can be used in their declarations. These include the four access modifiers 'public', 'protected', 'internal' and 'private' (discussed in lesson 12) and the 'new' keyword (discussed in lesson 14). The two remaining modifiers are:

static

By default, fields are associated with class instances. Use of the 'static' keyword, however, associates a field with a class itself, so there will only ever be one such field per class, regardless of the number of the class instances (and the static field will exist even if there are no class instances). A static field need not be constant, however; it can be changed by code. In the following code example the 'setStaticField' method illustrates such a change.

1.

public class MyClass

2.

{

3.

    public static int StaticField = 1;

4.

    

5.

    public MyClass()

6.

    {}

7.

    

8.

    public void setStaticField(int i)

9.

    {

10.

        MyClass.StaticField = i;

11.

    }

12.

}


readonly

Where a field is readonly, its value can be set only once, either in the class declaration, or (for non-static fields only) in the class constructor. The following code example (which, please note, deliberately doesn't compile) shows both cases: the field StaticReadonlyInt is set in the class declaration; the field readonlyString is set in the class constructor.

1.

public class MyClass

2.

{

3.

    public static readonly int StaticReadonlyInt = 1;

4.

    public readonly string readonlyString;

5.

    

6.

    public MyClass()

7.

    {

8.

        readonlyString = "test";

9.

    }

10.

    

11.

    // this method doesn't compile

12.

    public void NotCompile()

13.

    {

14.

        MyClass.StaticReadonlyInt = 4;

15.

        this.readonlyString = "test2";

16.

    }

17.

}


While we're on declarations, note also that a field declaration can involve multiple fields, as in the following line of code

public static int a = 1, b, c = 2;

which is equivalent to

public static int a = 1;
public static int b;
public static int c = 2;

Constants

Constants are unchanging types, associated with classes, that are accessible at compile time. Because of this latter fact, constants can only be value types rather than reference types. Constant declarations take the 'const' keyword (not 'static', even though they are associated with classes), and the five modifiers 'public', 'protected', 'internal', 'private' and 'new'.

The following is a simple constant declaration, although multiple constants can be simultaneously declared.

public const int area = 4;

If you've been reading carefully, you may be struck by the thought: what's the difference between declaring a field as 'const' and declaring a field 'static readonly'. Good question. I'll leave it to the professionals to provide the definitive answer, but the general point is that static readonly fields can be reference types as well as value types.

Properties

Properties can be thought of as 'virtual' fields. From the outside, a class' property looks just like a field. But from the inside, the property is generated using the actual class fields.

Property declarations take just those modifiers taken by methods (see lesson 13) Unlike languages like Java, C# provides dedicated support for accession and mutation of these properties. Suppose, for instance, that a type contains an internal field called 'age'. With the following code one could specify a property Age, providing accessors and mutators to this internal field.

1.

public int Age

2.

{

3.

    get

4.

    {

5.

        return this.age;

6.

    }

7.

    set

8.

    {

9.

        this.age = value;

10.

    }

11.

}


Notice that the term 'value' is used in the above piece of code. This variable always holds the value passed to the 'set' block. For instance, the execution of the following line of code (assuming the appropriate class instance) would automatically set 'value' in the 'set' block to 4.

person.Age = 4;

This property Age can be described as 'read-write' since it can be both read from and written to. To make a property 'write-only' one simply does not specify a 'get' block; to make it 'read-only' one does not specify a 'set' block. The following piece of code demonstrates the read-only property 'Adult':

1.

public bool Adult

2.

{

3.

    get

4.

    {

5.

        if (this.age<18)

6.

            return false;

7.

        else

8.

            return true;

9.

    }

10.

}


Indexers

If properties are 'virtual fields', indexers are more like 'virtual arrays'. They allow a class to emulate an array, where the elements of this array are actually dynamically generated by function calls.

The following piece of code defines a class to hold a list of runners in an athletics race. The runners are held in lane order, and an indexer is exposed which allows the list to be both read from and written to. The indexer deals gracefully with cases in which the lane number passed to it is either too high or too low.

1.

class RaceDetails

2.

{

3.

    private string[] lanes;

4.

    

5.

    public RaceDetails()

6.

    {

7.

        this.lanes = new string[8];

8.

    }

9.

    

10.

    public string this[int i]

11.

    {

12.

        get

13.

        {

14.

            return (i>=0 && i<8) ? this.lanes[i] : "error";

15.

        }

16.

        

17.

        set

18.

        {

19.

            if (i>=0 && i<8) this.lanes[i] = value;

20.

        }

21.

    }

22.

}


The following simple code illustrates use being made of the class just defined. The name of the person in the race's first lane is set, and then this name is sent to a console window.

1.

RaceDetails rd = new RaceDetails();

2.

rd[0] = "fred";

3.

Console.WriteLine("Lane One : " + rd[0]);


As can be seen from the example, an indexer is defined in a similar way to a property. One important difference is in the indexer's signature; the word 'this' is used in place of a name, and after this word indexing elements are provided.

Indexers aren't differentiated by name, and a class cannot declare two indexers with the same signature. However, this does not entail that a class is limited to just one indexer. Different indexers can have different types and numbers of indexing elements (these being equivalent to method parameters, except that each indexer must have at least one indexing element, and the 'ref' and 'out' modifiers cannot be used).

Because indexing elements are not limited to integers, the original description of indexers as 'virtual arrays' actually rather undersells them. For example, where the indexing elements include strings, indexers present themselves more like hash tables.

The following code shows an implementation for the RaceDetails class of an indexer whose indexing element is a string. Using this indexer it is possible to refer to a lane using the name of the person currently filling that lane.

1.

public string this[string s]

2.

{

3.

    get

4.

    {

5.

        int laneNum = getCorrespondingLane(s);

6.

        return (laneNum<0) ? "error" : this.lanes[laneNum];

7.

    }

8.

9.

    set

10.

    {

11.

        int laneNum = getCorrespondingLane(s);

12.

        if (laneNum>=0) this.lanes[laneNum] = value;

13.

    }

14.

}

15.

16.

private int getCorrespondingLane(string myName)

17.

{

18.

    for (int x=0; x<lanes.Length; x++)

19.

    {

20.

        if (myName==lanes[x]) return x;

21.

    }

22.

    return -1;

23.

}


The following piece of code gives an example of the kind of use one might make of this string indexer.

1.

rd["fred"] = "jill";


Link Building Information