Chapter 7

 

Operators

 

We all like to express ourselves in different ways. Some smile, some laugh, some screech and some even shout. The C# programming language lays a lot of emphasis on expressions and has one of the largest chapters in its documentation devoted to it. An expression is a sequence of operators and operands providing some computational activity.

 

An expression can be built in n number of ways. It can be as simple as a value resulting in a certain type. It can also consist of a variable with a type. A namespace can also occur in an expression but it must be only at the beginning. A type like a namespace can also occur on the left hand side of an expression. The usual gang of idiots like properties, indexers, properties can also be included in an expression. However, the final result of an expression can never ever be a namespace, type, method group or event access. These can only occur in intermediate results.

 

Expressions are not conjured out of thin air but are made up of only two entities, operators and what we offer them in homage, operands. Operands can include constants, variables, indexers etc. The second entity, operators is what this chapter is dedicated to.

 

 

Checked and Unchecked Operators

 

a.cs

class zzz

{

byte i = 255;

byte j = 256;

public static void Main()

{

byte k = -1;

}

}

 

Compiler Error

a.cs(7,10): error CS0031: Constant value '-1' cannot be converted to a 'byte'

a.cs(4,10): error CS0031: Constant value '256' cannot be converted to a 'byte'

 

A byte can only store values from 0 to 255. We have initialized i to the largest possible value a byte can store i.e. 255. For the second variable, we try and go beyond the range that a byte can handle. Well, the compiler comes back screaming at us with an error. This is the behaviour in the case of variable j. This is enough proof that the compiler checks the values, the variables are being initialized to. For that matter, it could be a local variable created in a function or an instance variable created out of a function. Come what may, C# does not allow you to take a byte and give it a value falling outside its range of 0 to 255.

 

a.cs

class zzz

{

public static void Main()

{

byte k = (byte)256;

byte l = (byte)257;

byte m = (byte)514;

System.Console.WriteLine(k + " " + l + " " + m);

}

}

Compiler Error

a.cs(5,11): error CS0221: Constant value '256' cannot be converted to a 'byte' (use 'unchecked' syntax to override)

a.cs(6,11): error CS0221: Constant value '257' cannot be converted to a 'byte' (use 'unchecked' syntax to override)

a.cs(7,11): error CS0221: Constant value '514' cannot be converted to a 'byte' (use 'unchecked' syntax to override)

 

However, one way out is by using a cast. A cast is some data type within () brackets. The object on the right temporarily is converted to the data type within the cast. It is not like an operation where you get a permanent scar. This conversion is forgotten when you leave the statement. No permanent damage or change is carried out on the object.

 

By casting, we are commanding the compiler to break or overlook its own rules. C# realizes that 256 is larger than 255, so it will keep subtracting the number from 256. The resultant value is the number assigned to the byte variables.

 

To place it more accurately, the compiler divides the number by 256 and the remainder is the assigned value to the variable. Pure mathematics in school revealed that when any number is divided by another number lets say 100, the remainder is always less than 100 (the divisor). This remainder is now a byte that can be equated to the byte variables on the left.

 

The error demands an unchecked modifier while casting.

 

a.cs

class zzz

{

public static void Main()

{

byte k = unchecked((byte)256);

byte l = unchecked((byte)257);

byte m = unchecked((byte)514);

System.Console.WriteLine(k + " " + l + " " + m);

}

}

 

Output

0 1 2

 

a.cs

class zzz {

int b = 1000000;

int c = 1000000;

public static void Main()

{

int j;

zzz a = new zzz();

j = a.abc(a.b,a.c);

System.Console.WriteLine(j);

j = a.pqr(a.b,a.c);

System.Console.WriteLine(j);

a.xyz(a.b,a.c);

}

int abc( int x, int y)

{

return x*y;

}

int pqr( int x, int y)

{

return unchecked(x*y);

}

int xyz( int x, int y)

{

return checked(x*y);

}

}

 

Output

-727379968

-727379968

 

Exception occurred: System.OverflowException: An exception of type System.OverflowException was thrown.

   at zzz.Main()

 

There are two operators in the C# programming language called checked and unchecked. They deal with problems of overflow or underflow in values. In the above examples, the compiler is aware of the problem at compile time. A vast majority of us believe that computers are highly intelligent. The same people also believe in the tooth fairy. What we would like to say very emphatically is that the compilers do not go far enough in understanding what our program is up to. They apply a simple set of rules and if our program fails them, an error is flagged.

 

By default, C# writes the operator unchecked before all our expressions. Thus the functions abc and pqr behave in the same way.

 

The compiler adds a lot of its code to the code we write as well as rewrites a major part of it too. In the function abc or pqr, the compiler does not actually replace the variables with their values while compiling. If it did so, it would come back and inform us about an overflow. The unchecked operator behaves like our parents. They normally let us do what we want as they believe we are old enough to decide our fate. In this case the compiler does assume you know what you are doing and simply gives you the wrong answer of overflow results.

 

Remember the compiler does not hand run your code. If we use the checked operator, the compiler will now behave in a different way at run time but not at compile time. It will now generate an exception if an overflow occurs. Thus, it keeps a watchful eye on your program at runtime. It is advisable to use the checked operator, as we do not know what values our variables may hold. Also, please catch the exception thrown to prevent the error message box showing up as it will scare the shits of the user.

 

a.cs

class zzz

{

const int x = 1000000;

const int y = 1000000;

static int abc() {

return checked(x * y);               

}

static int pqr() {

return unchecked(x * y);

}

static int xyz() {

return x * y;                                         

}

int aaa() {

return x * y;                                         

}

static void Main()

{

}

}

 

Compiler Error

a.cs(6,16): error CS0220: The operation overflows at compile time in checked mode

a.cs(12,8): error CS0220: The operation overflows at compile time in checked mode

a.cs(15,8): error CS0220: The operation overflows at compile time in checked mode

 

C# treats a const variable very differently from a normal one. In the case of a const, the compiler is cent percent sure that the value will never change.

 

Now C# hits the ceiling as in the function abc, it detects an overflow. It gives an error about the impending danger. In the case of function pqr, using unchecked, we have informed the compiler not to bother us as we are old enough to understand what we are doing; even though it is blatantly obvious that we are not in this case. C# bows to our wishes and issues no error message.

 

Under normal circumstances, when one of these operators is not specified for compile time checks, by default,  the reverse of it is applicable for the run time checks. At compile time, the checked operator is on for const variables by default, irrespective of the modifier static. This explains the last two errors. Thus for compile time checks the default is checked for const variables and the compiler will try as hard as possible to check for overflows. However, it will never replace variables with values.

 

 

 

a.cs

class zzz

{

public const int i = checked((int)2147483648);

public const int j = unchecked((int)2147483648);

}

 

Compiler Error

a.cs(3,31): error CS0221: Constant value '2147483648' cannot be converted to a 'int' (use 'unchecked' syntax to override)

 

a.cs

class zzz

{

public int k = unchecked((int)2147483648);

public int l = (int)2147483648;

public int m = checked((int)2147483648);

}

 

Compiler Error

a.cs(4,17): error CS0221: Constant value '2147483648' cannot be converted to a 'int' (use 'unchecked' syntax to override)

a.cs(5,25): error CS0221: Constant value '2147483648' cannot be converted to a 'int' (use 'unchecked' syntax to override)

 

The only difference between the two programs is that in the first program, the variables are const whereas in the second program they are not. The error messages are the same. We could not combine them into one program as at times the compiler stops at the first error message it encounters. Sometimes it does not display all of them.

 

We are trying to cast a number larger than an int to an int. Thus we see the error. Reduce the number by one and the error like a nightmare disappears. This happens only for checked and not unchecked.

 

Remember for compile time checks the default is checked in case you have become absentminded along the way. The ++, --  operators act on overflow in the same way.

 

a.cs

class zzz

{

public static void Main()

{

int k = 65537;

short j = (short)k;

System.Console.WriteLine(j);

short l = checked((short)k);

System.Console.WriteLine(l);

}

}

 

Output

1

Exception occurred: System.OverflowException: An exception of type System.OverflowException was thrown.

   at zzz.Main()

 

The same rule also applies to conversions. We cannot directly convert an int to a short as the short is smaller in range of values than an int. Hence the cast. The compiler is oblivious of the error we have made and when we run the program, the short j gets truncated. This is so because the number gets divided by 65536, but as the default is unchecked no exception is thrown. In the second case, however, we have explicitly used the operator checked.

 

Remember, the rules for a constant and non-constant expression are different. Run time behaviour can be changed by compile time switches. What we've learnt is the default behaviour as of this version. The behaviour can either be checked or unchecked but there is no in between option. The checked and unchecked operators are not nosy parkers. They restrict themselves only to the () brackets. Their domain does not extend beyond that.

 

Is operator

 

a.cs

class yyy

{

}

class zzz

{

public static void Main()

{

yyy a  = new yyy();

if ( a is yyy)

System.Console.WriteLine("yyy");

if ( a is System.String)

System.Console.WriteLine("string");

if ( a is object)

System.Console.WriteLine("object");

}

}

 

Compiler Warning

a.cs(9,6): warning CS0183: The given expression is always of the provided ('yyy') type

a.cs(11,6): warning CS0184: The given expression is never of the provided ('string') type

a.cs(13,6): warning CS0183: The given expression is always of the provided ('object') type

 

Output

yyy

object

 

The 'is' operator is used to determine the run time type of an object or its compatibility with another type. It returns either a true or false, that is, a boolean. As the object a is an instance of class yyy, 'a is yyy' is obviously true. Also, a not being a string will not execute the second System.Writeln. Finally, as all classes derive from object the last if results in a true hence object is displayed.

 

The warnings are issued as even a blind man will tell you that the conditions in the if statement are determinable at run time. Anything that the compiler can't figure out and can be determined at run time is flagged as a warning.

 

a.cs

class yyy

{

}

class zzz

{

public static void Main()

{

if ( yyy is a)

System.Console.WriteLine("yyy");

}

}

 

Compiler Error

a.cs(8,6): error CS0118: 'yyy' denotes a 'class' where a 'variable' was expected

 

The operands to the 'is' operator cannot be reversed. It first requires the name of the object and then the class or type name.

 

a.cs

class yyy

{

}

class xxx

{

}

class zzz

{

public static void Main()

{

zzz a = new zzz();

yyy b = new yyy();

xxx c = new xxx();

a.abc(b);

a.abc(c);

a.abc(20);

a.abc("hi");

}

public void abc(object o)

{

if ( o is yyy)

System.Console.WriteLine("yyy " + o);

if ( o is xxx )

System.Console.WriteLine("yyy " + o);

if ( o is string )

System.Console.WriteLine("yyy " + o);

}

}

 

Output

yyy yyy

yyy xxx

yyy hi

 

The compiler gives us no warnings. Whenever we call a function, we need to be very particular about the data type of the parameter or else it will generate an error. The first exception to this rule can be when objects are cast to each other. The second exception can be with objects that are derived from each other. As all classes derive from the Object class, the parameter set in function abc can be of object type, which means that abc can now receive different parameter types. It will not throw any error.

 

We have three is operators checking whether o is a yyy, xxx or string. The WriteLine function also displays the object. Remember we are checking o with different class names and the if statement will be true only once in each case.   

 

a.cs

class yyy

{

}

class xxx

{

}

class zzz

{

public static void Main()

{

zzz a = new zzz();

xxx c = new xxx();

if (c is yyy)

System.Console.WriteLine("yyy " + c);

}

}

Compiler Warning

a.cs(13,5): warning CS0184: The given expression is never of the provided ('yyy') type

 

In the above case, all that we receive is a warning. The object c is an instance of xxx and not yyy. There is no way for the compiler to convert a xxx object into a yyy object. The warning illustrates the wisdom of the compiler.

 

a.cs

class yyy

{

}

class xxx

{

public static explicit operator yyy (xxx a)

{

return new yyy();

}

}

class zzz

{

public static void Main()

{

xxx c = new xxx();

if (c is yyy)

System.Console.WriteLine("yyy " + c);

else

System.Console.WriteLine("false " + c);

}

}

 

Compiler Warning

a.cs(17,5): warning CS0184: The given expression is never of the provided ('yyy') type

 

Output

false xxx

 

In this program, we thought the explicit operator introduced would convert c, an xxx object to yyy. We pleaded hard enough but it just did not help. We replaced the explicit modifier with implicit but the compiler as adamant it is, refused to convert the xxx object into a yyy object. We should have read the warning more carefully.

 

a.cs

class yyy

{

}

class xxx

{

public static implicit operator yyy (xxx a)

{

System.Console.WriteLine("operator");

return new yyy();

}

}

class zzz

{

public static void Main() {

zzz a = new zzz();

xxx c = new xxx();

a.abc(c);

}

public void abc(yyy o)

{

if (o is yyy)

System.Console.WriteLine("yyy " + o);

}

}

 

Compiler Warning

a.cs(21,5): warning CS0183: The given expression is always of the provided ('yyy') type

 

Output

operator

yyy yyy

 

We now reverted to our earlier program where we passed the object as a type yyy class, yet the compiler gives us the same old warning. The compiler can convert an xxx to a yyy thanks to the operator. If we change the parameter in abc from class yyy to object, the is will   still be false.

The return value of an is operator notifies whether the object can be converted to a particular data type or not. The datatype follows the is operator whereas the object precedes it.  The is operator ignores user defined conversions and only looks at reference conversions. It can be used with value and reference types.

 

The as operator

 

a.cs

class zzz

{

public static void Main()

{

string s;

s =  "hi" as string;

System.Console.WriteLine(s);

s =  100 as string;

System.Console.WriteLine(s);

}

}

 

Compiler Error

a.cs(8,6): error CS0039: Cannot convert type 'int' to 'string' via a built-in conversion

 

The as operator behaves like a cast. It converts one data type into another. Thus in the first case, it converts "hi" into a string. As hi can be converted to a string, the value of s is hi. However, a 100 cannot be converted into a string and thus we get an error. A cast normally throws an exception whereas the as operator returns a error.

 

a.cs

class zzz {

public static void Main()

{

int i = 100 as int;

}

}

 

 

Compiler Error

a.cs(4,9): error CS0077: The as operator must be used with a reference type ('int' is a value type)

 

The error message for once makes it very clear that the as operator does not accept a value type like int but requires reference types like string. The earlier rules on user supplied conversions et all apply verbatim here also.

 

The Conditional Operator

 

a.cs

class zzz {

public static void Main() {

int i = 10;

string  j;

j = i >= 20 ? "hi" : "bye";

System.Console.WriteLine(j);

j = i >= 2 ? "hi" : "bye";

System.Console.WriteLine(j);

}

}

 

Output

bye

hi

 

The conditional operator is also called the ternary operator as it takes three operands. The first is the condition to be checked. If it results in true then the answer lies between the ? and :. If it evaluates to false, the answer is within the : to the semi colon. Thus the ? : operator operates like an if else on one line. As the C programming language offered us this operator and C++ followed suit, then how could C# refuse. It is available but use it at your own risk.

 

a.cs

class zzz {

public static void Main() {

int i = 10;

string  j;

j = i >= 20 ? 7 > 3 ? "no"  : "bye" : "yes" ;

System.Console.WriteLine(j);

j = i >= 20 ? 7 < 3 ? "no"  : "bye" : "yes" ;

System.Console.WriteLine(j);

j = i <= 20 ? 7 < 3 ? "no"  : "bye" : "yes" ;

System.Console.WriteLine(j);

j = i <= 20 ? 7 > 3 ? "no"  : "bye" : "yes" ;

System.Console.WriteLine(j);

}

}

 

Output

yes

yes

bye

no

 

The ?: operator is right associative, which means that it gets evaluated from right to left. In the first case, the compiler only sees the condition i >= 20. As it is false everything in between the ? : is ignored and the string j contains yes. In the second case, the same rule applies and the compiler misses the fact that we have changed something from the ? to colon. In the third case, the condition is true, the compiler proceeds to the ? onwards and here sees another conditional operator. As it evaluates to false, the result is bye and in the last case it evaluates to true and thus the result is no.

 

The type of the conditional operator depends on the type of its two operands. In the above case, the type is string as both the operands are strings.

 

a.cs

class zzz

{

public static void Main()

{

object i;

xxx x = new xxx();

yyy y = new yyy();

i =  7 > 3  ? x : y ;

System.Console.WriteLine(i);

}

}

class xxx

{

}

class yyy

{

}

 

Compiler Error

a.cs(8,15): error CS0173: Type of conditional expression can't be determined because there is no implicit conversion between 'xxx' and 'yyy'

 

The compiler tries to convert an xxx type object to a yyy type object but there is no such implicit conversion available.

 

a.cs

class zzz

{

public static void Main()

{

object i;

xxx x = new xxx();

yyy y = new yyy();

i =  7 > 3  ? x : y ;

System.Console.WriteLine(i);

}

}

class xxx

{

public static implicit operator yyy ( xxx a)

{

System.Console.WriteLine("operator");

return new yyy();

}

}

class yyy

{

}

 

 

Output

operator

yyy

 

We do not receive any error as the compiler converts the xxx object to a yyy object using the implicit operator supplied. The implicit operator performs its job by converting the xxx to another data type, in this case yyy. Finally, the ToString function of the object i is called which displays its data type as yyy.

 

a.cs

class zzz

{

public static void Main()

{

object i;

xxx x = new xxx();

yyy y = new yyy();

i =  7 < 3   ? x : y ;

System.Console.WriteLine(i);

}

}

class xxx

{

}

class yyy

{

public static implicit operator xxx ( yyy a)

{

System.Console.WriteLine("operator");

return new xxx();

}

}

 

Output

operator

xxx

 

We have brought about two changes. First, we've changed the condition to return false so that the result now is a yyy object. Next is that the implicit operator is used to convert the data type to xxx. So much done, the compiler now wants to determine the controlling type of the conditional operator. It tries to convert one to another and if it fails, it flags an error. It tries the type conversion from the 2nd operand to 3rd and then vice versa.

 

i =  7 >  3   ? x : y ;

 

If we reverse the logical condition, the resultant output now shows:

 

Output

xxx

 

The compiler has a mind of its own. It realizes that the logical condition is true and the result is x, an xxx object. As it is, i is already an xxx object and the conversion available is more to convert from yyy to xxx, so it doesn't use it. Hence, the operator is not called. When the condition turns out to be false, as seen earlier, the result is a yyy object. This needs conversion so we bring in a user defined operator to do it.

 

a.cs

class zzz

{

public static void Main()

{

object i;

xxx x = new xxx();

yyy y = new yyy();

i =  7 < 3   ? x : y ;

System.Console.WriteLine(i);

}

}

class xxx

{

public static implicit operator yyy ( xxx a)

{

System.Console.WriteLine("operator yyy");

return new yyy();

}

}

class yyy

{

public static implicit operator xxx ( yyy a)

{

System.Console.WriteLine("operator xxx");

return new xxx();

}

}

 

Compiler Error

a.cs(8,16): error CS0172: Type of conditional expression can't be determined because 'xxx' and 'yyy' both implicitly convert to each other  

 

The problems of plenty! We have supplied an operator to convert a yyy object to an xxx object and vice versa. This confuses the life out of the compiler and therefore we get an error as both operators can do the same job. If the compiler chooses any one of them, it could be accused of being impartial. Nor does it like gambling. So it flags an error.

 

a.cs

class zzz

{

public static void Main()

{

xxx i;

xxx x = new xxx();

yyy y = new yyy();

int a = 4, b= 6;

i =  a > b ? x : y ;

System.Console.WriteLine(i);

}

}

class xxx {

public static implicit operator yyy ( xxx a)

{

System.Console.WriteLine("operator");

return new yyy();

}

}

class yyy {

}

Compiler Error

a.cs(9,6): error CS0029: Cannot implicitly convert type 'yyy' to 'xxx'

 

The above program tours you into the mind of a compiler. It gives you a good insight into its workings. The conditional operator has a condition whose result can be estimated only at run time. Thus the compiler cannot fathom whether the type of the operator will be xxx or yyy. The compiler assumes the result to be a yyy object. Under this pretext, the following result of the conditional too has to be stored in a xxx type object. However, there is no such conversion available hence it errs. The compiler could have assumed the conditional to be true thereby resulting in an xxx object. Then no conversions would be required. Well, it performs both these checks, hence it fails just to be on the side of your safety.

 

If we change the data type of i from an xxx to yyy, we get no errors as the compiler assumes that the result of the operator will be a xxx as the worst case possible. It has an operator to convert to a yyy. Move the operator function to the yyy class and the error will now read as given below.

 

Compiler Error

a.cs(9,6): error CS0029: Cannot implicitly convert type 'xxx' to 'yyy'

 

Let us now find out how C# evaluates a conditional operator.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

int i =   a ? 10 : 20 ;

System.Console.WriteLine(i);

}

}

class yyy

{

}

Compiler Error

a.cs(6,11): error CS0029: Cannot implicitly convert type 'yyy' to 'bool'

 

The compiler expects a condition expression with a ternary operator. It tries to convert a yyy object into a bool and realizes that there is no such possibility. So it throws up its hands in despair and gives us the above error.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

int i =   a ? 10 : 20 ;

System.Console.WriteLine(i);

}

}

class yyy

{

public static bool operator true ( yyy a)

{

System.Console.WriteLine("operator true");

return true;

}

public static bool operator false ( yyy a)

{

System.Console.WriteLine("operator false");

return true;

}

}

 

Output

operator true

10

 

We create an operator called true, which the compiler calls to figure out the value of the logical expression. As we returned true in our operator, the conditional operator is considered as true and hence the result is 10.

The operator true is ideal for conditions where we supply an object in place of a condition and require a boolean. This operator converts a yyy to a boolean true . Replace the return true in operator true to return false. The output will be operator true and 20.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

int i =   a ? 10 : 20 ;

System.Console.WriteLine(i);

}

}

class yyy

{

public static implicit operator bool( yyy a)

{

System.Console.WriteLine("operator bool");

return true;

}

public static bool operator true ( yyy a)

{

System.Console.WriteLine("operator true");

return true;

}

public static bool operator false ( yyy a)

{

System.Console.WriteLine("operator false");

return true;

}

}

 

Output

operator bool

10

 

We've learnt from the experts, that is, C#, on how to complicate lives. To convert a yyy object to a boolean value, it first looks for the operator bool. This operator returns a true or false depending upon the parameter given to it. As we are explaining concepts, we return true. If the operator bool is present, like in our case, C# will not look further for an operator true. If it is not available, like in the earlier example, then the operator true gets called. Remember the order is first bool then true. If both are present, fortunately, we get no error.

 

So what happens to operator false? If both of these are not available, will it take operator false? Well, this is not the case. We land up with an error as shown below.

 

Compiler Error

a.cs(12,20): error CS0216: The operator 'yyy.operator false(yyy)' requires a matching operator 'true' to also be defined

 

Try giving only operator true and it will shoot up a similar error.

 

Boolean Operators

 

The if statement, until this moment, is the only known way to make our programs highly intelligent. To add some more intelligence to the if statement within our code, we use the boolean operators. They are the & (and)  and | (or). These operators make the if statement more restrictive. They also add more conditions or intelligence to the if statement. The more complex the if statement, the more dynamic our code becomes.

 

The & sign is a short form for and whereas the | sign stands for the or.

 

a.cs

class zzz

{

public static void Main()

{

int i = 1, j= 2; 

if ( i >= 1 & j > 1)

System.Console.WriteLine("& true");

if ( i >= 1 & j <= 1)

System.Console.WriteLine("& false");

if ( i >= 1 |  j < 1 )

System.Console.WriteLine("| true");

if ( i > 10 |  j >= 1)

System.Console.WriteLine("| again true");

}

}

 

Output

& true

| true

| again true

 

The & in the if make the if true when both conditions are true. As i is greater than equal to 1 and j is greater than 1, the first if is true. Regardless of the first condition being true, the second if evaluates to false because the second condition is false. The | is true if any one of the conditions is true. In the last two if's, even though one of the conditions is true, the expression  evaluates to true.

 

a.cs

class zzz {

public static void Main() {

int i = 1, j= 2; 

if ( i >= 1 && j > 1)

System.Console.WriteLine("&& true");

if ( i >= 1 && j <= 1)

System.Console.WriteLine("&& false");

if ( i >= 1 ||  j < 1 )

System.Console.WriteLine("|| true");

if ( i > 10 ||  j >= 1)

System.Console.WriteLine("|| again true");

}

}

 

Output

&& true

|| true

|| again true

 

We have simply replaced the & with && and ditto for the |. Yet, there seems to be no perceivable difference between them.

Lets us now write a set of programs to understand the difference.

 

a.cs

class zzz

{

public static void Main()

{

int i = 1, j= 2; int k =0; int l =0; 

if ( i >= ++k & j > ++l)

System.Console.WriteLine("& true " + " " + k + " " + l);

if ( i >= ++k & j <= ++l) ;

System.Console.WriteLine("& false " + " " + k + " " + l);

if ( i >= ++k |  j < l++ ) ;

System.Console.WriteLine("| true " + " " + k + " " + l);

k = -1;

if ( i > ++k |  j >= l++)

System.Console.WriteLine("| again true " + " " + k + " " + l);

}

}

 

Output

& true  1 1

& false  2 2

| true  3 3

| again true  0 4

 

a.cs

class zzz {

public static void Main()

{

int i = 1, j= 2; int k =0; int l =0; 

if (i >= ++k && j > ++l)

System.Console.WriteLine("&& true " + " " + k + " " + l);

if (i >= ++k && j <= ++l) ;

System.Console.WriteLine("&& false " + " " + k + " " + l);

if (i >= ++k ||  j < l++) ;

System.Console.WriteLine("|| true " + " " + k + " " + l);

k = -1;

if ( i > ++k ||  j >= l++)

System.Console.WriteLine("|| again true " + " " + k + " " + l);

}

}

Output

&& true  1 1

&& false  2 1

|| true  3 2

|| again true  0 2

 

Two identical programs except the difference between the single and double logical operators and you see two completely different outputs.

 

In the first program, C# simply increments the variable l each time it increments k. A single &, after evaluating the first condition, proceeds to the second condition and increments l. Hence we see k and l with the value of 1and 1 for each of them.

 

Let's take a similar case in the second program. The condition in the if statement reads (i >= ++k && j > ++l). The value of k which is zero first becomes one. The value of i is one and hence the first condition is true as '1 is >= 1'.  C# now goes to the second condition as if this condition is false, the entire expression evaluates to false. It now increments l to 1 and as j is 2, (2>1), thus making the condition true.

 

The second if statement in the second program reads (i >= ++k && j <= ++l). At first k grows from 1 to 2 whereas i remains at 1, hence the condition is false. C# now realizes that it is fruitless evaluating the second condition as evaluating it to true or false will not affect the if result of false anymore. It ignores the second condition, therefore l retains its old value of 1.

 

&& are called short circuit logical operators. A single & will always execute both the conditions and show an increase in the value of l unlike && which distances itself from the second condition on failure of the first.

 

The single | works in a slightly similar way. When the first condition of the or is true, C# considers it to be pointless in evaluating the remaining conditions as the or will remain true irrespective of the result of the second condition.  In the condition (i >= ++k ||  j < l++), variable i still holds 1 and variable k increases to 3. As the first condition is false, C# executes the second as its result can effect the overall result of the compound condition.

 

In the last case, the first condition is true, so no more of the if conditions are read. The variable l restores its original value.

 

a.cs

class zzz

{

public static void Main()

{

System.Console.WriteLine( true ^ false);

System.Console.WriteLine( false ^ true);

System.Console.WriteLine( false ^ false);

System.Console.WriteLine( true ^ true);

}

}

 

Output

True

True

False

False

 

The ^ operator works in a similar way to the | operator with one subtle change. If both sides are true, the answer is not true but false. The rest of the conditions work like an or. This is called the logical xor operator. More on this issue a couple of pages later.

 

a.cs

class zzz

{

public static void Main()

{

System.Console.WriteLine( true != false);

System.Console.WriteLine( false != true);

System.Console.WriteLine( false != false);

System.Console.WriteLine( true != true);

}

}

 

Output

True

True

False

False

 

If you observe carefully, the != operator and the ^ operator produce the same result. Let the time of the day decide on the option you choose for a logical comparison. Many a times, like in life, you wonder why C# programming language has certain language features, which are redundant and can duplicated by another set of commands.

 

a.cs

class zzz

{

public static void Main() {

yyy a = new yyy();

yyy b = new yyy();

System.Console.WriteLine( a && b);

}

}

class yyy

{

public static bool operator & (yyy x,yyy y)

{

System.Console.WriteLine("op");

return true;

}

}

 

Compiler Error

a.cs(6,27): error CS0217: In order to be applicable as a short circuit operator a user-defined logical operator ('yyy.operator &(yyy, yyy)') must have the same return type as the type of its 2 parameters.

 

The same error keeps popping up. A large number of operators demand their return type to be the class they are created in. At times, we understand why and at other times, we feel we are not intelligent enough to fathom a higher intellect.

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

yyy b = new yyy();

System.Console.WriteLine( a && b);

}

}

class yyy

{

public static yyy operator & (yyy x,yyy y)

{

System.Console.WriteLine("op");

return new yyy();

}

}

 

Compiler Error

a.cs(7,27): error CS0218: The type ('yyy') must contain declarations of operator true and operator false

 

We do as ordered by the error message and bring in  an operator true and an operator false. These are needed as our answer belongs to the boolean world. To evaluate the  condition in the WriteLine function, we need the above true and false operators. Along with it, we also include the implicit string operator as System.Console.WriteLine takes a string.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy(1);

yyy b = new yyy(2);

System.Console.WriteLine( a && b);

System.Console.WriteLine( a & b);

}

}

class yyy

{

int j;

public yyy ( int v)

{

j = v;

}

public static yyy operator & (yyy x,yyy y)

{

System.Console.WriteLine("op " + x.j + " " + y.j);

return new yyy(x.j+y.j);

}

public static bool operator true(yyy x)

{

System.Console.WriteLine("true " + x.j);

return true;

}

public static bool operator false(yyy x)

{

System.Console.WriteLine("false " + x.j);

return true;

}

public static implicit operator string (yyy x)

{

System.Console.WriteLine("string " + x.j);

return "yyy " + x.j;

}

}

 

Output

false 1

string 1

yyy 1

op 1 2

string 3

yyy 3

 

We were astonished with the results. Operator & is not called ever. We introduced two new operators for it and & operator itself never gets called. So, we removed the code from our class yyy. When we do so, we get an error.

 

 

Compiler Error

a.cs(7,27): error CS0019: Operator '&&' cannot be applied to operands of type 'yyy' and 'yyy'

 

If we want it to understand the yyy object, we need to overload the && operator. After a while, we realized that our user defined function only implements one & while we had used && in the WriteLine function. We tried to implement the && by adding one more & to our operator. On doing so, we get the following error.

 

Compiler Error

a.cs(12,28): error CS1020: Overloadable binary operator expected

 

Thus we cannot overload the &&. The user-defined operator & handles both the & and the && but with a subtle difference between them. The expression a && b is evaluated as T.false(a) ? a : T.&(a,b). In the above case, object a is represented by the number 1 and the object b by a number 2.

 

C# first calls the operator false using object a with j value as 1. As we are returning true, the answer is object a (from ? to : ) , the string operator is used to produce yyy 1.

 

For the non-short circuit operators, the & is called and life moves on in the fast track like normal as we are returning an object comprised of the sums of objects a and b. This is converted into a string and its values are displayed.

 

Comment the lines for the & as it remains the same always.

 

//System.Console.WriteLine( a & b);

 

Now let the fireworks start. Instead of returning true in the operator false, we now give false.

 

public static bool operator false(yyy x)

{

System.Console.WriteLine("false " + x.j);

return false;

}

We now see the following result"

 

Output

false 1

op 1 2

string 3

yyy 3

 

As the operator false returns false, condition T.false(a) ? a : T.&(a,b) evaluates to false. This calls the operator & with a and b, which creates a new object. Hence, we see op displayed. The explanation for operator & holds true thereafter. The short circuit operator works on the principle that if the operator false returns true, the entire statement is false and no more code is executed.

 

For the or operator |, the rule is a || b and it gets evaluated to T.True(a) ? a : T.|(a,b). We change the & to the | in the operator and also in the WriteLine functions. Make the following changes to Main

 

public static void Main() {

yyy a = new yyy(1);

yyy b = new yyy(2);

System.Console.WriteLine( a || b);

System.Console.WriteLine( a | b);

}

 

…and in class yyy we've changed the & operator to |

 

public static yyy operator | (yyy x,yyy y)

{

System.Console.WriteLine("op " + x.j + " " + y.j);

return new yyy(x.j+y.j);

}

 

With the true operator returning true, we see the following output.

 

Output

true 1

string 1

yyy 1

op 1 2

string 3

yyy 3

 

With the true operator returning false, the results are as follows:

 

Output

true 1

op 1 2

string 3

yyy 3

op 1 2

string 3

yyy 3

 

The | works the same way as the &. There is no change at all. To understand the above output, we need to jog our memory with the fact that a || will only proceed if it meets a false condition at the beginning. If it meets a true condition, it blatantly ignores the following condition as the return value of true or false would not affect the answer. Thus, as in life, if we get what we want we do not push ourselves. We also learnt that a true or false operator is used to figure out the end result of a condition. Also, the object a is always evaluated only once but the object b may or may never be evaluated. An expression to be used as a logical expression must either have an operator bool or a true.

 

Heavy stuff! time to shift our minds to something different and easier.

 

Shift Operators

 

a.cs

class zzz

{

public static void Main()

{

int i,j = 8;

i = j >> 2;

System.Console.WriteLine(i + " " + j);

i = j << 2;

System.Console.WriteLine(i + " " + j);

}

}

 

Output

2 8

32 8

 

The right shift operator >> shifts all the bits to the right. Shifting bits to the right is the same as dividing the value by 2. The bits, that fall off from the right end are replaced with bits having a zero value from the left. The left shift operator works in the reverse way and move bits towards the right, The bits that come in from the right are zero bits and the ones that fall off from the left are forgotten. This is like multiplying the value by 2 for each shift that we make.

 

a.cs

class zzz {

public static void Main()

{

yyy a = new yyy(10);

int i = a >> 2;

System.Console.WriteLine(i);

}

}

class yyy

{

int j ;

public yyy(int p)

{

j = p;

}

public static int operator >> ( yyy a, int b)

{

System.Console.WriteLine(a.j+ " " + b);

return a.j + b;

}

}

 

Output

10 2

12

We are allowed to overload the >> or the << operators as we overload any other operator. The constructor is called with one parameter. The value of this parameter is given to the local variable j. The object is now right shifted by 2 and the result is stored in i.

 

The right shifting passes two parameters in the overloaded operator, that is, the instance of the object and the next is the number if bits to be shifted. As long as we return a data type that is the return value of the operator, what we do in the function is entirely at our discretion. This is the beauty of operator overloading. It's like life. We decide what to make of it and it is nobody's business to interfere in it. For an int, the shift count is not what we specify but count & 0x1f and for a long, count & 0x3f. Count is the number of bits to shift. God only knows why the above restriction. If the shift count is zero, the answer does not change. Also, these operators cannot cause an overflow and produce the same results in checked and unchecked mode.

 

++ and -- operators

 

The increment operator ++ can be used in two contexts with a variable. Prefix which means before and postfix which means after. The results are normally the same but differ in a very subtle way.

 

a.cs

class zzz {

public static void Main()

{

int i,j;

j = 1;

i = ++j;

System.Console.WriteLine(i + " " + j);

i = j++;

System.Console.WriteLine(i + " " + j);

}

}

 

Output

2 2

2 3

The ++ before a variable is called the prefix increment operator. Here, the variable j, which has a value of 1, first becomes 2 and then this value is stored in i. Thus, at the end of the statement, both i and j share the same value.

 

In the case of the postfix increment operator, the ++ appears after the variable name.

 

i=++j, in simple language reads as, initialize i to  the existing value of the variable j that is 2. The minute you reach the end of the statement, increment the value of the variable j by 1. While the statement is being executed, j has a value of 2, outside the statement j has a value of 3.

 

There are a large number of cases where the postfix operator makes our programming life easier. Can we not have an operator that makes the rest of life better?

 

a.cs

class zzz {

public static void Main() {

yyy a = new yyy();

System.Console.WriteLine(++a.i);

}

}

class yyy

{

public int i

{

get {return 5;}

set {}

}

}

 

Output

6

 

The postfix and prefix operators can also work with a property, provided the property has both a get and set accessor. If we remove the set accessor from the above example we receive the following error message.

Compiler Error

a.cs(6,28): error CS0200: Property or indexer 'yyy.i' cannot be assigned to -- it is read only

 

…and if we remove the get accessor and only have the set accessor.

 

Compiler Error

a.cs(6,28): error CS0154: The property or indexer 'yyy.i' cannot be used in this context because it lacks the get accessor

 

The above errors are obvious as a ++ operator with a variable, i.e. writing i++, is a short form of writing i = i + 1.

 

In such a case, we first need to read the value of a variable, therefore we need a get accessor and then we change the value of the same variable. So we require a set accessor too. The indexer also works in the same fashion where the compiler does not even bother to have a separate error message for them. The error message for the property and indexer are merged into one. A lazy programmer !!!.

 

The predefined data types that understand the ++ and -- are sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, and any other enum type. What works for a ++ also works in the same way for a --.

 

a.cs

class zzz

{

public static void Main()

{

}

}

class yyy

{

public static int operator ++ (yyy a)

{

}

}

 

 

Compiler Error

a.cs(9,19): error CS0559: The parameter and return type for ++ or -- operator must be the containing type

 

The ++ operator belongs to a class and hence must return an object that is an instance of the same class. The ++ operator in our code must return a yyy and not an int.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy(3);

int i;

i = ++a;

System.Console.WriteLine(i);

i = a++;

System.Console.WriteLine(i);

}

}

class yyy

{

int j;

public yyy( int p)

{

j = p;

}

public static yyy operator ++ (yyy a)

{

yyy b = a;

System.Console.WriteLine("++ " + a.j);

b.j++;

return b;

}

public static implicit operator int (yyy a)

{

System.Console.WriteLine("int " + a.j);

return a.j;

}

}

 

Output

++ 3

int 4

4

++ 4

int 5

5

 

In the ++ operator of the class, we return an object b, which is an instance of class yyy. We first initialize b to the parameter passed and then display the value of j. System.Console.WriteLine shows ++3

 

The next line increments the member j by one thereby storing the value of 4 in j and then the object is returned. Since the value is stored in an int variable, the implicit int function nows comes into picture. The int operator converts the yyy to an int by returning the value of the member j.  Within int, the display line will show 'int 4' and finally the WriteLine in Main will display 4.

 

However, in the second case of a++, the same thing happens. The identical operator gets called and we get the same result. Our reasoning was that the value of the yyy object would be 4 and not 5 as the ++ was used in the postfix context. But, there is no difference at all.

 

Thus, in the user defined operators, the ++ works the same whether it is used in a prefix or a postfix context. We do not have two different operators and our operators do not know which context they are being called in. There is only one operator being used but in two dissimilar ways.

 

Logical Negation ! Operator

 

a.cs

class zzz

{

public static void Main()

{

System.Console.WriteLine(!true);

System.Console.WriteLine(!false);

}

}

 

Output

False

True

 

This operator only acts on a bool. It reverses the condition. A true becomes false and a false becomes true.

 

a.cs

class zzz {

public static void Main()

{

yyy a = new yyy();

int i = !a;

System.Console.WriteLine(i);

}

}

class yyy

{

public static int operator ! (yyy a)

{

return 1;

}

}

 

Output

1

 

The negation operator can be overloaded only for objects. This object becomes a parameter. The important thing here is that there is no rule stating the data type of the return value. In the above case we are returning an int for all that C# cares. Common sense demands that it should be an int but… One case where C# should have enforced a sensible rule but does not. May be we need to wait till the next version arrives.

 

 

 

Complement operator

 

a.cs

class zzz {

public static void Main()

{

int i = 32768;

int j = ~i;

System.Console.WriteLine(j);

}

}

 

Output

-32769

 

The bitwise operator ~ negates the bits. The zeros become one and the ones becomes zeros. This operator is used when you want to reverse the bits in a byte.

 

Arithmetic Operators

 

a.cs

class zzz {

public static void Main() {

double i = 3 * 2.0;

int j = (int)3 * 2.0;

System.Console.WriteLine(2 * (float)3.0);

System.Console.WriteLine(2 * 3.0);

System.Console.WriteLine(3.0 * (double)2);

}

}

 

Compiler Error

a.cs(4,10): error CS0029: Cannot implicitly convert type 'double' to 'int'

 

The multiplication * operator can multiply a float or a double with an int. The second line has a number with decimal places i.e. a double multiplied with an integer. It does the multiplication and the answer is obviously a double. The multiplication goes through but the double value in no way can be held in an int. The fault, dear Brutus, lies not in the stars, but in the internal conversion from a double into an int.

 

Let us understand more of the above conversions.

 

Every time we create a variable, C# allocates some memory for it. We can determine this memory by using the sizeof operator in an unsafe context. The size of the basic data types are as follows, double 8, float 4, long 8, int 4, uint 4, char 2, byte 1.

 

a.cs

class zzz

{

public static void Main()

{

double i = 0; float j = 0;

i = j;

j = i;

int k =0; long l =0;

k = l;

l = k;

byte m = 0;

m = k;

k = m;

}

}

 

Compiler Error

a.cs(7,5): error CS0029: Cannot implicitly convert type 'double' to 'float'

a.cs(9,5): error CS0029: Cannot implicitly convert type 'long' to 'int'

a.cs(12,5): error CS0029: Cannot implicitly convert type 'int' to 'byte'

 

C# has a very simple rule when it comes to equating objects belonging to the simple data types. It does not like any waste. Hence, we can equate a larger data type to a smaller data type. Here, larger and smaller refer to the amount of memory a data type is allocated at the time of creation.

On equating a larger data type to a smaller one, there is no reason for loss of data as in the memory, variable on the left is large enough to hold the value stored of the right.

 

int = byte

 

However in the reverse case, the variable on the right will theoretically store a larger value than the left side variable. As the variable on the left can store a smaller value, there is a possibility of loss of data.

 

byte=int

 

Thus an int can be equated to a byte but a byte cannot be equated to an int. The range of values an int can store is by far larger than what a byte can handle.

 

a.cs

class zzz {

public static void Main() {

int i;

i = 10/0;

}

}

 

Compiler Error

a.cs(4,5): error CS0020: Division by constant zero

 

Whenever any number is divided by zero, the answer, as taught to us in school, is infinity. No computer can ever evaluate this expression as it holds no definition. Thus, one of the million error checks that a compiler reviews is divide by zero.

 

a.cs

class zzz {

public static void Main()

{

int i; int j=0;

i = 10/j;

}

}

Output

Exception occurred: System.DivideByZeroException: Attempted to divide by zero.

   at zzz.Main()

 

At times, compilers can be exasperating. We love calling them all sorts of names. Here we have very clearly, in front of the world, initialized the variable j to zero. The compiler knows about it but seems to turn a blind eye to it. For some reason, as we mentioned earlier, the compiler ignores alll variable values until the program is executed. This now throws an exception at run time and since we haven't caught it, we see the error.

 

a.cs

class zzz

{

public static void Main()

{

System.Console.WriteLine(10/3);

System.Console.WriteLine((double)10/3);

System.Console.WriteLine((double)(int)10/3);

System.Console.WriteLine(10/3.0);

System.Console.WriteLine(10.0/3);

}

}

 

Output

3

3.33333333333333

3.33333333333333

3.33333333333333

3.33333333333333

 

When we divide 10 by 3 the answer is a whole number as both the operands are numbers. This is called integer division and the little that you are knowledgeable about mathematics would tell you that the answer is 3.3333… up to a million 3s. To convince the compiler to give you the right answer, we need to convert one of the numbers into a floating number i.e. a number with decimal places. The first way is by using a cast. The first time we cast it to double and in the second case, 10 is first casted to an int and then again to a double. C# only remembers the last cast. The other way out is to make one of the parameters a double, it does not matter which one. Its division results in a floating point answer.

 

a.cs

class zzz

{

public static void Main()

{

System.Console.WriteLine(19%5);

System.Console.WriteLine(-19%5);

System.Console.WriteLine(19%-5);

}

}

 

Output

4

-4

4

A large number of times, we are interested in the remainder of a division and not the answer. To accomplish that, we have been offered the reminder operator %. 19 divided by 5 gives us an answer of 3 and a remainder 4. In the second case the answer is a negative value while in the last case the remainder is again a positive 4. The result of x%y is x-(x/y)*y. If we divide by zero we get an exception thrown. Also, this operator never produces an overflow.

 

a.cs

class zzz

{

public static void Main()

{

System.Console.WriteLine(19%5.1);

}

}

 

Output

3.7

 

This book will at times compel you to read the documentation to reason out on answers like above. We quote verbatim. If z is the result of x % y and is computed as x - n * y, where n is the largest possible integer that is less than or equal to x / y. Try making sense out of it. The next line also throws in some rule called IEEE 754.

 

a.cs

class zzz {

public static void Main()

{

yyy a = new yyy();

System.Console.WriteLine("hi " + 200);

System.Console.WriteLine(200 + " hi");

System.Console.WriteLine("hi " + a);

}

}

class yyy

{

}

 

Output

hi 200

200 hi

hi yyy

 

The + operator is normally used to add two numbers but is also smart enough to join or concatenate two string together. If it does not find two strings, but a number and a string in any order, it first converts the number to a string and then joins them . If we do not state a number but an object like class yyy, it will first call the ToString function of the object class. This will return a string representation of the class, and then join the two strings.

 

What works for operator + also works for operator - The rules of enum apply here too. Obviously, you cannot subtract two strings from each other.

 

Anding and Oring

 

a.cs

class zzz

{

public static void Main()

{

System.Console.WriteLine(21 & 15 );

System.Console.WriteLine(21 | 11 );

System.Console.WriteLine(21 ^ 11 );

}

}

 

Output

5

31

30

 

The single & is very different from the &&. It stands for a logical anding and specifies that both sides must be true to get a true result. If even one side is false, the answer is false. Similarly a single | is called bitwise oring. In bitwise anding, we compare individual bits and not the entire byte. If the individual bits are both one i.e. true then the answer is one. Even if one of them is zero, the answer is zero. In bitwise oring, even if one of the bits is one, the answer is a 1. The ^ sign is called Xoring which is similar to oring except that if both bits are 1, or 0 , in a xor we get 0. If one of the bits is 0 and the other 1, then we see 1.

 

a.cs

class zzz {

public static void Main()

{

int i,j,k;

i = 21; j = 11;

k = i ^ j;

System.Console.WriteLine(k);

System.Console.WriteLine(k ^ i);

System.Console.WriteLine(k ^ j);

}

}

 

Output

30

11

21

Why, on earth, would anyone want an xor operator? The above program gives you one such reason. When we xor 21 with 11, we get a answer of 30. Now, we take this answer and xor with 21. Surprisingly, our answer is 11, the value of j. May be a fluke. So we now xor k with j and now we get 21 which is the value of i.

 

Thus, the value obtained on xoring, when xored with any of the original number will give the other original number. Xoring is the cornerstone of all encryption. These operators do not produce an overflow. The xoring with numbers is useful rather than logical conditions that we learnt earlier. These operators work seamlessly with any enumeration that we create with no extra effort on our part.

 

Equality operators

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

yyy b = new yyy();

System.Console.WriteLine( a == b);

System.Console.WriteLine( a != b);

a = b;

System.Console.WriteLine( a == b);

System.Console.WriteLine( a != b);

}

}

class yyy

{

}

 

Output

False

True

True

False

 

 

We are comparing two objects for equality. Both are an instance of a class yyy, which is devoid of anything. As the two objects a and b have been created in two separate areas of memory, they are represented by a different memory location hence they are not equal to each other. Thus the first WriteLine function displays False and as they are not equal the second displays a True. A user defined == or != operator are covered in the next series of programs.

 

When we equate a to b, both a and b now, store a similar number. This is the address of b in memory. As this number is the same, from C# point of view, they represent the same reference object and hence we see a result of True.

 

A reference object is different from a value because the reference object refers to or stores a value. This indicates its presence in memory. Simultaneously, class yyy may have a hundred variables with different values in objects a and b, nevertheless, if we wrote a = b, they would be termed as equal reference objects. As we mentioned earlier, value objects are considered equal depending upon the value of the objects.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

yyy b = new yyy();

System.Console.WriteLine( a == b);

System.Console.WriteLine( a != b);

}

}

class yyy

{

public static bool operator == ( yyy x, yyy z)

{

System.Console.WriteLine("==");

return true;

}

public static bool operator != ( yyy x, yyy z)

{

System.Console.WriteLine("!=");

return true;

}

}

 

Output

==

True

!=

True

 

We are allowed to overwrite most of the operators provided we follow some basic rules of the game. At times we have to return a certain data type. In the above case, we must have both,  == and  !=  unless we are looking for the error as shown below.

 

Compiler Error

a.cs(13,20): error CS0216: The operator 'yyy.operator ==(yyy, yyy)' requires a matching operator '!=' to also be defined

 

We now rewrite the != operator by making it return an int.

 

public static int operator != ( yyy x, yyy z)

{

System.Console.WriteLine("!=");

return 6;

}

 

On doing so, we get the following error.

 

Compiler Error

a.cs(13,20): error CS0216: The operator 'yyy.operator ==(yyy, yyy)' requires a matching operator '!=' to also be defined

a.cs(18,19): error CS0216: The operator 'yyy.operator !=(yyy, yyy)' requires a matching operator '==' to also be defined

 

We really confused the compiler as it expected both the operators to return the same values. Then it must tell us so, as we've been telling you very often that return values do not count as method signatures. Confusion everywhere!

 

We change the == operator to returning an int, and as both return an int now, C# has no objections at all. Here, there are no rules in the datatype of returned objects.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

xxx b = new xxx();

System.Console.WriteLine( a == b);

System.Console.WriteLine( a != b);

a = b;

System.Console.WriteLine( a == b);

System.Console.WriteLine( a != b);

}

}

class yyy

{

}

class xxx : yyy

{

}

 

Output

False

True

True

False

 

The result is the same as above due to the fact that what works with the same class will obviously work with derived classes also. a and b start with not being true and then on equating them, since they refer to the same object in memory, the reverse holds true.

 

Two small points which may have skipped your mind:

 

   an xxx object in memory comprises of a xxx and yyy

   we cannot equate b to a.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

xxx b = new xxx();

System.Console.WriteLine( a == b);

System.Console.WriteLine( a != b);

}

}

class yyy

{

}

class xxx

{

}

 

Compiler Error

a.cs(7,27): error CS0019: Operator '==' cannot be applied to operands of type 'yyy' and 'xxx'

a.cs(8,27): error CS0019: Operator '!=' cannot be applied to operands of type 'yyy' and 'xxx'

 

We cannot equate any two objects from any two classes with each other. This gives us a compile time error rather than a true or false. C# has not been enlightened on the rules of comparing a yyy and a xxx object for equality, hence the error.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

xxx b = new xxx();

System.Console.WriteLine( a == b);

System.Console.WriteLine( a != b);

}

}

class yyy

{

public static bool operator == ( yyy x, xxx z)

{

System.Console.WriteLine("==");

return true;

}

public static bool operator != ( yyy x, xxx z)

{

System.Console.WriteLine("!=");

return true;

}

}

class xxx

{

}

 

Output

==

True

!=

True

 

To eliminate the above error, we overload a set of == and != operators in the class yyy. The second parameter to the operator must be changed to an xxx object as the default or free == operator takes two similar objects and checks for equality.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

xxx b = new xxx();

System.Console.WriteLine( a == b);

System.Console.WriteLine( b != a);

}

}

class yyy

{

public static bool operator == ( yyy x, xxx z)

{

System.Console.WriteLine("yyy ==");

return true;

}

public static bool operator != ( yyy x, xxx z)

{

System.Console.WriteLine("yyy !=");

return true;

}

}

class xxx

{

public static bool operator == ( xxx x, yyy z)

{

System.Console.WriteLine("xxx ==");

return true;

}

public static bool operator != ( xxx x, yyy z)

{

System.Console.WriteLine("xxx !=");

return true;

}

}

 

Output

yyy ==

True

xxx !=

True

 

In this program, we have introduced the same code in class xxx and now we get no errors. There is only one difference and that is the change in order of parameters. Here the object xxx is placed as the first parameter and not second. Thus in the ==  as the yyy object is first and then the xxx object, the == gets called from the yyy class. For the != operator, the parameters type are reversed and as xxx object comes first, the != operator gets called from xxx instead.

 

We now rewrite only class xxx as follows and also change the WriteLine function back to a != b.

 

class xxx

{

public static bool operator == ( yyy x, xxx z)

{

System.Console.WriteLine("xxx ==");

return true;

}

public static bool operator != ( yyy x, xxx z)

{

System.Console.WriteLine("xxx !=");

return true;

}

}

 

Compiler Error

a.cs(7,27): error CS0121: The call is ambiguous between the following methods or properties: 'xxx.operator ==(yyy, xxx)' and 'yyy.operator ==(yyy, xxx)'

a.cs(8,27): error CS0019: Operator '!=' cannot be applied to operands of type 'xxx' and 'yyy'

 

The error appears as C# is all at sea and cannot decide which == operator to call. At a == b, it can call both, the one from xxx as well as the one from yyy. When we have two equally probable options, C# does not randomly chose one of them but throws up its hands in exasperation and gives us an error.

 

a.cs

class zzz {

public static void Main()

{

yyy a = new yyy();

xxx b = new xxx();

System.Console.WriteLine( a == b);

System.Console.WriteLine( b != a);

}

}

class yyy

{

public static bool operator == ( yyy x, xxx z)

{

System.Console.WriteLine("yyy ==");

return true;

}

public static bool operator != ( yyy x, xxx z)

{

System.Console.WriteLine("yyy !=");

return true;

}

}

class xxx

{

}

 

Compiler Error

a.cs(8,27): error CS0019: Operator '!=' cannot be applied to operands of type 'xxx' and 'yyy'

 

Method signatures are extremely important. The != can only compare an object of type yyy and xxx. The yyy object comes first as the function signature specifies it first. In our case, the xxx object comes first and thus the error. At certain places, especially function parameters, we cannot change the order of parameters. One option is to have a == with both orders of objects.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

xxx b = new xxx();

System.Console.WriteLine( a == b);

System.Console.WriteLine( b != a);

}

}

class yyy

{

public static bool operator == ( yyy x, xxx z)

{

System.Console.WriteLine("In class yyy ==");

return true;

}

public static bool operator == ( xxx x, yyy z)

{

System.Console.WriteLine("In class yyy  == 1");

return true;

}

public static bool operator != ( yyy x, xxx z)

{

System.Console.WriteLine("In class yyy !=");

return true;

}

public static bool operator != ( xxx x, yyy z)

{

System.Console.WriteLine("In class yyy != 1");

return true;

}

}

class xxx

{

}

 

Output

In class yyy ==

True

In class yyy != 1

True

 

A rather long program for no fault of ours. We first place a != operator and reverse the parameters in System.Console.WriteLine. Now we overload the == and the != operators but with different ordering of the objects, so the method signature changes. These two newly introduced functions are placed in class yyy.  Remember, whenever any entity comes in pairs, the data type of the parameter list is important.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

xxx b = new xxx();

System.Console.WriteLine( a == b);

System.Console.WriteLine( b != a);

}

}

class yyy

{

public static bool operator == ( yyy x, yyy z)

{

System.Console.WriteLine("yyy ==");

return true;

}

public static bool operator != ( yyy x, yyy z)

{

System.Console.WriteLine("yyy !=");

return true;

}

}

class xxx

{

public static implicit operator yyy(xxx a)

{

System.Console.WriteLine("operator yyy");

return new yyy();

}

}

 

Output

operator yyy

yyy ==

True

operator yyy

yyy !=

True

 

Is there something wrong somewhere ?.  a == b does generate any error as b, an xxx  object, can be converted to a yyy object using the operator yyy in class xxx. The operator yyy has only one job to do and that is to convert an xxx object into a yyy object.

 

We are highly impressed with the compiler as it has a lot of foresight. It would realize that the == cannot accept an xxx as the first parameter. It can only accept a yyy. There is, however, some light at the end of the tunnel as there is a possibility of converting an xxx to a yyy object. Once done, the == operator is called with two yyy objects as parameters. Ditto for the !=. Smart guy.

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

xxx b = new xxx();

System.Console.WriteLine( a == b);

System.Console.WriteLine( "----");

System.Console.WriteLine( b != a);

}

}

class yyy

{

public static bool operator == ( yyy x, xxx z)

{

System.Console.WriteLine("yyy ==");

return true;

}

public static bool operator != ( yyy x, xxx z)

{

System.Console.WriteLine("yyy !=");

return true;

}

public static implicit operator xxx(yyy a)

{

System.Console.WriteLine("operator xxx ");

return new xxx();

}

}

class xxx

{

public static implicit operator yyy(xxx a)

{

System.Console.WriteLine("operator yyy");

return new yyy();

}

}

 

Output

yyy ==

True

----

operator yyy

operator xxx

yyy !=

True

 

C# goes to great lengths not to give you any error. The == operator has no problems at all. It has the right parameters and the right data types also. For the !=, we have lots of trouble. The != operator in yyy requires the first parameter to be a yyy and the second to be an xxx whereas the expression to be evaluated has an xxx first and then a yyy. C# realizes that it can convert an xxx object to a yyy object using the operator yyy in class xxx. This has been explained earlier. So the first parameter to the != meets its match. The second one is a problem as it is a yyy whereas it should have been an xxx. C# wears its thinking cap and realizes that the class yyy has an operator xxx which it can use to convert the yyy object to an xxx, thus satisfying the parameter data type for !=.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

xxx b = new xxx();

aaa c = new aaa();

System.Console.WriteLine( c == b);

System.Console.WriteLine( b != a);

}

}

class yyy

{

public static implicit operator aaa(yyy a)

{

System.Console.WriteLine("yyy aaa");

return new aaa();

}

}

class xxx

{

public static implicit operator aaa(xxx a)

{

System.Console.WriteLine("xxx aaa");

return new aaa();

}

}

class aaa

{

public static bool operator == ( aaa x, aaa z)

{

System.Console.WriteLine("aaa ==");

return true;

}

public static bool operator != ( aaa x, aaa z)

{

System.Console.WriteLine("aaa !=");

return true;

}

}

 

Compiler Error

a.cs(9,27): error CS0019: Operator '!=' cannot be applied to operands of type 'xxx' and 'yyy'

 

This version of C# will never enter the Mensa club as it may be smart but not a genius. We expected a lot from the C# compiler. What we thought it would do is convert both yyy and xxx into aaa objects using the operator aaa functions from each class. Now that it has two aaa objects, it could use them and call the != from class aaa. Unfortunately, it did go far enough, but not very far.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

yyy b = new yyy();

System.Console.WriteLine( a == b);

System.Console.WriteLine( a != b);

}

}

struct yyy

{

}

 

Compiler Error

a.cs(7,27): error CS0019: Operator '==' cannot be applied to operands of type 'yyy' and 'yyy'

a.cs(8,27): error CS0019: Operator '!=' cannot be applied to operands of type 'yyy' and 'yyy'

 

Hey, remember we told you a long time ago that structures were value types. Only reference types have the pleasure of getting a free == and != operators not value types.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

yyy b = new yyy();

System.Console.WriteLine( a == b);

System.Console.WriteLine( a != b);

}

}

struct yyy

{

public static bool operator == ( yyy x, yyy z)

{

System.Console.WriteLine("yyy ==");

return true;

}

public static bool operator != ( yyy x, yyy z)

{

System.Console.WriteLine("yyy !=");

return true;

}

}

 

Output

yyy ==

True

yyy !=

True

 

Thus the only way out here is, to do as earlier, create our own  != and == operators. All the above rules hold good here too.

 

a.cs

class zzz

{

public static void Main()

{

yyy a = new yyy();

yyy b = new yyy();

System.Console.WriteLine( (object)a == b);

System.Console.WriteLine( a != (object)b);

}

}

class yyy

{

public static bool operator == ( yyy x, yyy z)

{

System.Console.WriteLine("yyy ==");

return true;

}

public static bool operator != ( yyy x, yyy z)

{

System.Console.WriteLine("yyy !=");

return true;

}

}

 

Output

False

True

 

Our operators are not called at all! The reason being that if we cast any one of the parameters to an object, then the == and != operators from the object class are called. We call the == signs as the reference type equality operator. We could comfortably remove the code of the == and != operators from class yyy and still get no errors. This proves that they are not needed in the first place.

 

a.cs

class zzz

{

public static void Main()

{

string s = "zzz";

string t = string.Copy(s);

System.Console.WriteLine(s == t);

System.Console.WriteLine((object)s == t);

System.Console.WriteLine(s == (object)t);

System.Console.WriteLine((object)s == (object)t);

}

}

 

Output

True

False

False

False

 

The example above is brazenly stolen from the documentation without any compulsions. S is a  string initialized to zzz. t, also a string, is initialized using the Copy command to a newly allocated area in memory storing zzz..

 

We have two zzz strings  in memory in different locations. The == operator of the string class does not look at the values of the objects but when it peeks into their different memory locations, it realizes that the strings are the same even though the objects are different. As a result, it returns true.

 

On the next line, we cast one of the parameters to an object. The original == operator applies its own set of rules where it compares actual references and not what is stored. This results in the last three WriteLine functions giving a false. Had we simply equated a to t as in s = t, all the WriteLine functions would result in true as the strings are equal in value and what they represent.

 

a.cs

class zzz

{

public static void Main()

{

int s = 10;

int t = 10;

System.Console.WriteLine(s == t);

//System.Console.WriteLine((object)s == t);

System.Console.WriteLine((object)s == (object)t);

}

}

 

Output

True

False

 

When we equate two value objects, the == operator checks for actual value of the object. As s and t have the same value of 10, they are considered to be at par. If, however, we cast one of them to an object, an operation called boxing takes place.

 

In a case such as this, we are actually creating a reference object that looks like object in a certain area of memory. Thus, the s is created in one part of memory as an object. Ditto for t. Now that t  resides in a separate area of memory, inspite of having the same value, as reference objects they are considered unequal. Therefore, the System.Console. WriteLine function displays false.

 

If we remove the above comment, the compiler flings an error.

 

Compiler Error

a.cs(8,27): error CS0019: Operator '==' cannot be applied to operands of type 'object' and 'int'

 

The == operator has overloads that work with ints or objects but  as such there is no overload that takes an object and an int. The only rational reason available to us at this moment is that it makes no sense in equating a value object to a reference object. Impressive enough, C# is the first language known to us to make a profound distinction between value type variables and reference type variables.

 

Delegate equality

 

Man has always fought for equality all his life. Wars have been won or lost for the principles of equality. What if we write some pages on equality of delegates.

 

a.cs

public class zzz

{

public static void Main()

{

aa a = new aa();

a.abc();

}

}

public class aa

{

public delegate void xyz();

public delegate void pqr();

void  pqr1 ()

{

}

void  xyz1()

{

}

public void abc()

{

pqr d  = new pqr(pqr1);

pqr e  = new pqr(pqr1);

System.Console.WriteLine( d == e);

xyz f = new xyz(xyz1);

System.Console.WriteLine( d == f);

xyz g = new xyz(pqr1);

System.Console.WriteLine( d == g);

System.Console.WriteLine( f == g);

}

}

 

Output

True

False

True

False

 

Delegates are considered equal if the reference is made to the same delegate type. Even though, delegates d and e are created with a separate new i.e. they are stored in different parts of memory, from the viewpoint of delegates, they are treated as equal. Delegate f is of xyz type but more important while it was being instantiated we used xyz1, a different function from class aa and hence they are different delegates. Delegate g is equal to d as the same function pqr1 is used at the time of instantiating. The irony of the matter is that even though f an g are technically of the same type, they are not equal as they represent different functions. The function signatures are more important than delegate types. Thus, we have seen a situation where delegates of different types are equal but the same types are unequal.

 

Assignment

 

The assignment operator = can only have three types of entities on its left or better said as its left operand. These are: a variable, a property and a indexer. The last two must have a set accessor defined if they are to be used.

 

a.cs

public class zzz

{

public static void Main()

{

7 = 9;

}

}

 

Compiler Error

a.cs(5,1): error CS0131: The left-hand side of an assignment must be a variable, property or indexer

 

If we are still unsure about the left operand of a assignment operator, the above error message removes all doubts form our minds. The = is called the simple assignment operator.

a.cs

public class zzz

{

public static void Main()

{

int i,j,k;

k = 10;

i = j = k;

System.Console.WriteLine(i + " " + j + " " + k);

}

}

 

Output

10 10 10

 

We can write multiple = on the same line. This assignment is right-associative. In plain English language it means, read from right to left or even better, backwards. First, the compiler only sees j = k. As the value of k is 10, it initializes j to 10. Then i becomes 10, the value of j. The above is simple case of convenience. i=j=k can also be read as i=(j=k).

 

The right operand to the equal to must be converted to the left operand type or else await a casting error. You must have seen this error n number of times in the past. The = operation simply assigns the value of the right operand to the left and is classified as a value.

 

a.cs

public class zzz {

public static void Main()

{

yyy a = new yyy(10,20);

yyy b;

b = a;

System.Console.WriteLine( b.i + " " + b.j);

}

}

class yyy

{

public int i,j;

public yyy(int x, int y)

{

System.Console.WriteLine("Const");

i=x;j=y;

}

}

 

Output

Const

10 20

 

As we have created only one object that looks like yyy, the constructor gets called only once. Also, there is no code of the overloaded = operator in class yyy. Yet, we do not get any error and also the assignment works as advertised. This is due to a free assignment operator from C#.

 

Lately, we have been procuring a lot of free goodies from the compiler. This free operator simply copies all the values of the variables from the object to the right to the object to the left. Thus, the variables i and j within the object b store the values of i and j variables from a.

 

We have not created object b to look like yyy and if you do remember what we learnt earlier, b now stores the address of a. Had we instantiated an object that looks like yyy, the effect would have been identical.

 

a.cs

public class zzz

{

public static void Main()

{

}

}

class yyy

{

public int i,j;

public yyy(int x, int y)

{

System.Console.WriteLine("Const");

i=x;j=y;

}

public static yyy operator = ( yyy a)

{

return new yyy(1,2);

}

}

 

Compiler Error

a.cs(15,28): error CS1019: Overloadable unary operator expected

 

Error messages should always be loud and clear rather than beat around the bush. C# should be telling us that come what may, there is one operator that no man can ever overload and that is the assignment operator. The above meek error message is saying just  the same, something we forcefully said earlier. Thou ain't allowed to overload the assignment operator under any circumstance!

a.cs

public class zzz

{

public static void Main()

{

string [] s = new string[3];

object [] t = s;

t[0] = null;

t[1] = "hi";

t[2] = new yyy();

}

}

class yyy

{

}

 

Output

Unhandled Exception:System.ArrayTypeMismatchException: An exception of type System.ArrayTypeMismatchException was thrown.

   at zzz.Main()

 

The compiler gives no errors whatsoever. However, when we try to run our program, an exception gets forcefully thrown at us. An array object is allowed to be a reference to another array object.

Remember all objects are finally derived from object, the base class. Even though, t is an array of objects, it now co-exists with an array of strings. This is because we have initialized t to s. Initializing object t[0] to null is permitted for reference types only. t[1]="hi" gives us no error as a string is derived form object. Ditto for yyy. At compile time, we get no errors at all. This rule is called the array co-variance rule.

 

When we run the program, an exception is thrown. One of the reasons is that at runtime, a check is performed on the possibility of converting a yyy object to a string. The data type of the array is string. As it fails, an exception is thrown. Remember t may be an array of objects, but s is an array of strings and at the end of the day, the rules binding s apply. This check is carried out at run time only. Why, only C# knows?!

 

a.cs

public class zzz

{

public static void Main()

{

object [] s = new object[3];

string [] t = s;

}

}

 

Compiler Error

a.cs(6,15): error CS0029: Cannot implicitly convert type 'object[]' to 'string[]'

 

The other way round generates a compiler error.

 

a.cs

public class zzz {

public static void Main()

{

string [] s = new string[3];

object [] t = s;

t[0] = (string)new yyy();

System.Console.WriteLine(t[0]);

t[1] = new yyy();

System.Console.WriteLine(t[1]);

}

}

class yyy

{

public static implicit operator string ( yyy a)

{

return "hi";

}

}

 

Output

hi

Unhandled Exception:System.ArrayTypeMismatchException: Exception of type System.ArrayTypeMismatchException was thrown.

   at zzz.Main()

 

We tried to act oversmart. We added an implicit operator string to convert a yyy into a string. We expected C# to realize at run time, that there is an operator string in class yyy which it can use to prevent an exception being thrown. By using this operator it can convert a yyy into a string. False hopes as C# ignores the operator completely.

 

In the earlier statement, we've explicitly type casted yyy to a string thereby enforcing the compiler to use the string operator. At times we have to be explicit otherwise no one listens to us. Being subtle does not help at all.

 

a.cs

public class zzz {

public static void Main() {

yyy a = new yyy(1);

a.x = 2;

System.Console.WriteLine("---" + a.x);

xxx b = new xxx();

b.r = new yyy(10);

b.r.p = 50;

System.Console.WriteLine("---" + b.r.p);

b.r.x = 100;

System.Console.WriteLine("---" + b.r.x);

}

}

class yyy

{

public int p;

public yyy(int i)

{

p= i;

}

public int x

{

get

{

System.Console.WriteLine("yyy get " + p);

return p;

}

set

{

System.Console.WriteLine("yyy set " + value);

p = value;

}

}

}

struct xxx

{

yyy q;

public yyy r

{

get

{

System.Console.WriteLine("xxx get " + q.p + " " + q.x);

return q;

}

set

{

q = value;

System.Console.WriteLine("xxx set " + q.p + " " + q.x + " " + value);

}

}

}

Output

yyy set 2

yyy get 2

---2

yyy get 10

xxx set 10 10 yyy

yyy get 10

xxx get 10 10

yyy get 50

xxx get 50 50

---50

yyy get 50

xxx get 50 50

yyy set 100

yyy get 100

xxx get 100 100

yyy get 100

---100

 

In the above example, yyy is a class with an instance variable called p and a property called x. At the time of creation, the p variable in object a is initialized to 1. Then, using the set property in x, it is reinitialized to 2. Thus, the WriteLine function displays 2.

 

The structure xxx contains a variable q that looks like yyy and a property r that returns a yyy. We first, create an object that looks like xxx and store it in b. Then we initialize the property r to an object that looks like yyy. Within the set of the property, we initialize the object q to this new yyy object. Thus, the variable p of object q will now be 10.

 

When we write b.r.p=50 or b.r.x=100, we are first calling the get of the property r. This will return a value object that looks like yyy and from this object we are changing p or x as the case may be to 50 or 100. These objects are all stack-based hence the original remains the same. Remember, with a structure we can only get a value, it is not the actual location of the variable and therefore any changes made will never change the values in the original variable. As a result, the left operand must always be the name of a variable if we ever need to see its value change.

 

A compound assignment is the assignment operator with a tweeze of lemon.

 

a.cs

public class zzz

{

public static void Main()

{

int i = 2;

int j = 10;

j += i;

System.Console.WriteLine(j);

}

}

 

Output

12

 

There are a million ways to skin a cat and there are a million ways to initialize a variable. We learnt of two ways earlier and this is the third way. Why waste your time and mine with one more way in performing the same thing. Most of the operators behave in the same way as a + does. So the minus operator has an equivalent -= avatar. Thus i += j becomes i = i + j bearing in mind that i is evaluated only once.

 

a.cs

public class zzz

{

int [] a = {1,2,3};

public static void Main()

{

zzz z = new zzz();

z.xyz()[z.pqr()] += z.abc();

System.Console.WriteLine(z.a[1]);

}

public int abc()

{

System.Console.WriteLine("abc");

return 10;

}

public int pqr()

{

System.Console.WriteLine("pqr");

return 1;

}

public int [] xyz()

{

System.Console.WriteLine("xyz");

return a;

}

}

 

Output

xyz

pqr

abc

12

 

Mindblowing example!  When we see a statement as in z.xyz()[z.pqr()] += z.abc(), we are sure to be all lost. The compiler maintains its sanity and starts from the beginning of the line. First, it simply executes a function called xyz. This function returns an array a which has three members; the second element a[1] has a value of two. Thus the compiler substitutes z.xyz() with a, in this specific case. Now it needs the index of the array. Here it sees one more function pqr. It executes this function and reverts with a 1. Hence the statement now reads as a[1] += z.abc(). As function abc returns 10, the expression reads as a[1]+=10. The value in a[1] is increased by 10 thereby making it 12.

 

Break up the code and see only the parts C# sees and the confusion will disappear from your mind. Wherever statements like x = x + y are used, they can be replaced with x += y. All the earlier rules of the simple assignment apply to the compound assignment also.

 

Operator Overloading

 

When it starts on, the C# compiler overloads most of the operators to understand the inbuilt data types. We have almost to a hundred of operator overloads already available with us. We can create our own and as general principle, C# gives our user defined operators a first shot and then tries its in built operators. If none of the above work, it then flags us an error. These user defined operator overloads works in the same manner for a  class and a struct.  We are allowed to overload only eight unary operators, which are +, -, !, ~, ++, --,true and false. However, when it comes to the binary oeprators we are offered more variety. Here we are allowed 15 of them, namely +, -, *, /, %, &, |, ^, <<, >>, ==, !=, >, <, >= and finally the <=.

 

For reasons unknown to us, we cannot overload all the operators. The ones beyond our bounds are member access, method invocation (), =, &&, ||, ?:, new, typeof, sizeof, and finally the is operator. There is normally method behind madness and in this case for some these overloads there exists a reasonable reason why the compiler forbids the overloads.

 

a.cs

public class zzz {

public static void Main()

{

yyy a = new yyy(1);

yyy b = new yyy(2);

yyy c = new yyy(3);;

c += a;

System.Console.WriteLine(a + b);

System.Console.WriteLine(c);

}

}

class yyy

{

int i;

public yyy(int j)

{

i = j;

}

public static yyy operator + ( yyy a , yyy b)

{

System.Console.WriteLine("operator += " + a.i + " " + b.i);

return new yyy(a.i+b.i);

}

public static implicit operator string(yyy a)

{

return "yyy " + a.i;

}

}

 

Output

operator += 3 1

operator += 1 2

yyy 3

yyy 4

 

Here, the class yyy is differentiated from another instance of it by means of the instance variable i. If a class only had functions, there would be no way to distinguish one instance from another. The constructor initializes the variable i to a value of 1 and 2 respectively. We have overloaded the + operator. This operator automatically overloads the += as a statement c += a finally becomes c = c + a. Even if we wanted to, the compiler would give an error, as the + operator doubles up for the += also. This explains the first WriteLine function where the value of i in object c is 3.

 

In the operator plus, we are creating a new object whose variable i is the summation of the i's of the objects a and b. The normal plus operator works as usual. We normally overrule the predefined string operator that always gets called whenever our user defined type has to be converted to a string. This is needed to get a confirmation that the plus operator actually did the job it was supposed to do.

 

a.cs

public class zzz {

public static void Main()

{

}

}

class yyy {

public static yyy operator + ( xxx a, xxx b)

{

return new yyy();

}

public static yyy operator ++ ( xxx a)

{

return new yyy();

}

}

class xxx

{

}

 

Compiler Error

a.cs(7,19): error CS0563: One of the parameters of a binary operator must be the containing type

a.cs(11,19): error CS0559: The parameter and return type for ++ or -- operator must be the containing type

 

Whenever an operator is overloaded, one of the parameters to the function must be the data type of the class that contains the operator. In this case, while overloading the binary operator +, at the most one of its parameters must be of type yyy. If not, then it is assumed to be redefining an operator of another class. In the above case, the code of the operator + belongs to class xxx and not yyy.

 

In the case of a unary operator, it is evident that the only parameter must be the type of the class. In case you have forgotten, the only ternary operator cannot be overloaded. By failing the above rules, you can never supersede an existing predefined operator definition.

 

The cast operators are resolved using the same principles but by using type names instead of operator symbols. Element access like an array is achieved using indexers that has absolutely has no relationship at all with operator overloads.

 

We do not have the leeway of doing whatever we want with an operator. We cannot change the precedence or the associativity of the operator under any other circumstance. By overwriting the plus operator, there is no way we can specify what its precedence now should be.

 

The compiler does not enforce any rules of sanity when we write code for an operator overload. Thus, even if common sense tells us that the logical operator == should always return a boolean value, it is not mandatory to do so. If you return, say, an int instead, C# turns a blind eye to it. It is your fiduciary responsibility as an adult to return appropriate values and write the right logical code in your operator functions. Do not look to the compiler ever for help in catching logical errors. In this century at least!

 

a.cs

public class zzz {

public static void Main()

{

xxx a = new xxx();

xxx b = new xxx();

System.COnsole.WriteLine(a+b);

}

}

class yyy

{

public static yyy operator + ( yyy a, yyy b)

{

return new yyy();

}

}

class xxx

{

public static implicit operator aaa ( xxx a)

{

return new aaa();

}

}

class aaa

{

public static implicit operator yyy ( aaa a)

{

return new yyy();

}

}

 

Compiler Error

a.cs(6,26): error CS0019: Operator '+' cannot be applied to operands of type 'xxx' and 'xxx'

 

Call it a limitation or a lack of foresight on the part of the compiler. We are trying to add two objects that look like xxx. The compiler goes to the class and tries to find an operator that can add two xxx objects. It does not find one and tries various permutations and combinations all within the class xxx only. As it does not succeed, it tries all of its predefined + operators. None succeed and hence we get an error.

 

What we expected of the compiler was to have realized that there was a way to convert a xxx object into an aaa object. On converting, it should have looked into the aaa class and converted the aaa object into a yyy object using the respective operators. Then it should have called the overloaded + operator of the class yyy and got the job done.

 

The point here is that C# is so one-track that it keeps trying in the same class and does not bother to look into another class at all. The programming language C++ on which C# is based would stand on its head to prevent an error from happening. C# is not as adventurous as we expected it to be. You let us down Pal.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy(1);

yyy b = new yyy(2);

System.Console.WriteLine(a+b);

}

}

class yyy

{

int i;

public yyy(int j)

{

i = j;

}

public static yyy operator + ( yyy a, xxx b)

{

System.Console.WriteLine("op + " + a.i + " " + b.j);

return new yyy(a.i+b.j);

}

public static implicit operator xxx(yyy a)

{

System.Console.WriteLine("op xxx " + a.i);

return new xxx(a.i);

}

public static implicit operator string( yyy a)

{

return "yyy " + a.i;

}

}

class xxx

{

public int j;

public xxx(int k)

{

j = k;

}

}

 

Output

op xxx 2

op + 1 2

yyy 3

 

As mentioned earlier, C# tries a little to prevent an error from happening but it does not try hard enough. We are trying to add two yyy objects. However, in the class yyy, we have an overloaded plus operator that accepts a yyy and a xxx object. Thus, the compiler now realizes that it can convert a yyy object into a xxx object using the operator xxx. It converts the second parameter b to a xxx object and now calls the operator plus with the right data types. For the benefit of the WriteLine function, it will convert the resulting yyy object into a string. If the string operator was not overloaded, it would use the ToString function of the object class and simply display yyy. There would be no way to verify on the value of i, which is 3.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy(1);

yyy b = new yyy(2);

System.Console.WriteLine(a+b);

}

}

class yyy

{

int i;

public yyy(int j)

{

i = j;

}

public static implicit operator string( yyy a)

{

return "yyy " + a.i;

}

}

 

Output

yyy 1yyy 2

 

Hey, it works! No errors at all. How come? The compiler realized that there was no overload of the + operator that accepts two yyy objects in class yyy. So, it acted smart and converted both of them into a string. It calls the string operator of yyy twice to convert both objects a and b to a string and then concatenates them together. How's that for an intelligent compiler!

 

Remember, it does not peek into another class not try any sort of permutations and combinations. It will only look in the current class and first try and use the user defined plus before trying the predefined plus operator. We have confirmation of the above statement as in the earlier program, it did not use the predefined + operator but our user defined operator. If we remove the operator xxx from class yyy from the above program + 1, the string operator would be called. This is because the compiler cannot use the user defined + operator as there exists no conversion between a yyy to a xxx object. Remember C# uses his own operators as a last resort only.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy(1);

yyy b = new yyy(2);

System.Console.WriteLine(a+b);

}

}

class yyy

{

int i;

public yyy(int j)

{

i = j;

}

public static implicit operator string( yyy a)

{

return "yyy " + a.i;

}

public static implicit operator int ( yyy a)

{

return a.i;

}

}

 

Compiler Error

a.cs(7,26): error CS0034: Operator '+' is ambiguous on operands of type 'yyy' and 'yyy'

 

 

It is very difficult to confuse the compiler. We have added a way for the compiler to convert a yyy object to an int. Thus we have created a problem for ourselves. It is equally probable for C# to convert the two yyy objects into strings and then add them up or convert them to numbers and do the same. Equally probable, thus C# does not like to play favorites and gives us an error.

 

An operator definition or declaration must include the modifiers public and static. The compiler generates an error if you do not specify one. You have to specify both. These are like death and taxes. You cannot escape any of them. Maybe it would be a better idea for the compiler not to mandate on modifiers. Why always write the obvious.

 

a.cs

class yyy

{

public static yyy operator + (yyy a, ref int i)

{

return new yyy();

}

}

 

An operator can only accept parameters passed as value and not passed as value or ref. Had the designers of C# favoured, they could have easily removed the above restriction. Once again, we come across a design decision made by the designers thus conveying to follow their dictates with a smile on our faces.

 

The signature of an operator does not include the return type like a function. We cannot have two operators having the same parameter list but differing only in return types. If you recall, we had the same rule for overloading functions in a class. Consistency, thy name is C#.

 

a.cs

class yyy

{

public static yyy operator + (yyy a, yyy b)

{

return new yyy();

}

public static yyy op_Addition (yyy a, yyy b)

{

return new yyy();

}

}

 

Compiler Error

a.cs(7,19): error CS0111: Class 'yyy' already defines a member called 'op_Addition' with the same parameter types

 

Every time we create an operator function, internally the compiler changes its name. A plus operator internally becomes op_Addition. How did we figure this out?? In a very simple manner. We created two plus operators that differed only in return type. The compiler gave us the above error message with the new name of the function.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy();

yyy b = new yyy();

System.Console.WriteLine(a+b);

}

}

class yyy

{

public static yyy op_Addition (yyy a,yyy b)

{

return new yyy();

}

}

 

Compiler Error

a.cs(7,26): error CS0019: Operator '+' cannot be applied to operands of type 'yyy' and 'yyy'

 

Extremely difficult to trick the compiler! We tried and we lost miserably. As mentioned earlier the compiler converts the plus operator to a function op_Addition. We thought that by placing such a function, we could trick the compiler into accepting op_Addition as the plus operator. Unfortunately, it does this name conversion later. It first checks for a plus operator, then perform the parameters match-making and if this results in errors then and only then will it rename the plus operator to op_Addition. It does it at the fag end of the compilation process.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy();

yyy b = new yyy();

System.Console.WriteLine(a+b);

}

}

class yyy : xxx

{

public static implicit operator string (yyy a)

{

return "hi";

}

}

class xxx

{

public static xxx operator + (xxx a,xxx b)

{

return new xxx();

}

public static implicit operator string (xxx a)

{

return "bye";

}

}

 

Output

bye

 

An odd thing happens along the way. We've learnt that derived classes inherit everything form the base class except the constructors and destructors. yyy is derived from xxx and within class xxx, we overload the plus operator. The derived class yyy does not have a similar overload, hence the xxx function takes effect. When we add two yyy objects, the compiler calls the plus operator from the base class, xxx. Since, the resulting object is an xxx object, while executing System.Console.WriteLine, the  string operator from xxx is called and not the one from yyy.

 

The only rational explanation for the above behavior is that a yyy object is made up of two objects, a xxx object and a yyy object. Here the compiler scales down the yyy object to a xxx object and then calls the plus operator from it. As the returned object is of type xxx, it does not require the string operator form the class yyy, hence it calls it from xxx. In our humble point of view, we should have received an error as adding two yyy objects is very different form adding two xxx objects.

 

a.cs

public class zzz

{

public static void Main()

{

yyy a = new yyy();

yyy b = new yyy();

System.Console.WriteLine(a+b);

}

}

class yyy : xxx

{

public new static xxx operator + (xxx a, yyy b)

{

return new xxx();

}

}

class xxx

{

public static xxx operator + (xxx a,xxx b)

{

return new xxx();

}

}

 

Compiler Error

a.cs(12,23): error CS0106: The modifier 'new' is not valid for this item

 

At times, we try very hard to simulate an error. In the books we are writing, we have promised to display every error message the compiler throws up. The keyword new is used to hide a method declared earlier in a base class hence it cannot be used in front of a operator.

The problem with an operator is that it requires at least one of the parameters to be the type of the class it resides in. Thus we can never ever duplicate the signature of a operator in a base class in a derived class. As this duplication is not possible, writing the keyword new is improbable as it cannot hide something that does not exist in the base class. Thus, new is not required and not allowed as a modifier to an operator.

 

Summary

 

We can have only three types of operators. Unary operators, the one that accept only one operand and can be either in a pre-fix state, that is, before the operator example -x or post-fix after the operator example x++.

 

The second type of operator is called a binary operator that accepts two operands and the operator is used in between the operands. It is also known as the in-fix notation. The most commonly used example everywhere is x + 10.

 

The third type of operator is like the Indian tiger, rarely seen or heard. This is the ternary operator that requires 3 operands and uses the infix notation. There is only one such operator in existence in the entire C# language. This proves the importance of the ternary operator to the designers of the C# language. If I was incharge, I would have banished the only ternary operator ?: to kingdom hell!

 

Operators can be overloaded. This implies that operators become social beings and are now capable of interacting with more data types. Overloading operators adds intelligence to the existing operator thereupon helping them act on different user created data types.  C# is oblivious to them. Another interpretation to operator overloading would be using the existing function of operators on the data types that we create. Operator overloading does not mean creating new operators.

 

There are four unary operators +, -, ! and ~ which are allowed to return any type they like, including the type of the class they reside in. The ++ and the -- can only return the data type of the class they reside in. This limitation make sense as a ++ is a glorified addition and assignment put together. The true and false operators must only return a bool as they evaluate an expression to either false or true.

 

For all operators, the signature consists of the operator name or token and its formal parameter types. The return value and the name of the parameters do not participate in the operator signature ever. Amongst the unary operators, only the true and false comes in pairs. Either have both or none at all.

 

Binary operators can return any type that their hearts desire, there absolutely is no restrictions whatsoever. There are however three pair wise operators here, == and !=, > and < and finally >= and <=.

 

We take a simple example of x + y * z * a. The above statement may confuse you but not the C# compiler. We have a situation where we have multiple operators on a single line. The compiler does not look at the entire line but checks for the operator of higher importance. It is familiar with of the order of importance with operators. Thus, between the + and *, C# has been informed that the * or multiplication is more important that the addition or +. In other words the precedence of the * is more than that of the +.

 

Precedence simply defines the order of importance to C# and this decides which operator is to be executed first. C# has a table internally called the precedence table which shows the relative importance of the operators within each other. The higher you are in this table the more important C# thinks you are and the earlier you will be recognized in an expression by the compiler. If you fall near the bottom of the table, wait your turn before being invited for the ball! Mr Documentation voices the same thing in a better way. It is as follows: "The precedence of an operator is established by the definition of its associated grammar production". Good luck in trying to comprehend what is being said. At the end of the day or year as the case may be, we are both saying the same thing, albeit in a slightly different way.

 

We now have another problem to resolve. We have the * operator twice on the same line. Which one will the compiler read first ? We have a very simple rule. All binary operators are read forwards. This means that the compiler will only see y * x and then only, their product is multiplied by z.

 

Thus the associativity of binary operators is forwards or from left to right. The only two exceptions to this rule are the assignment operator that moves backwards or right to left and the only ternary operator. These two operators are called right-associative. This applies not only to multiple similar operators on the same line but also to operators having the same precedence. Thus, the rules of precedence decide which operator is to be executed first and if by chance many have the same precedence number, then the rules of associativity apply. Thus, the earlier expression of x + y * z * a would have read as follows - (x + ((y * z) * a)). These rules of precedence and associativity are now cast in stone like the Ten Commandments and cannot be changed at all.

 

Alike everything in life, there is one small caveat. Using the parenthesis, we are allowed to create our own rules of precedence and associativity, but they are not permanent and  only apply during the expression. Unless stated otherwise, the compiler applies its own predefined set of rules for operators.

 

The Other Odds and Ends

 

a.cs

class yyy

{

public static implicit operator float(yyy x)

{

return 0;

}

public static implicit operator decimal(yyy x) {

return 0.0m;

}

}

class zzz

{

static void Main()

{

yyy a = new yyy();

a = -a;  

}

}

 

Compiler Error

a.cs(16,5): error CS0035: Operator '-' is ambiguous on an operand of type 'yyy'

 

The operator - can act on a float and a decimal. We have given the - sign to an object that looks like yyy. It will try to convert a yyy into a operator that it can deal with. As it is equally at home with a decimal or a float, it does not know which one to use and hence the ambiguity error. If we have two equally probable ways of doing the same thing, we have a problem.

 

a.cs

public class zzz

{

public static void Main()

{

int i = zzz() ? 1: 2;

}

}

 

Compiler Error

a.cs(5,9): error CS0119: 'zzz' denotes a 'class' which is not valid in the given context

 

The above error is similar to the earlier one. Using stuff in the wrong context. A constructor is not allowed to return vales. We are using the non-existent return value in a ?: operator. Use things in the wrong place and will return back a different error message

 

a.cs

class zzz

{

public static void Main()

{

string i = "hi";

i++;

} }

Compiler Error

a.cs(6,1): error CS0187: No such operator '++' defined for type 'string'

 

The designers of the string class have written code in a manner that some of the predefined C# operators now understand the string class. We call this operator overloading. The designers of the string class did not overload the ++ operator for reasons best known to them. Thus, we are also not permitted to use such non-overloaded operators with the string class.

 

a.cs

public class zzz {

public static byte operator true(zzz a)

{

return 1;

}

}

 

Compiler Error

a.cs(3,20): error CS0215: The return type of operator True or False must be bool

 

The operator true or false must return only a logical value or a bool. There are restriction on what some operators can return and their parameter types. Fortunately they are few and far in between.

 

a.cs

class zzz

{

public static int operator +( char i)

{

}

}

 

Compiler Error

a.cs(3,19): error CS0562: The parameter of a unary operator must be the containing type

 

An operator overload must have the first parameter as the data type of the class it belongs to. A unary operator can have one and only one parameter. Thus, common sense dictates that with unary operators only, the parameter must be of the class it resides in. Any other data type as a parameter results in an error.

 

a.cs

public class zzz

{

public static zzz operator ++ (zzz c)

{

return null;

}

public static int ii

{

set { }

}

public static void Main()

{

op_Increment(null);  

set_ii(1);

}

}

 

Compiler Error

a.cs(13,1): error CS0571: 'zzz.operator ++(zzz)': cannot explicitly call operator or accessor

a.cs(14,1): error CS0571: 'zzz.ii.set': cannot explicitly call operator or accessor

 

Nearly all the operators or properties or indexers have internal names. This means that the compiler will convert what we write to something else. In our book ‘C# to IL” we have gone into the depths of these internal names. Thus an operator ++ becomes op_Increment and the set accessor of a property ii becomes set_ii.

 

a.cs

class zzz

{

public static void operator + ( zzz a, zzz b)

{

}

}

 

Compiler Error

a.cs(3,20): error CS0590: User-defined operators cannot return void

 

If a user defined operator returns a void, then we cannot club up a multiple of them together. The bigger reason is that the operators are supposed to perform operations and return a value. Of what use is an operator that does not return a values. It is the anti-thesis of what an operator stands for.

 

a.cs

public class zzz {

public static void Main()

{

int[] a;

 a[);

}

}

 

Compiler Error

a.cs(6,4): error CS1525: Invalid expression term ')'

a.cs(6,5): error CS1003: Syntax error, ']' expected

 

A syntax error will confuse you many a times as the compiler will generate a CS1003 error. Here it is telling us very clearly what brackets it expects but the first error is more misleading.

 

a.cs

class zzz

{

public static zzz operator + ( zzz a, zzz b,zzz c)

{

}

}

 

Compiler Error

a.cs(3,44): error CS1534: Overloaded binary operator '+' only takes two parameters

 

There are only three types of operators unary, binary and ternary. The + is called a binary operator as it takes only two parameters and not three parameters, hence the error.

 

a.cs

class zzz

{

public static zzz operator ++ ()

{

}

}

 

Compiler Error

a.cs(3,32): error CS1535: Overloaded unary operator '++' only takes one parameter

 

In the same way, a unary operator takes only one parameter and not zero. Besides, the compiler prefers the + to be a binary operator and not a unary one.

 

a.cs

class zzz

{

public static operator ++ zzz(zzz f)

{

}

}

 

Compiler Error

a.cs(3,27): error CS1554: Declaration is not valid; use '<type> operator ++ (...' instead

 

The same rules work for operators also. The order is public, static, then the return value, the reserved keyword operator and finally the operator character itself. Changing anything is a recipe for disaster.

 

a.cs

public class zzz

{

public static void abc()

{

yyy a;

a->j = 10;

}

}

struct yyy

{

int j;

}

 

Compiler Error

a.cs(6,1): error CS0193: The * or -> operator must be applied to a pointer

 

The -> operator can only be used by a pointer. A is an object that is an instance of a struct and in no way a pointer. Certain operators are reserved, like the Nobel prize, to be conferred on only a selected few.

 

a.cs

interface iii

{

int operator +(iii aa, iii bb)

{

}

}

 

Compiler Error

a.cs(3,5): error CS0567: Interfaces cannot contain operators

 

Interfaces for reasons unknown to us cannot contain operators at all. The only rationalization is that an object that looks like an interface is not an  object at all as an interface contains no code. Why would we want to add two objects that do not carry any code. We hope that the above is a rational explanation.