C# 3.0 includes a few syntactical additions to the language. For the most part, Microsoft added these language additions to support Language Integrated Query (LINQ). These features include (but are not limited to) lambda expressions, extensions methods, anonymous types, implicitly typed local variables, automatic properties, and object initializers.
Extension methods have an innate ability to pollute your namespace with currently no way to scope them.
Most of the syntax additions fulfill very specific needs and should not reduce the importance of established coding and design methodologies and guidelines. When in doubt, prefer your established guidelines over the new syntax.
Microsoft’s Anson Horton has a great article on LINQ’s impact on the design of C# 3.0, The Evolution Of LINQ And Its Impact On The Design Of C# that goes into detail of these language features from a different perspective.
Lambda Expressions
You can think of lambda expressions as an evolution of C# 2.0’s anonymous methods and it is an attempt at bringing functional programming-a paradigm that treats computation as the evaluation of mathematical functions and avoids state and mutable data-to C#.
Lambda expressions are based upon lambda calculus. See the References sidebar for more information.
PREFER methods over lambda expressions when the same code is used repeatedly.
PREFER using lambda expressions where anonymous delegates would have been appropriate in C# 2.0.
Extension Methods
Arguably one of the most controversial additions to C#, extension methods allow designers to “inject” static methods into any other class. Essentially extension methods are syntactic sugar for creating a static method that operates on the instance of a particular type. Previous to C# 3.0, you might write a general utility method like this:
public static bool IsStringPlural(String text,
CultureInfo cultureInfo)
{/* ... */}
…and may be called as such:
String text = "languages";
Boolean isPlural =
StringExtensions.IsStringPlural(text,
new CultureInfo("en"));
…and operates on a String object (ideally telling you that that word/phrase is plural for that given context). Extension methods allow you to associate a similar method to a class in a way that allows it to be called as if it were a member of that class. Creating an IsPlural extension method for the String class doesn’t change much from the original syntax of the IsStringPlural method declaration-it just adds a this keyword in the parameter list:
public static bool IsPlural(this String text,
CultureInfo cultureInfo)
{/* ... */}
…and may be called as such:
String text = "languages";
Boolean isPlural = text.IsPlural(
new CultureInfo("en"));
This method call syntax is arguably easier to read when used, but it reduces discoverability-there’s no way to tell from the line of code calling IsPlural that it really isn’t a member of String or what class the method really is declared in.
Extension methods compile to ordinary static methods, and can be used as such:
String text = "languages";
Boolean isPlural = StringExtensions.IsPlural(text,
new CultureInfo("en"));
Extension methods are intended to be used in association with lambda expressions to provide vastly more readable and terser query expressions.
The main drawback of extension methods is resolution. Essentially all extension methods are global; each method with the same name and same argument count cannot currently be differentiated. Extension methods have an innate ability to pollute your namespace with currently no way to scope. This means, by simply adding a using statement to your file you can introduce compile errors.
DO use extension methods sparingly.
DO put extension methods in their own static class.
CONSIDER grouping extension methods that extend a particular class into a single static class and name that class “ClassNameExtensions”. If you do run into a name collision and are forced to use the static method call syntax, you don’t want to end up with reduced readability.
DO keep extension method classes in their own namespace to mitigate potential name collisions (if you run into a name collision you’re forced back to using a static method call).
Anonymous Types
Anonymous types allow you instantiate a class without having to declare it. This, of course, requires that you use the var keyword because you don’t have a name for your class. For example:
var person = new { Name = "Peter", Age=4};
As you might have inferred by the syntax, there’s no way to define a method on an anonymous type. This really restricts its usefulness in real-world scenarios. Since you’re not really declaring a type you can’t add attributes to the type; so, you can’t make it serializable.
Another feature of anonymous types is that they are immutable. This essentially means when you instantiate an anonymous type you’re not declaring fields, but properties; and those properties only have a get, not a set.
Anonymous types are really only useful for very short-lived data.
AVOID anonymous types for long-lived data.
Implicitly Typed Local Variables
The other most controversial addition in C# has to be implicitly typed local variables, aka var. Naming aside (having the name var greatly reduces the possibility that it will collide with a type in the wild), var was introduced largely because the type returned by a LINQ query could contain an anonymous type and therefore may not have a human-readable name.
Although still strongly-typed, use of implicitly typed local variables defers choice to the compiler and reduces your ability to force the compiler to find bugs at compile time.
The type resulting from a LINQ statement can also be very complex. When not dealing with anonymous types, the designer may infer the resulting type and manually type it in for the declaration; but database queries are often very mutable, requiring the designer to re-infer the type whenever the query changes.
Apart from their uses with LINQ statements, var tends to make source code less readable and impossibly searchable. If I declare a class MyClass and use var whenever I instantiate this class, for example, I have no way to perform a textual search for MyClass to find out where it’s used unless the right-hand-side of the expression being assigned to the var variable uses the class name.
Most expressions in C# result in an expected type; but some do not. Binary operations and integer types are a good example. All integer binary operations do not support operations where one side is signed and the other side is unsigned. The following generates an error:
uint unsignedNumber = 42;
int signedNumber = 10;
int result = unsignedNumber * signedNumber;
The error is a little misleading in the context: “Cannot implicitly convert type 'long' to 'int'” and it mentions long because there’s an implicit conversion from uint to long. What the compiler has done is this:
int result = (long)unsignedNumber *
…which is one way of performing a binary operation on a signed and unsigned integer; but since result is int it can’t implicitly convert long to int, and generates a warning.
Put var in place of int for result and the error goes away:
var result = unsignedNumber * signedNumber;
But, result is neither uint nor int, it’s long. Plus, it’s long, and not ulong.
Another, less subtle example has to do with method return types. If var is used with results of a method, the code that uses that variable is now coupled to the implementation of that method; if you change the return type of that method, you change the effects of the code that uses that variable. For example:
private int GetWeight ( )
{
return 17;
}
private decimal GetQuantity ( )
{
return 11;
}
String Method()
{
var quantity = GetQuantity ();
decimal result = GetWeight() *
quantity / 4;
Trace.WriteLine("result:" + result);
return "result: " + result;
}
…the result is:
result:46.75
If you modify GetQuantity to return an int instead:
static int GetQuantity ( )
{
return 11;
}
…you’ll see this result:
total:46
The change is very subtle indeed. Textually, the contents of Method() has not changed; but its code has different effects depending on the return type of GetQuantity.
Although still strongly-typed, use of implicitly typed local variables defers choice to the compiler and reduces your ability to force the compiler to find bugs at compile time.
AVOID var with intrinsic types.
DO use var with LINQ statements whose result is not an intrinsic type.
PREFER explicitly-typed local variables.
Object Initializers
Object initializers are something that should have been in C# from day one, to put it on par with many other high-level languages. In comparison to C/C++, this gives C# aggregate initialization abilities. This ability is probably the most generally useful abilities added to support LINQ. Microsoft added object initializers because LINQ statements are single statements-there’s no way to initialize an object with the results of a query unless it can be done within one statement.
PREFER Object initializers to one-property-per-statement object/element initialization.
Automatic Properties
Automatic properties are syntactic sugar for the tedious task of writing properties that wrap a single field for the backing store. In C# 2.0 you could implement a property like this:
private int age;
public int Age
{
get
{
return age;
}
set
{
age = value;
}
}
Not too much work; but with many properties it can get tedious fast. In C# 3.0 you can create properties without having to declare a field for the backing store:
public int Age
{
get ;
set ;
}
The field used for the backing store is automatically generated by the compiler but you don’t have access to it. So, all your accesses to this property must be through the property getter/setter.
PREFER automatic properties over public fields.
CONSIDER not using automatic properties if performance is an issue.
Microsoft created much of the new C# 3.0 syntax for specific reasons but developers can use it anywhere applicable. While these guidelines offer methods to avoid some of the pitfalls you can encounter with general usage of the new C# 3.0 syntax, they should not be considered cast in stone. There are rare circumstances where you may have clear reason to violate these guidelines. These guidelines should help you better understand the new C# 3.0 syntax and write better C# code using the new syntax.