Author : Joshua Trupin
Page : << Previous 2 Next >>
= NS1.NS2.ClassA;
class ClassB: A {}
}
This effect can be accomplished at any level of an object hierarchy. For instance, you could also create an alias for NS1.NS2 like this:
namespace NS3
{
using C = NS1.NS2;
class ClassB: C.A {}
}
Like coding languages, the needs of programmers evolve over time. What was once revolutionary is now sort of, well, dated. Like that old Toyota Corolla on the neighbor's lawn, C and C++ provide reliable transportation, but lack some of the features that people look for when they kick the tires. This is one of the reasons many developers have tinkered with the Java language over the past few years.
C# goes back to the drawing board and emerges with several features that I longed for in C++. Garbage collection is one example—everything gets cleaned up when it's no longer referenced. However, garbage collection can have a price. It makes problems caused by certain risky behavior (using unsafe casts and stray pointers, for example) far harder to diagnose and potentially more devastating to a program. To compensate for this, C# implements type safety to ensure application stability. Of course, type safety also makes your code more readable, so others on your team can see what you've been up to—you take the bad with the good, I guess. I'll go into this later in this article.
C# has a richer intrinsic model for error handling than C++. Have you ever really gotten deep into a coworker's code? It's amazing—there are dozens of unchecked HRESULTs all over the place, and when a call fails, the program always ends up displaying an "Error: There was an error" message. C# improves on this situation by providing integral support for throw, try…catch, and try…finally as language elements. True, you could do this as a macro in C++, but now it's available right out of the box.
Part of a modern language is the ability to actually use it for something. It seems simple enough, but many languages completely ignore the needs for financial and time-based data types. They're too old economy or something. Borrowing from languages like SQL, C# implements built-in support for data types like decimal and string, and lets you implement new primitive types that are as efficient as the existing ones. I'll discuss some of the new support for data types and arrays later in the article.
You'll also be glad to see that C# takes a more modern approach to debugging. The traditional way to write a debuggable program in C++ was to sprinkle it with #ifdefs and indicate that large sections of code would only be executed during the debugging process. You would end up with two implementations—a debug build and a retail build, with some of the calls in the retail build going to functions that do nothing. C# offers the conditional keyword to control program flow based on defined tokens.
Remember the MSDNMag namespace? A single conditional statement can make the SayHi member a debug-only function.
using System;
namespace MSDNMag
{
public class HelloWorld
{
[conditional("DEBUG")]
public static void SayHi()
{
Console.WriteLine("Hello, World!");
return;
}
•••
}
}
Conditional functions must have void return types (as I've set in this sample). The client program would then have to look like this to get a Hello World message:
using System
using MSDNMag
#define DEBUG
class CallingMSDNMag
{
public static void Main(string[] args)
{
HelloWorld.SayHi();
return 0;
}
}
The code is nice and uncluttered without all those #ifdefs hanging around, waiting to be ignored. Finally, C# is designed to be easy to parse, so vendors can create tools that allow source browsing and two-way code generation.
Yeah, yeah. C++ is object oriented. Right. I've personally known people who have worked on multiple inheritance for a week, then retired out of frustration to North Carolina to clean hog lagoons. That's why C# ditches multiple inheritance in favor of native support for the COM+ virtual object system. Encapsulation, polymorphism, and inheritance are preserved without all the pain.
C# ditches the entire concept of global functions, variables, and constants. Instead, you can create static class members, making C# code easier to read and less prone to naming conflicts.
And speaking of naming conflicts, have you ever forgotten that you created a class member and redefined it later on in your code? By default, C# methods are nonvirtual, requiring an explicit virtual modifier. It's far harder to accidentally override a method, it's easier to provide correct versioning, and the vtable doesn't grow as quickly. Class members in C# can be defined as private, protected, public, or internal. You retain full control over their encapsulation.
Methods and operators can be overloaded in C#, using a syntax that's a lot easier than the one used by C++. However, you can't overload global operator functions—the overloading is strictly local in scope. The overloading of method F below is an example of what this looks like:
interface ITest
{
void F(); // F()
void F(int x); // F(int)
void F(ref int x); // F(ref int)
void F(out int x); // F(out int)
void F(int x, int y); // F(int, int)
int F(string s); // F(string)
int F(int x); // F(int)
}
The COM+ component model is supported through the implementation of delegates—the object-oriented equivalent of function pointers in C++.
Interfaces support multiple inheritance. Classes can privately implement internal interfaces through explicit member implementations, without the consumer ever knowing about it.
Although some power users would disagree with me, type safety promotes robust programs. Several features that promote proper code execution (and more robust programs) in Visual Basic have been included in C#. For example, all dynamically allocated objects and arrays are initialized to zero. Although C# doesn't automatically initialize local variables, the compiler will warn you if you use one before you initialize it. When you access an array, it is automatically range checked. Unlike C and C++, you can't overwrite unallocated memory. In C# you can't create an invalid reference. All casts are required to be safe, and you can't cast between integer and reference types. Garbage collection in C# ensures that you don't leave references dangling around your code. Hand-in-hand with this feature is overflow checking. Arithmetic operations and conversions are not allowed if they overflow the target variable or object. Of course, there are some valid reasons to want a variable to overflow. If you do, you can explicitly disable the checking.
As I've mentioned, the data types supported in C# are somewhat different from what you might be used to in C++. For instance, the char type is 16 bits. Certain useful types, like decimal and string, are built in. Perhaps the biggest difference between C++ and C#, however, is the way C# handles arrays.
C# arrays are managed types, meaning that they hold references, not values, and they're garbage collected. You can declare arrays in several ways, including as multidimensional (rectangular) arrays and as arrays of arrays (jagged). Note in the following examples that the square brackets come after the type, not after the identifier as in some languages.
int[ ] intArray; // A simple array
int[ , , ] intArray; // A multidimensional array
// of rank 3 (3 dimensions)
int[ ][ ] intArray // A jagged array of arrays
int[ ][ , , ][ , ] intArray; // A single-dimensional array
// of three-dimensional arrays
// of two-dimensional arrays
Arrays are actually objects; when you first
Page : << Previous 2 Next >>