C# Language Features, From C# 2.0 to 4.0

Contents

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 Reset operations 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 generic IEnumerable<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.Value automatically 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 a private 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 this useful 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 EventArgs argument. 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.

License

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

from:http://www.codeproject.com/Articles/327916/C-Language-Features-From-C-2-0-to-4-0#WithoutGenerics3

发表评论

电子邮件地址不会被公开。 必填项已用*标注