This tutorial aims to give a brief and an 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.
Generics, Lambda expressions, Extension methods and GUI programming
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 datatype for transporting references
to methods. This datatype 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 compiler will look up if it already
created the class for it. If it did not, the compiler will create a
class from the generic type, which basically replaces the type
parameters with the specified type. 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 generated 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 which one we
are referring to.Constraints
Generics alone are already quite powerful, yet they are 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
that it has been given types needed to have a default constructor. If we
do not specify this, then we cannot create an instance of 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 that way.
Instead we will look at an easier (and nowadays more common) way of
creating them, by 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 compile 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 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 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 quite a 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 it 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 re-specify the name for the element each time (omitting the
m =>
statements in the example above).
Another benefit cannot be seen from the example above. One thing that
has been mentioned already is that the query expression syntax also
introduces 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 not 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 because it is quite an 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
use 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. In short, 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 evewill 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 possibility 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 create 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 also need to create 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 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 comes
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;
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:
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.
Comments
Post a Comment