C# Compared to Other Languages

Tiis chapter compares C# to other languages. C#, C++, and Java all share common roots and are more similar to each other than they are to many other languages. Visual Basic isn’t as similar to C# as the other languages are, but it still shares many syntactical elements.

Also, this chapter discusses the .NET versions of Visual C++ and Visual Basic, since they’re also somewhat different than their predecessors.

1. Differences Between C# and C/C++

C# code will be familiar to C and C++ programmers, but it has a few big differences and a number of small differences. The following sections give an overview of the differences. For a more detailed perspective, see the MSDN article “C++ -> C#: What You Need to Know to Move from C++ to C#” by Jesse Liberty (http://msdn.microsoft.com/msdnmag/issues/01/07/ctocsharp/ default.aspx).

1.1. A Managed Environment

C# runs in the .NET runtime environment. This not only means many things aren’t under the programmer’s control but it also means it provides a new set of frameworks. Together, this means a few things are different:

  • The garbage collector performs object deletion sometime after the object is no longer used. You can use destructors (a.k.a. finalizers) for some cleanup but not in the way you use C++ destructors.
  • The C# language doesn’t have pointers; well, it has them in unsafe mode, but they’re rarely used. References are used instead, and they’re similar to C++ references without some of the C++ limitations.
  • Source is compiled to assemblies, which contain both the compiled code (expressed in the .NET IL) and metadata to describe that compiled code. All .NET languages query the metadata to determine the same information that’s contained in C++ .h files, and the include files are therefore absent.
  • Calling native code requires a bit more work
  • No C/C++ runtime library exists. The same things—such as string manipulation, file I/O, and other routines—exist within the .NET Framework libraries and reside in the namespaces that start with System.
  • C# uses exception handling instead of error returns.

1.2. .NET Objects

C# objects all have the ultimate base class object, and there’s only single inheritance of classes (though there’s multiple implementation of interfaces).

You can declare lightweight objects, such as data types, as structs (also known as value types), which means they’re allocated on the stack instead of the heap.

You can use C# structs and other value types (including the built-in data types) in situa­tions where objects are required by boxing them, which automatically copies the value into a heap-allocated wrapper that’s compliant with heap-allocated objects (also known as reference objects). This unifies the type system, allowing any variable to be treated as an object but without overhead when unification isn’t needed.

C# supports properties and indexers to separate the user model of an object from the implementation of the object, and it supports delegates and events to encapsulate function pointers and callbacks.

C# contains the params keyword to provide support similar to varargs.

1.3. C# Statements

C# statements have high fidelity to C++ statements. A few notable differences exist:

  • The new keyword means “obtain a new instance of.” The object is heap allocated if it’s a reference type and stack or inline allocated if it’s a value type.
  • All statements that test a Boolean condition now require a variable of type bool. There’s no automatic conversion from int to bool, so if (i) isn’t valid.
  • switch statements disallow fall-through to reduce errors. You can also use switch on string values.
  • You can use Foreach to iterate over objects and collections.
  • You can use Checked and unchecked to control whether arithmetic operations and conversions are checked for overflow.
  • Definite assignment requires that objects have a definite value before being used.

1.4. Anonymous Methods

C# provides the ability to use anonymous methods, which are a form of inline delegate in which the code that will be executed as part of the delegate is placed inline with the delegate’s construction. See Chapter 23 for more details.

C++/CLI doesn’t support anonymous methods, but the same functionality is still possible with standard delegates.

1.5. Nullable Types

C++/CLI doesn’t support nullable types at a language level, but the generic type System.Nullable<T>, which ships with the .NET Framework library and is wrapped by C# language nullable types, is available.

Native C++ doesn’t have the concept of the value-type/reference-type divide; hence, nullable types have no relevance or meaning in this context.

1.6. Iterators

C++/CLI doesn’t support iterators, which are a C# language feature that make it easier to write enumerations. Chapter 20 covers iterators. The same functionality is available through enumerators, though it may be considerably more complex to implement in C++/CLI.

1.7. Attributes

Attributes are annotations written to convey declarative data from the programmer to other tools or code. That other code might be the runtime environment, a designer, a code-analysis tool, or some other custom tool. You can retrieve attribute information through a process known as reflection.

You can write attributes inside square brackets and can place them on classes, members, parameters, and other code elements. Here’s an example:

[CodeReview(“1/1/1999″, Comment=”Rockin'”)]

class Test

{

}

1.8. Versioning

C# enables better versioning than C++. Because the runtime handles member layout, binary compatibility isn’t an issue. The runtime provides side-by-side versions of components if desired and correct semantics when versioning frameworks, and the C# language allows the programmer to specify versioning intent.

1.9. Code Organization

C# has no header files; all code is written inline, and although there’s preprocessor support for conditional code, there’s no support for macros. These restrictions make it both easier and faster for the compiler to parse C# code and also make it easier for development environments to understand C# code.

In addition, C# has no order dependence and no forward declarations. The order of classes in source files is unimportant; you can rearrange classes at will.

1.10. Missing C# Features

The following C++ features aren’t in C#:

  • Multiple inheritance.
  • Const member functions or parameters. Const fields are supported.
  • Global variables.
  • Conversion by construction.
  • Default arguments on function parameters.
  • Automatic disposal of stack-declared IDisposable objects. C# requires a using block. In C++/CLI, the compiler adds the equivalent of the using block.
  • Automatic generation of a member variable for simple properties. In C++/CLI, you have no need to create a separate property and member variable if all the property accessors do is get and set the member variable. You can use the property keyword to instruct the compiler to generate the backing member variable.

2. Differences Between C# and Java

It’s no surprise that there are similarities between C# and Java. You’ll see a fair number of differences between them, however. The biggest difference is that C# sits on the .NET Framework and runtime, and Java sits on the Java framework and runtime.

2.1. Data Types

C# has more primitive data types than Java. Table 42-1 summarizes the Java types and their C# analogs.

In Java, the primitive data types are in a separate world from the object-based types. For primitive types to participate in the object-based world (in a collection, for example), they must be in an instance of a wrapper class, and the wrapper class must be in that collection.

C# approaches this problem differently. In C#, primitive types are stack allocated as in Java, but they’re also considered to be derived from the ultimate base class, object. This means the primitive types can have member functions defined and called on them. In other words, you can write the following code:

using System; class Test {

public static void Main()

{

Console.WriteLine(5.ToString());

}

}

The constant 5 is of type int, and the ToString() member is defined for the int type, so the compiler can generate a call to it and pass the int to the member function as if it’s an object.

This works well when the compiler knows it’s dealing with a primitive, but it doesn’t work when a primitive needs to work with heap-allocated objects in a collection. Whenever a prim­itive type is used in a situation where a parameter of type object is required, the compiler will automatically box the primitive type into a heap-allocated wrapper. Here’s an example of boxing:

using System; class Test {

public static void Main()

{

int v = 55;

object o = v;                 // box v into o

Console.WriteLine(“Value is: {0}”, o);

int v2 = (int) o;             // unbox back to an int

}

}

In this code, the integer is boxed into an object and then passed off to the Console.WriteLine() member function as an object parameter. We declared the object variable for illustration only; in real code, v would be passed directly, and the boxing would happen at the call site. You can extract the boxed integer by a cast operation, which will extract the boxed int.

2.2. Extending the Type System

The primitive C# types (with the exception of string and object) are also known as value types, because variables of those types contain actual values. Other types are known as reference types, because those variables contain references.

In C#, a programmer can extend the type system by implementing a custom value type. These types are implemented using the struct keyword and behave similarly to built-in value types; they’re stack allocated, can have member functions defined on them, and are boxed and unboxed as necessary. In fact, the C# primitive types are all implemented as value types, and the only syntactical difference between the built-in types and user-defined types is that you can write the built-in types as constants.

To make user-defined types behave naturally, C# structs can overload arithmetic opera­tors so that numeric operations can be performed and can overload conversions so implicit and explicit conversions can be performed between structs and other types. C# also supports overloading on classes.

You write a struct using the same syntax as a class, except that a struct can’t have a base class (other than the implicit base class object), though it can implement interfaces.

2.3. Classes

C# classes are quite similar to Java classes, with a few important differences relating to constants, base classes and constructors, static constructors, virtual functions, hiding, and versioning, accessibility of members, ref and out parameters, and identifying types.

Constants

Java uses static final to declare a class constant. C# replaces this with const. In addition, C# adds the readonly keyword, which is used in situations where the constant value can’t be determined at compile time. Readonly fields can be set only through an initializer or a class constructor.

Base Classes and Constructors

C# uses the C++ syntax both for defining the base class and interfaces of a class and for calling other constructors. A C# class that does this might look like this:

public class MyObject: Control, IFormattable {

public MyObject(int value)

{

this.value = value;

}

public MyObject() : base(value)

{

}

int value;

}

Static Constructors

As well as using a static initialization block, C# provides static constructors, which are written using the static keyword in front of a parameterless constructor.

Virtual Functions, Hiding, and Versioning

In C#, all methods are nonvirtual by default, and you must specify virtual explicitly to make a function virtual. Because of this, C# doesn’t contain any final methods, but you can achieve the equivalent of a final class using sealed.

C# provides better versioning support than Java, and this results in a few small changes. Because versioning is specified explicitly in C#, adding a virtual function in a base class won’t change a program’s behavior. Consider the following:

public class B

{

}

public class D: B {

public void Process(object o) {}

}

class Test {

public static void Main()

{

D d = new D();

d.Process(15); // make call

}

}

If the provider of the base class adds a process function that’s a better match, the behavior will change:

public class B {

public void Process(int v) {}

}

public class D: B {

public void Process(object o) {}

}

class Test {

public static void Main()

{

D d = new D();

d.Process(15); // make call

}

}

In Java, this will now call the base class’s implementation, which is unlikely to be correct. In C#, the program will continue to work as before.

To handle the similar case for virtual functions, C# requires that you specify the versioning semantics explicitly. If Process() had been a virtual function in the derived class, Java would assume that any base class function that matched in signature would be a base for that virtual, which is unlikely to be correct.

In C#, virtual functions are overridden only if the override keyword is specified. See Chapter 11 for more information.

Accessibility of Members

In addition to public, private, and protected accessibility, C# adds internal. You can access members with internal accessibility from other classes within the same project but not from outside the project.

Operator Overloading

C# allows the user to overload most operators for both classes and structs so that objects can participate in mathematic expressions. C# doesn’t allow the overloading of more complex operators such as member access, function invocation, assignment, or the new operator, because overloading these can make code much more complicated.

ref and out Parameters

In Java, parameters are always passed by value. C# allows parameters to be passed by reference by using the ref keyword. This allows the member function to change the value of the parameter.

C# also allows parameters to be defined using the out keyword, which functions the same as ref, except that the variable passed as the parameter doesn’t have to have a known value before the call.

Enumerations

The C# enum type is akin to the C++ enum type and is used similarly. Implicit conversions between an enum and its underlying type are more restricted, however.

Identifying Types

Java uses the GetClass() method to return a Class object, which contains information about the object on which it’s called. The Type object is the .NET analog to the Class object; you can obtain it in several ways:

  • By calling the GetType() method on an instance of an object
  • By using the typeof operator on the name of a type
  • By looking up the type by name using the classes in System.Reflection

2.4. Interfaces

Although Java interfaces can have constants, C# interfaces cannot. When implementing interfaces, C# provides explicit interface implementation. This allows a class to implement two inter­faces from two different sources that have the same member name, and you can also use it to hide interface implementations from the user. For more information, see Chapter 10.

2.5. Properties and Indexers

Java programs often use the property idiom by declaring get and set methods. In C#, a property appears to the user of a class as a field but has a get and set accessor to perform the read and/or write operations.

An indexer is similar to a property, but instead of looking like a field, an indexer appears as an array to the user. Like properties, indexers have get and set accessors, but unlike properties, an indexer can be overloaded on different types. This enables a database row that can be indexed both by column number and by column name and a hash table that can be indexed by hash key.

2.6. Delegates and Events

When an object needs to receive a callback in Java, an interface specifies how the object must be formed, and a method in that interface is called for the callback. You can use a similar approach in C# with interfaces.

C# adds delegates, which can be thought of as type-safe function pointers. A class can create a delegate on a function in the class, and then that delegate can be passed off to a function that accepts the delegate. That function can then call the delegate.

C# builds upon delegates with events, which are used by the .NET Framework. Events implement the publish-and-subscribe idiom; if an object (such as a control) supports a click event, any number of other classes can register a delegate to be called when that event fires.

2.7. Attributes

Attributes are annotations written to convey declarative data from the programmer to other code. That other code might be the runtime environment, a designer, a code-analysis tool, or some other custom tool. You can retrieve attribute information through a process known as reflection.

You write attributes inside square brackets, and you can place them on classes, members, parameters, and other code elements. Here’s an example:

[CodeReview(“1/1/1999″, Comment=”Rockin'”)]

class Test

{

}

2.8. Statements

Statements in C# will be familiar to the Java programmer, but there are a few new statements and a few differences in existing statements to keep in mind.

import vs. using

In Java, the import statement locates a package and imports the types into the current file.

In C#, this operation is split. You must explicitly specify the assemblies that a section of code relies upon, either on the command line using /r or in the Visual Studio IDE. The most basic system functions (currently those contained in mscorlib.dll) are the only ones imported automatically by the compiler.

Once an assembly has been referenced, the types in it are available for use, but you must specify them using their fully qualified names. For example, the regular expression class is named System.Text.RegularExpressions.Regex. You could use that class name directly, or you could use a using statement to import the types in a namespace to the top-level namespace. With the following using clause, you can specify the class merely by using Regex:

using System.Text.RegularExpressions;

Also, a variant of the using statement allows aliases for types to be specified if there’s a name collision.

Overflows

Java doesn’t detect overflow in conversions or mathematical expressions.

In C#, you can control the detection of these by the checked and unchecked statements and operators. Conversions and mathematical operations that occur in a checked context will throw exceptions if the operations generate overflow or other errors; such operations in an unchecked context will never throw errors. The /checked compiler flag controls the default context.

Unsafe Code

Unsafe code in C# allows the use of pointer variables, and you use it when performance is extremely important or when interfacing with existing software, such as COM objects or native C code in DLLs. The fixed statement is used to “pin” an object so that it won’t move if a garbage collection occurs.

Because unsafe code can’t be verified to be safe by the runtime, it can be executed only if it’s fully trusted by the runtime. This prevents execution in download scenarios.

Strings

You can index the C# string object to access specific characters. Comparison between strings evaluates the values of the strings rather than the references to the strings.

String literals are also a bit different; C# supports escape characters within strings that are used to insert special characters. The string \t will be translated to a tab character, for example.

Generics

Java generics and C# generics are very different. As discussed in Chapter 17, generics in C# have first-class runtime support, and a C# generic type is fully compatible with a generic type produced in any other .NET language. In contrast, Java generics are entirely a compile-time concept, and the compiler will simply replace the type parameter with an object reference in a process referred to as type eraser. This means the Java bytecode (which is Java’s equivalent to MSIL) is entirely nongeneric, minimizing the upgrade burden on Java Virtual Machine (JVM) implementers.

The cost of implementing generics at compile rather than runtime is that the efficiency gains of strong runtime typing aren’t available in Java, and boxing/unboxing will still occur in Java for generic collections of primitive types. In addition, by implementing generics as a language rather than platform features, it isn’t possible to use or query for generic information at runtime using technologies such as reflection.

Documentation

The XML documentation in C# is similar to Javadoc, but C# doesn’t dictate the organization of the documentation, and the compiler checks for correctness and generates unique identifiers for links.

Miscellaneous Differences

A few miscellaneous differences exist:

  • The >>> operator isn’t present, because the >> operator has different behavior for signed and unsigned types.
  • The is operator is used instead of instanceof.
  • There’s no labeled break statement; goto replaces it.
  • The switch statement prohibits fall-through, and you can use switch on string variables.
  • There’s only one array declaration syntax: int[] arr.
  • C# allows a variable number of parameters using the params keyword.

3. Differences Between C# and Visual Basic 6

C# and Visual Basic 6 are fairly different languages. C# is an object-oriented language, and VB 6 has only limited object-oriented features. VB .NET adds object-oriented features to the VB language, and it may therefore be instructive to also study the VB .NET documentation.

3.1. Code Appearance

In VB, statement blocks end with some sort of END statement, and multiple statements can’t appear on a single line. In C#, blocks are denoted using braces ({}), and the location of line breaks doesn’t matter, because a semicolon indicates the end of a statement. Though it might be bad form and ugly to read, you can write the following in C#:

for (int j = 0; j < 10; j++) {if (j == 5) Func(j); else return;}

That line means the same as this:

for (int j = 0; j < 10; j++)

{

if (j == 5)

Func(j);

else

return;

}

This constrains the programmer less, but it also makes agreements about style more important.

3.2. Data Types and Variables

Although there’s a considerable amount of overlap in data types between VB and C#, there are some important differences; therefore, a similar name may mean a different data type.

The most important difference is that C# is stricter on variable declaration and usage. All variables must be declared before they’re used, and they must be declared with a specific type— there’s no Variant type that can hold any type.[2]

You can make variable declarations simply by using the name of the type before the variable; no dim statement exists.

Conversions

Conversions between types are also stricter than in VB. C# has two types of conversions: implicit and explicit. Implicit conversions are those that can’t lose data—that’s where the source value will always fit into the destination variable. For example:

int v = 55;

long x = v;

Assigning v to x is allowed because int variables can always fit into long variables.

Explicit conversions, on the other hand, are conversions that can lose data or fail. Because of this, the conversion must be explicitly stated using a cast:

long x = 55;

int v = (int) x;

Though in this case the conversion is safe, the long can hold numbers that are too big to fit in an int, and therefore the cast is required.

If detecting overflow in conversions is important, you can use the checked statement to turn on the detection of overflow. See Chapter 15 for more information.

Data Type Differences

In Visual Basic, the integer data types are Integer and Long. In C#, these are replaced with the types short and int. There’s a long type as well, but it’s a 64-bit (8-byte) type. This is something to keep in mind, because if long is used in C# where Long would have been used in VB, programs will be a bit bigger and a bit slower. Byte, however, is merely renamed to byte.

C# also has the unsigned data types ushort, uint, and ulong and the signed byte sbyte. These are useful in some situations, but not all languages in .NET can use them, so you should use them only as necessary.

The floating-point types Single and Double are renamed float and double, and the Boolean type is known simply as bool.

Strings

Many of the built-in functions that are present in VB don’t exist for the C# string type. Functions exist to search strings, extract substrings, and perform other operations; see the documentation for the System.String type for details.

String concatenation takes place using the + operator rather than the & operator.

Arrays

In C#, the first element of an array is always index 0; also, there’s no way to set upper or lower bounds and no way to redim an array. However, an ArrayList in the System.Collection namespace does allow resizing. The System.Collection namespace also contains many other useful collection classes.

4. Operators and Expressions

The operators that C# uses have a few differences from VB, and the expressions will therefore take some getting used to. Table 42-2 shows the C# equivalent for some common VB operators.

4.1. Classes, Types, Functions, and Interfaces

Because C# is an object-oriented language, the class is the major organizational unit; rather than having code or variables live in a global area, they’re always associated with a specific class. This results in code that’s structured and organized quite differently than VB code, but still some common elements exist. You can still use properties, but they have a different syntax and no default properties.

Functions

In C#, function parameters must have a declared type, and you can use ref instead of ByRef to indicate that the value of a passed variable may be modified. You can achieve the equivalent of the ParamArray function by using the params keyword.

4.2. Control and Program Flow

C# and VB have similar control structures, but the syntax used is a bit different.

If Then

C# has no Then statement; after the condition comes the statement or statement block that should be executed if the condition is true, and after that statement or block comes an optional else statement.

For example, you can rewrite the following VB code:

If size < 60 Then

value = 50

Else

value = 55

order = 12

End If

as follows:

if (size < 60)

value = 50;

else

{

value = 55;

order = 12;

}

C# has no Elself statement in C#, but it has a simple statement, Else If.

For

The syntax for for loops is different in C#, but the concept is the same, except that in C# the operation performed at the end of each loop must be explicitly specified. In other words, you can rewrite the following VB code:

For i = 1 To 100

‘ other code here

Next

as follows:

for (int i = 0; i < 10; i++)

{

// other code here

}

For Each

C# supports the For Each syntax through the foreach statement, which can be used on arrays, collections classes, and other classes that expose the proper interface.

Do Loop

C# has two looping constructs to replace the Do Loop construct. The while statement loops while a condition is true, and do while works the same way, except that one trip through the loop is ensured even if the condition is false. For example, you can rewrite the following VB code:

I = 1

fact = 1

Do While I <= n

fact = fact * I

I = I + 1

Loop

as follows:

int I = 1;

int fact = 1;

while (I <= n)

{

fact = fact * I;

I++;

}

A loop can be exited using the break statement or continued on the next iteration using the continue statement.

4.2. Select Case

The switch statement in C# does the same thing as Select Case. You can rewrite this VB code:

Select Case x

Case 1

Funcl

Case 2

Func2

Case 3

Func2

Case Else

Func3

End Select

as follows:

switch (x)

{

case 1:

Func1();

break;

case 2:

case 3:

Func2();

break;

default:

Func3();

break;

}

4.3. On Error

C# doesn’t have any On Error statement. Error conditions in .NET are communicated through exceptions. See Chapter 4 for more details.

4.4. Missing Statements

C# doesn’t have With, Choose, or the equivalent of Switch. It also doesn’t have any CallByName feature, but you can do this through reflection.

5. Other .NET Languages

Visual C++ and Visual Basic have both been extended to work in the .NET world.

In the Visual C++ world, a set of Managed Extensions have been added to the language to allow programmers to produce and consume components for the CLR. The Visual C++ model allows the programmer more control than the C# model; the user is allowed to write both managed (garbage-collected) and unmanaged (using new and delete) objects.

You can create a .NET component using keywords to modify the meaning of existing C++ constructs. For example, when you place the_ gc keyword in front of a class definition, you

enable the creation of a managed class and restrict the class from using constructs that can’t be expressed in the .NET world (such as multiple inheritance). You can also use the .NET system classes from the Managed Extensions.

The Managed Extensions for C++ that existed in Visual C++ 2002 and 2003 have been superceded by a new binding of C++ to .NET called C++/CLI that will be part of Visual C++ 2005. C++/CLI offer a cleaner and more consistent way to use .NET features and also allow C++ code to be written and compiled so that it can be verifiably type-safe. For developers who left C++ because of the ugliness and difficulty of Managed C++, it’s worth evaluating C++/CLI.

In addition to the Microsoft languages, several third-party languages have been announced for the .NET platform. See http://www.gotdotnet.com for more information.

Source: Gunnerson Eric, Wienholt Nick (2005), A Programmer’s Introduction to C# 2.0, Apress; 3rd edition.

Leave a Reply

Your email address will not be published. Required fields are marked *