Вы находитесь на странице: 1из 20

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?

Вам также может понравиться