Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Three Cool New Features in C# : Page 2

Everyone who programs in C# will want to take advantage of these new features. CoDe Magazine Publisher Markus Egger covers the basics of anonymous methods, partial classes, and generics.




Full Text Search: The Key to Better Natural Language Queries for NoSQL in Node.js

Partial Classes
In general, classes are defined in a single source file. Sometimes, this can lead to difficulties. One example is a class that is partially generated by a code generator, and partially modified and maintained by the developer. This is a very common scenario. Both WinForms and Web Forms designers encounter this issue, making these classes vulnerable to accidental modification of source code that is not meant to be modified by the developer. To solve this problem, the WinForm designer-generated code is hidden in a region and never touched at all, or only touched by very skilled developers.

The new version of C# introduces a feature that can solve these problems: partial classes. These are classes that are defined in more than one source file. This way, part of a partial class could be maintained by the code generator and a second part of the class could be maintained by the developer. When the code gets compiled, the C# compiler performs what can be described as a fancy text merge operation that combines all parts into one class.

Here's a code sample. First, there could be one part of a class defined in one source file:

public partial class Class1 { public string Test() { return "Test"; } }

Then, there could be a second source file with the following class definition:

public partial class Class1 { public string SomethingElse() { return "Something Else"; } }

The significant part is the definition of the class. First of all, both class definitions sport the same class name. Secondly, there is the partial keyword, which makes the duplicate class name a valid construct, and instructs the compiler to merge the two parts together.

Note that all (there could be any number, really) class parts combined must create a valid class. In other words, it would be invalid if multiple parts defined the same exact methods, as that would result in a duplicate method name. The same is true for fields, properties, constructors, and so forth. The parts of the class are merged together on class level. Therefore, multiple methods of the same name are not merged into one large method. Similarly, a method defined in one part cannot inherit functionality defined in another part. Once again, this is really mostly a text-merging feature, and has no impact on the inheritance functionality native to C# and Visual Studio .NET in general.

What is interesting is that partial types are fully integrated into the Visual Studio .NET IDE. Type "this." in any of the parts, and you will see full IntelliSense functionality, including the members defined in the other part(s).

Of course, this merge-awareness can be useful for a number of different practical applications. Code generators are one of them. Also, partial classes can be used to logically break down large classes into smaller bits, which can be very useful in team development scenarios., This can be a handy feature when it comes to adding debug functionality to a class. One of the parts could have all the debug functionality, and depending on the desired behavior, the whole debug part could be removed when you're ready to go live.

All Visual Studio .NET languages, including C# of course, are strongly typed. This means that pretty much everything you create has to be of a certain type. This is great for quality assurance, and in general, is a much desired feature. Unfortunately, there is a bit of a side effect as well. It is very difficult to create generic constructs without giving up type safety. The only way to create code in Visual Studio .NET that can handle any type is by making the utilized reference into an Object. Everything in Visual Studio .NET inherits from the Object class at some level. Therefore, everything in .NET is an object and can be typed to be an object. This is true from the simplest data types, such as an integer, all the way up to the heavy hitters such as the DataSet object.

So let's take a look at a specific sample. Let's say you want to create a collection object that can store references to any type of object. That collection object could have the following methods:

public class MyCollection { public void Add(object o1) { // Store the object } public object GetByIndex(int i) { // Retrieve the object return null; } }

As you can see, this is a simplified code example and not a true collection, but I think it is sufficient to illustrate the issue. Using this construct, you can now store any type in the collection, and retrieve it later. For instance, you could store an integer or a DataSet:

MyCollection Col1 = new MyCollection(); Col1.Add(1); Col1.Add(new DataSet());

The compiler knows that both the DataSet and the integer (1) inherit from "object" and therefore, the parameter types are valid. It gets a bit trickier when you retrieve these types again, because the return type is always "object." If you want to get at the original types, you have to perform a cast:

DataSet ds2 = (DataSet)Col1.GetByIndex(0);

As you may have noticed, there is a bit of an issue here! Performing the cast to type DataSet is a runtime operation. The compiler cannot know whether or not that case will succeed at runtime. And in fact, in this example, it would not be successful, as the item with index 0 is an integer and not a DataSet.

Another problem can be performance. Integers, for instance, are value types. And object (as well as the DataSet) is a reference type. This means that whenever the runtime casts from an integer to an object and back, operations known as boxing and unboxing have to be executed. This means that the value type is taken from the stack, packaged into an object, and put on the heap. This is not an operation you want to use just to store a reference.

So clearly, there is an issue here as type safety is not maintained. At the same time, you really would like a generic collection class because storing object references in this manner is a rather common task. And creating specific collection classes for every possible type that occurs in the .NET Framework, as well as your own code, is not feasible either. You'd be doubling the number of used classes on the spot!

Generics solve these issues. They allow creating code that is as generic as the collection above, but when the code is used, a specific type is assumed. Here's the redefined collection using Generics:

public class MyCollection<myType> { public void Add(myType o1) { // ... } public myType GetByIndex(int i) { // ... } }

In this example, the whole class is defined with a parameter called myType. This myType parameter (there could be more than one) is used to define the parameter type as well as the return type. Of course myType is not defined anywhere. Instead, the code that uses this class defines the "real" type that is to be used. For instance, if you want to use this collection to store integers, you use it like so:

MyCollection Col1 = new MyCollection<int>(); Col1.Add(1);

Internally, the collection now operates with integers. There is no boxing and unboxing. Also, values retrieved from the collection are integers without having to be converted. And on top of that, this instance of the collection is limited to storing integers. An attempt to assign a DataSet is trapped as an error during compilation. You could easily use the whole collection for DataSets as well:

MyCollection Col1 = new MyCollection<DataSet>();

It is worth mentioning that any code using Generics can also apply a filter (constraint), so only a specific list of interfaces or classes can be used as the generic parameter. This is especially useful when the code inside the method that handles the Generic performs more complex operations than are supported by the object type. When no constraints are defined, there is no way for the compiler to know how the Generic is going to be used. Therefore, the lowest common denominator has to be assumed, and that, once again, is the object.

As a side-note, Generics are a CLR (runtime) feature and therefore not truly specific to C# (although that's where they originated). So other languages, like Visual Basic .NET, (can) support Generics as well.

The Whidbey-release of C# adds a number of great features, cementing the position of C# as an innovative and modern language. Some of the features are not entirely new and unheard of, but they are, nevertheless, very useful. Note also that some of these features require changes in the runtime (CLR), which means that they are available to other .NET languages as well, if they chose to support them.

Markus Egger is the President and Chief Software Architect of EPS Software Corp., located in Houston, Texas. He is also the founder of EPS Software Austria, located in Salzburg.He concentrates in consulting and development of custom software based on Microsoft technologies. Markus' passion lies with object-oriented technology. He is an international author and speaker, and is the publisher of CoDe Magazine.For the past 8 years, Markus has received the Microsoft MVP award, originally for Visual FoxPro, and now for C#.Several applications he has worked on (mostly as project manager) have received Microsoft Excellence Award nominations.He is the author of several developer tools such as GenRepoX, Fox Extension Class, and the Voodoo Web Controls.A full bio can be found on the web at www.eps-cs.com and www.MarkusEgger.com. You can reach him here.
Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date