Tour of .NET

.NET is a general purpose development platform. It has several key features, such as support for multiple programming languages, asynchronous and concurrent programming models, and native interoperability, which enable a wide range of scenarios across multiple platforms.

This article offers a guided tour through some of the key features of the .NET platform. See the .NET Architectural Components topic to learn about the architectural pieces of .NET and what they're used for.

How to run the code samples

To learn how to set up a development environment to run the code samples, see the Getting Started topic. Copy and paste code samples from this page into your environment to execute them.

Programming languages

.NET supports multiple programming languages. The .NET runtimes implement the Common Language Infrastructure (CLI), which among other things specifies a language-independent runtime and language interoperability. This means that you choose any .NET language to build apps and services on .NET.

Microsoft actively develops and supports three .NET languages: C#, F#, and Visual Basic (VB).

  • C# is simple, powerful, type-safe, and object-oriented, while retaining the expressiveness and elegance of C-style languages. Anyone familiar with C and similar languages finds few problems in adapting to C#. Check out the C# Guide to learn more about C#.

  • F# is a cross-platform, functional-first programming language that also supports traditional object-oriented and imperative programming. Check out the F# Guide to learn more about F#.

  • Visual Basic is an easy language to learn that you use to build a variety of apps that run on .NET. Among the .NET languages, the syntax of VB is the closest to ordinary human language, often making it easier for people new to software development.

Automatic memory management

.NET uses garbage collection (GC) to provide automatic memory management for programs. The GC operates on a lazy approach to memory management, preferring app throughput to the immediate collection of memory. To learn more about the .NET GC, check out Fundamentals of garbage collection (GC).

The following two lines both allocate memory:

var title = ".NET Primer";
var list = new List<string>();

There's no analogous keyword to de-allocate memory, as de-allocation happens automatically when the garbage collector reclaims the memory through its scheduled run.

The garbage collector is one of the services that help ensure memory safety. A program is memory safe if it accesses only allocated memory. For instance, the runtime ensures that an app doesn't access unallocated memory beyond the bounds of an array.

In the following example, the runtime throws an InvalidIndexException exception to enforce memory safety:

int[] numbers = new int[42];
int number = numbers[42]; // Will throw an exception (indexes are 0-based)

Working with unmanaged resources

Some objects reference unmanaged resources. Unmanaged resources are resources that aren't automatically maintained by the .NET runtime. For example, a file handle is an unmanaged resource. A FileStream object is a managed object, but it references a file handle, which is unmanaged. When you're done using the FileStream, you need to release the file handle.

In .NET, objects that reference unmanaged resources implement the IDisposable interface. When you're done using the object, you call the object's Dispose() method, which is responsible for releasing any unmanaged resources. .NET languages provide a convenient using syntax for such objects, as shown in the following example:

using System.IO;

using (FileStream stream = GetFileStream(context))
    // Operations on the stream

Once the using block completes, the .NET runtime automatically calls the stream object's Dispose() method, which releases the file handle. The runtime also does this if an exception causes control to leave the block.

For more details, see the following topics:

Type safety

An object is an instance of a specific type. The only operations allowed for a given object are those of its type. A Dog type may have Jump and WagTail methods but not a SumTotal method. A program only calls the methods belonging to a given type. All other calls result in either a compile-time error or a run-time exception (in case of using dynamic features or object).

.NET languages are object-oriented with hierarchies of base and derived classes. The .NET runtime only allows object casts and calls that align with the object hierarchy. Remember that every type defined in any .NET language derives from the base Object type.

Dog dog = AnimalShelter.AdoptDog(); // Returns a Dog type.
Pet pet = (Pet)dog; // Dog derives from Pet.
Car car = (Car)dog; // Will throw - no relationship between Car and Dog.
object temp = (object)dog; // Legal - a Dog is an object.

Type safety is also used to help enforce encapsulation by guaranteeing the fidelity of the accessor keywords. Accessor keywords are artifacts which control access to members of a given type by other code. These are usually used for various kinds of data within a type that are used to manage its behavior.

private Dog _nextDogToBeAdopted = AnimalShelter.AdoptDog()

C#, VB, and F# support local type inference. Type inference means that the compiler deduces the type of the expression on the left-hand side from the expression on the right-hand side. This doesn't mean that the type safety is broken or avoided. The resulting type does have a strong type with everything that implies. From the previous example, dog and cat are rewritten to introduce type inference, and the remainder of the example is unchanged:

var dog = AnimalShelter.AdoptDog();
var pet = (Pet)dog;
Car car = (Car)dog; // will throw - no relationship between Car and Dog
object temp = (object)dog; // legal - a Dog is an object
car = (Car)temp; // will throw - the runtime isn't fooled
car.Accelerate() // the dog won't like this, nor will the program get this far

F# has even further type inference capabilities than the method-local type inference found in C# and VB. To learn more, see Type Inference.

Delegates and lambdas

A delegate is represented by a method signature. Any method with that signature can be assigned to the delegate and is executed when the delegate is invoked.

Delegates are like C++ function pointers except that they're type safe. They're a kind of disconnected method within the CLR type system. Regular methods are attached to a class and are only directly callable through static or instance calling conventions.

In .NET, delegates are commonly used in event handlers, in defining asynchronous operations, and in lambda expressions, which are a cornerstone of LINQ. Learn more in the Delegates and lambdas topic.


Generics allow the programmer to introduce a type parameter when designing their classes that allows the client code (the users of the type) to specify the exact type to use in place of the type parameter.

Generics were added to help programmers implement generic data structures. Before their arrival in order for a type such as the List type to be generic, it would have to work with elements that were of type object. This had various performance and semantic problems, along with possible subtle runtime errors. The most notorious of the latter is when a data structure contains, for instance, both integers and strings, and an InvalidCastException is thrown on working with the list's members.

The following sample shows a basic program running using an instance of List<T> types:

using System;
using System.Collections.Generic;

namespace GenericsSampleShort
    public static void Main(string[] args)
        // List<string> is the client way of specifying the actual type for the type parameter T
        List<string> listOfStrings = new List<string> { "First", "Second", "Third" };

        // listOfStrings can accept only strings, both on read and write.

        // Below will throw a compile-time error, since the type parameter
        // specifies this list as containing only strings.

For more information, see the Generic types (Generics) overview topic.

Async programming

Async programming is a first-class concept within .NET with async support in the runtime, framework libraries, and .NET language constructs. Internally, they're based on objects (such as Task), which take advantage of the operating system to perform I/O-bound jobs as efficiently as possible.

To learn more about async programming in .NET, start with the Async overview topic.

Language Integrated Query (LINQ)

LINQ is a powerful set of features for C# and VB that allow you to write simple, declarative code for operating on data. The data can be in many forms (such as in-memory objects, a SQL database, or an XML document), but the LINQ code you write typically doesn't differ by data source.

To learn more and see some samples, see the LINQ (Language Integrated Query) topic.

Native interoperability

Every operating system includes an application programming interface (API) that provides system services. .NET provides several ways to call those APIs.

The main way to do native interoperability is via "platform invoke" or P/Invoke for short, which is supported across Linux and Windows platforms. A Windows-only way of doing native interoperability is known as "COM interop," which is used to work with COM components in managed code. It's built on top of the P/Invoke infrastructure, but it works in subtly different ways.

Most of Mono's (and thus Xamarin's) interoperability support for Java and Objective-C are built similarly, that is, they use the same principles.

Read more about it native interoperability in the Native interoperability topic.

Unsafe code

Depending on language support, the CLR lets you access native memory and do pointer arithmetic via unsafe code. These operations are needed for certain algorithms and system interoperability. Although powerful, use of unsafe code is discouraged unless it's necessary to interop with system APIs or implement the most efficient algorithm. Unsafe code may not execute the same way in different environments and also loses the benefits of a garbage collector and type safety. It's recommended to confine and centralize unsafe code as much as possible and test that code thoroughly.

The following example is a modified version of the ToString() method from the StringBuilder class. It illustrates how using unsafe code can efficiently implement an algorithm by moving around chunks of memory directly:

public override String ToString()
    if (Length == 0)
        return String.Empty;

    string ret = string.FastAllocateString(Length);
    StringBuilder chunk = this;
        fixed (char* destinationPtr = ret)
                if (chunk.m_ChunkLength > 0)
                    // Copy these into local variables so that they are stable even in the presence of ----s (hackers might do this)
                    char[] sourceArray = chunk.m_ChunkChars;
                    int chunkOffset = chunk.m_ChunkOffset;
                    int chunkLength = chunk.m_ChunkLength;

                    // Check that we will not overrun our boundaries.
                    if ((uint)(chunkLength + chunkOffset) <= ret.Length && (uint)chunkLength <= (uint)sourceArray.Length)
                        fixed (char* sourcePtr = sourceArray)
                            string.wstrcpy(destinationPtr + chunkOffset, sourcePtr, chunkLength);
                        throw new ArgumentOutOfRangeException("chunkLength", Environment.GetResourceString("ArgumentOutOfRange_Index"));
                chunk = chunk.m_ChunkPrevious;
            } while (chunk != null);
    return ret;

Next steps

If you're interested in a tour of C# features, check out Tour of C#.

If you're interested in a tour of F# features, see Tour of F#.

If you want to get started with writing code of your own, visit Getting Started.

To learn about important components of .NET, check out .NET Architectural Components.