#1,099 – Overloading the Increment Operator

You can overload the increment (++) operator in a class, providing custom increment functionality for an object.

The example below shows an overloaded increment operator defined in a Dog class.  The effect is to add 1 to the age of the dog.  We are careful to return a reference to the object that was passed in so that no other data in the instance changes.

    public class Dog 
    {
        // Increment a Dog
        public static Dog operator ++(Dog d)
        {
            d.Age++;
            return d;
        }

        public string Name { get; set; }
        public int Age { get; set; }
        public string Nickname { get; set; }

        public Dog(string name, int age)
        {
            Name = name;
            Age = age;
            Nickname = "?";
        }

        public override string ToString()
        {
            return string.Format("{0}, Age {1}, Nickname [{2}]", Name, Age, Nickname);
        }
    }

We can use the operator as follows:

            Dog kirby = new Dog("Kirby", 10);
            kirby.Nickname = "Ball Chaser";
            Console.WriteLine(kirby);

            kirby++;
            Console.WriteLine(kirby);

1099-001

#1,075 – Operator Precedence Doesn’t Affect Operand Evaluation Order

Rules for operator precedence and associativity determine the order in which the operators within an expression will be evaluated.  Parentheses can also change the order in which the operators are evaluated.

For example:

            // * operator evaluated before +
            int sum1 = 1 + 2 * 3;    // 7

            // + operator evaluated first
            int sum2 = (1 + 2) * 3;  // 9

Operands, however, are evaluated from left-right, regardless of the order of evaluation of the operators. For example, in the code below, the sub-expression Return2() * Return3() is evaluated before adding Return1().  But console output shows us that Return1() was executed first.

        static int Return1()
        {
            Console.WriteLine("1");
            return 1;
        }

        static int Return2()
        {
            Console.WriteLine("2");
            return 2;
        }

        static int Return3()
        {
            Console.WriteLine("3");
            return 3;
        }

        static void Main(string[] args)
        {
            // * operator evaluated before +
            int sum1 = Return1() + Return2() * Return3();    // 7
        }

1075-001

#1,074 – Use Parentheses in Expressions to Make Code More Readable

In the absence of parentheses within an expression, operator precedence and associativity dictate how the expression will be evaluated. Technically, you only need parentheses in the expression if you want the expression to be evaluated differently, relative to the precedence and associativity rules.

If your expression doesn’t require parentheses in order to evaluate correctly, it’s often still a good idea to include them.  The parentheses will typically improve the readability of the expression because they make the evaluation order more clear.

            // This is tough to read
            int i5 = 1 + 10 / 5 * 2 - 12 / 4 + 24 % 5 / 2 * 4;

            // This is a bit better
            i5 = 1 + ((10 / 5) * 2) - (12 / 4) + (((24 % 5) / 2) * 4);

#1,073 – Arithmetic Binary Operators are Left-Associative

All arithmetic binary operators (+, -, *, /, %) are left-associative.  This means that when there are multiple operators having the same precedence, the expression is evaluated from left to right.  Below are some examples.

            // Multiplicative, left-right

            // (10 / 5) * 2 = 4
            // [result would be 1 if right-associative]
            int i = 10 / 5 * 2;

            // (40 % 12) * 2 = 8
            // [result would be 16 if right-associative]
            int i2 = 40 % 12 * 2;

            // Additive, left-right

            // (4 - 3) + 5 = 6
            // [result would be -4 if right-associative]
            int i3 = 4 - 3 + 5;

Note that the multiplicative operators (*, /, %) have a higher precedence than the additive (+, -).  This means that if there are no parentheses, the multiplicative operators are evalated before the arithmetic.

            // 1 + (2 * 3) = 7
            int i4 = 1 + 2 * 3;

            // Equivalent to 1 + ((10 / 5) * 2) = 5
            int i5 = 1 + 10 / 5 * 2;

#1,072 – How the Unary Minus Operator Can Fail

There are cases when applying a unary operator to an operand results in a value that does not fit into the data type of the operand. Consider the int type, whose range is -2,147,483,648 to 2,147,483,647.  If we assign the minimum value (largest negative number) and try to negate it with the unary operator, the negation will fail.

By default, arithmetic operations work in an unchecked context.  In this case, the unary operator does nothing, just returning the value of the original operand.

            // int range is -2,147,483,648 to 2,147,483,647
            int n = int.MinValue;
            n = -n;  // n unchanged

If we do the same operation in a checked context, we’ll get an overflow exception.

1072-001

 

Note that a cast to a “larger” data type fails if it’s done outside of the unary operator.  But casting before applying the operator does work.

            long l = (long)-n;  // Still fails

            long l2 = -(long)n;  // This works

 

#1,071 – The Unary Minus Operator

Unary operators are operators that affect a single operand.

The unary – operator is used to negate a numeric value.  (Subtract the value from zero).  The unary + operator does not affect a value, so is not worth mentioning.

            // Unary minus operator applied to constants
            int x = 5;
            int y = -5;
            Console.WriteLine("x={0}, y={1}", x, y);

            // Unary minus operator applied to variable
            x = -x;
            y = -y;
            Console.WriteLine("x={0}, y={1}", x, y);

1071-001

The unary operator also works with floating point and decimal values.

            double d = -4.2;
            float f = -4.2f;
            Console.WriteLine("d={0}, f={0}", d, f);

            decimal m = -0.01m;
            Console.WriteLine("m={0}", m);

1071-002

 

#592 – Optional Parameters in Indexers

In addition to defining optional parameters within methods, you can also include an optional parameter in an indexer.

For example, assume that we have a Dog class that keeps track of the sounds that a dog makes when he barks and that we’ve defined an indexer for the class that retrieves a “bark sound” from that list.

        public string this[int i]
        {
            get
            {
                return BarkRecord[i];
            }
        }

Using the indexer:

            kirby.Bark("Woof");
            kirby.Bark("Bow-wow");
            kirby.Bark("Urgh");

            Console.WriteLine(kirby[2]);


We can now add an optional parameter to the indexer, to indicate that we’d like the date/time of the bark returned as well.

        public string this[int i, bool includeTime = false]
        {
            get
            {
                string barkInfo = BarkRecord[i];

                if (includeTime)
                    barkInfo = barkInfo + string.Format(" at {0}", BarkTime[i]);

                return barkInfo;
            }
        }

Using the indexer:

            Console.WriteLine("Bark at [2]={0}", kirby[2]);
            Console.WriteLine("Bark at [1]={0}", kirby[1, true]);

#580 – as Operator Can Generate Compile-Time Errors

The as operator can perform reference conversions between types in a type hierarchy, returning null if the conversion is not possible.  But there are some cases when the compiler knows that no conversion is possible between the specified types, so it will generate an error at compile time.

Dog d = new Dog("Lassie", 12);

// Can't convert a Dog to a Cow--compile-time error
Cow c = d as Cow;

#579 – Typical Pattern for Using as Operator

When you use the as operator to attempt to convert an expression to a particular type, you typically follow the pattern shown below.

In most cases, you have a variable of a base class that stores some object and you use the as operator to determine if it stores an object of a particular derived type.  In the example below, Terrier is a class that inherits from Dog.

            // Dog variable referring to object of type Terrier
            Dog d = new Terrier("Jack", 17, "Crabby");

            // Elsewhere in our code, we have variable
            // of type Dog and want to see if it refers to a
            // Terrier.
            Terrier t = d as Terrier;
            if (t != null)
                t.TerrierMethod();

You can do the same thing with the is operator, though it is a little less efficient, because it actually does the type conversion twice.

            // A bit less efficient
            if (d is Terrier)
                ((Terrier)d).TerrierMethod();

#578 – Using the as Operator to Do Type Conversions

The as operator attempts to convert an expression to a specified type, returning the value of null if the conversion fails.  It behaves similarly to doing the conversion using a cast, but does not throw an exception if the cast fails.

// (Terrier is a sub-class of Dog)

Dog d = new Dog("Fido", 5);
Terrier t = new Terrier("Jack", 17, "Crabby");
Dog d2 = t;

Dog dTest = d as Dog;   // ok
dTest = d as Terrier;   // null

dTest = t as Dog;       // ok
Terrier tTest = t as Terrier;   // ok

dTest = d2 as Dog;      // ok
tTest = d2 as Terrier;  // ok

Object oTest = d as object;    // ok