devxlogo

C# 3.0 Syntax Additions—Design Guidelines

C# 3.0 Syntax Additions—Design Guidelines

# 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, extension methods, anonymous types, implicitly typed local variables, automatic properties, and object initializers.

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 wrote a great article “The Evolution Of LINQ And Its Impact On The Design Of C#” that discusses LINQ’s impact on the design of C# 3.0 and covers the new language features from a different perspective.

Lambda Expressions
You can think of lambda expressions as an evolution of C# 2.0’s anonymous methods. Lambda methods are 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.

Recommendations:

  • Prefer methods over lambda expressions when the same code is used repeatedly.
  • Prefer 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. Before C# 3.0, you might write a general utility method like this:

   public static bool IsStringPlural(String text,      CultureInfo cultureInfo)   {/* ... */}

?and call it like this:

   String text = "languages";   Boolean isPlural =       StringExtensions.IsStringPlural(        text, new CultureInfo("en"));
Extension methods have an innate ability to pollute your namespace, and currently there’s no way to scope them.

The IsStringPlural method 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 so you can call it as if it were a member of that class. For example, 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)   {/* ... */}

You’d call this extension method as follows:

   String text = "languages";   Boolean isPlural = text.IsPlural(      new CultureInfo("en"));

While this method call syntax is arguably easier to read, it also 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 terse and vastly more readable query expressions.

The main drawback of extension methods is resolution. Essentially all extension methods are global; methods with the same name and same argument count cannot currently be differentiated. Therefore, extension methods have an innate ability to pollute your namespace; and there’s currently no way to scope them. In other words, by simply adding a using statement to your file you can introduce compile errors.

Recommendations:

  • Use extension methods sparingly.
  • 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 “Extensions.” 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.
  • 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).
Editor’s Note: This article was first published in the January/February 2008 issue of CoDe Magazine, and is reprinted here by permission.

Anonymous Types
Anonymous types let 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, which truly restricts its usefulness in real-world scenarios. Because you’re not really declaring a type, you can’t add attributes to the type; so, you can’t make it serializable, either.

Anonymous types are also immutable. This essentially means that when you instantiate an anonymous type you’re not declaring fields, but read-only properties; those properties have only a get, not a set.

In sum, anonymous types are really only useful for very short-lived data.

Recommendations:

  • Avoid anonymous types for long-lived data.

Implicitly Typed Local Variables
Another controversial addition in C# is 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.

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 both less readable and unsearchable. For example, if you declare a class (MyClass) and use var whenever you instantiate this class, you have no way to perform a code 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. Integer binary operations do not support operations where one side is signed and the other side is unsigned. The following code generates an error:

   uint unsignedNumber = 42;   int signedNumber = 10;   int result = unsignedNumber * signedNumber;

The error message that results from running the preceding code is a little misleading: “Cannot implicitly convert type ‘long’ to ‘int’.” The error mentions the long type because the compiler performs an implicit conversion from uint to long. This is what the compiler has done:

   int result = (long)unsignedNumber * signedNumber

That’s one way of performing a binary operation on a signed and unsigned integer; but since result must be an int, the compiler can’t implicitly convert the long to an int, so it generates a warning.

If you use var in place of int for the result, the error goes away:

   var result = unsignedNumber * signedNumber;

That works, but the result is neither a uint nor an int?it’s a long. And it’s not a ulong, either.

Another, less subtle example has to do with method return types. If you use var with results of a method, the code that uses that variable is now coupled to the implementation of that method. If you later change the method’s return type, you also change the effect 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;   }

When you run the preceding code, the result is:

   result:46.75

Now, if you modify GetQuantity to return an int instead:

   static int GetQuantity ( )   {      return 11;   }

?you’ll see this result:

   total:46
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 change is subtle. Although the body of the GetQuantity method has not changed; its ouput changes 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.

Recommendations:

  • Avoid using var with intrinsic types.
  • Use var with LINQ statements where the 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 of the capabilities 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.

Recommendations:

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

That’s not too much work; but when you need to implement many properties it can get tedious in a hurry. In C# 3.0 you can create properties without having to declare a field for the backing store:

      public int Age      {         get ;         set ;      }

The compiler automatically generates the field used for the backing store, but you don’t have access to it; all access to this property must go through the property getter/setter.

Recommendations:

  • Prefer automatic properties over public fields.
  • Consider avoiding automatic properties when performance is an issue.

Although Microsoft created much of the new C# 3.0 syntax for specific reasons, developers can use the new capabilities anywhere they’re applicable. These guidelines offer advice that can help you avoid some of the pitfalls you may encounter when using the new C# 3.0 syntax, but they should not be considered as cast in stone. In rare circumstances you may have clear reasons to violate these guidelines, but typically, they should help you better understand the new C# 3.0 syntax and write better C# code.

devxblackblue

About Our Editorial Process

At DevX, we’re dedicated to tech entrepreneurship. Our team closely follows industry shifts, new products, AI breakthroughs, technology trends, and funding announcements. Articles undergo thorough editing to ensure accuracy and clarity, reflecting DevX’s style and supporting entrepreneurs in the tech sphere.

See our full editorial policy.

About Our Journalist