Chapter 6
Conversions: Implicit and Explicit
Most of us imply all the time and
are rarely explicit.
The C# programming language has
exactly 78 keywords in all. But there are only two types of conversions,
implicit and explicit. As the name suggest, an explicit conversion has to be
specifically supplied by using a cast and at all other times, the conversion is
considered to be implicit. Conversion
comes into play when converting from a source type to a target type, which is
finally the return value of the operator.
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 implicit operator xxx (int b)
{
return new xxx();
}
}
class xxx
{
}
Compiler Error
a.cs(12,8): error CS0556: User-defined conversion must convert
to or from the enclosing type
The rules for user-defined
conversions are extremely simple. They have to accept one parameter, similar to
unary operators, and must be of the same data type as of the class. Under
normal circumstances, conversion operators do not have a return type as it
assumes the return type to be that of the operator type. The return data type
should directly or indirectly be the data type that is being enclosed within
the brackets.
a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
yyy b = new yyy();
}
}
class yyy
{
public static implicit operator xxx (yyy b)
{
return new yyy();
}
}
class xxx
{
}
At times, we can catch the
compiler napping. We are returning a yyy object and not an xxx. As per the
compiler rules, we should have received an error as we are to return an xxx,
whereas we are returning a yyy object. May be, as all classes are derived from
object, so the compiler either misses the error or condones it.
a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
yyy b = new yyy();
}
}
class yyy
{
public static implicit operator xxx (yyy b)
{
return new aaa();
}
}
class xxx
{
}
class aaa
{
}
Compiler Error
a.cs(13,8): error CS0029: Cannot implicitly convert type 'aaa'
to 'xxx'
We believe the above is a special
case. Here we are returning an aaa object instead of xxx. The compiler tries to convert the aaa object
to an xxx object and realizes there is no such conversion possible in class
yyy. Hence, the error. To remove the error, place an operator xxx in class aaa,
which will convert the aaa object into an xxx like object. It will make an
exception only if the return type is the same class that the code resides in.
We yet are firm in our belief that someone somewhere goofed up.
a.cs
class yyy {
public static implicit operator yyy(yyy b)
{
return new yyy();
}
}
Compiler Error
a.cs(3,15): error CS0555: User-defined operator cannot take an
object of the enclosing type and convert to an object of the enclosing type
Thus we can only convert from one
type to another and not to the same type again. It does not make any sense even
to a madman why we would like to take a yyy object and convert it back again to
a yyy object. Also, these conversion operators must lie in the same class and
not in any other class.
a.cs
public class zzz {
public static void Main()
{
yyy a = new yyy();
xxx b = new xxx();
b = a;
a = b;
}
}
class yyy
{
public static implicit operator xxx (yyy b)
{
System.Console.WriteLine("op xxx");
return new xxx();
}
}
class xxx
{
public static implicit operator yyy (xxx b)
{
System.Console.WriteLine("op yyy");
return new yyy();
}
}
Output
op xxx
op yyy
The code to convert an xxx object
to a yyy must reside in the class yyy only. If we create such an operator, it
will work on a=b, as the xxx object b can now be converted into a yyy like
object. However, b = a will give an error unless we have an impicit operator
yyy in class xxx. If there is no such conversion available in class xxx to
convert a yyy object to an xxx object, a compiler error is generated. This
conversion cannot reside in yyy or any other class, it has to be in the xxx
class only.
a.cs
public class zzz {
public static void Main() {
yyy a = new yyy();
xxx b = new xxx();
b = a;
a = b;
}
}
class yyy
{
public static implicit operator xxx (yyy b)
{
System.Console.WriteLine("op xxx");
return new xxx();
}
public static implicit operator yyy (xxx b)
{
System.Console.WriteLine("op yyy");
return new yyy();
}
}
class xxx
{
}
Output
op xxx
op yyy
Should we apologize to the
compiler? No. Did we lead you up the garden path? No. Every language has
certain quirks. We want to convert a yyy object to an xxx object and vice
versa. C# felt it to be too cumbersome to write the code in two separate
classes. So it made a one time exception to all its rules. You can have a
conversion operator take a single parameter other than the class data type. We
have thus broken one golden rule in operator yyy within class yyy.
Class yyy contains a function
that accepts one parameter, not a yyy object but an xxx object. The code is
much more compact where logically relevant code is place together.
Remember there is always a method
behind the madness and at times breaking rules makes the world go round better.
a.cs
class yyy {
public static implicit operator xxx (yyy b)
{
return new xxx();
}
}
interface xxx
{
}
Compiler Error
a.cs(3,15): error CS0552: 'yyy.implicit operator xxx(yyy)':
user-defined conversion to/from interface
We cannot convert to and from an
interface. The above restriction is due to the fact that an interface and a
class differ in concept. A class has code whereas an interface doesn't. Similar
to water and oil that do not mix and match, we cannot convert from two
differing concepts.
a.cs
class yyy : xxx
{
public static implicit operator xxx (yyy b)
{
return new xxx();
}
}
class xxx
{
}
Compiler Error
a.cs(3,15): error CS0553: 'yyy.implicit operator xxx(yyy)':
user-defined conversion to/from base class
An object that looks like yyy
also looks like xxx as yyy is derived from xxx. Thus there is already a way for
a yyy object to scale down and become an xxx object. This is built in the
inheritance structure. We do not need an operator to do what already happens by
default.
Converting to a base class is an
error as we try to duplicate what the compiler has already done for us. In the
same vein, we cannot convert to an object as the Object class is the base class
for all classes. All classes implicitly convert to an Object class and these
conversions are already predefined. We are also not allowed to override a
predefined conversion. The signature of the operator is its name, return type
and parameter type. Unlike operators, the return type is part of the signature.
The keyword implicit and explicit are not part of the signature.
a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
xxx b = new xxx();
b = a;
}
}
class yyy
{
public static implicit operator xxx (yyy b)
{
throw new System.Exception();
return new xxx();
}
}
class xxx
{
}
Output
Exception occurred: System.Exception: An exception of type
System.Exception was thrown.
at
yyy.op_Implicit(yyy b)
at zzz.Main()
A user-defined operator is not
supposed to throw an exception. Remember an exception is thrown when something
goes wrong. We are not supposed to lose information in a user-defined
conversion. By convention, an implicit operator never throws an exception, whereas
an explicit one is allowed to. You are permitted to float a convention at your
own peril.
a.cs
public class zzz
{
public static void Main()
{
}
}
class yyy
{
public static implicit operator xxx (yyy b)
{
return new xxx();
}
public static xxx op_Implicit(yyy b)
{
return new xxx();
}
}
class xxx
{
}
Compiler Error
a.cs(13,19): error CS0111: Class 'yyy' already defines a
member called 'op_Implicit' with the same parameter types
By throwing the above exception,
we now know that our conversion operator is renamed to a function called
op_Implicit with one parameter, a yyy object in the above case. What if we have
two such conversion operators?
a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
aaa b = new aaa();
xxx c = new xxx();
try
{
b = a;
}
catch ( System.Exception e)
{
System.Console.WriteLine(e);
}
try
{
c = a;
}
catch ( System.Exception e)
{
System.Console.WriteLine(e);
}
}
}
class yyy
{
public static implicit operator xxx (yyy b)
{
throw new System.Exception();
return new xxx();
}
public static implicit operator aaa(yyy c)
{
throw new System.Exception();
return new aaa();
}
}
class xxx
{
}
class aaa
{
}
Output
System.Exception: An exception of type System.Exception was
thrown.
at
yyy.op_Implicit(yyy c)
at zzz.Main()
System.Exception: An exception of type System.Exception was
thrown.
at
yyy.op_Implicit(yyy b)
at zzz.Main()
Here is what surprises us. We
always thought that two functions could not ever have the same name. Here the
compiler has converted the implicit functions to have the same name and if we
had not changed the name of the object in the parameter list from b to c, they
would have been identical. In the earlier example, where we had a function
called op_ Implicit, the compiler, gave us an error, but in the above example,
the compiler actually scales down to two functions with the same name.
How does it internally separate
them, we have no idea at all. Life is a drag. One set of rules for the
compiler, another set for us. The compiler can break rules with impunity, we
cannot.
There are seven types of implicit
conversions, which are identity, implicit numeric, implicit enumeration,
implicit reference, boxing, implicit constant expression and finally
user-defined implicit conversions.
We are allowed to equate any type
to our user-defined type. This type of conversion is called an identity
conversion where it lets an entity of a particular type to be converted to the
type we want.
a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
sbyte i = 10;
System.Console.WriteLine(a+i);
}
}
class yyy
{
public static string operator + (yyy b,int x)
{
return "int " + x;
}
public static string operator + (yyy b,short x)
{
return "short " + x;
}
public static string operator + (yyy b,decimal x)
{
return "decimal " + x;
}
}
Output
short 10
An implicit numeric conversion
converts an sbyte to short, int, long, float, double, or decimal. The above
line is courtesy Mr Documentation. It simply means that whenever the compiler
sees a byte, it will try and find a matching function or operator that accepts
a byte as a parameter. In this case, there is no operator + that accepts a byte
as a parameter. So, the compiler now looks for another function that accepts a
short as a parameter. If it finds one, it promotes the byte to a short, rolls
up its sleeves and calls the function. Thus, we get short 10 displayed as the
result.
C# does all of this without
taking our permission. Thus it is called an implicit numeric conversion because
it happens silently in the background without our knowledge. When we remove the
code of the operator + that accepts a short, we get the following answer.
Output
int 10
The answer is very intuitive. The
compiler starts the whole process explained above once again. It first tries to
find a function that accepts a short. No luck. The compiler does not give up
and now tries to find one that accepts an int. Bingo, it finds one so it
executes the function.
If we now remove the plus
operator with the int, then only the one with decimal stays. As per the above
rules, a match will be found and the function will be called.
The order of conversion is as
decided by the compiler. We cannot change the above order, all that we can do
is gracefully accept it and carry on with life. If you notice, this order is
from small to large in terms of memory allocation by the compiler. The order
makes a lot of sense now.
a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
sbyte i = 10;
System.Console.WriteLine(a+i);
}
}
class yyy
{
public static string operator + (yyy b,decimal x)
{
return "decimal " + x;
}
public static implicit operator string ( yyy a)
{
return "hi";
}
}
Output
decimal 10
Strange are the ways of a
compiler. In the above case, we presumed the compiler would face a dilemma. It
could either convert the byte to a decimal, or convert a yyy object to a string
and then call the plus operator. It prefers the first option as it means fewer
steps to be carried out. Remove the plus operator totally and the second option
will get executed. The string operator of the class yyy is called which returns a string This is then concatenated
with 10 to display hi10.
a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
sbyte i = 10;
System.Console.WriteLine(a+i);
}
}
class yyy
{
public static string operator + (yyy b,decimal x)
{
return "decimal " + x;
}
public static implicit operator string ( yyy a)
{
return "hi";
}
public static implicit operator byte ( yyy a)
{
return 21;
}
}
Output
decimal 10
The compiler has a one-track
mind. It always prefers an implicit numeric conversion. It could have converted
the yyy object to a byte and then called the predefined plus to add two
numbers. It did not do it then for a string, it will not do it now either for
two numbers. It has its own preferences on doing things its way. If we remove
the operator plus code from the class, we get the following error.
Compiler Error
a.cs(7,26): error CS0034: Operator '+' is ambiguous on
operands of type 'yyy' and 'byte'
Here the compiler has no
preferences over calling the string or the byte conversion operators. Hence, it
flags an error whenever it cannot make up its mind or is in two minds. The
documentation came up with other rules for the other data types. We have copied
them and would advise you to remember them on a rainy day.
Byte to short, ushort, int, uint,
long, ulong, float, double, or decimal. Short to int, long, float, double, or
decimal. Ushort to int, uint, long, ulong, float, double, or decimal. Int to
long, float, double, or decimal. Uint to long, ulong, float, double, or
decimal. Long to float, double, or decimal. Ulong to float, double, or decimal.
Char to ushort, int, uint, long, ulong, float, double, or decimal and finally
Float to double.
We have no choice but to have a
piece of paper with the above rules written down as a ready reckoner. We feel
very guilty when we copy-paste from the documentation and avoid doing it like
the plague. However, we have no choice as rules are rules are rules!!.
Finally, a char stores unicode
characters which are finally numbers. Working with chars is a one-way street.
You are allowed to convert a char to another data type but not vice versa. For
instance, a byte cannot be converted to a char.
An implicit enumeration
conversion lets the number zero be converted to any enum type. This was
expected, as an enum is nothing but a glorified number with a name.
Implicit
Reference conversions
The above conversions are what we
have been talking about throughout this book. The only difference is that we
are pegging a label or a name to what we already know. All reference objects
can be scaled down to an object without a murmur. A derived class can be scaled
down to a base class or a base interface. Ditto for interfaces derived from
each other. Any array type is scaled to System.Array and delegate to
System.Deleagte. In turn, an array or a delegate can also become an interface
System.IClonable. The null type can be used to initialize any reference object.
Arrays at times pose a small
problem. We can equate arrays only if they are of the same dimension and they
store reference types. Then they become simple objects and a conversion must
exist to convert one to the other. These do not require any compile time checks
and do not change the value of the object, they may change the data type only.
Implicit constant conversions
only deal with constants. They are very simple in nature. An int can be
converted to a sbyte, byte, short, ushort, uint, or ulong provided the range of
the destination is not exceeded. In the same way , a ulong can be converted to
a long. How boring! Makes us feel sleepy writing all this original travail. How
many times did you yawn? We, a hundred thousand times!
Explicit
Conversions
Explicit conversions are made up
of all implicit conversions, explicit numeric conversions, explicit enumeration
conversions, explicit reference conversions, explicit interface conversions,
unboxing conversions and finally user-defined explicit conversions. Remember,
for the last time, an explicit conversion only exists in a cast expression. It
is like beauty, it can only lie in the eyes of the beholder.
An explicit conversion has a
large number of useful properties. It may or may not succeed. There is no way
to prove that explicit conversions will always succeed and that is why they are
placed in the explicit category. We may lose some information on our path to
conversion. Perfectly acceptable. They also have vision and can convert from a
large range of domain types. We can have as many redundant casts as we like in
a single expression.
a.cs
public class zzz {
public static void Main() {
yyy a = new yyy();
yyy b = (xxx)(aaa) a;
}
}
class yyy
{
public static implicit operator aaa( yyy a)
{
System.Console.WriteLine("op aaa");
return new aaa();
}
}
class aaa
{
public static implicit operator xxx( aaa a)
{
System.Console.WriteLine("op xxx");
return new xxx();
}
}
class xxx
{
public static implicit operator yyy( xxx a)
{
System.Console.WriteLine("op yyy");
return new yyy();
}
}
Output
op aaa
op xxx
op yyy
In the above case, we have come a
full circle. We are first converting a yyy object into an aaa object then to an
xxx object and finally, once again to a yyy object. We can have as many casts
as we want on a single line and the compiler will do our bidding for us. It
does not check the veracity of our casting nor does it realize that we are
coming back to square one. Thus, multiple redundant casts are allowed always.
Explicit
numeric conversions
a.cs
public class zzz
{
public static void Main()
{
char i = 'A';
short b = 65;
i = b;
}
}
Compiler Error
a.cs(7,5): error CS0029: Cannot implicitly convert type
'short' to 'char'
We told you earlier that a char
cannot be converted to any other data type but the reverse was possible. Now we
want the above conversion to work, by hook or crook.
a.cs
public class zzz
{
public static void Main()
{
char i = 'A';
short b = 65;
i = (char)b;
System.Console.WriteLine(i);
}
}
Output
A
The only way out is a simple
cast. Something that was not possible implicitly is now available with a simple
cast. This is the beauty of an explicit conversion. We are going a step further
than what the implicit conversion does, hence these conversions are over and
above the implicit ones. Explicit conversions are more powerful and useful than
implicit ones.
One more round of copying from
the documentation just to make the book complete. These are the conversions
that are available to us if and only if we use a cast. They are as follows
verbatim. Sbyte to byte, ushort, uint, ulong, or char. Byte to sbyte and char.
Short to sbyte, byte, ushort, uint, ulong, or char. Ushort to sbyte, byte,
short, or char. Int to sbyte, byte, short, ushort, uint, ulong, or char. Uint
to sbyte, byte, short, ushort, int, or char. Long to sbyte, byte, short,
ushort, int, uint, ulong, or char. Ulong to sbyte, byte, short, ushort, int,
uint, long, or char. From char to sbyte, byte, or short. Float to sbyte, byte,
short, ushort, int, uint, long, ulong, char, or decimal. Double to sbyte, byte,
short, ushort, int, uint, long, ulong, char, float, or decimal. Decimal to
sbyte, byte, short, ushort, int, uint, long, ulong, char, float, or double.
It breaks our heart to
cut-and-paste from the documentation and fill up pages of our the book. Do we
have a choice? No. Should you remember all the above rules ? Never, not even on
a rainy day!
Thus, we are able to convert from
one numeric type to another as they are covered by either implicit or explicit
conversions. The only way to remember the above rule is to try and convert one
numeric type to another. If we get an error then simply cast. As we are
permitted to convert from one type to another, we must be prepared to catch
exceptions being thrown at run time. Also, some information may be lost due to
the conversion. The exception gets
thrown depending upon the context, checked or unchecked.
a.cs
public class zzz
{
public static void Main()
{
byte i = (byte)3.6;
System.Console.WriteLine(i);
}
}
Output
3
A float, double or decimal type
can be cast to an integer type. However, the compiler will not round off the
double to an integer but instead remove everything after the decimal place. The
documentation calls this rounding towards zero.
By now, you must have realized
that we are reading the documentation with a fine toothcomb. It is, after all,
the last word on the C# programming language. The last word may not read like a
snazzy detective novel, but nor does our book. The more we criticize the
documentation on grounds of legibility, readability, etc, the more we will
sound like the pot calling the kettle black. People staying in glass houses
should not throw stones and we have already thrown enough.
Enums work the way for
conversions as expected and no point cutting more trees to explain what they
do. We honestly feel you can spend a lifetime writing C# code and yet not use
an enum ever. The Java programming language does not use enums and nobody
misses them ever.
a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
xxx b = new xxx();
b = a;
b = (xxx)a;
a = (yyy)b;
a = b;
}
}
class yyy
{
public static implicit operator xxx( yyy a)
{
return new xxx();
}
}
class xxx {
}
Compiler Error
a.cs(9,6): error CS0030: Cannot convert type 'xxx' to 'yyy'
a.cs(10,5): error CS0029: Cannot implicitly convert type 'xxx'
to 'yyy'
We have created a standard
implicit conversion of a yyy object to an xxx object, as usual, by using the
operator xxx. An explicit operator is not created as we can use only one of the
modifiers either implicit or explicit. However, each time we use the implicit
modifier we get a free explicit one also. Thus the line b = (xxx) a does not
give us any error.
It is better to use an implicit
modifier as it also doubles up for an explicit modifier. Thus, we now
understand that when we equate a byte to an int, it is an implicit modifier as
it works with and without a cast. Alas, the reverse is not true.
We wrote a conversion from class
yyy to class xxx and not from xxx to yyy. The last two lines of main give us an
error. Nothing in life is free and thus we have to actually write the
conversion ourselves.
a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
xxx b = new xxx();
b = a;
b = (xxx)a;
}
}
class yyy
{
public static explicit operator xxx( yyy a)
{
return new xxx();
}
}
class xxx
{
}
Compiler Error
a.cs(7,5): error CS0029: Cannot implicitly convert type 'yyy'
to 'xxx'
Now that we have asked for the
conversion to be explicit, the statement b = a will give an error. This is
because we have not used a cast and are implying a conversion. The cast on the
next line, however, passes the muster as it invokes the explicit operator. To sum
up in a sentence, the implicit conversion operator is a superset of the
explicit operator. An explicit modifier is to be used only when the programmer
is to be compelled to use a cast.
Conversions for the last time -
we promise.
a.cs
public class zzz
{
public static void Main()
{
}
}
class yyy
{
public static implicit operator xxx( yyy a)
{
return new aaa();
}
}
class xxx
{
}
class aaa
{
}
Compiler Error
a.cs(11,8): error CS0029: Cannot implicitly convert type 'aaa'
to 'xxx'
In the above program, we have the
operator xxx that accepts one parameter, yyy, as we are in class yyy. This has
been made abundantly clear in the past. What we were vague about was the return
type. Nowhere in the world does it say that the user defined conversion
operator xxx has to return an xxx type. All that it said is that it should
return an object that can be converted to an xxx. It does not have to, we
repeat again, return an xxx object. The class aaa does not convert from an aaa
object to an xxx object and thus the error is generated.
a.cs
public class zzz
{
public static void Main()
{
yyy a = new yyy();
xxx x = new xxx();
x = a;
}
}
class yyy
{
public static implicit operator xxx( yyy a)
{
System.Console.WriteLine("op xxx yyy");
return new aaa();
}
}
class xxx
{
}
class aaa
{
public static implicit operator xxx( aaa a)
{
System.Console.WriteLine("op xxx aaa");
return new xxx();
}
}
Output
op xxx yyy
op xxx aaa
We now get no errors even if the
operator xxx from class yyy returns an aaa object. This is because the compiler
can now convert to an xxx using the operator xxx in class aaa. The end result
brings an xxx object into being. If the object is being indirectly returned
then you can discount returning the same type as the operator within a class.
a.cs
class zzz
{
public static void Main()
{
zzz a = new zzz();
yyy b = new yyy();
xxx c = new xxx();
aaa d = new aaa();
a.abc(b);
System.Console.WriteLine();
}
public void abc(xxx x)
{
System.Console.WriteLine("abc xxx");
}
public void abc(aaa x)
{
System.Console.WriteLine("abc aaa");
}
}
class yyy
{
public static explicit operator xxx ( yyy a)
{
System.Console.WriteLine("op xxx");
return new xxx();
}
public static implicit operator aaa( yyy a)
{
System.Console.WriteLine("op aaa");
return new aaa();
}
}
class xxx
{
}
class aaa {
}
Output
op aaa
abc aaa
The function abc is overloaded to
accept either an xxx or an aaa object. We are passing it however, a yyy object.
The compiler will first check whether there is some way to convert a yyy object
to either an xxx or an aaa object. It finds two such operators in the class
yyy. Therefore it does not give us an error at this point, as the two operators
are not at the same level of importance for the compiler. One of them, the
operator aaa is implicit so the compiler gives it a greater importance than the
explicit operator. Hence, operator aaa gets called.
If both were implicit, then we
would get an error as the compiler in confused state will not know which
operator to call. If both were explicit, a different error results and the
compiler will not call any. In one case, it is a problem of plenty, and in the
second case, a problem of scarcity.
Thus an implicit can stand in for
an explicit but not vice versa. The above statement means that if we explicitly
cast the object in the function abc to an aaa i.e. a.abc((aaa)b), it would yet
call the implicit operator aaa in class yyy. However, the converse is not true.
If we explicitly cast parameter b to a xxx, it will always call the operator
xxx in class yyy irrespective of the modifier explicit or implicit. In case you
have a tendency to forget, look at it in this way; an implicit is the elder,
more intelligent brother and explicit is the younger, less developed brother.
Implicit is the superset of explicit.
a.cs
public class zzz
{
public static void Main()
{
}
}
class yyy
{
public static implicit operator xxx( yyy a)
{
return new aaa();
}
}
class xxx
{
}
class aaa
{
public static implicit operator bbb( aaa a)
{
return new bbb();
}
}
class bbb
{
public static implicit operator xxx( bbb a)
{
return new xxx();
}
}
Compiler Error
a.cs(11,8): error CS0029: Cannot implicitly convert type 'aaa'
to 'xxx'
However, the compiler does not go
far enough for us. It stops short. For various reasons known only to the
compiler, it does not look beyond a class for user-defined conversions. This
means that in the above case an error will be generated as within aaa, it will
only check for an operator that can convert an aaa object into an xxx object.
But when the compiler digs
deeper, it finds a way out. The compiler can covert an aaa object to a bbb
object and then convert that bbb object to an xxx object using the operator xxx
in class bbb. For some weird reasons, it simply looks at the class and as it
has no operator xxx, it simply spews out an error. It does not look beyond the
confines of class aaa thus proclaiming that it has a myopic view.
By following the above approach,
it makes it easier for the programmer who wrote the compiler, as he does not
have to make it any more intelligent. We would have preferred a more
intelligent companion or compiler on our side.
Before opting for a conversion
method, the compiler follows a predefined set of rules. It first figures out
the guest list. This is a list of classes that can contribute an operator. It
comprises of the source and target classes as well as their base classes. From
now on, unless otherwise stated, a struct can also be used seamlessly for a
class. Then, to choose a user defined function, it has to match two conditions.
First and foremost, the argument/parameter type of the source must match that
of the operator and more important, the return value of the operator must be
convertible to the target type.
Now comes the problem. The
compiler needs to determine which conversion to use, if there are more than one
matches. The one that most closely matches is the one chosen, bearing in mind,
that we do not go astray and look into a million classes. Hence, a conversion
may have to convert the source type to match the parameter of the operator,
then execute the operator, and finally convert to the return type if necessary.
Some big words again. We want to
convert from type A to type B both don't fall under interface types. If we use
implicit conversion, then type A is said to be encompassed by type B and B
encompasses type A. Very well said !
Numeric
Promotions
Take a look at this:
int operator *(int x, int y);uint
operator *(uint x, uint y);long operator *(long x, long y);ulong operator
*(ulong x, ulong y);float operator *(float x, float y);double operator *(double
x, double y);decimal operator *(decimal x, decimal y);
We cheated. We found out that the
multiplication operator * is not defined once but overloaded seven times as
seen above. If you observe the above seven carefully, you will realize that
there is no overload to multiply a byte and a short. If we had all permutations
and combinations of numeric types, we would have to write a million such
operator overloads. Coming back to our earlier example. The compiler would
convert our byte and short to an int as they both can be converted to ints. One
more reason to this is that the first operator on the above list accepts two
ints. However, if we had to multiply a double and an int, both would be
converted to a double and the second last operator would be called. The
compiler will try its best to find a match higher in the hierarchy in case of
any in dissimilarities.
Unary operator promotions apply
only to three operators +, - and ~. All that they do is convert sbyte, byte,
short, ushort or char to an int. As simple as that. Thus if we write a unary
plus operator on a byte, the byte gets automatically promoted to an int. The
unary - goes a step further, it also converts a uint to a long.
Binary promotions also work in a
similar way. There are however many more binary operators than any of the other
two types of operators. To jog your memory, we
reproduce the binary operators once again. +, -, *, /, %, &, |, ^,
==, !=, >, <, >=, and <=.
The rules of binary promotion are
as follows in the order specified. First the compiler checks if any of the
operands is of type decimal. If it is, the other operand is now converted to a
decimal. But if the other operand happens to be float or a double, an error
results. This concludes that we cannot have a binary operation performed on a
decimal and a float or double. C# does not like the above potent combination.
If you cast, all is forgiven.
The mystery as to why C# first
checks for decimal operands is yet not resolved. If none of the operands is a
decimal, the compiler checks with it can now see a double. If it does, it
converts the other to a double. If none of the parameters are a double it
checks if one of them is a float. If affirmative, then it gets converted to a
float. This completes the numbers with decimal places. It then checks if the
other parameter is a ulong. If yes, the other parameter is converted to a ulong,
if and only if it is not a sbyte, short, int or long. If it is we get an error
as usual. Then it checks for a long and converts the other operand to a long.
No complications as in the case of a ulong. The compiler also checks for a uint
as one parameter and the other a sbyte, short or int. If true, then hold your
horses as both parameters are converted to a long and not a uint.
Whatever happened to rationality
and consistency? Why did change only for a uint. If one of them is a uint
and the other not a decimal, float,
short etc, then we follow the old golden rules and both are converted to a
uint. This means a uint and byte in a binary operator, the byte becomes a uint.
Finally, if none apply, both parameters become an int. This applies not to user
defined operators but in expressions.
To reiterate some of the
restrictions; we cannot mix decimal with double and float types. A complete
no-no as there are no implicit conversions defined between these type. We
cannot mix and match a ulong with any signed integral type. The reason being
there exists no third data type that can represent the range of a ulong and a
signed data type at the same time. This explains the caveats mentioned earlier
for a ulong.
a.cs
public class zzz
{
public static void Main()
{
double i = 1.3;
decimal j = 1.2;
System.Console.WriteLine(i+j);
}
}
Compiler Error
a.cs(6,13): error CS0664: Literal of type double cannot be
implicitly converted to type 'decimal'; use an 'M' suffix to create a literal
of this type
a.cs(7,26): error CS0019: Operator '+' cannot be applied to
operands of type 'double' and 'decimal'
By default a number with a
decimal place is a double. We cannot equate a double to a decimal or add a
decimal and a double as discussed earlier.
a.cs
public class zzz {
public static void Main() {
double i = 1.3;
decimal j = 1.2m;
System.Console.WriteLine(i+(double)j);
System.Console.WriteLine((decimal)i+j);
}
}
Output
2.5
2.5
A number ending with m becomes a
decimal number. Also, we can cast a decimal to a double and vice versa and the
compiler shows thumbs up. Casting can accomplish anything you can dream up. It
can convert raw dirt into gold only if you learn to say please the right way.
a.cs
class zzz
{
public static int implicit operator (zzz f)
{
}
}
Compiler Error
a.cs(3,15): error CS1553: Declaration is not valid; use
'implicit operator <dest-type> (...' instead
The compiler loves things being placed in the right order. For a conversion operator, the order is access modifier, then static, followed by implicit or explicit and then the name of the operator to convert to. This order cannot be changed under any circumstance. May be it's times for the compiler to get off its high horse, wot?