![]()
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 divid