C# Corner

Exploring C# 3.0

The latest iteration of C# introduces a host of new language features, most of which were created to enable functionality that you see in the .NET Framework 3.5's LINQ.

Technology Toolbox: C#

C # 3.0 is part of the new version of the .NET Framework (Fx 3.5) released in November, along with Visual Studio 2008. Fx 3.5 adds a huge array of enhancements to the foundation of Fx 2.0, and 3.0 (see Figure 1) and includes service pack level changes for these previous versions. Fx 3.5 also introduces new libraries for LINQ. Many of the compiler changes in C# 3.0 were driven by Language Integrated Query (LINQ), but the changes to C# 3.0 are important in their own right because they can enhance your code tremendously.

In addition to new language features, Fx 3.5 provides enhancements to the base class libraries (primarily delivered in System.Core.dll) and Fx 3.0 improvements such as strategies that help

Windows Presentation Foundation (WPF) and Windows Communication Foundation (WCF) work together. Additions include System.Collections.Generic.HashSet for high performance set operations including subsets, intersections, and unions. The new System.AddIn namespace lets you include plug-ins in your own applications with abstraction layers to manage application evolution, while supporting existing add-ins. Separate application domains protect your application and allow sandboxing.

The Visual Studio 2008 IDE now sorts "using" statements. Using statements tend to proliferate, so my favorite IDE feature is the ability to right click on the using block and remove all unused using statements. This builds on VS 2005's feature that lets you click on the autocorrect button under an invalid class name to add the using statement. VS 2008 also includes new categorized and resizable dialogs for adding projects and items. And don't overlook Ctl-Tab to shift between windows in Visual Studio with a cool new dialog box. If you select a property snippet, you might be surprised to find a new shorthand version of properties:

public string LastName { get; set; }

The compiler creates normal property get and set accessors and a backing field. You don't care about the name of the backing field because you access it only through the get and set accessors, never directly. Read-only properties aren't allowed with this shortcut syntax because you could never set the value. To simulate read-only behavior, provide a private set:

public int Id { get; private set; }

Inside Partial Classes
Fx introduced partial classes, which allow you to separate a class into multiple files. Typically, you do this to manage responsibilities more easily, splitting the primary responsibility between programmers and code generation. Unfortunately, the only mechanism to communicate between code in different partial class files in Fx 2.0 is through events. That's a clunky and imperfect approach, so Fx 3.5 adds partial methods. One portion of the class declares partial methods that other partials may choose to implement (see Listing 1). You use the method by supplying an implementation in an adjacent partial class file:

partial void DoCustomValidation(
   ref bool refValidation)
{
   // Do custom validation here
   refValidation =  true;
}

If the partial method is implemented, all the compiler needs to do is remove the partial method declaration from the IL. The code in the implemented partial method fulfills the need for a DoCustomValidation method within the Validate method. If there isn't a partial class, or it doesn't contain an implementation of the partial method, the compiler removes the partial method declaration along with all calls to the partial method. For example, the call to DoCustomValidation in the Validate method is removed from the IL. If the partial method is implemented more than once, the compiler throws an error.

Partial methods must be private and void. They cannot return values; this helps avoid confusion if the calling code is removed. Partial methods can take parameters and these parameters can be ref or out parameters. You need to redesign public methods to use internal partial methods.

Previous versions of Fx did not treat interface variables as objects. For example, you had to use some variation of this odd-looking code to retrieve the type of a specific command:

IDbCommand command;
// . . .
Type type = ((object)command).GetType();

Fx 3.5 recognizes that everything in .NET is an object and allows this syntax:

type = command.GetType();

Work with Extension Methods
Extension methods rank as my favorite C# 3.0 feature. Extension methods add logical methods to any class, including framework classes. You don't modify the class, but the syntax looks almost as if you did. Extension methods are static methods in a static class with the "this" modifier on the first parameter (see Listing 2). Once you reference this code and make the namespace available through using statements, you can call extension methods like methods of the class:

string testValue = 
   element.Attribute(
   "TestAttribute").GetString();

The compiler rearranges this to call the static method. You can also call the static method and produce identical results:

XmlExtensionMethods.GetString(
   element.Attribute("TestAttribute"));

Extension methods appear in IntelliSense for the class with a special icon (see Figure 2). IntelliSense support makes extension methods especially valuable when you need to guide yourself or other programmers to the correct way to accomplish a given task. A good role for extension methods is encouraging programmers to do an insensitive string comparison correctly. The EqualsIgnoreCase method guides programmers to the right way to perform this comparison.

Inferred typing was controversial during the beta program. You can assign a value within a declaration and the compiler flags an error if you do not pass the correct type:

int y = 2;

C# 3.0 takes this a step further and lets you declare local variables with the var keyword:

var y1 = 2;

This still declares an int (System.Int32). It is not an object, nor anything vaguely resembling a variant data type.

The compiler determines the type with the same rules it uses for type checking. The variable is strongly typed with the type determined by the assigned value. Replacing longer type names saves more keystrokes than replacing native types:

SqlConnectionStringBuilder builder 1=
      new SqlConnectionStringBuilder();
var builder2 = 
      new SqlConnectionStringBuilder();

Another great opportunity to leverage inferred typing occurs when casting objects:

public override void DoSomething(
      BizObjectBase bizObject)
{
      InvoiceLineItem item = 
         bizObject as InvoiceLineItem;
         var item1 = bizObject 
         as InvoiceLineItem;
}

The compiler can infer typing from method return values, but you'll want to avoid this when the method name doesn't clearly indicate the type:

MyTypeName val = MyMethod();
var val1 = MyMethod();

Note that you can't use inferred typing where you have an identifier named var in scope.

If you hate inferred typing, you'll have to remove it during manual code review because you can't turn it off. It's compiler magic, so you also can't catch it with FxCop. Try it out, you might be surprised by how much you like being able to reduce your concern about variable typing, while remaining strictly typed.

Traditionally, you followed up creating a new item with a parameterless constructor by setting individual fields:

var customer = new Customer();
customer.FirstName = "Kathleen";
customer.LastName = "Dollard";
customer.Id = 42;

You can now simplify this syntax to initialize within the same statement as the declaration:

var customer = new Customer() 
   { FirstName = "Kathleen", 
   LastName = "Dollard", 
   Id = 42 };

You can only initialize properties and fields that are currently visible in scope.

This is more than syntactic sugar. The two approaches return different results if the variable survives an initialization failure. One way to survive such an occurrence is to wrap the variable creation in a try … catch with the declaration before the block:

Customer customer1 = null;
try
{
   customer1 = new Customer()
   {
      FirstName = "Kathleen",
      LastName = "Dollard",
      Id = 42
   };
}
catch { }

If an error occurs in one of the set accessors, the C# 3.0 style in-line object intializer leaves the customer variable set to null. The earlier, multi-statement syntax leaves the customer variable instance with partially initialized values.

In addition to object initializers, you can also initialize any class that implements IEnumerable, including arrays and lists:

list = new List<Customer>
   {
      new Customer{
         FirstName="Kathleen", 
         LastName="Dollard", Id=42},
      new Customer{
         FirstName="Joe", 
         LastName="Smith", Id=3}, 
      new Customer{
         FirstName="Greg", 
         LastName="Jones", Id=6}
};

As with object initializers, null is assigned to the variable if any part of the initialization fails.

Lambda Expressions Provide Cleaner Syntax
Lambda expressions are a cleaner syntax for anonymous delegates, which are sometimes called anonymous methods. For example, the System.Collection.Generic.List class has a method to find items in a list based on a generic predicate delegate. A predicate is code that takes an item and returns a Boolean. You can use the FindAll method with an anonymous delegate:

public List<Customer> 
   GetCustomerInState_B(string state)
{
   var customers = Customer.GetCustomers();
   return customers.FindAll(
      delegate(Customer customer)
      {
         return customer.State == state;
      }
   );
}

Lambda expressions simplify this syntax considerably:

public List<Customer> 
   GetCustomerInState_C(string state)
{
   var customers = Customer.GetCustomers();
   return customers.FindAll(
      customer => 
      customer.State == state);
}

Note that you do not need to specify the type of the lambda expression parameter. To pass several parameters, surround the parameter list with parentheses. You also don't use a return statement; expression lambdas must be a single expression and must return a value.

In addition to putting the code in line where you can easily see the intent, both of these syntaxes provide what's called a closure. This means the anonymous delegate or lambda expression captures the local variable and passes it outside its original context--in this case passing it to a CLR method.

C# 3.0 provides statement lambdas as well as expression lambdas. Statement lambdas differ from expression lambdas because they can be multiple lines long and do not need to return a value:

var customers = 
   Customer.GetCustomers();
customers.ForEach(customer => 
{ 
   DoSomething1(); 
   DoSomething2(); 
});

Lambda expressions are more than tidy syntax. They can be executed as IL, which happens in the examples I've given, or stored as data in the form of an expression tree. You create an expression tree by assigning the lambda expression to a variable of type Expression<T>, where T is one of the Func delegates declaring the signature. Lambda expressions can be quite complex with 46 different expression node types and complex nesting. Statement lambdas cannot be contained in expression trees.

You can alter expression trees on the fly because they aren't emitted as code until an operation needs it. Expression trees support different providers that can create different code from the same expression tree. LINQ uses this capability to output lambda expressions as TSQL, or other external query languages.

LINQ Offers Query Operations
No discussion of C# 3.0 would be complete without covering LINQ. LINQ extends the C# language to include query operators. In fact, LINQ requirements drove most of the language enhancements for both C# and VB, and it represents a fundamental shift in how you deal with anything that supports IEnumerable. LINQ syntax can be simple:

public List<Customer>
   GetCustomerInState_Linq(string state)
{
   var customers = 
      Customer.GetCustomers();
   var query = 
      from customer in customers 
         where customer.State == state 
         select customer;
   return query.ToList<Customer>();
}

The syntax reads well and doesn't depend on specific methods of the underlying list. LINQ also allows projections, which is a fancy way of saying your output can look different than your input. For example, you might want only the first and last names of people within a current state:

var query = 
   from customer in customers
   where customer.State == state
   select new {
      customer.FirstName, 
      customer.LastName };

This introduces another language feature in C# 3.0. The select statement returns an anonymous type that contains two string fields: FirstName and LastName. Property names and types are inferred from the select projection. Anonymous types are useful in local code, but they are rarely useful beyond local scope because you can only hand them around when cast to an object and it's tricky to recast them to a specific type.

Regardless of what you might've heard, C# 3.0 does have LINQ to XML. The features it lacks are XML literals, XML namespace support, and XML IntelliSense. While not supporting these features, C# provides better syntax for creating and querying XML than past versions of .NET. Creating an XML document from List or anything else that supports IEnumerable is easy:

var customers = Customer.GetCustomers();
var xmlRoot = new XElement("customers",
   from customer in customers
   select new XElement("customer", 
      new XAttribute(
         "LastName", customer.LastName), 
      new XAttribute("State", customer.State)));
Console.WriteLine(xmlRoot);

Running that code produces this output:

<customers>
   <customer LastName=
      "Dollard" State="CO" />
   <customer LastName=
      "Smith" State="AZ" />
   <customer LastName=
      "Jones" State="AZ" />
</customers>

Querying XML is also straightforward:

static public List<Customer>
   GetCustomersInState_FromXml(string state)
{
   var root = MakeCustomerXml();
   var query = from customer in 
      root.Elements("customer")
      where (string)customer.Attribute(
         "State") == state
      select new Customer()
      
      {
      FirstName = customer.Attribute(
         "FirstName").GetString(),
      LastName = customer.Attribute(
         "LastName").GetString(),
      State = state,
         Id = customer.Attribute(
         "Id").GetInt32()
      };
   
   return query.ToList<Customer>();

}

This example begins to open up the power of LINQ and shows how coherently new language features work together. It uses inferred typing, extension methods (GetString and GetInt32), Lambda expressions and expression trees (within LINQ), automatic properties (within the Customer class), and object initializers.

C# 3.0 is a great release because deep language features with broad applicability were required to build LINQ. Each of these features has the capacity to improve your code dramatically in its own right.

comments powered by Disqus

Featured

  • See Prompts Microsoft Engineers Use for Bleeding-Edge Multimodal RAG AI Research

    Vision-centric queries show how front-line experts are prompting LLMs these days.

  • AI Explains Expressions in Update to Java on VS Code

    "The Spring Tools now show code lenses above these expressions that allow you to quickly let GitHub Copilot explain those statements for you."

  • Microsoft Eases Integration with Semantic Kernel AI SDK

    The basic idea is to provide unified API abstractions, especially for idiomatic C# code, to help platform developers and others work with any provider with standard implementations for caching, telemetry, tool calling and other common tasks.

  • Final .NET 9 Preview Ships with Go-Live License

    Visual Studio developers can now download the SDK for .NET 9 Release Candidate 2 with a go-live license, meaning devs get Microsoft support for production applications even before the framework reaches general availability next month.

Subscribe on YouTube