Mastering C# - Lecture Notes Part 2 of 4
November 08, 2019
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.
- Introduction
- Enumerations
- Delegates
- Auto-generated properties
- Generic types
- Generic methods
- Constraints
- Lambda expressions
- Anonymous objects & inferring types
- Extension methods
- LINQ
- Windows Forms development
- Custom drawing in Windows Forms
- Outlook
- Other Articles in this Series
- References
- 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,
Eleven = 11,
Two,
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,
Thirtytwo = 0x20
Sixteen = 0x10,
}
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:
Hide Copy Code
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.
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:- 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).
- We need to be in the namespace of the extension methods or have the namespaces of the extension methods included with a
using
directive. - 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.
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:- It enables the query expression.
- 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
andGroupJoin
(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
orGroupJoin
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 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:- A file for our code, ending with *.cs (like Form1.cs).
- 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.
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:
- Translation (given by the vector or the entries in the last column).
- Rotation (given by all elements of the 2x2 matrix).
- 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;
public Color FillColor
thickness = 2f;
}
{
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:
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
- Lecture Notes Part 1 of 4 - An advanced introduction to C#
- Lecture Notes Part 2 of 4 - Mastering C#
- Lecture Notes Part 3 of 4 - Advanced programming with C#
- Lecture Notes Part 4 of 4 - Professional techniques for C#