C# Language Features

You might also like

Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 22

C# Language Features, From C# 2.0 to 4.

0
By Marc Clifton, 15 Mar 2012

4.94 (190 votes)

Prize winner in Competition "Best overall article of February 2012"


Prize winner in Competition "Best C# article of February 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

Collapse | Copy Code


public void WithoutGenerics()
{
ArrayList list = new ArrayList();

// ArrayList is of type object, therefore essentially untyped.


// Results in boxing and unboxing of value types
// Results in ability to mix types which is bad practice.
list.Add(1);
list.Add("foo");
}
Without generics, we incur a "boxing" penalty because lists are of type "object", and furthermore, we
can quite easily add incompatible types to a list.

With Generics

Collapse | Copy Code


public void WithGenerics()
{
// Generics provide for strongly typed collections.
List<int> list = new List<int>();
list.Add(1); // allowed
// list.Add("foo"); // not allowed
}

With generics we are prevented from using a typed collection with an incompatible type.

Constraints and Method Parameters and Return Types

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:

Collapse | Copy Code


public class MyVector
{
public int X { get; set; }
public int Y { get; set; }
}

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;
}

static void Main(string[] args)


{
MyVector a = new MyVector();
a.X = 1;
a.Y = 2;
MyVector b = new MyVector();
b.X = 10;
b.Y = 11;
MyVector c = AddVector(a, b);
Console.WriteLine(c.X + ", " + c.Y);
}
}
Notice the constraint. Read more about constraints here. The constraint is telling the compiler that
the generic parameter must be of type MyVector, and that it is an object (the "new()") constraint,
rather than a value type. The above code is not very helpful because it would require writing an
"AddVector" method for vectors of different types (int, double, float, etc.)

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:

Collapse | Copy Code


public class MyVector<T>
{
public T X { get; set; }
public T Y { get; set; }

// 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

You might see generics used in factories. For example:

Collapse | Copy Code


public static T Create<T>() where T : new()
{
return new T();
}

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:

Collapse | Copy Code


public partial class MyPartial
{
public int Foo { get; set; }
}

File 2:

Collapse | Copy Code


public partial class MyPartial
{
public int Bar { get; set; }
}

We can use the class, which has been defined in two separate files:

Collapse | Copy Code


public class PartialExample
{
public MyPartial foobar;

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:

Collapse | Copy Code


// Begin Tool Generated Code: DO NOT TOUCH
... code ...
// End Tool Generated Code

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.

The Old Way

Before anonymous delegates, we would have to write a separate method for the delegate
implementation:

Collapse | Copy Code


public class Holloween
{
public event EventHandler ScareMe;

public void OldBoo()


{
ScareMe+=new EventHandler(DoIt);
}

public void Boo()


{
ScareMe(this, EventArgs.Empty);
}

public void DoIt(object sender, EventArgs args)


{
Console.WriteLine("Boo!");
}
}

The New Way

With anonymous methods, we can implement the behavior inline:

Collapse | Copy Code


public void NewBoo()
{
ScareMe += delegate(object sender, EventArgs args) { Console.WriteLine("Boo!"); };
}

Async Tasks

We can do the same thing with the Thread class:

Collapse | Copy Code


public void AsyncBoo()
{
new Thread(delegate() { Console.WriteLine("Boo!"); }).Start();
}
Note that we cast the method as a "delegate()"--note the '()'--because there are two delegate forms
and we have to specify the parameterless delegate form.

Updating the UI

My favorite example is calling the main application thread from a worker thread to update a UI
component:

Collapse | Copy Code


/// <summary>
/// Called from some async process:
/// </summary>
public void ApplicationThreadBoo()
{
myForm.Invoke((MethodInvoker)delegate { textBox.Text = "Boo"; });
}

Iterators

Read more.

Iterators reduce the amount of code we have to write to iterate over a custom collection.

The Old Way

Previous to C# 2.0, we had to implement the IEnumerator interface, supplying


the Current, MoveNext, and Resetoperations manually:

Collapse | Copy Code


public class DaysOfWeekOld : IEnumerable
{
protected string[] days = new string[] { "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday" };

public int Count { get { return days.Length; } }


public string this[int idx] { get { return days[idx]; } }

public IEnumerator GetEnumerator()


{
return new DaysOfWeekEnumerator(this);
}
}

public class DaysOfWeekEnumerator : IEnumerator


{
protected DaysOfWeekOld dow;
protected int pos = -1;

public DaysOfWeekEnumerator(DaysOfWeekOld dow)


{
this.dow = dow;
}
public object Current
{
get { return dow[pos]; }
}

public bool MoveNext()


{
++pos;

return (pos < dow.Count);


}

public void Reset()


{
pos = -1;
}
}

The New Way

In the new approach, we can use the yield keyword to iterate through the collection:

Collapse | Copy Code


public class DaysOfWeekNew : IEnumerable
{
protected string[] days = new string[] { "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday" };

public IEnumerator GetEnumerator()


{
for (int i = 0; i < days.Length; i++)
{
yield return days[i];
}
}
}

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:

Collapse | Copy Code


public class DaysOfWeekNewGeneric : IEnumerable<string>
{
protected string[] days = new string[] { "Monday", "Tuesday", "Wednesday", "Thursday",
"Friday", "Saturday", "Sunday" };

IEnumerator IEnumerable.GetEnumerator()
{
return Enumerate();
}
public IEnumerator<int> GetEnumerator()
{
return Enumerate();
}

public IEnumerator<string> Enumerate()


{
for (int i = 0; i < days.Length; i++)
{
yield return days[i];
}
}
}

So, for example, in the non-generic version, I could write:

Collapse | Copy Code


DaysOfWeekNew dow2 = new DaysOfWeekNew();

foreach (string day in dow2)


{
Console.WriteLine(day);
}

which is perfectly valid, but I could also write:

Collapse | Copy Code


DaysOfWeekNew dow2 = new DaysOfWeekNew();

foreach (int day in dow2)


{
Console.WriteLine(day);
}

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:

Collapse | Copy Code


DaysOfWeekNewGeneric dow3 = new DaysOfWeekNewGeneric();

foreach (int day in dow3)


{
Console.WriteLine(day);
}

The above code is invalid and generates the compiler error:

"error CS0030: Cannot convert type 'string' to 'int'"


Thus, the implementation of generic iterators in C# 2.0 increases readability and type safety when
using iterators.

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:

Collapse | Copy Code


public class Record
{
public int ID { get; set; }
public string Name { get; set; }
public int? ParentID { get; set; }
}

public class NullableTypes


{
protected DataTable people;

public NullableTypes()
{
people = new DataTable();

// Note that I am mixing a C# 3.0 feature here, Object Initializers,


// with regards to how AllowDBNull is initialized. I'm doing because I think
// the example is more readable, even though not C# 2.0 compilable.

people.Columns.Add(new DataColumn("ID", typeof(int)) {AllowDBNull=false});


people.Columns.Add(new DataColumn("Name", typeof(string)) { AllowDBNull = false });
people.Columns.Add(new DataColumn("ParentID", typeof(int)) { AllowDBNull = true });

DataRow row = people.NewRow();


row["ID"] = 1;
row["Name"] = "Marc";
row["ParentID"] = DBNull.Value; // Marc does not have a parent!
people.Rows.Add(row);
}

public Record GetRecord(int idx)


{
return new Record()
{
ID = people.Rows[idx].Field<int>("ID"),
Name = people.Rows[idx].Field<string>("Name"),
ParentID = people.Rows[idx].Field<int?>("ParentID"),
};
}
}

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.

Private Setters (properties)

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:

Collapse | Copy Code


public class PrivateSetter
{
public int readable;
public readonly int readable2;

public int Readable


{
get { return readable; }
// Accessible only by this class.
private set { readable = value; }
}

public int Readable2


{
get { return readable2; }
// what would the setter do here?
}

public PrivateSetter()
{
// readonly fields can be initialized in the constructor.
readable2 = 20;
}

public void Update()


{
// Allowed:
Readable = 10;
// Not allowed:
// readable2 = 30;
}
}

Contrast the above implementation with C# 3.0's auto-implemented properties, which I discuss
below.

Method Group Conversions (delegates)


I must admit to a "what the heck is this?" experience for this feature. First (for my education) a
"method group" is a set of methods of the same name. In other words, a method with multiple
overloads. This post was very helpful. I stumbled across this post that explained method group
conversion with delegates. This also appears to have to do with covariance and contravariance,
features of C# 4.0. Read more here. But let's try the basic concept, which is to assign a method to a
delegate without having to use "new" (even though behind the scenes, that's apparently what the IL
is emitting).

The Old Way

Collapse | Copy Code


public class MethodGroupConversion
{
public delegate string ChangeString(string str);
public ChangeString StringOperation;

public MethodGroupConversion()
{
StringOperation = new ChangeString(AddSpaces);
}

public string Go(string str)


{
return StringOperation(str);
}

protected string AddSpaces(string str)


{
return str + " ";
}
}

The New Way

We replace the constructor with a more straightforward assignment:

Collapse | Copy Code


public MethodGroupConversion()
{
StringOperation = AddSpaces;
}

OK, that seems simple enough.

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:

Collapse | Copy Code


public void Example1()
{
// old:
Dictionary<string, int> explicitDict = new Dictionary<string, int>();

// 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.

Object and Collection Initializers

Read more.

The Old Way

Previously, to initialize property values from outside of the class, we would have to write either use a
constructor:

Collapse | Copy Code


public Record(int id, string name, int? parentID)
{
ID = id;
Name = name;
ParentID = parentID;
}
...
new Record(1, "Marc", null);

or initialize the properties separately:

Collapse | Copy Code


Record rec=new Record();
rec.ID = 1;
rec.Name = "Marc";
rec.ParentID = null;

The New Way

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:

Collapse | Copy Code


Record r = new Record() {ID = 1, Name = "Marc", ParentID = 3};

More interestingly is how this feature is used to initialize anonymous types (see below) especially
with LINQ.

Initializing Collections

Similarly, a collection can be initialized inline:

Collapse | Copy Code


List<Record> records = new List<Record>()
{
new Record(1, "Marc", null),
new Record(2, "Ian", 1),
};

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:

Collapse | Copy Code


public class AutoImplement
{
public int Readable { get; private set; }
public int Readable2 { get { return 20; } }

public void Update()


{
// Allowed:
Readable = 10;
// Not allowed:
// Readable2 = 30;
}
}

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:

Collapse | Copy Code


public void Example()
{
List<Record> records = new List<Record>();
{
new Record(1, "Marc", null),
new Record(2, "Ian", 1),
};

var idAndName = from r in records select new { r.ID, r.Name };


}

Here we see how several features come into play at once:

LINQ
Implicit types
Object initialization
Anonymous types

If we run the debugger and inspect "idAndName", we'll see that it has a value:

Collapse | Copy Code


{System.Linq.Enumerable.WhereSelectListIterator<CSharpComparison.Record,
<>f__AnonymousType0<int,string>>}

and (ready for it?) the type:

Collapse | Copy Code


System.Collections.Generic.IEnumerable<<>f__AnonymousType0<int,string>>
{System.Linq.Enumerable.WhereSelectListIterator<CSharpComparison.Record,
<>f__AnonymousType0<int,string>>}

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

Before extension methods, I would probably end up writing something like this:

Collapse | Copy Code


string graphVizObjectName = "\"" + name +"\"";

Not very readable, re-usable, or bug proof (what if name is null?)

With Extension Methods

With extension methods, I can write an extension:

Collapse | Copy Code


public static class StringHelpersExtensions
{
public static string Quote(this String src)
{
return "\"" + src + "\"";
}
}

(OK, that part looks pretty much the same) - but I would use it like this:

Collapse | Copy Code


string graphVizObjectName = name.Quote();

Not only is this more readable, but it's also more reusable, as the behavior is now exposed
everywhere.

Query Expressions

Read more.

Query expressions seems to be a synonymous phrase for LINQ (Language-Integrated Query).


Humorously, the Microsoft website I just referenced has the header "LINQ Query Expressions."
Redundant!
Query expressions are written in a declarative syntax and provide the ability to query an enumerable
or "queriable" object using complex filters, ordering, grouping, and joins, very similar in fact to how
you would work with SQL and relational data.

As I wrote about above with regards to anonymous types, here's a LINQ statement:

Collapse | Copy Code


var idAndName = from r in records select new { r.ID, r.Name };

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!

Left and Right Joins

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.)

In LINQ, I could write:

Collapse | Copy Code


var idAndName = from r in records
where r.Name=="Marc"
select new { r.ID, r.Name };

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:

Collapse | Copy Code


var idAndName2 = records.All(r => r.Name == "Marc");

LINQ and lambda expressions can be combined. For example, here's some code from an article I
recently wrote:

Collapse | Copy Code


var unassoc = from et in dataSet.Tables["EntityType"].AsEnumerable()
where !(dataSet.Tables["RelationshipType"].AsEnumerable().Any(
rt =>
(rt.Field<int>("EntityATypeID") == assocToAllEntity.ID) &&
(rt.Field<int>("EntityBTypeID") == et.Field<int>("ID"))))
select new { Name = et.Field<string>("Name"), ID = et.Field<int>("ID") };

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).

Collapse | Copy Code


public class MyVector<T>
{
private static readonly Func<T, T, T> Add;

// Create and cache adder delegate in the static constructor.


// Will throw a TypeInitializationException if you can't add Ts or if T + T != T
static MyVector()
{
var firstOperand = Expression.Parameter(typeof(T), "x");
var secondOperand = Expression.Parameter(typeof(T), "y");
var body = Expression.Add(firstOperand, secondOperand);
Add = Expression.Lambda<Func<T, T, T>>(body, firstOperand, secondOperand).Compile();
}

public T X { get; set; }


public T Y { get; set; }

public MyVector(T x, T y)
{
X = x;
Y = y;
}

public MyVector<T> AddVector(MyVector<T> v)


{
return new MyVector<T>(Add(X, v.X), Add(Y, v.Y));
}
}

The above example comes from a post on StackOverflow.


C# 4.0 Features
Dynamic Binding

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.

Collapse | Copy Code


public class MyVector<T>
{
public MyVector() {}

public MyVector<T> AddVector(MyVector<T> v)


{
return new MyVector<T>()
{
X = (dynamic)X + v.X,
Y = (dynamic)Y + v.Y,
};
}
}

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).

Named and Optional Arguments

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:

Collapse | Copy Code


public class NamedAndOptionalArgs
{
public void Foo()
{
Bar(a: 1, c: 5);
}

public void Bar(int a, int b=1, int c=2)


{
// do something.
}
}

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.

Optional Arguments, The Old Way

Previously, we would have to write something like this:

Collapse | Copy Code


public void OldWay()
{
BarOld(1);
BarOld(1, 2);
}

public void BarOld(int a)


{
// 5 being the default value.
BarOld(a, 5);
}

public void BarOld(int a, int b)


{
// do something.
}

The syntax available in C# 4.0 is much cleaner.


Generic Covariance and Contravariance

What do these words even mean? From Wikipedia:

covariant: converting from wider to smaller (like double to float)


contravariant: converting from narrower to wider (like float to double)

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.

Also this excellent technical blog on Code Project.

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.

But How Do I Define My Own?

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);

T2 is the covariant return type and T1 is the contravariant method parameter.

A further example is here.

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.

You might also like