Академический Документы
Профессиональный Документы
Культура Документы
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
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()
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.
Explicit Conversions
Explicit conversions are made up of all implicit conversions, explicit numeric
conversions, explicit enumeration conversions, explicit reference conversions, explicit
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.
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'
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
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'
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?