#517 – Static Classes

A static class is a class that contains only static members and cannot be instantiated.  You declare a class as static using the static keyword.

A static class:

  • Can only have static members (can’t contain instance members)
  • Cannot be instantiated
  • Cannot serve as the type of a variable
  • Cannot serve as a parameter type
  • Cannot inherit from another class
  • Cannot serve as a parent of another class

    public static class DogMethods
    {
        private static int NumBarks = 5;

        public static void BarkALot(Dog d)
        {
            for (int i = 1; i <= NumBarks; i++)
                d.Bark();
        }

        public static string MarriedName(Dog d1, Dog d2)
        {
            return d1.Name + d2.Name;
        }
    }

            Dog d1 = new Dog("Kirby", 12);
            Dog d2 = new Dog("Lassie", 47);

            DogMethods.BarkALot(d1);
            Console.WriteLine(DogMethods.MarriedName(d1, d2));

#366 – Defining an Indexer with More than One Parameter

An indexer will typically have a single parameter, often based on an integer type.

        public LogMessage this[int i]
        {
            get { return messages[i]; }
            set { messages[i] = value; }
        }

You can also define an indexer that takes more than one parameter.  In the example below, we have an internal data structure that stores a list of messages for each day of the week.  We can retrieve a specific message by passing in a parameter representing the day, as well as a 0-based index into the list of messages for that day.

            // Get 1st Saturday message
            LogMessage msg = log[Days.Sat, 0];

Here’s what the definition of the indexer looks like.

        private Dictionary<Days, List<LogMessage>> dailyMessages = new Dictionary<Days, List<LogMessage>>();

        public LogMessage this[Days day, int i]
        {
            get { return dailyMessages[day][i]; }
        }

#365 – Overloading an Indexer

You can define several versions of an indexer in a class, each indexed using a different type, overloading the indexer.

In the example below, we can index using the Days enumerated type or a 0-based integer representing the day.

    public class Logger
    {
        private Dictionary<Days, LogMessage> dailyMessages = new Dictionary<Days, LogMessage>();

        public LogMessage this[Days day]
        {
            get { return dailyMessages[day]; }
            set { dailyMessages[day] = value; }
        }

        public LogMessage this[int i]
        {
            get { return dailyMessages[(Days)i]; }
            set { dailyMessages[(Days)i] = value; }
        }
    }

Using this class, we could then index using either the Days or the integer type.

            Logger log = new Logger();

            log[Days.Mon] = new LogMessage("Monday was a good day");
            log[2] = new LogMessage("Tuesday not bad either");

In this example, we indexed into the same internal data object.  But we could have also indexed into different objects, depending on the indexer’s type.

#364 – Defining an Indexer whose Parameter Is an Enumerated Type

You typically define an indexer that is indexed using an integer parameter.  When you define an indexer, the parameter used as the index can actually be of any type.

Below is an example of using an enumerated type as the index.

Suppose you want a class that stores a log message for each day of the week.  We might want to use the class like this:

            Logger log = new Logger();

            log[Days.Mon] = new LogMessage("Monday was a good day");
            log[Days.Fri] = new LogMessage("Everyone went home");

            LogMessage msg = log[Days.Mon];  // Retrieve Monday message

We define an indexer that takes a parameter whose type is the enumerated Days type.

    public class Logger
    {
        // Internal collection
        private Dictionary<Days, LogMessage> dailyMessages = new Dictionary<Days, LogMessage>();

        // Indexer
        public LogMessage this[Days day]
        {
            get { return dailyMessages[day]; }
            set { dailyMessages[day] = value; }
        }
    }

#363 – An Indexer Can Have Both get and set Accessors

Similar to a property, an indexer in C# can have either a get accessor for reading an element, a set accessor for writing an element, or both.

  • get accessor – read-only behavior
  • set accessor – write-only behavior
  • get and set accessors – read-write behavior

Here’s an example of a read-write accessor.

    public class Logger
    {
        private List<LogMessage> messages = new List<LogMessage>();

        // Read-write indexer
        public LogMessage this[int i]
        {
            get { return messages[i]; }
            set { messages[i] = value; }
        }

        public void LogAMessage(LogMessage msg)
        {
            messages.Add(msg);
        }
    }

We can then use the indexer to read and write elements in the internal list.

            Logger log = new Logger();

            log.LogAMessage(new LogMessage("This happened", 5));
            log.LogAMessage(new LogMessage("Something else happened", -1));

            LogMessage lm = log[1];   // Get 2nd element
            log[0] = new LogMessage("New 1st guy", 1);  // Replace 1st element

#362 – Defining an Indexer

An indexer is a class member that allows client code to index into an instance of class using an integer-based (0..n-1) index.  The indexer allows an instance of a class to behave like an array.

Assume that we define a Logger class that stores a series of log messages in an internal collection:

    public class Logger
    {
        private List<LogMessage> messages = new List<LogMessage>();

        public void LogAMessage(LogMessage msg)
        {
            messages.Add(msg);
        }

    }

We define an indexer by defining a member that looks like a property, but uses the this keyword.

        public LogMessage this[int i]
        {
            get
            {
                return messages[i];
            }
        }

The indexer allows us to write code that indexes into an instance of the class:

            Logger log = new Logger();

            log.LogAMessage(new LogMessage("This happened", 5));
            log.LogAMessage(new LogMessage("Something else happened", -1));

            LogMessage lm = log[1];   // returns 2nd element

#361 – An Abstract Property Has No Implementation

You can declare a property as abstract, indicating that it has no implementation in the base class.  The base class indicates the property’s type and whether it has get and set accessors in any derived classes.

A base class that defines an abstract property must also be marked as abstract itself.  It wouldn’t make sense to create instances of the base class, since the implementation for the abstract property is missing.

A derived class must provide the implementation (in get/set accessors) for any properties that are abstract in the base class.

Defining an abstract property:

    public abstract class Dog
    {
        public abstract string Temperament { get; }
    }

Providing an implementation for the get accessor in a derived class:

    public class Terrier : Dog
    {
        protected string temperament;
        public override string Temperament
        {
            get
            {
                return string.Format("Terrier {0} is {1}", Name, temperament);
            }
        }

#360 – Property Modifiers Required for Polymorphic Behavior

There are three combinations of modifiers that make sense, in determining whether properties in a class are virtual or non-virtual.

Typical combinations of modifiers for base class / derived class (assuming that the property’s name and type are the same in both the base and derived class):

  • (no modifier) / new – Both properties are non-virtual, derived class property hides the base class property
  • virtual / override – Both properties are virtual and support polymorphic behavior
  • virtual / new – Base class property is virtual, derived class property is non-virtual, derived class property hides the base class property

There are two other combinations that are allowed, but result in a compiler warning indicating that you should use new in the derived class to be explicit:

  • (no modifier) / (no modifier) – is equivalent to: (no modifier) / new
  • virtual / (no modifier) – is equivalent to: virtual / new

#359 – The Difference Between Virtual and Non-Virtual Properties

In C#, virtual properties support polymorphism, by using a combination of the virtual and override keywords.  With the virtual keyword on the property in the base class and the override keyword on the property in the derived class, both properties are said to be virtual.

Properties that don’t have either the virtual or override keywords, or that have the new keyword, are said to be non-virtual.

When you read or write a virtual property through an object reference, the run-time type of the object is used to determine which implementation of the property to use.

When you read or write a non-virtual property through an object reference, the compile-time type of the object is used to determine which implementation of the property to use.

#358 – Virtual Properties Support Polymorphism

In C#, polymorphism is implemented using virtual members–which can be methods, properties, indexers or events.

A virtual property has an implementation in the base class that can be overridden in a derived class.  When the property is read or written, the get or set accessor that is used is determined at run-time based on the type of the underlying object.

A virtual property is defined in the base class using the virtual keyword.

        protected string temperament;
        public virtual string Temperament
        {
            get
            {
                return string.Format("{0} is {1}", Name, temperament);
            }
        }

A virtual property is overridden in a derived class using the override keyword.

        public override string Temperament
        {
            get
            {
                return string.Format("Terrier {0} is {1}", Name, temperament);
            }
        }

Using the property:

            Dog kirby = new Dog("Kirby", 15);
            Console.WriteLine(kirby.Temperament);  // Kirby is Average

            Dog jack = new Terrier("Jack", 15);
            Console.WriteLine(jack.Temperament);   // Terrier Jack is Surly

Follow

Get every new post delivered to your Inbox.

Join 37 other followers