65.9K
CodeProject is changing. Read more.
Home

C# Keywords, Part II: Type Conversion, Operators and Object Types

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.44/5 (32 votes)

Dec 26, 2002

8 min read

viewsIcon

168712

downloadIcon

1

In part II of this multipart tutorial, I describe the keywords that can loosely be catagorized as dealing with object types, operators and type conversions.

Introduction

In part II of this multipart tutorial, I describe the keywords that can loosely be categorized as dealing with object type, operators and type conversion.

Why Is This Useful To You?

My interest in this series of articles is to provide a concise repository for the entire collection of keywords in C#. I plan to use this myself as a reference, and hopefully you will find it useful too. In Part I[^], I provided an overview of all the keywords and described in detail, with some code examples, the keywords that can be considered modifiers to entities such as methods, parameters, etc.

In the "old" days, languages used to be taught differently: start with the keywords, then move into the syntax, and then start writing some basic (as in simple!) programs. This approach, while it doesn't satisfy the "immediate gratification" crowd is still a valid approach when trying to gain a well rounded understanding of a programming language.

Why Is This Better Than Microsoft's Help On Keywords?

The primary answer to that question is not so much that what I have to say is better, but that in some cases, I provide supplementary understanding that the help topics don't provide. Also, personally, I get tired of bouncing around from one link to another. Sometimes it's nice to see everything laid out in a contiguous way. Also, I find that the more ways a concept can be described, the better I'll understand it, and there's nothing better to test one's comprehension than by trying to explain it to someone else!

What Have I Learned?

In writing Part II, I've learned that unary operators appear (to this author) pretty much useless because operators are required to be static members. Also, binary operators are limiting (this is true for unary operators as well) because they cannot return references to objects. So much for implementing streams in C# or other features found in STL. It seems that without the ability to operate on information in the enclosing class, and without the ability to return references, C# will never support the nice capabilities of templates and STL that we've grown to love using C++ (of course, I could be wrong!).

I also learned a lot about the inner "logic" of C#, especially regarding the rules for implicit and explicit casts.

The Keywords

Following is a table of (almost all) the keywords in C#.

abstract event new struct
as explicit null switch
base extern object this
bool false operator throw
break finally out true
byte fixed override try
case float params typeof
catch for private uint
char foreach protected ulong
checked goto public unchecked
class if readonly unsafe
const implicit ref ushort
continue in return using
decimal int sbyte virtual
default interface sealed volatile
delegate internal short void
do is sizeof while
double lock stackalloc  
else long static  
enum namespace string  

Keywords dealing with object type and type conversions:

These are the keywords that will be discussed in this article:

  • as
  • explicit
  • implicit
  • is
  • operator
  • sizeof
  • typeof

The "as" Keyword

The as keyword attempts to convert one object to another, similar to a cast. If the conversion fails, a null is returned instead of an exception being thrown. For example:

object n=123;
object obj="abc";

string s1=n as string;
string s2=obj as string;

Console.WriteLine("s1="+s1);
Console.WriteLine("s2="+s2);

results in:

s1=
s2=abc

Note that in some cases, the compiler will generate an error because it is attempting to do the cast at compile time.

int i=123;
string s1=i as string;

results in a compiler error: "Cannot convert type 'int' to 'string' via a built-in conversion."

To illustrate the difference between a cast and using the as operator, note that the following code:

string s3=(string)n;

generates an exception, whereas, when using the as operator, the string s1 was merely set to null.

Note that the as keyword contains an implicit "is" test, using the keyword described next.

The "is" Keyword

The is operator tests whether an object is of a particular type. For example:

object n1=123;

bool b1=n1 is int;
bool b2=n1 is string;

Console.WriteLine("n1 is int ? "+b1.ToString());
Console.WriteLine("n1 is string ? "+b2.ToString());

Results in:

n1 is int ? true
n1 is string ? false

With the as keyword, the compiler would generate a compile time error if it could resolve the object cast at compile time. Similarly, the compiler will generate a warning if the is operator evaluates to true or false at compile time--meaning that the expression is always either true or false.

The "operator" Keyword

This keyword declares an operator on a class or structure. An operator does two things. The first is it performs an operation on one or two parameters, including a conversion from the parameter type to the resulting type. The second thing an operator does is to perform a conversion from one type to another. This capability is used in conjunction with the explicit and implicit keywords, and will be discussed in the next two sections. In this section, we'll look at operators that perform an operation.

Operators are always public and they are always static. An operator is therefore always specified as:

public static ...

Unary Operators

A unary operator takes one operand and manipulates it. The operand must be of the same type as the containing class or struct. Because the operator function is declared as static, it cannot reference anything except the members in its operand. This differs from C++, and in my opinion, makes unary operators basically useless except in highly specialized cases where it actually means something to "add" or "subtract" the members of an operand.

For example:

public class PointEx
{
    protected Point p;

    public PointEx() {p=new Point(0, 0);}
    public PointEx(int x, int y) {p=new Point(x, y);}
    public PointEx(Point p) {this.p=p;}

    public static int operator +(PointEx p)
    {
        return p.p.X+p.p.Y;
    }
}

can be used syntactically as:

PointEx p=new PointEx(1, 2);
int n=+p;

which results in n=3. This seems fairly pointless. However, overloading the "negative", "not", "exclusive-or", "increment" and "decrement" unary operators may be useful in certain instances.

Binary Operators

Binary operators perform an operation on two operands. At least one of the operands must be of the the enclosing struct or the class. This mechanism prevents you from redefining built in operators, for example, public static int operator +(int a, int b) {return a + b -1;} is not legal. An example of a binary operator is:

public class PointEx
{
    protected Point p;

    public PointEx() {p=new Point(0, 0);}
    public PointEx(int x, int y) {p=new Point(x, y);}
    public PointEx(Point p) {this.p=p;}

    public static PointEx operator +(PointEx p, int scalar)
    {
        return new PointEx(p.p.X+scalar, p.p.Y+scalar);
    }
}

PointEx p=new PointEx(1, 2);
p+=5;

The Cast Operator

The cast operator is, in my opinion, another fairly useless item because, unlike C++, it cannot return a reference. This means that you cannot cast perform a cast on an "l-value". For example:

public class PointEx
{
   protected Point p;

   public static implicit operator Point(PointEx p)
   {
       return p.p;
   }

   public static PointEx operator +(PointEx p, int xlat)
   {
    ((Point)p).X+=xlat;
    ((Point)p).Y+=xlat;
    return p;
   }
}

In the operator + method, the ((Point)p.).X cast is illegal because the operator Point cast is not, and can not, as the language is currently defined, return a reference.

However, with the above code, we could of course write:

Point pp=p;

and the compiler will dutifully and automatically invoke the cast operator which returns the Point member of PointEx.

The "implicit" Keyword

The above example uses the implicit keyword in the definition of the operator. This defines it as a cast, and one that will occur automatically without the programmer knowing it.

The "explicit" Keyword

The explicit keyword also defines a cast operation but requires that the programmer explicitly selects the cast that is to be performed. If the cast operation has been defined, the compiler will dutifully generate the code to invoke the cast. For example, (modifying the above class):

public static explicit operator Point(PointEx p)
{
    return p.p;
}

Now, the implicit cast we used before generates a compiler error:

Point pp=p;            // !!! compiler error !!!
Point pp2=(Point)p;        // works

What are the rules in determining whether a cast should be defined as implicit or explicit?

Microsoft has this to say on the subject:

By eliminating unnecessary casts, implicit conversions can improve source code readability. However, because implicit conversions can occur without the programmer's specifying them, care must be taken to prevent unpleasant surprises. In general, implicit conversion operators should never throw exceptions and never lose information so that they can be used safely without the programmer's awareness. If a conversion operator cannot meet those criteria, it should be marked explicit.

  • Rule 1: Don't throw an exception in an implicit cast
  • Rule 2: Don't lose information. This is why, for example:
    int i=5;        // an integer
    double d=i;        // no loss of information
    int j=d;        // oops--potential loss of information

    the statement int j=d generates a compiler error: "No implicit cast...".

The "sizeof" Keyword

The sizeof keyword can only be used in "unsafe" mode. It returns the size of the object. For example:

int i=sizeof(short)

results in i=2.

Note: I am not sure why this keyword can only be used in "unsafe" mode. I assume that the reason this is an "unsafe" keyword is that the underlying "generic" Instruction Language (IL) requires compiler and platform specific information to resolve the size of an object--whether a simple type like an integer or, a complex type like an object with multiple derivations or interfaces.

The "typeof" Keyword

This keyword returns the type of an object (not an instance). To return the type of an instance, use the GetType member. For example:

double d=0;
Type t1=d.GetType();
Type t2=typeof(double);

These two statements are equivalent, except that GetType is applied to the instance of an object, whereas typeof is applied to the definition of an object. The value of obtaining a Type at run time is in the richness of this object--it includes information regarding access (private or public), and whether the object is a class, an interface or an ANSI class (just to name a couple). The Type object also has methods to extract the type's members and methods. This can be useful in conjunction with functions in the System.Reflection namespace.

Conclusion

I hope this series of articles helps anyone interested in understanding the capabilities of C#!