In this article by Marino Posadas, the author of the book, Mastering C# and .NET Programming, we will cover the core concepts of C# and .NET, starting from the initial version and principal motivations behind its creation, and covering also the new aspects of the language, that appeared in version 2.0 and 3.0.
(For more resources related to this topic, see here.)
We'll illustrate all the main concepts with small code snippets, short enough to facilitate its understanding and easy reproduction.
We will cover the following topics:
I had the chance to chat with Hejlsberg a couple of times about the C # language and what the initial purposes and requirements imposed in its creation were and which other languages inspired him or contributed to his ideas.
The first time we talked, in Tech-Ed 2001 (at Barcelona, Spain), I asked him about the principles of his language and what makes it different from others. He first said that it was not only him who created the language, but also a group of people, especially Scott Wiltamuth, Peter Golde, Peter Sollich, and Eric Gunnerson.
One of the first books ever published on the subject was, A Programmer's Introduction to C#, Gunnerson's.E., APress, 2000).
About the principles, he mentioned this:
One of the key differences between C# and these other languages, particularly Java, is that we tried to stay much closer to C++ in our design. C# borrows most of its operators, keywords, and statements directly from C++. But beyond these more traditional language issues, one of our key design goals was to make the C# language component-oriented, to add to the language itself all of the concepts that you need when you write components. Concepts such as properties, methods, events, attributes, and documentation are all first-class language constructs.
He stated also this:
When you write code in C#, you write everything in one place. There is no need for header files, IDL files (Interface Definition Language), GUIDs and complicated interfaces.
This means that you can write code that is self-descriptive in this way given that you're dealing with a self-contained unit (let's remember the role of the manifest, optionally embedded in assemblies). In this mode, you can also extend existing technologies in a variety of ways, as we'll see in the examples.
The C# language is a strongly typed language: this means that any attempt to pass a wrong kind of parameter as an argument, or to assign a value to a variable that is not implicitly convertible, will generate a compilation error. This avoids many errors that only happen at runtime in other languages.
In addition, by dynamic, we mean those languages whose rules are applied at runtime, while static languages apply their rules at compile time. JavaScript or PHP are good examples of the former case, and C/C++ of the latter. If we make a graphic representation of this situation, we might come up with something like what is shown in the following figure:
In the figure, we can see that C# is clearly strongly typed, but it's much more dynamic than C++ or Scala, to mention a few. Of course, there are several criteria to catalog languages for their typing (weak versus strong) and for their dynamism (dynamic versus static).
Note that this has implications in the IDE as well. Editors can tell us which type is expected in every case, and if you use a dynamic declaration such as var, the right side of the equality (if any) will be evaluated, and we will be shown the calculated value for every declaration:
Even outside of the .NET world, Visual Studio's IDE is now able to provide strongly typed and Intellisense experiences when using languages such as TypeScript, a superset of JavaScript that transpiles (converts into) pure JavaScript but can be written using the same coding experience as what we would have in C# or any other .NET language.
It's available as a separate type of project, if you're curious about it, and the latest up-to-date version is TypeScript 1.8, and it was recently published (you can take a look at a detailed description of its new capabilities at https://blogs.msdn.microsoft.com/typescript/2016/02/22/announcing-typescript-1-8-2/).
So, going back to the title, what made C# different? I'll point out five core points:
// Boxing and Unboxing
int y = 3; // this is declared in the stack
// Boxing y in a Heap reference z
// If we change z, y remains the same.
object z = y;
// Unboxing y into h (the value of
// z is copied to the stack)
int h = (int)z;
static short counter = 1;
private void btnLaunch_Click(object sender, RoutedEventArgs e)
{
// Establish a reference to this window
Type windowType = this.GetType();
// Creates an instance of the Window
object objWindow = Activator.CreateInstance(windowType);
// cast to a MainWindow type
MainWindow aWindow = (MainWindow)objWindow;
aWindow.Title = "Reflected Window No: " +
(++counter).ToString();
aWindow.Show();
}
While all this is important, I was surprised by the fact that every event handler in C# (as also in other .NET languages) would have two and only two arguments. So, I asked Anders about it, and his answer was one of the most clear and logical ones that I've ever heard.
As we see, even from the very beginning, the Hejlsberg's team started with a complete, flexible, and modern platform, capable to be extended in many ways as technology evolves. This intention became clear since version 2.0.
The first actual fundamental change that took place in the language was the incorporation of Generics. Don Syme, who would later on lead the team that created the F# language, was very active and led this team as well, so it was ready for version 2.0 of the .NET Framework (not just in C# but in C++ and VB.NET as well).
The purpose of generics was mainly to facilitate the creation of more reusable code (one of the principles of OOP, by the way). The name refers to a set of language features that allow classes, structures, interfaces, methods, and delegates to be declared and defined with unspecified or generic type parameters instead of specific types (see https://msdn.microsoft.com/en-us/library/ms379564(v=vs.80).aspx, for more details).
So, you can define members in a sort of abstract definition, and later on, at the time of using it, a real, concrete type will be applied.
The basic .NET classes (BCL) were enhanced in the System namespace and a new System.Collections.Generic namespace was created to support this new feature in depth. In addition, new support methods were added to ease the use of this new type, such as Type.IsGenericType (obviously, to check types), Type.GetGenericArguments (self-descriptive), and the very useful Type.MakeGenericType, which can create a generic type of any kind from a previous nonspecified declaration.
The following code uses the generic type definition for a Dictionary (Dictionary<,>) and creates an actual (build) type using this technique. The relevant code is the following (the rest, including the output to the console is included in Demo_02_03):
// Define a generic Dictionary (the
// comma is enough for the compiler to infer number of
// parameters, but we didn't decide the types yet.
Type generic = typeof(Dictionary<,>);
ShowTypeData(generic);
// We define an array of types for the Dictionary (Key, Value)
// Key is of type string, and Value is of -this- type (Program)
// Notice that types could be -in this case- of any kind
Type[] typeArgs = { typeof(string), typeof(Program) };
// Now we use MakeGenericType to create a Type representing
// the actualType generic type.
Type actualType = generic.MakeGenericType(typeArgs);
ShowTypeData(actualType);
As you see, MakeGenericType expects an array of (concrete) types. Later on (not in the preceding code), we use GetGenericTypeDefinition, IsGenericType, and GetGenericArguments in order to introspect the resulting types and present the following output in the console:
So, we have different ways to declare generics with identical results as far as the operations in the code are concerned.
Obviously, manipulating already constructed generic types is not the only possibility, since one of the main goals of generics is to avoid casting operations by simplifying the work with collections. Up until version 2.0, collections could only hold basic types: integers, longs, strings, and so on, along with emulating different types of data structures, such as stacks, queues, linked lists, and so on.
Besides this, Generics have another big advantage: you can write methods that support working with different types of arguments (and return values) as long as you provide a correct way to handle all possible cases.
Once again, the notion of contract will be crucial here.
Other useful feature is the possibility to use custom generic types. Generic types and the support for optional values through the System.Nullable<T> type were, for many developers, two of the most important features included in version 2.0 of the language.
Imagine you have a Customer class, which your application manages. So, in different use cases, you will read collections of customers and perform operations with them. Now, what if you need an operation such as Compare_Customers? What would be the criteria to use in this case? Even worse, what if we would like to use the same criteria with different types of entities, such as Customer and Provider?
In these cases, some characteristics of generics come in handy. To start with, we can build a class that has an implementation of the IComparer interface, so we establish out of any uncertainty what the criteria to be used is in order to consider customer C1 bigger or smaller than customer C2.
For instance, if the criteria is only Balance, we can start with a basic Customer class, to which we add a static method in order to generate a list of random customers:
public class Customer
{
public string Name { get; set; }
public string Country { get; set; }
public int Balance { get; set; }
public static string[] Countries = { "US", "UK", "India", "Canada", "China" };
public static List<Customer> customersList(int number)
{
List<Customer> list = new List<Customer>();
Random rnd = new Random(System.DateTime.Now.Millisecond);
for (int i = 1; i <= number; i++)
{
Customer c = new Customer();
c.Name = Path.GetRandomFileName().Replace(".", "");
c.Country = Countries[rnd.Next(0, 4)];
c.Balance = rnd.Next(0, 100000);
list.Add(c);
}
return list;
}
}
Then, we build another CustomerComparer class, which implements the IComparer interface. The difference is that this comparison method is a generic instantiation customized for the Customer objects, so we have the freedom of implementing this scenario just in the way that seems convenient for our logic.
In this case, we're using Balance as an ordering criteria, so that we would have the following:
public class CustomerComparer : IComparer<Customer>
{
public int Compare(Customer x, Customer y)
{
// Implementation of IComparer returns an int
// indicating if object x is less than, equal to or
// greater than y.
if (x.Balance < y.Balance) { return -1; }
else if (x.Balance > y.Balance) return 1;
else { return 0; } // they're equal
}
}
We can see that the criteria used to compare is just the one we decided for our business logic. Finally, another class, GenericCustomer, which implements an entry point of the application, uses both classes in this manner:
public class GenericCustomers
{
public static void Main()
{
List<Customer> theList = Customer.customersList(25);
CustomerComparer cc = new CustomerComparer();
// Sort now uses our own definition of comparison
theList.Sort(cc);
Console.WriteLine(" List of customers ordered by Balance");
Console.WriteLine(" " + string.Concat(Enumerable.Repeat("-", 36)));
foreach (var item in theList)
{
Console.WriteLine(" Name: {0}, Country: {1}, t Balance: {2}",
item.Name, item.Country, item.Balance);
}
Console.ReadKey();
}
}
This produces an output of random customers order by their balance:
This is even better: we can change the method so that it supports both customers and providers indistinctly. To do this, we need to abstract a common property of both entities that we can use for comparison.
If our implementation of Provider has different or similar fields (but they're not the same), it doesn't matter as long as we have the common factor: a Balance field.
So we begin with a simple definition of this common factor, an interface called IPersonBalance:
public interface IPersonBalance
{
int Balance { get; set; }
}
As long as our Provider class implements this interface, we can later create a common method that's able to compare both objects, so, let's assume our Provider class looks like this:
public class Provider : IPersonBalance
{
public string ProviderName { get; set; }
public string ShipCountry { get; set; }
public int Balance { get; set; }
public static string[] Countries = { "US", "Spain", "India", "France", "Italy" };
public static List<Provider> providersList(int number)
{
List<Provider> list = new List<Provider>();
Random rnd = new Random(System.DateTime.Now.Millisecond);
for (int i = 1; i <= number; i++)
{
Provider p = new Provider();
p.ProviderName = Path.GetRandomFileName().Replace(".", "");
p.ShipCountry = Countries[rnd.Next(0, 4)];
p.Balance = rnd.Next(0, 100000);
list.Add(p);
}
return list;
}
}
Now, we rewrite the Comparer method to be a GenericComparer class, capable of dealing with both types of entities:
public class GenericComparer : IComparer<IPersonBalance>
{
public int Compare(IPersonBalance x, IPersonBalance y)
{
if (x.Balance < y.Balance) { return -1; }
else if (x.Balance > y.Balance) return 1;
else { return 0; }
}
}
Note that in this implementation, IComparer depends on an interface, not on an actual class, and that this interface simply defines the common factor of these entities.
Now, our new entry point will put everything together in order to obtain an ordered list of random Provider classes that uses the common comparison method just created:
public static void Main()
{
List<Provider> providerList = Provider.providersList(25);
GenericComparer gc = new GenericComparer();
// Sort now uses our own definition of comparison
providerList.Sort(gc);
Console.WriteLine(" List of providers ordered by Balance");
Console.WriteLine(" " + ("").PadRight(36, '-'));
foreach (var item in providerList)
{
Console.WriteLine(" ProviderName: {0}, S.Country: {1}, t Balance: {2}",
item.ProviderName, item.ShipCountry, item.Balance);
}
Console.ReadKey();
}
In this way, we obtain an output like what is shown in the following figure (note that we didn't take much care of formatting in order to focus on the process):
The example shows how generics (and interfaces: also generic) come to our rescue in these type of situations, and—as we'll have the opportunity to prove when talking about implementations of design patterns—this is key to facilitating good practices.
So far, some of the most critical concepts behind generics have been discussed. However, the real power comes from joining these capabilities with two new features of the language: lambda expressions and the LINQ syntax.
Finally, we can extend existing classes' functionality. This means extending even the .NET Framework base types, such as int or String. This is a very useful feature, and it's performed in the way it is recommended by the documentation; no violation of basic principles of OOP occur.
The procedure is fairly simple. We need to create a new public static top level (not nested) class containing a public static method with an initial argument declaration especially suited for the compiler to assume that the compiled code will be appended to the actual functionality of the type.
The procedure can be used with any class, either belonging to the .NET framework or a customized user or class.
Once we have the declaration, its usage is fairly simple, as shown in this code:
public static class StringExtension
{
public static string ExtendedString(this string s)
{
return "{{ " + s + " }}";
}
}
Note that the first argument, referred with the this keyword, references the string to be used; so, in this example, we will call the method without any extra arguments (although we can pass as many arguments as we need for other extensions). To put it to work, we just have to add something like this:
Console.WriteLine("The word " + "evaluate".ExtendedString() + " is extended");
We will get the extended output with the word enclosed in double brackets:
So in this article we saw some of the most relevant enhancements made to the C# language in versions 2 and 3.
We started by reviewing the main differences between C# and other languages and understanding the meaning of strongly typed, in this case, together with the concepts of static and dynamic.
We followed this up with an examination of the generics feature that appeared in version 2.0 of the framework and analyzed some samples to illustrate some typical use cases, including the creation of custom generic methods. Finally, we covered the extension methods.
Further resources on this subject: