Click here to Skip to main content
14,026,538 members
Click here to Skip to main content
Add your own
alternative version

Stats

62.4K views
116 bookmarked
Posted 18 Apr 2016
Licenced CPOL

Mastering C# - Lecture Notes Part 2 of 4

, 10 May 2016
Rate this:
Please Sign up or sign in to vote.
In the second part we learn about generics, Lambda expressions, Extension methods and GUI programming.

These articles represent lecture notes, which have been given in form of tutorials originally available on Tech.Pro.

  1. Introduction
  2. Enumerations
  3. Delegates
  4. Auto-generated properties
  5. Generic types
  6. Generic methods
  7. Constraints
  8. Lambda expressions
  9. Anonymous objects & inferring types
  10. Extension methods
  11. LINQ
  12. Windows Forms development
  13. Custom drawing in Windows Forms
  14. Outlook
  15. Other Articles in this Series
  16. References
  17. History

This tutorial aims to give a brief and advanced introduction into programming with C#. The prerequisites for understanding this tutorial are a working knowledge of programming, the C programming language and a little bit of basic mathematics. Some basic knowledge of C++ or Java could be helpful, but should not be required.

Introduction

This is the second part of a series of tutorials on C#. In this part we are going to discuss more advanced features of C# like writing templates with generics, creating anonymous methods in form of lambda expressions and extending existing type definitions with extension methods. Finally we will apply our existing knowledge by creating graphical user interfaces by using the Windows Forms UI framework.

For further reading a list of references will be given in the end. The references provide a deeper look at some of the topics discussed in this tutorial.

Enumerations

In C# we have several possibilities for creating types. In the previous tutorial we had a look at creating struct, class and interface types. Additionally we have delegates and enumerations. Still our main differentiation between structure (value) and class (reference) types holds, since, e.g., any enumeration is just a collection of a certain structure type. So what is an enumeration?

An enumeration is a collection of constants. Instead of defining a static class and adding public constants, we have a much cleaner and nicer syntax that provides some benefits:

enum MyEnumeration
{
    None = 0,
    First = 1,
    Second = 2,
    Last = 100
}

This defines an enumeration of integers. We can simply access them by using MyEnumeration.None, MyEnumeration.Last or others. By default an enumeration is of integer type, however, this can be changed by using the inheritance operator : like in the following example.

enum AnotherEnumeration : byte
{
    Zero,
    One,
    Two,
    Eleven = 11,
    Twelve
}

What is the value of each constant here? The C# compiler determines that automatically. In this case Zero would be 0, One would be 1 and Two would be 2. Since we tell the compiler to set Eleven to 11, it will automatically set Twelve to be 12. Also each value is of the byte type.

There are big advantages of using enumerations instead of plain constants or integers. Enumerations are strongly typed and have an improved string representation using the ToString method. Also using an enumeration as a parameter for a method is strongly encouraged as compared to a plain integer or a similar type. The advantage is that any other programmer is able to see which values are valid and what they basically stand for.

Also enumerations support the well-known bitwise operations like and (&), or (|) or xor (^). Usually if an enumeration is being used as a bit-flag collection, we would mark it with an attribute. We will not discuss attributes in this tutorial, but we will discuss some specific attributes if useful. Applying the attribute works like in the following example:

[Flags]
enum MyBitEnum
{
    None = 0,
    One = 0x1,
    Two = 0x2,
    Four = 0x4,
    Eight = 0x8,
    Sixteen = 0x10,
    Thirtytwo = 0x20
}

There are several advantages by applying this attribute. First of all we get an improved string representation for combinations. When we apply the ToString method on MyBitEnum for MyBitEnum.One | MyBitEnum.Two we see that this results in a string that reads "One, Two". If we would not have used the Flags attribute, we would just get a string that reads "3". Another advantage is that everyone can recognize that the enumeration is used with bitwise operations, resulting in combinations of the specified entries.

Having discussed enumerations we will now have a look at another type, which we can specify in C#: a delegate.

Delegates

In order to keep everything object-oriented, strongly typed and clean, we need to introduce another data type for transporting references to methods. This data type is another reference type and must be used for accepting methods as parameters. The basic syntax to create a delegate type is quite similar to creating an abstract method:

delegate [return type] [delegate name]([parameter types and names])

All in all a delegate is something like a managed function pointer. A question that might arise at this point is probably why one needs to specify names for the parameters. A little example will illustrate the advantage in having to name the parameters:

delegate double OneDimensionalFunction(double x);

public double Integrate(double start, double end, OneDimensionalFunction f)
{
    /* ... use f like a method, i.e., f(x) with any double x */
}

If we now use an instance of the delegate OneDimensionalFunction within the method Integrate, we see that we are getting some help. We will not only see that the method f requires one double parameter, but also that we named it "x". This name can now give a hint on the intended usage. Therefore we see that this can be used to distinguish between delegates, which use the same signature. Compare the delegate above against this one:

delegate double MapSegment(double position);

We see directly that this delegate is defined for different purposes, even though the signature is the same. In the .NET-Framework there are several delegates with the same signature, but different purposes. A really popular signature is no return type and object as single parameter.

We will use delegates more often once we introduce lambda expressions.

Auto-generated properties

Obviously the C# compiler does a lot for us like the automatic generation of a default constructor if we have not specified one. In the previous tutorial we already introduced the concept of writing properties to wrap variables. Variables should never be used with the public modifier. Using variables with an internal or protected modifier should only be done for a purpose. Instead properties should be used to give other objects access to variables. The advantage is that properties can control the flow, i.e., they can restrict access to read or write, evaluate values before setting a variable's value or perform actions after a variable's value has been updated.

Given those arguments it seems obvious to always use properties, like in the following construct:

private int myVariable;

public int MyVariable
{
    get { return myVariable; }
    set { myVariable = value; }
}

This seems like a lot of typing. Indeed there is a faster way using the Visual Studio (there is always a faster way using the Visual Studio). Once we specified a variable, we can right click on it and select "Refactor, Encapsulate field". This will create a property with the usual .NET convention of starting the property with a capital letter instead of the lowercase version that is used for the variable's name.

However, there is an even shorter way, which does not rely on VS (refactoring or code snippets): using C#'s auto-generated properties. The construct above could also be written in one line:

public int MyVariable { get; set; }

This will give us a property called MyVariable, which can be read and written from other objects. Using such automatic properties we will not have access to the variable behind, since the C# compiler will assign a name, which is unknown until compilation. Such auto-generated properties require a get and a set block, however, as with normal properties we can still adjust the access modifier for each block:

public int MyVariable { get; private set; }

This is actually one of the most used auto-generated property definition (a public getter and private setter). Here we have the benefit of restricting a variable to be modified from inside the object, while still making its value visible to the outside.

Even though auto-generated properties are quite limited (we cannot write specific instructions for one block while leaving the other block in automatic mode), they still provide a recommended structure to start with. So once we need more advanced instructions, e.g., for validating a value before setting it, we just have to add all statements require to build a normal property.

One thing we have to be aware about is the fact that abstract properties have the same look as those auto-generated properties. The only difference is the abstract keyword. Let's see an example:

public abstract MyBaseClass
{
    public abstract int MyVariable { get; set; }
}

This looks very much like our auto-generated property above. The only difference is the abstract keyword, which can only be used in abstract classes.

So to conclude this topic: Auto-generated properties are a good starter, which are sufficient for most situations. They can be modified easily to provide more advanced capabilities. The general advice would be to use them as often as possible, since we should never expose variables to be accessed by other objects directly.

Generic types

C# gives us two ways of providing reusable code. One way was to specify structures or classes, which can then be instantiated. Another way is to specify a template of a type, which can then be used with other values and instantiated. Those templates are called generics and are quite similar to the templates of C++, however, they are far less powerful. The advantage is that they are more straight forward and easier to understand.

The goal of these generics is to use a certain type with a variety of types. Let's think of an example first: In the .NET-Framework there is a class called ArrayList. This is a list, which is basically an array with the ability to resize. The Length property of any .NET array has been renamed to Count, which gives us the current number of elements in the list. Additionally we have methods like Add or Remove.

Now ArrayList is very general list, taking only instances of type object (which is any object). The problem is that we therefore just get objects of type object back. This does not seem like a problem at first, since we can just cast the objects back to their specific type, however, since no type check is performed at adding new objects, this could result in an exception.

Now we could create a new class which uses the ArrayList internally. This class would then only take objects of a certain type and return just objects of the same type. The problem seems to be solved at this point, but once we want to use our class with another type, we need to do all the previous steps again with the other type. The .NET-Framework designers did something similar and came up with classes like StringCollection (for the string type) or NameValueCollection (for storing key / value pairs).

At this point we should already see that this work requires a lot more copy / paste than it should be. The solution is using generics to create a template class. Any generic type is specified with the angle brackets (< and >) behind it. The angle brackets specify the type parameters. Here are some examples:

class MyGenericOne<T>
{
}

class MyGenericTwo<T, V>
{
}

class MyGenericThree<TOne, TTwo, TThree>
{
}

The first class MyGenericOne has one type parameter named T. The second class MyGenericTwo has two type parameters named T and V. The third class MyGenericThree has three parameters named TOne, TTwo and TThree. Right now any of those parameters can be represented by any type. Let's see how we can use those parameters:

class MyGenericOne<T>
{
    T myvariable;

    public MyGenericOne(T myvariable)
    {
        this.myvariable = myvariable;
    }

    public T MyVariable { get { return myvariable; } }

    public override string ToString()
    {
        return myvariable.ToString();
    }
}

So the parameter type T is just used as a normal type. The only thing that we will recognize right now is that we only have the possibilities of object on the myvariable instance. This is due to the fact that T can be everything, i.e., T could be the most general object, which is of type object. We will see later how we can restrict type parameters and therefore enable more possibilities on instances of parameter types.

Now that we've seen how we create generic types, we have to know how to use them. Generic types are used like normal types, the only difference being that we have to specify the type parameters.

MyGenericOne<int> a = new MyGenericOne<int>(5);

Once we use a generic type the runtime will look up if it already instantiated the specific type for it. If it did not, it will create an instance of the class from the generic type, which basically replaces the type parameters with the given type(s). A really good example in the .NET-Framework is the generic List<T> class. This solves our initial problem of having to create several classes that basically do the same thing. The List<T> is a strongly typed collection (list), that has been introduced with the .NET-Framework version 2. If we use List<int>, List<double>, two classes will be instantiated from the generic type. However, while generics are a runtime feature, the compiler may still perform optimizations leading to (almost) no performance hit in general.

Hence the following picture should contain labels "runtime generated class", however, in most cases the subtitle is close enough to the truth for our purposes.

Generic types will be provided by the compiler

Right now we only looked at classes, but generics can really be used with most types. They do not make sense for enumerations, but could be helpful for interfaces, structures or delegates. Let's have a look at some generic delegates:

delegate TReturn Func<TReturn>();

delegate TReturn Func<TArg, TReturn>(TArg arg);

delegate TReturn Func<TArg1, TArg2, TReturn>(TArg1 arg1, TArg2 arg2);

With those three generic delegates we have reusable delegates for any method returning non-void that takes zero, one or two parameters. This is such a reasonable concept that the .NET-Framework already contains generic delegates called Func (returning non-void), Action (return void) and Predicate (returning bool). Therefore the only reason to create a delegate should be to give other programmers a hint what the method that is expected should do. Otherwise we can just use the given generic delegates.

Generic methods

Right now the concept of generics seems to be limited to types, however, we can also apply this concept to a single method. Hence not only classes, structures, interfaces or delegates can be generated, but also methods. The syntax is similar, but the usage is (usually) much simpler, since the compiler can (mostly) infer the type(s) for the generic method. Let's have a look at an example, a generic Swap method:

class MyClass
{
    public static void Swap<T>(ref T l, ref T r)
    {
        T temp = r;
        r = l;
        l = temp;
    }
}

If we now want to use this method we can simply use Swap without specifying the type, e.g., int:

int a = 3;
int b = 4;
MyClass.Swap(ref a, ref b);

The compiler is able to infer the type to int and generate the required method. A question that might instantly arise is the following: If the compiler is able to determine the type here, why is the compiler not able to determine the type in our example above, i.e., MyGenericOne like new MyGenericOne(5) instead of new MyGenericOne<int>(5). The answer is quite simple: The compiler would be able to infer the type in such special cases, but there could be another class that is just named MyGenericOne like in the following code:

class MyGenericOne
{
}

class MyGenericOne<T>
{
}

This is possible and leads to the conclusion that new MyGenericOne(5) would point to the non-generic type MyGenericOne, while new MyGenericOne<int>(5) would point to the generic version. Hence type-inference is not working here, since the compiler could (in some cases) not know to which one we are referring to.

Constraints

Generics alone are already quite powerful, yet they very limited. Additionally to the natural limitations to only types (which is not given in C++ templates), we have seen that every instance is only treated as object. This is where the concept of generic constraints come into play.

By setting constraints the limitation of having only very general options can be overcome. A constraint is started with the where keyword. Let's see a very easy constraint:

class MyGenericFour<T> where T : Random
{
    //In here we have all options of the Random class on T
}

This generic class takes all kinds of types which can be casted to the Random class, i.e., the Random class or derivatives. The constraint can also refer to the type parameter(s), like in the following example:

class MyGenericFive<T, V> where T : List<V>
{
    //In here we have all options of the List generic class
}

Even combinations are possible. Constraints on multiple types are always separated by where keywords. Let's look at a combined example:

class MyGenericSix<T, V> where T : List<V> where V : Random
{
    //In here we have all options of the List<Random> generic class
}

There are several other possibilities, e.g., new() means has that given types need to have a default constructor. If we do not specify this, then we cannot create an instance of type parameter types. Let's have a direct look at that with a generic method:

T CreateNewRight<T>() where T : new()
{
    return new T();//Works
}

T CreateNewWrong<T>()
{
    //Does not work
    //return new T();

    //But this works always
    return default(T);
}

If we did not put that new() constraint on our type parameter, C# will not let us use a default constructor. However, we can always use C#'s default() method. This will always return null for reference types and default value for value types (e.g., zero for int or false for bool).

More constraints on one type are also possible. In this case we separate the multiple constraints by a comma, starting with the strongest limitation and ending with the weakest. Let's see a short example of that:

T CreateStopwatch<T>() where T : Stopwatch, new()
{
    return new T();
}

In this example T has to be a Stopwatch (or derivative), however, it cannot be such a derivative, which does not have an empty default constructor.

Lambda expressions

Earlier in this tutorial we introduced the delegate type, which is a managed function pointer. Now we will require a delegate to give us the reference to a method, which has no name. Methods that have no name are known as anonymous methods. Such anonymous methods can be created by using the delegate keyword, but we will not look at that way. Instead we will look at an easier (and nowadays more common) way of creating those, using the fat arrow operator =>. This operator creates a relation between the arguments on the left side and the function on the right side. Let's see some examples:

x => x * x;

(x, y) => x * x + y;

() => 42;

() => Console.WriteLine("No arguments given");

All those statements would be correct lambda expressions, however, the compiler would not let us those without specifying where the reference to the lambda expression should be stored. Another point why those statements would not compile alone is that we are still strongly-typed and did not specify the argument types. We will correct those issues soon. For now let's look at the main points from those examples:

  • If we just have a single argument we do not need round brackets around it.
  • Multiple (or no) arguments require round brackets.
  • The right hand side automatically returns the value.
  • If the value on the right hand side is void, then nothing is returned.

The right hand side could also consist of multiple statements given in curly brackets. If we use curly brackets, then we need to use the return keyword as in normal methods. Let's see a couple of examples:

x => 
{ 
    return x > 0 ? -1 : (x < 0 ? 1 : 0); 
};

() => 
{
    Console.WriteLine("Current time: " + DateTime.Now.ToShortTimeString());
    Console.WriteLine("Current date: " + DateTime.Now.ToShortDateString());
};

() =>
{
    Console.WriteLine("The answer has been generated.");
    return 42;
};

Okay, so everything we still have to do is specifying a type for those lambda expressions. Let's go back to our first round of examples:

Func<double, double> squ = x => x * x;

Func<double, double, double> squoff = (x, y) => x * x + y;

Func<int> magic = () => 42;

Action noarg = () => Console.WriteLine("No arguments given");

This would now compile and it would do everything we need.

There is also another reason why we are introducing lambda expressions instead of the delegate based syntax for anonymous methods. First of all lambda expressions could be compiled to a special type called Expression. We will not go into details of that, but this is a very handy feature of lambda expressions. The other reason is that it feels very natural to capture local scoped variables in a lambda expressions. Then the lambda expressions is called a closure. Consider the following example:

class MyClass
{
    public void WriteCount(int min, int max)
    {
        int current = min;
        Action wc = () => Console.WriteLine(current++);

        while (current != max)
            wc();
    }
}

In this example we are creating a local scoped variable called current (additionally we have local scoped variables min and max from the arguments). The lambda expression referenced in the variable wc is not taking any argument and returns void, however, it is using the local scoped variable current. Therefore it is capturing the local scoped variable. Additionally it is also changing it by using the increment operator.

Anonymous objects & inferring types

Lambda expressions are already pretty cool, but the C# compiler is capable of even more. After creating anonymous methods we can also go ahead and create instances of anonymous classes! Those anonymous objects can be pretty handy if we just want to group elements temporarily, but are either too lazy to create a class definition, or need some features like immutability or equality operators defined. Let's see what is meant here:

new { Name = "Florian", Age = 28 };

This short snippet already creates an anonymous object with two properties, one named Name containing a string that reads "Florian" and another one named Age containing an integer with value 28. However, we now face a similar (but more severe) problem as with lambda expressions. We need to assign this anonymous instance to a variable, otherwise we will get a compilation error. Assigning this value to a variable requires us (since we are strongly-typed) to specify a type. What type should we use now? Let's consider the following example:

object anonymous = new { Name = "Florian", Age = 28 };

This will work, but we will see that using the properties Name and Age is not allowed. So even though this will compile, it will not be very useful for our purposes. Here is where C# comes to help! The C# compiler is able to infer all kinds of types using the var keyword. A few examples are:

var myint = 3;//The type will be int
var mydouble = 3.0;//The type will be double
var mychar = '3';//The type will be char
var mystring = "3";//The type will be string

It is important to know that the compiler will actually resolve this and infer the correct type. This has nothing to do with dynamic programming and var is certainly something different in C# than in JavaScript or other dynamic languages.

Now with type inference we could try again to create an anonymous method:

var anonymous = new { Name = "Florian", Age = 28 };

//This works now:
Console.WriteLine(anonymous.Name);

If we hover the var keyword we will see which type has been inferred by the compiler. In case of an anonymous method we will always get something that actually tells us that we are using anonymous type. The var keyword is restricted to local variables and cannot be used for global variables or parameter types. Therefore we still face one big disadvantage by using anonymous types: Access to the members of the anonymous type is possible only in the local method, where it has been defined. Hence passing an anonymous object is only possible as object.

There are other interesting things about anonymous objects. First: Every anonymous object has the a specialized version of the ToString method. Instead of just returning a string telling us that this is an anonymous type, we get some string that contains all properties and their values. Another interesting thing is that Equals and GetHashcode are specialized as well, making any anonymous object an ideal object to compare to others. Finally all properties are read-only, resulting in an immutable object.

Extension methods

Another cool feature the C# compiler gives us are extension methods. They basically solve a quite common problem: Suppose we get a set of already given types and we want to extend (some of) them with a set of really useful methods, the only thing we can do is to create some static methods in another class. Let's see some example based on some .NET internal types:

public static class MyExtensions
{
    public static void Print(string s)
    {
        Console.WriteLine(s);
    }

    public static int DigitSum(int number)
    {
        var str = number.ToString();
        var sum = 0;

        for (var i = 0; i < str.Length; i++)
            sum += int.Parse(str[i].ToString());

        return sum;
    }
}

Now the only way to use these methods is indirectly like in the following snippet:

MyExtensions.Print("Hi there");
var sum = MyExtensions.DigitSum(251);

However, from an object-oriented point of view this is wrong. In reality we want to say something like:

"Hi there".Print();
var sum = 251.DigitSum();

That way is not only shorter, but represents more closely what we meant. Until C# 3 it was impossible to extend existing types with such external methods, however, using the this keyword in the list of parameters makes it possible. Let's rewrite our initial code:

public static class MyExtensions
{
    public static void Print(this string s)
    {
        Console.WriteLine(s);
    }

    public static int DigitSum(this int number)
    {
        var str = number.ToString();
        var sum = 0;

        for (var i = 0; i < str.Length; i++)
            sum += int.Parse(str[i].ToString());

        return sum;
    }
}

Not much changed... If we look closely enough we will eventually find out that the signatures of the two methods changed. Additionally we now specified the this keyword before the first argument's type. While our first way of using those two methods is still working, we will now have access to the second way, given the following prerequisites:

  1. We need to have a reference to the library where those methods are defined (if they are specified in the same project, there is no problem).
  2. We need to be in the namespace of the extension methods or have the namespaces of the extension methods included with a using directive.
  3. We need to use an instance of the (derived or base) type that has been specified.

If all those requirements are met, then we are good to go. Methods using the this keyword in their signature are called extension methods and will be displayed using an additional blue down arrow in their icon represented by the original VS IntelliSense list.

LINQ

Until now we worked hard to get to the point where we can use all those given technologies. The Language Integrated Query (Linq, LinQ or LINQ) is a language / framework technology where all of those features are highly useful. We will see that

  • lambda expressions will be required in every step,
  • making small temporary packages with anonymous types will be very useful,
  • LINQ expressions are just (generic) extension methods themselves and
  • the type inference will make our (programming) life extremely easy.

So what is LINQ? LINQ is a set of really useful extension methods that live in the System.Linq namespace. Since these extension methods are that useful, it is quite natural that every C# standard template has the required namespace included. Every IEnumerable<T> instance (i.e., every array, list or class that implements the IEnumerable interface) has LINQ methods. The main purpose of LINQ is to run queries of (large) datasets and reduce the amount of required code.

Let's look at a very easy example: We have an array with integer numbers, which might contain duplicates. Now we want those duplicates to be removed.

//Create an array and directly initialize the values
int[] myarray = new int[] { 1, 2, 3, 4, 4, 5, 2, 9, 11, 1 };

//Use LINQ
myarray = myarray.Distinct().ToArray();

It took us just one line of code to reduce the array from 10 entries to 7 entries losing all duplicates. Everything we had to do is call the extension method Distinct. That gave us a IEnumerable<int> instance, which has been saved as an int[] using the ToArray extension method.

Let's see another example: Ordering a list of doubles with LINQ!

//Create a list and directly initialize
var a = new List<double>(new [] { 3.0, 1.0, 5.5, -1.0, 9.0, 2.5, 3.1, 1.1, 0.2, -5.2, 10.0 });

//Set up the query
var query = a.OrderBy(m => m);

a.Add(0.0);
//Run query and save in the variable a
a = query.ToList();

What's going on here? First this seems like the same thing as before, only that we save the query temporarily in a variable called query. We then use this variable later to create the list with the extension method ToList. Nothing special so far. However, if we look closely at the result we will find out that it contains the value 0. This value has not been in the list when we set up the query. How is that possible? The answer lies in the fact that LINQ does never perform a query, without anyone requesting the result. A result may be requested by iterating over the elements, which is implied when storing the result in an array or list. This feature of LINQ is called deferred execution.

Another (more illustrative) example would be to get only even elements with a LINQ query:

var elements = new List<int>(new int[] { 1, 3, 5, 7 });
var query = elements.Where(m => m % 2 == 0);
elements.Add(4);
elements.Add(6);
var firstEven = query.First();
var lastEven = query.Last();
elements.Add(2);
var newLastEven = query.Last();

Even though there have not been any even elements in the list when we set up the query we get the latest result once we request it. A request for the result is also given by First or Last. Those kinds of methods (there is also ElementAt and others) only work if the given set is not empty. Therefore skipping the calls of the Add method will result in an exception. There are multiple ways around this exception. Obviously we could use a try-catch-block, but that would be too much. We could also just use the given FirstOrDefault (or LastOrDefault etc.) method, which will not result in an exception, but just returns the default(T) value, which is 0 for the integer type and null for any reference type.

The most elegant solution (and most generic one) is certainly to run a short evaluation if the query contains any elements at all. This is done using the Any extension method.

var elements = new List<int>(new int[] { 1, 3, 5, 7 });
var query = elements.Where(m => m % 2 == 0);

//Try uncommenting the following line to see the difference
//elements.Add(2);

if (query.Any())
{
    var firstEven = query.First();
    var lastEven = query.Last();
}
else
{
    Console.WriteLine("The query cannot run ...");
}

This is the part where we need to get more sensitive about LINQ. Obviously LINQ is quite handy, since it will save us a lot of lines of code. How does it work? Well LINQ builds up on iterators which can be used in combination with the foreach loop. This loop is more expensive than for (requires 1 more operation, which is getting the iterator and then needs to call the Next method in every step). So there is some overhead associated with LINQ statements naturally. It is also quite obvious that LINQ cannot take any performance optimizations into account, which would be possible for certain data structures.

Also as we've seen LINQ statements are deferred, i.e., they are only performed if the result (or part of it) is requested. If our dataset changes, the LINQ result will change as well. Therefore the golden rule is: If we want the result at a certain point in (code) time, then we need to perform a ToArray, ToList call, or iterate over the query. LINQ does extend any IEnumerable<T>. Everything that is an IEnumerable<T> is a so called in-memory query, while every IQueryable<T> is a so called for remote-data query. For us the difference does not matter, since we will only use in-memory queries in this tutorial. In general one does care a lot when working for a Data Access Layer (DAL). Using LINQ for a database query is simple and straight forward and will result in less error-prone code. The biggest disadvantage of using LINQ for writing database queries is that certain LINQ statements will not work (completely or the same way as before).

Until now we mainly used lambda expressions for our LINQ queries. In the next example we will also make use of anonymous objects:

var powers = Enumerable.Range(0, 100).Select(m => new {
    Number = m,
    Square = m * m,
    Cube = m * m * m
}).ToArray();

First we are using the Range extension method to give us an enumerable with 100 integers starting at 0. Then we select a new anonymous object from each element. The Select extension method allows us to use the currently given element in order to set which element should be presented from that moment on. This LINQ statement is a so-called projector, since it projects the currently given elements to a new set of elements. Even though it seems to be quite similar to the select-statement in SQL, it is a little bit different. If we do not specify it, we will just get the elements as they are. Also we are not using it to specify which columns we want, but which kind of properties or variable we would like to get. Finally we can also use it to call methods and create new objects.

An important part of LINQ is the so-called language extension. LINQ comes in two flavors:

  • A set of extension methods, used like normal methods.
  • A sub-language in C#, which looks quite similar to SQL.

Until now we did only look at the first part, however, the second part should also be discussed a bit.

The sub-language to use for LINQ queries is defined by a set of new keywords like where, select, from or let. Queries written in the sub-language are called query expressions. On the other hand using extension methods is referred to as using a fluent syntax. Let's see an example of a query expression formulation first:

var names = new[] { "Tom", "Dick", "Harry", "Joe", "Mary" };
var query = from m in names
    where m.Contains("a")
    orderby m.Length
    select m.ToUpper();

Every query expression starts with a from keyword and ends with a select or group clause. In between we are free to do whatever we want to. The next graphic shows an overview of all possible keywords and their allowed order.

LINQ query expression keywords order

If we compare the query expression with the fluent syntax we can see that the translation is quite simple:

var names = new[] { "Tom", "Dick", "Harry", "Joe", "Mary" };
var query = names
    .Where(m => m.Contains("a"))
    .OrderBy(m => m.Length)
    .Select(m => m.ToUpper());

Now we can actually see that the from statement does two things:

  1. It enables the query expression.
  2. It sets the name for the element (argument), such that it can be used within the query.

So the big advantage of the query expression syntax is that we do not need to respecify the name for the element each time (omitting the m => statements in the example above).

Another benefit cannot be seen by the example above. One thing that has been mentioned already is that the query expression syntax introduces also a let keyword. People that know F# or some functional languages already know that let is quite often used to allocate a new (local) variable.

Let's see that in a short example, which extends the code from above:

var names = new[] { "Tom", "Dick", "Harry", "Joe", "Mary" };
var query = from m in names
    let vowelless = Regex.Replace(m, "[aeiou]", string.Empty)
    where vowelless.Length > 2
    orderby vowelless
    select m + " -> " + vowelless;

Here vowelless is a local variable for each element. It should be clear that the compiler does this by using a Select method in combination with a new anonymous object. However, we never see this construction and have a nice declarative statement.

To complete this discussion, we need to make sure to get the right judgment about both, the query expression and the fluent syntax.

There are advantages in using the fluent syntax:

  • It is not limited to Where, Select, SelectMany, OrderBy, ThenBy, OrderByDescending, ThenByDescending, GroupBy, Join and GroupJoin (e.g., Min is only available in the fluent syntax).
  • It can be easily extended.
  • It is directly visible what methods will be called and in what order.

On the other side we also have some advantages in using the query expression syntax:

  • It lets us use the let keyword for introducing new variables.
  • It simplifies queries with multiple generators followed by an outer range variable reference a lot.
  • Also Join or GroupJoin queries will be a lot simpler.

What is the bottom line here? Most people tend to use the fluent syntax, since it is more powerful and easier to understand for people, who have never seen a LINQ query before. Since both ways can be mixed (using the query expression for the general query and fluent syntax for subparts which are not available in the query expression syntax), there is no recommendation possible. In the end it will depend on personal preference.

Windows Forms development

There are several possibilities to create graphical user interfaces (GUI) in general, and also quite a lot for Microsoft Windows. Some frameworks to create GUI even aim to be cross-platform. We will now look a bit into Windows Forms, which was the first GUI framework for C# and the .NET-Framework. Windows Forms is also a nice example of object-oriented code, with a clear class-hierarchy and an easy-to-learn philosophy.

To create a new Windows Forms Project in Visual Studio, we just have to select the File menu, then "New, Project, C#, Windows Forms Application". That's it! The template for this kind of project will automatically do some nice things like creating a new main window or starting it in the Main method.

The first thing we will now notice is that a new kind of editor has opened. This is the so called designer. Instead of writing code to place the various components, we can place controls, set properties and do the basic design in a specialized editor. Behind the curtain this designer will do the coding for us.

The screenshot shows the designer with the toolbox on the left and properties window. Controls from the toolbox can be dragged onto any form that is open in the designer. Once a control (that includes the form itself) is selected we can change its properties in the properties window.

The integrated designer with the toolbox (left) and the properties dialog (right)

The designer uses a feature called partial classes. The partial keyword tells the compiler that the definition of a class is split into multiple files. In our case VS will generate two (code) files for each form:

  1. A file for our code, ending with *.cs (like Form1.cs).
  2. A file for the designer, ending with *.Designer.cs (like Form1.Designer.cs).

If we take a look at our code file it will be quite similar to the following code:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace MyFirstForm
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }
    }
}

While the System namespace should always be included, we require the System.Windows.Forms namespace to use Form without explicitly writing the namespace. The System.Drawing namespace on the other hand is not used in this snippet, but is very useful in general for Windows Forms development.

So what's so special about this code? The first thing to notice is the usage of the partial keyword in the class definition. The second thing is that a default constructor has been placed explicitly. This is required to call the InitializeComponent method. This is no inherited method and obviously it is not defined in the code snippet. Where is it defined and what does it do?

The InitializeComponent method is responsible for using the input of the designer. Everything that is done by the designer is placed in the other code file. This file looks similar to the following code snippet:

namespace MyFirstForm
{
    partial class Form1
    {
        private System.ComponentModel.IContainer components = null;

        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
                components.Dispose();

            base.Dispose(disposing);
        }

        private void InitializeComponent()
        {
            this.button1 = new System.Windows.Forms.Button();
            this.SuspendLayout();
            // 
            // button1
            // 
            this.button1.Location = new System.Drawing.Point(84, 95);
            this.button1.Name = "button1";
            this.button1.Size = new System.Drawing.Size(75, 23);
            this.button1.TabIndex = 0;
            this.button1.Text = "button1";
            this.button1.UseVisualStyleBackColor = true;
            // 
            // Form1
            // 
            this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
            this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
            this.ClientSize = new System.Drawing.Size(284, 262);
            this.Controls.Add(this.button1);
            this.Name = "Form1";
            this.Text = "Form1";
            this.ResumeLayout(false);
        }

        private System.Windows.Forms.Button button1;
    }
}

In this example we already placed a Button control somewhere on the form. Here we can learn the following things:

  • The designer works with a very explicit code, not using any namespaces.
  • The designer always uses the this to refer to global variables.
  • The designer defines the InitializeComponent method.
  • All variables for components are placed in the designer file.
  • All instances for components are initialized in the InitializeComponent method.

Every modification in the constructor should therefore just happen after the InitializeComponent method has been called. This is important to avoid any errors involving a null-reference exception, which might happen if we want to access variables that will be initialized by the designer.

Now that we have a rough knowledge about the designer and how the designer works, we have to look closer at the Windows Forms UI framework. The reason why we are taking a look at this particular UI framework is that is a quite excellent example for object-oriented design. Even though that is quite true for nearly all UI frameworks, some of those frameworks make it hard to see it that easily. Also other UI frameworks might present a much steeper learning curve.

Obviously there is a big OOP tree behind Windows Forms. For us the most important class is Control. Nearly every control derives directly or indirectly from it. A really important derivative is Form, which represents a window. Every Control can be drawn or hosted inside a list of controls.

Let's check out how we could place our own control without using the designer. The following code snippet is the constructor in the previous example:

public Form1()
{
    InitializeComponent();

    //Create a new instance
    var myLabel = new Label();
    //Set some properties
    myLabel.Text = "Some example text";
    myLabel.Location = new Point(100, 60);
    myLabel.ForeColor = Color.Red;
    //Without the following line we would see nothing
    this.Controls.Add(myLabel);
}

All we need to do is apply our knowledge. We create a new instance of some class (in this case we use Label, which is basically a text label), change some properties to the desired values and then add it to the collection of some object, which makes us of it. In this case adding it to the Controls collection of the current form, will host the new control directly in the form. We can also host controls in other controls.

This hosting will then do the drawing and logic of the control. Otherwise if we do not host the control somewhere, then nothing will happen.

This magic (drawing and logic) has to start somewhere. It starts with the Form, of course, but how does the application know which Form to take and how to construct it? Taking a look at Program.cs reveals a code quite similar to the following:

using System;
using System.Windows.Forms;

namespace MyFirstForm
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.Run(new Form1());
        }
    }
}

Right now we do not care about the [STAThread] attribute. To say it shortly: This attribute is essentially a requirement for the Windows message pump to communicate with COM components. The message pump (or message loop) is started with the Run method in the static Application class. Our message pump lives with the instance of Form1, i.e., if we close this (main) window, then the application closes.

That loop is actually integrated in Windows. Once we call the Run method Windows will register our application and post messages to the queue. These messages arrive in our Form instance as events. Such an event can be something like mouse over, mouse click, key down, or others. The state of most controls is based on such events (like hover).

The next graphic shows how the various components are interacting which each other. While the teal fields are external inputs, the purple fields are controlled by the OS (e.g., Windows). Windows controls the message queue or starts the redrawing / update process. The blue field is the hand of the framework, while the red field is could our event handler or any code we've written to handle the event.

The interaction of message queue, message loop and our window

It is also possible to register our own handlers, but right now its enough to do it with the designer. Doing it with the designer is possible by double clicking a control (will create the event handler for the default event, e.g., click in case of a Button control) or opening the tab with the lightning symbol in the properties view. Double clicking on the desired event there will create the event handler.

An event handler is nothing other than a method that will be called once the event has been fired. So if a user clicks a Button control on our window and we specified an event handler for this control, then the specified method will be called.

Usually this will look similar to this:

private void button1_Click(object sender, EventArgs e)
{
    //Write code here, like...
    MessageBox.Show("The button has been clicked!");
}

The object-oriented design of the Windows Forms framework allows us to easily create our own controls. All we need to do is derive from a suitable base class, like Control or something more specialized. If we want to create a label that blinks, we could do the following:

//Here we directly inherit from Label, which derives from Control
public class BlinkLabel : Label
{
    Timer timer;

    public BlinkLabel()
    {
        timer = new Timer();
        //Set the timer to an interval of 1s
        timer.Interval = 1000;
        //This will be explained in the next tutorial
        timer.Tick += DoBlink;
    }

    //This is the event handler for the tick event
    void DoBlink(object sender, EventArgs e)
    {
        //Just switch the Visible state
        Visible = !Visible;
    }
}

After compilation we will find our own BlinkLabel control in the toolbox, where all controls are placed. This means that we can easily drag and drop an instance of BlinkLabel on any Form instance, which can be modified by the designer.

Another possible that we find is overriding some of the given methods. Let's take a look at the following code:

public class Ellipse : Control
{
    public Ellipse()
    {
    }

    protected override OnPaint(PaintEventArgs e)
    {
        base.OnPaint(e);
        /* Implementation in the next section */
    }
}

Here we use one of several ways to include our own drawing logic. The taken path is to override the OnPaint method, which gives us an argument of type PaintEventArgs. Here we are mostly interested in the ClipRectangle and Graphics properties. The first one gives us the boundaries (in form of a Rectangle instance), while Graphics is the so-called graphics pointer.

Another way would be to create an event handler for the Paint event. This way should only be used with instances of controls. Some controls (e.g., ListBox) give us a third way of specifying some drawing logic. This third way implies setting something like, e.g., the DrawMode property of the ListBox instance. Finally we have to create an event handler, e.g., for DrawItem in the ListBox case. This third way enables us to draw specific portions of the control, e.g., the contained items in the ListBox case.

We will now have a closer look at drawing in Windows Forms.

Custom drawing in Windows Forms

The (2D) drawing API of Windows is called GDI (Graphics Device Interface). GDI's most significant advantages over more direct methods of accessing the hardware are perhaps its scaling capabilities and its abstract representation of target devices. Using GDI, it is very easy to draw on multiple devices, such as a screen and a printer, and expect proper reproduction in each case.

The Windows Forms UI framework extends this API to GDI+, which will give us an object-oriented layer above GDI. This layer also contains a set of really useful helper methods. While GDI only contains a set of very basic pixel-manipulator functions, GDI+ already has methods for drawing an ellipse, a rectangle or complex paths.

The central object for using GDI+ is called Graphics. We cannot construct it directly using new, but indirectly using a static method of Graphics. Calling such a method requires a parameter, like image or the screen. Images do have advantages (e.g., buffering, modifications, ...), but they consume a lot of memory. Creating an image is quite easy:

var bmp = new Bitmap(400, 300);

The Bitmap class is an implementation or the abstract base class Image. If we now want to use the Graphics object we simply have to write the following statement:

var g = Graphics.FromImage(bmp);

This creates a new Graphics object using the given Bitmap instance. Now we have access to methods that start with either Draw and Fill. Those methods draw the border or fill the content of the given shape. For drawing we need to an instance of a Pen, which is a class that defines the style for any border.

Filling is done by an instance of a class that derives from the base class Brush. A very basic implementation is given by SolidBrush, which fills paths with a uniform color. A more advanced implementation is given by LinearGradientBrush (generates a color gradient) or by TextureBrush (uses an Image as a texture).

Let's now look at a method using GDI+ drawing. The method will create a 400px (width) times 300px (height) bitmap with some rectangles and ellipses.

Bitmap DrawSimpleRectangle()
{
    //Create the bitmap
    Bitmap bmp = new Bitmap(400, 300);
    //Get the graphics context for the bitmap
    Graphics g = Graphics.FromImage(bmp);

    //The smoothing mode enables drawing of intermediate pixels
    g.SmoothingMode = SmoothingMode.AntiAlias;

    //Draw a simple rectangle (fill the complete rectangle with yellow)
    g.FillRectangle(Brushes.Yellow, new Rectangle(0, 0, 400, 300));

    //Drawing a rectangle with some big border
    g.DrawRectangle(new Pen(Color.Red, 4f), new Rectangle(10, 10, 380, 280));

    //Let's create another rectangle for our circle (circle is a special ellipse with width = height)
    var circle = new Rectangle(15, 15, 270, 270);

    //Drawing a very simple linear gradient requires using a LinearGradientBrush object
    var lgb = new LinearGradientBrush(new Point(15, 15), new Point(295, 295), Color.Red, Color.Black);

    //Now we can fill an ellipse with the gradient brush
    g.FillEllipse(lgb, circle);

    //Let's just try another circle
    g.DrawEllipse(Pens.DarkGreen, circle);

    return bmp;
}

This is really just a matter of calling some methods and passing in the right arguments. Usually we would need to create also a lot of different Pen, Brush and Color instances, but luckily we could use some already defined objects given in static properties like Red of Color or DarkGreen of Pens.

After this first example we can try around to draw even fancier things or start an animation. An animation is just a series of different bitmaps (called frames), which contain some differences between the frames. If we think of a rotating rectangle we see that the only difference lies in the angle of the rectangle.

The question that might arise here is: How can we draw a rectangle with a different angle? There is no argument for specifying the angle of the rectangle. This is where the concept of transformations is coming in. This concept has been integrated into GDI+ and plays a very important role. Every method of Graphics is using the currently set of transformation.

The transformation is set by a matrix with 3 columns and 2 rows. This matrix is just a compressed (single-object) form of a 2x2 matrix with an additional vector that has length 2. There are three transformations, which change the values of the matrix:

  1. Translation (given by the vector or the entries in the last column).
  2. Rotation (given by all elements of the 2x2 matrix).
  3. Scale (given by the diagonal elements of the matrix).

We do not need to care about matrix manipulations since we already have methods like TranslateTransform, RotateTransform or ScaleTransform given by the Graphics object. They will do the math for us.

Let's have a look at an example, which rotates a given rectangle:

Bitmap DrawTransformedRectangle()
{
    //The same start as before
    Bitmap bmp = new Bitmap(400, 300);
    Graphics g = Graphics.FromImage(bmp);
    g.SmoothingMode = SmoothingMode.AntiAlias;

    //Fill some rectangle (full image)
    g.FillRectangle(Brushes.Turquoise, new Rectangle(0, 0, 400, 300));

    //Use the translate to change (px, py) to (px', py') by using px' = px + a, py' = py + b
    g.TranslateTransform(200, 150);//a = 200, b = 150

    //Use rotate to change px', py' to px'', py'' by using cos(alpha) * px' - sin(alpha) * py', cos(alpha) * px' + sin(alpha) * py'
    g.RotateTransform(45f);//Here: alpha in degrees!

    //Scale it with px'', py'' to px''', py''' by using a * px'', b * py''
    g.ScaleTransform(0.3f, 0.3f);//a = 0.3, b = 0.3

    //Draw a rectangle using these transformations
    g.DrawRectangle(Pens.Red, new Rectangle(-100, -50, 200, 100));

    return bmp;
}

Transformations are really useful as they help us to scale, rotate and translate (parts of) an image without doing much mathematics. The magic happens behind the curtain when the pixel positions are calculated using the matrix.

Another handy thing about drawing in the Windows Forms framework is the concept of paths. A path is a list of points. We say that a path is closed when the last (final) point is connected to the first (initial) point. A closed path creates a region.

We can apply operators (like union) on regions, resulting in interesting new regions, which can be filled or drawn. Let's see how we could create a very simple arbitrary path:

Bitmap DrawArbitraryPath()
{
    //Same start again
    Bitmap bmp = new Bitmap(400, 300);
    Graphics g = Graphics.FromImage(bmp);

    //Create the path, i.e., a new `GraphicsPath` object
    GraphicsPath p = new GraphicsPath();

    //Add some fixed lines by adding the points
    p.AddLines(new Point[] 
    { 
        new Point(200, 0),
        new Point(220, 130),
        new Point(400, 130),
        new Point(220, 150),
        new Point(300, 300),
        new Point(200, 150)
    });

    //Fill the path (this will fill one (the right) half)
    g.FillPath(Brushes.Red, p);

    //Scale with -1, i.e., 200 will be -200, 220 will be -220
    g.ScaleTransform(-1f, 1f);

    //Transform with -400, i.e., -200 will be 200, -220 will be 180
    g.TranslateTransform(-400, 0);

    //Fill the second half (left half)
    g.FillPath(Brushes.Blue, p);

    return bmp;
}

This example gives us an image with a star that has a blue and a red half.

Finally we have enough information to finish our Ellipse control. We will make this control in such a fashion, that it can play an animation.

public class Ellipse : Control
{
    float thickness;
    float angle;
    Color fillColor;
    Color borderColor;

    public Ellipse()
    {
        borderColor = Color.Black;
        fillColor = Color.White;
        thickness = 2f;
    }

    public Color FillColor
    {
        get { return fillColor; }
        set { fillColor = value; Refresh(); }
    }

    public Color BorderColor
    {
        get { return borderColor; }
        set { borderColor = value; Refresh(); }
    }

    public float Angle
    {
        get { return angle; }
        set { angle = value; Refresh(); }
    }

    public float Thickness
    {
        get { return thickness; }
        set { thickness = value; Refresh(); }
    }

    protected override void OnPaint(PaintEventArgs e)
    {
        //Introduce g as a shorthand for e.Graphics
        var g = e.Graphics;
        g.SmoothingMode = SmoothingMode.AntiAlias;
        //We want a circle, i.e., min(width, height)
        var w = Math.Min(Width, Height) / 2;
        //Let's create a rectangle for the circle
        var circle = new Rectangle(-w, -w, 2 * w, 2 * w);
        //We will need that pen more often
        var pen = new Pen(borderColor, thickness);
        //Go into the center of our circle
        g.TranslateTransform(Width / 2, Height / 2);
        //And rotate
        g.RotateTransform(angle);
        //Now fill and draw the circle
        g.FillEllipse(new SolidBrush(fillColor), circle);
        g.DrawEllipse(pen, circle);
        //And then draw the line such that we see the angle
        g.DrawLine(pen, new Point(0, 0), new Point(0, -w));
    }
}

After compilation we can use our own control with the designer. The toolbox contains the Ellipse control. We can drag and drop it to a form. This will look like in the following screenshot:

Modifying the properties with the designer

Changing the angle property will result in the rotation around the center of the circle.

Outlook

This concludes the second part of this tutorial series. In the next part we will have a look at asynchronous models, dynamic programming with C# and the DLR, as well as multi-threading, the Task Parallel Library (TPL) and reflection. We will also continue to program GUI with Windows Forms and learn how to create our own events or add an event handler without the designer.

Other Articles in this Series

  1. Lecture Notes Part 1 of 4 - An advanced introduction to C#
  2. Lecture Notes Part 2 of 4 - Mastering C#
  3. Lecture Notes Part 3 of 4 - Advanced programming with C#
  4. Lecture Notes Part 4 of 4 - Professional techniques for C#

References

History

  • v1.0.0 | Initial Release | 19.04.2016
  • v1.0.1 | Added article list | 20.04.2016
  • v1.0.2 | Refreshed article list | 21.04.2016
  • v1.0.3 | Updated some typos | 22.04.2016
  • v1.1.0 | Updated structure w. anchors | 25.04.2016
  • v1.1.1 | Added table of contents | 29.04.2016
  • v1.2.0 | Thanks to Christian Andritzky for spotting the problem with generics | 10.05.2016

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Florian Rappl
Chief Technology Officer
Germany Germany
Florian lives in Munich, Germany. He started his programming career with Perl. After programming C/C++ for some years he discovered his favorite programming language C#. He did work at Siemens as a programmer until he decided to study Physics.

During his studies he worked as an IT consultant for various companies. After graduating with a PhD in theoretical particle Physics he is working as a senior technical consultant in the field of home automation and IoT.

Florian has been giving lectures in C#, HTML5 with CSS3 and JavaScript, software design, and other topics. He is regularly giving talks at user groups, conferences, and companies. He is actively contributing to open-source projects. Florian is the maintainer of AngleSharp, a completely managed browser engine.

You may also be interested in...

Comments and Discussions

 
SuggestionGreat article with great information Pin
Stuart Smith16-May-16 18:13
memberStuart Smith16-May-16 18:13 
GeneralRe: Great article with great information Pin
Florian Rappl16-May-16 22:21
professionalFlorian Rappl16-May-16 22:21 
BugCompiler does not generate classes from generics Pin
Christian Andritzky10-May-16 6:46
memberChristian Andritzky10-May-16 6:46 
GeneralRe: Compiler does not generate classes from generics Pin
Florian Rappl10-May-16 7:14
professionalFlorian Rappl10-May-16 7:14 
QuestionBlinking label doesn't work Pin
Member 120616006-May-16 10:57
memberMember 120616006-May-16 10:57 
AnswerRe: Blinking label doesn't work Pin
Florian Rappl6-May-16 11:20
professionalFlorian Rappl6-May-16 11:20 
GeneralMy vote of 5 Pin
Dmitriy Gakh25-Apr-16 20:12
professionalDmitriy Gakh25-Apr-16 20:12 
GeneralRe: My vote of 5 Pin
Florian Rappl25-Apr-16 21:58
professionalFlorian Rappl25-Apr-16 21:58 
GeneralMy vote of 5 Pin
Matthias Adloff22-Apr-16 9:56
professionalMatthias Adloff22-Apr-16 9:56 
GeneralRe: My vote of 5 Pin
Florian Rappl22-Apr-16 10:31
professionalFlorian Rappl22-Apr-16 10:31 
GeneralMy vote of 5 Pin
DarkDynamite22-Apr-16 2:35
memberDarkDynamite22-Apr-16 2:35 
GeneralRe: My vote of 5 Pin
Florian Rappl22-Apr-16 2:39
professionalFlorian Rappl22-Apr-16 2:39 
GeneralMy vote of 5 Pin
D V L22-Apr-16 2:27
professionalD V L22-Apr-16 2:27 
GeneralRe: My vote of 5 Pin
Florian Rappl22-Apr-16 2:36
professionalFlorian Rappl22-Apr-16 2:36 
GeneralMy vote of 4 Pin
ByeByeByeByeBye21-Apr-16 4:40
memberByeByeByeByeBye21-Apr-16 4:40 
GeneralRe: My vote of 4 Pin
Florian Rappl21-Apr-16 4:48
professionalFlorian Rappl21-Apr-16 4:48 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web04 | 2.8.190419.4 | Last Updated 10 May 2016
Article Copyright 2016 by Florian Rappl
Everything else Copyright © CodeProject, 1999-2019
Layout: fixed | fluid