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.