C# Language Features
C# Language Features
0
By Marc Clifton, 15 Mar 2012
Contents
Introduction
C# 2.0 Features
o Generics
Without Generics
With Generics
Constraints and Method Parameters and Return Types
Factories
o Partial Types
o Anonymous Methods
The Old Way
The New Way
Async Tasks
Updating The UI
o Iterators
The Old Way
The New Way
o Nullable Types
o Private Setters (properties)
o Method Group Conversions (delegates)
The Old Way
The New Way
C# 3.0 Features
o Implicitly Typed Local Variables
Restrictions
o Object and Collection Initializers
The Old Way
The New Way
Initializing Collections
o Auto-Implemented Properties
o Anonymous Types
o Extension Methods
Before Extension Methods
With Extension Methods
o Query Expressions
Left and Right Joins
o Lambda Expressions
o Expression Trees
C# 4.0 Features
o Dynamic Binding
o Named and Optional Arguments
Example
Optional Arguments, The Old Way
o Generic Covariance and Contravariance
Delegates
Generics
But How Do I Define My Own?
Conclusion
Introduction
This article discusses the language features introduced in C# 2.0, 3.0, and 4.0. The purpose of writing
this article is to have a single repository of all the new language features introduced over the last
seven years and to illustrate (where applicable) the advantages of the new features. It is not intended
to be a comprehensive discussion of each feature, for that I have links for further reading. The
impetus for this article is mainly because I could not find a single repository that does what this
article does. In fact, I couldn't even find a Microsoft webpage that describes them. Instead, I had to
rely on the universal authority for everything, Wikipedia, which has a couple nice tables on the
matter.
C# 2.0 Features
Generics
First off, generics are not like C++ templates. They primarily provide for strongly typed collections.
Without Generics
With Generics
With generics we are prevented from using a typed collection with an incompatible type.
Generics can also be used in non-collection scenarios, such as enforcing the type of a parameter or
return value. For example, here we create a generic method (the reason we don't create a
generic MyVector will be discussed in a minute:
class Program
{
public static T AddVector<T>(T a, T b)
where T : MyVector, new()
{
T newVector = new T();
newVector.X = a.X + b.X;
newVector.Y = a.Y + b.Y;
return newVector;
}
What we can't do with generics (but could with C++ templates) is perform operator functions on
generic types. For example, we can't do this:
// Doesn't work:
public void AddVector<T>(MyVector<T> v)
{
X = X + v.X;
Y = Y + v.Y;
}
}
This results in a "operator '+=' cannot be applied to operands of type 'T' and 'T'" error! More on
workarounds for this later.
Factories
The above is a very silly thing to do, but if you are writing an Inversion of Control layer, you might be
doing some complicated things (like loading assemblies) based on the type the factory needs to
create.
Partial Types
Partial types can be used on classes, structs, and interface. In my opinion, partial types were created
to separate out tool generated code from manually written code. For example, the Visual Studio
form designer generates the code-behind for the UI layout, and to keep this code stable and
independent from your manually written code, such as the event handlers, Visual Studio creates two
separate files and indicates that the same class is of partial type. For example, let's say we have two
separate files:
File 1:
File 2:
We can use the class, which has been defined in two separate files:
public PartialExample()
{
foobar.Foo = 1;
foobar.Bar = 2;
}
}
Do not use partial classes to implement a model-view-controller pattern! Just because you can
separate the code into different files, one for the model, one for the view, and one view the
controller, does not mean you are implementing the MVC pattern correctly!
The old way of handling tool generated code was typically to put comments in the code like:
And the tool would place its code between the comments.
Anonymous Methods
Read more.
Anonymous methods let us define the functionality of a delegate (such as an event) inline rather
than as a separate method.
Before anonymous delegates, we would have to write a separate method for the delegate
implementation:
Async Tasks
Updating the UI
My favorite example is calling the main application thread from a worker thread to update a UI
component:
Iterators
Read more.
Iterators reduce the amount of code we have to write to iterate over a custom collection.
In the new approach, we can use the yield keyword to iterate through the collection:
This is much more readable and also ensures that we don't access elements in the collection beyond
the number of items in the collection.
We can also implement a generic enumerator, which provides a type safe iterator, but requires us to
implement both generic and non-generic GetEnumerator method:
IEnumerator IEnumerable.GetEnumerator()
{
return Enumerate();
}
public IEnumerator<int> GetEnumerator()
{
return Enumerate();
}
The error in casting from a string to an integer is caught at runtime, not compile time. Using a
genericIEnumerable<T>, an improper cast is caught at compile time and also by the IDE:
Nullable Types
Read more.
Nullable types allow a value type to take on an additional "value", being "null". I've found this
primarily useful when working with data tables. For example:
public NullableTypes()
{
people = new DataTable();
In the above example, the Field extension method (I'll discuss extension methods later)
converts DBNull.Valueautomatically to a "null", which in this schema is a valid foreign key value.
You will also see nullable types used in various third party frameworks to represent "no value." For
example, in the DevExpress framework, a checkbox can be set to false, true, or no value. The reason
for this is again to support mapping a control directly to a structure that backs a table with nullable
fields. That said, I think you would most likely see nullable types in ORM implementations.
Read more.
A private setter exposes a property as read-only, which is different from designating the property
as readonly. With a field designated as readonly, it can only be initialized during construction or in
the variable initializer. With aprivate setter, the property can be exposed as readonly to the outside
world the class implementing the property can still write to it:
public PrivateSetter()
{
// readonly fields can be initialized in the constructor.
readable2 = 20;
}
Contrast the above implementation with C# 3.0's auto-implemented properties, which I discuss
below.
public MethodGroupConversion()
{
StringOperation = new ChangeString(AddSpaces);
}
C# 3.0 Features
Implicitly Typed Local Variables
Read more.
The "var" keyword is a new feature of C# 3.0. Using the "var" keyword, you are relying on the
compiler to infer the variable type rather than explicitly defining it. So, for example, instead of:
// new:
var implicitDict = new Dictionary<string, int>();
}
While it seems like syntactical sugar, the real strength of implicit types is its use in conjunction with
anonymous types (see below.)
Restrictions
Note the phrase "local variables" in the heading for this section. Implicitly typed variables cannot be
passed to other methods as parameters nor returned by methods. As Richard Deeming commented
below, what I mean by this is that you cannot specify var as a parameter or return type, but you can
call a method with an implicit type of the method's parameter is an explicit type, and similarly (and
more obviously) with return parameters -- an explicit return type can be assigned to a var.
Read more.
Previously, to initialize property values from outside of the class, we would have to write either use a
constructor:
In its explicit implementation, this simply allow us to initialize properties and collections when we
create the object. We've already seen examples in the code above:
More interestingly is how this feature is used to initialize anonymous types (see below) especially
with LINQ.
Initializing Collections
Auto-Implemented Properties
In the C# 2.0 section, I described the private setter for properties. Let's look at the same
implementation using auto-implemented properties:
The code is a lot cleaner, but the disadvantage is that, for properties that need to fire events or have
some other business logic or validation associated with them, you have to go back to the old way of
implementing the backing field manually. One proposed solution to firing property change events
for auto-implemented properties is to use AOP techniques, as written up by Tamir Khason's Code
Project technical blog.
Anonymous Types
Read more.
Anonymous types lets us create "structures" without defining a backing class or struct, and rely on
implicit types (vars) and object initializers. For example, if we have a collection of "Record" objects,
we can return a subset of the properties in this LINQ statement:
LINQ
Implicit types
Object initialization
Anonymous types
If we run the debugger and inspect "idAndName", we'll see that it has a value:
Imagine having to explicitly state that type name. We can see advantages of implicit types, especially
in conjunction with anonymous types.
Extension Methods
Read more.
Extension methods are a mechanism for extending the behavior of a class external to its
implementation. For example, the String class is sealed, so we can't inherit from it, but there's a lot of
useful functions that the String class doesn't provide. For example, working with Graphviz, I often
need to put quotes around the object name.
Before extension methods, I would probably end up writing something like this:
(OK, that part looks pretty much the same) - but I would use it like this:
Not only is this more readable, but it's also more reusable, as the behavior is now exposed
everywhere.
Query Expressions
Read more.
As I wrote about above with regards to anonymous types, here's a LINQ statement:
LINQ expressions can get really complex and working with .NET classes and LINQ relies heavily on
extension methods. LINQ is far to large a topic (there are whole books on the subject) and is
definitely outside the purview of this article!
Joins by default in LINQ are inner joins. I was perusing recently for how to do left and right joins and
came across thisuseful post.
Lambda Expressions
Read more.
Lambda expressions are a fundamental part of working with LINQ. You usually will not find LINQ
without lambda expressions. A lambda expression is an anonymous method (ah ha!) that "can
contain expressions and statements, and can be used to create delegates or expression tree types...The
left side of the lambda operator specifies the input parameters (if any) and the right side holds the
expression or statement block." (taken from the website referenced above.)
and I'd get the names of people with the name "Marc". With a lambda expression and the extension
methods provided for a generic List, I can write:
LINQ and lambda expressions can be combined. For example, here's some code from an article I
recently wrote:
LINQ, lambda expressions, anonymous types, implicit types, collection initializers and object
initializers all work together to more concisely express the intent of the code. Previously, we would
have to do this with nested for loops and lots of "if" statements.
Expression Trees
Read more.
Let's revisit the MyVector example. With expression trees, we can however compile type-specific
code at runtime that allows us to work with generic numeric types in a performance efficient manner
(compare with "dynamic" in C# 4.0, discussed below).
public MyVector(T x, T y)
{
X = x;
Y = y;
}
Read more.
Let's revisit the MyVector implementation again. With the dynamic keyword, we can defer the
operation to runtime when we know the type.
Because this uses method invocation and reflection, it is very performance inefficient. According to
MSDN referenced in the link above: The dynamic type simplifies access to COM APIs such as the Office
Automation APIs, and also to dynamic APIs such as IronPython libraries, and to the HTML Document
Object Model (DOM).
Read more.
As with the dynamic keyword, the primary purpose of this is to facilitate calls to COM. From the
MSDN link referenced above:
Named arguments enable you to specify an argument for a particular parameter by associating the
argument with the parameter's name rather than with the parameter's position in the parameter list.
Optional arguments enable you to omit arguments for some parameters. Both techniques can be
used with methods, indexers, constructors, and delegates.
When you use named and optional arguments, the arguments are evaluated in the order in which
they appear in the argument list, not the parameter list.
Named and optional parameters, when used together, enable you to supply arguments for only a
few parameters from a list of optional parameters. This capability greatly facilitates calls to COM
interfaces such as the Microsoft Office Automation APIs.
I have never used named arguments and I rarely need to use optional arguments, though I
remember when I moved from C++ to C#, kicking and screaming that optional arguments weren't
part of the C# language specification!
Example
We can use named an optional arguments to specifically indicate which arguments we are supplying
to a method:
As this example illustrates, we can specify the value for a, use the default value for b, and specify a
non-default value for c. While I find named arguments to be of limited use in regular C#
programming, optional arguments are definitely a nice thing to have.
First, let's look at co-contravariance with delegates, which has been around since Visual Studio 2005.
Delegates
Read more.
Not wanting to restate the excellent "read more" example referenced above, I will simply state that
covariance allows us to assign a method returning a sub-class type to the delegate defined as
returning a base class type. This is an example of going from something wider (the base class) to
something smaller (the inherited class) in terms of derivation.
Contravariance, with regards to delegates, lets us create a method in which the argument is the base
class and the caller is using a sub-class (going from narrower to wider). For example, I remember
being annoyed that I could not consume an event having a MouseEventArgs argument with a
generic event handler having an EventArgsargument. This example of contravariance has been
around since VS2005, but it makes for a useful example of the concept.
Generics
Read more.
Again, the MSDN page referenced is an excellent read (in my opinion) on co-contravariance with
generics. To briefly summarize: as with delegates, covariance allows a generic return type to be
covariant, being able specify a "wide" return type (more general) but able to use a "smaller" (more
specialized) return type. So, for example, the generic interfaces for enumeration support covariance.
Conversely, contravariance lets us go from something narrow (more specialized, a derived class) to
something wider (more general, a base class), and is used as parameters in generic interfaces such as
IComparer.
To specify a covariant return parameter, we use the "out" keyword in the generic type. To specify a
contravariant method parameter, we use the "in" keyword in the generic type. For example (read
more here):
Collapse | Copy Code
public delegate T2 MyFunc<in T1,out T2>(T1 t1);
Conclusion
In writing this, I was surprised how much I learned that deepened my understanding of C# as well as
getting a broader picture of the arc of the language's evolution. This was a really useful exercise!
History
Updated the article based on comments received.