Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon

Introduction to C# and .NET

Save for later
  • 17 min read
  • 11 Nov 2016

article-image

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:

  • C# and its role in the Microsoft Development ecosystem
  • Difference between strongly typed and weakly typed languages
  • The evolution in versions 2.0 and 3.0
  • Generics
  • Extension methods

C#: what's different in the language

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.

Languages: strongly typed, weakly typed, dynamic, and static

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:

introduction-c-and-net-img-0

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:

introduction-c-and-net-img-1

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/).

The main differences

So, going back to the title, what made C# different? I'll point out five core points:

  • Everything is an object. Other languages, such as Smalltalk, Lisp, among others, have done this earlier, but due to different reasons, the performance penalty was pretty hard.
  • As you know, it's enough to take a look at the Object Explorer to be able to check where an object comes from. It's a good practice to check the very basic values, such as int or String, which are nothing but aliases of System.Int32 and System.String, and both come from object, as shown in the following screenshot:

    introduction-c-and-net-img-2

  • Using the Boxing and Unboxing techniques, any value type can be converted into an object, and the value of an object can be converted into a simple value type.
  • These conversions are made by simply casting the type to an object (and vice versa) in this manner:
    // 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;

  • Using Reflection (the technique that allows you to read a component's metadata), an application can call itself or other applications, creating new instances of their containing classes.
  • As a short demo, this simple code launches another instance of a WPF application (a very simple one with just one button, but that doesn't matter):
    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();
    }

  • Now, every time we click on the button, a new instance of the window is created and launched, indicating its creation order in the title's window:

    introduction-c-and-net-img-3

  • You can have access to other components through a technology called Platform Invoke, which means you can call operating systems' functions by importing the existing DLLs using the DllImport attribute:
    • For instance, you can make an external program's window the child of your own window using the SetParent API, which is part of User32.dll, or you can control operating system events, such as trying to shut down the system while our application is still active.
    • Actually, once the permissions are given, your application can call any function located in any of the system's DLL if you need access to native resources.
    • The schema that gives us access to these resources looks like what is shown in the following figure:

      introduction-c-and-net-img-4

    • If you want to try out some of these possibilities, the mandatory resource to keep in mind is http://www.PInvoke.net, where you have most of the useful system APIs, with examples of how to use them in C#.
    • These interoperation capabilities are extended to interactions with applications that admit Automation, such as those in the Microsoft Office Suite, AutoCAD, and so on.

  • Finally, unsafe code allows you to write inline C code with pointers, perform unsafe casts, and even pin down memory in order to avoid accidental garbage collection. However, unsafe does not mean that it is unmanaged. Unsafe code is deeply tied into the security system.
  • There are many situations in which this is very useful. It might be an algorithm that's difficult to implement or a method whose execution is so CPU-intensive that performance penalties become unacceptable.

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.

The evolution in versions 2.0 and 3.0

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).

Generics

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.

Unlock access to the largest independent learning library in Tech for FREE!
Get unlimited access to 7500+ expert-authored eBooks and video courses covering every tech area you can think of.
Renews at €18.99/month. Cancel anytime

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:

introduction-c-and-net-img-5

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.

Creating custom generic types and methods

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:

introduction-c-and-net-img-6

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):

introduction-c-and-net-img-7

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.

Extension methods

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:

introduction-c-and-net-img-8

Summary

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.

Resources for Article:


Further resources on this subject: