Chapter 3
Defining Extension and Partial Methods

In This Chapter

Image Extension Methods and Rules of the Road

Image Defining Extension Methods

Image How Extension Methods Support LINQ

Image Implementing a “Talking” String Extension Method

Image Defining Partial Methods

“Do you see a man skilled in his work? He will serve before kings; he will not serve before obscure men.”

—Proverbs 22:29

Conceptually, think of extension methods as an implementation of the Decorator Structural pattern. If you check http://www.dofactory.com, you will see that the actual decorator is intended to add behavior dynamically using inheritance and composition. The ToolTip control is a closer technical match to the decorator, but extension methods are logically close enough.

Extension methods are designed to support adding behaviors that look like they belong to an existing class when you can’t add behaviors. For example, you can’t inherit and add behaviors for sealed classes, and you can’t inherit and add behaviors to intrinsic types like int and string.

This chapter looks at extension methods, including how to implement them and how they support Language INtegrated Query (LINQ), and partial methods, including where and how they are used.

Extension Methods and Rules of the Road

Historically, extension methods have been used to add new behaviors when possible and wrapper classes have been used when inheritance couldn’t be used. Extension methods clean up the clumsy syntax of wrapper methods and permit you to extend sealed classes and intrinsic types.

In addition, extension methods can help you avoid deep inheritance trees. Deep inheritance trees get hard to manage, and the extension method provides another way to add a feature without full-blown inheritance.

Listing 3.1 demonstrates how you can add an extension method called Dump to an object state dumper for debugging purposes. Dump uses Reflection to display the internal state of all the properties of a class.

Listing 3.1 An Extension Method That Displays the State of Any Object, Useful for Debugging Purposes

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostincs;

namespace ExtensionMethodDump
{
  class Program
  {
    static void Main(string[] args)
    {
      var song = new {Artist=“Jussi Bjorling”, Song=“Aida”};
      song.Dump();
      Console.ReadLine();
    }
  }

  public static class Dumper
  {
    public static void Dump(this Object o)
    {
      PropertyInfo[] properties = o.GetType().GetProperties();
      foreach(PropertyInfo p in properties)
      {
        try
        {
          Debug.WriteLine(string.Format(“Name: {0}, Value: {1}”, p.Name,
            p.GetValue(o, null)));
        }
        catch
        {
          Debug.WriteLine(string.Format(“Name: {0}, Value: {1}”, p.Name,
            “unk.”));
        }
      }
    }
  }
}

In Listing 3.1, an anonymous type is defined containing an Artist and Song, Jussi Björling interpretation of “Aida.” Because Dump operates upon type object and every type inherits from object, the anonymous type has access to Dump. Calling Dump on the anonymous type referred to by song displays the artist and song to the Debug window.

As with any feature, some rules of the road define when and how a feature can be used. The following list describes how extension methods can be used and provides useful information about them:

Image Use extension methods to extend sealed types without inheritance.

Image Use extension methods to keep deep inheritance hierarchies from getting out of hand.

Image Extension methods are static methods in static classes (see Listing 3.1).

Image The first argument of an extension method must use the this modifier preceding the argument type; this refers to the type being extended.

Image Extension properties, events, and operators are not supported (but might be in the future).

Image Extension methods are less discoverable and more limited than instance methods.

Image Extension methods are invoked using instance syntax.

Image Extension methods have a lower precedence than regular methods; therefore, if a class has a similarly named method, the instance method will be called.

Image Extension methods are supported by IntelliSense.

Image Generic extension methods are supported, which is, in fact, how many of the LINQ keywords are implemented in the System.Linq namespace.

Image Extension methods can be invoked on literals; assuming you had an extension method public static void SayIt(this string what), you could invoke SayIt with “Hello World”.SayIt().

Image Extension methods can be used on sealed classes and intrinsic types like int because extension methods automatically support boxing and unboxing; that is, when value types are used like objects, the .NET Framework wraps a class around the value type.

Image Extension methods are not really members, so you can only access public members of the object being extended.

Image An extension method implicitly uses the ExtensionAttribute; the ExtensionAttribute is used explicitly in VB .NET.

The term you will hear associated with extension methods is duct typing. Pun intended.

Defining Extension Methods

Extension methods follow a consistent pattern. Define a public static class. In that class, define a public static method. The first argument of the method must use the this modifier. The first argument, modified by this, represents the class of the object being extended. After the first argument, add additional arguments. Static methods can be overloaded by parameters and can be generic methods. This section explores extension methods, overloaded extension methods, and generic extension methods.

Implementing Extension Methods

Listing 3.1 demonstrated a basic extension method, and the previous section covered the basic guidelines for extension methods. It’s now time to look at extension methods that have return types and extension methods with multiple parameters.

Listing 3.2 contains a variation on the Dump extension method to include a return type. In Listing 3.2, a StringBuilder is used to compile the properties and return them as a string. The StringBuilder is used here because strings are immutable in .NET, so the StringBuilder is the way to go when building text output. Listing 3.3 demonstrates how to use additional parameters in extension methods. In Listing 3.3, the second parameter to Dump is a TextWriter; this approach permits passing in Console.Out as the TextWriter, sending the content to the console.

Listing 3.2 Defining an Extension Method with a Return Type

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;

namespace ExtensionWithReturn
{
  class Program
  {
    static void Main(string[] args)
    {
      var songs = new{
          Artist=“Green Day”, Song=“Wake Me Up When September Ends”};

      Console.WriteLine(songs.Dump());
      Console.ReadLine();
    }
  }

  public static class Dumper
  {
    public static string Dump(this Object o)
    {
      PropertyInfo[] properties = o.GetType().GetProperties();
      StringBuilder builder = new StringBuilder();
      foreach(PropertyInfo p in properties)
      {
        try
        {
          builder.AppendFormat(string.Format(“Name: {0}, Value: {1}”, p.Name,
            p.GetValue(o, null)));
        }
        catch
        {
          builder.AppendFormat(string.Format(“Name: {0}, Value: {1}”, p.Name,
            “unk.”));
        }
        builder.AppendLine();
      }
      return builder.ToString();
    }
  }
}

Listing 3.3 Adding Additional Parameters to the Extension Method; As Demonstrated Here, You Can Pass in Console.Out as the TextWriter Parameter

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;
using System.IO;

namespace ExtensionWithAdditionalParameters
{
  class Program
  {
    static void Main(string[] args)
    {
      var song = new{
          Artist=“Avril Lavigne”, Song=“My Happy Ending”
        };

      song.Dump(Console.Out);
      Console.ReadLine();
    }
  }

  public static class Dumper
  {
    public static void Dump(this Object o, TextWriter writer)
    {
      PropertyInfo[] properties = o.GetType().GetProperties();
      foreach(PropertyInfo p in properties)
      {
        try
        {
          writer.WriteLine(string.Format(“Name: {0}, Value: {1}”, p.Name,
            p.GetValue(o, null)));
        }
        catch
        {
        writer.WriteLine(string.Format(“Name: {0}, Value: {1}”, p.Name,
          “unk.”));
        }
      }
    }
  }
}

Overloading Extension Methods

You also have the option of overloading extension methods. Listing 3.4 has two extension methods named Dump. The first accepts the extended type object and dumps the single-object properties, and the second Dump extends an IList, iterating over each item in a collection and calling Dump on that item.

Listing 3.4 Dump Is Overloaded to Extend Object and IList; the Framework and Compiler Determine Which Version to Call

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Diagnostics;

namespace OverloadedExtensionDump
{
  class Program
  {
    static void Main(string[] args)
    {
      var songs = new[]
        {
          new {Artist=“Jussi Bjorling”, Song=“Aida”},
          new {Artist=“Sheryl Crow”, Song=“Steve McQueen”}
        };

      songs.Dump();
      Console.ReadLine();
    }
  }

  public static class Dumper
  {
    public static void Dump(this Object o)
    {
      PropertyInfo[] properties = o.GetType().GetProperties();
      foreach(PropertyInfo p in properties)
      {
        try
        {
          Debug.WriteLine(string.Format(“Name: {0}, Value: {1}”, p.Name,
            p.GetValue(o, null)));
        }
        catch
        {
          Debug.WriteLine(string.Format(“Name: {0}, Value: {1}”, p.Name,
            “unk.”));
        }
      }
    }

    public static void Dump(this IList list)
    {
      foreach(object o in list)
        o.Dump();
    }

  }
}

Before object-oriented programming and overloading, you had to write code like DumpObject and DumpList. The challenge is that such code becomes cumbersome very quickly. With overloading and an understanding that object is the root type for all types and that many kinds of collections implement IList, you can support just writing Dump in its various forms and let the compiler track which specific version to use.

Defining Generic Extension Methods

Extension methods start to get really powerful when combined with generics. An ever-present thorn in the side of developers is writing all of the plumbing that initializes null but necessary entity objects—open a connection, call the stored procedure, read every record, read every field checking for null, and initialize each object, adding it to the collection. This code is terribly dull after the ten-thousandth time. Listing 3.5 combines a generic extension method for a base class called the EntityClass and a single generic SafeRead<T> method that reads any kind of field and if it is not null, SafeRead returns that value. If the field is null, a default value is provided.

Listing 3.5 solves an ongoing pickle of a problem. In pure object-oriented programming, objects should be self-initializing. However, to make entity objects self-initializing, you would have to carry ADO.NET around with your entities. Using an extension method lets entities look self-initializing, but you can put the plumbing outside of the entity class itself.

Listing 3.5 The Generic Extension Method SafeRead<T> Lets Your Entities Behave Like a Self-Initializing Database Class (or Entities) While Keeping the Plumbing Separate

using System;using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.Data.SqlClient;
using System.Reflection;
using System.Diagnostics;
using System.IO;

namespace GenericExtensionMethods
{
  class Program
  {
    static void Main(string[] args)
    {
      string connectionString = “Data Source=CASPAR;Initial Catalog=
→Northwind;Integrated Security=True”;
      // create the list of orders
      List<Order> orders = new List<Order>();
      // open and protect the connection
      using(SqlConnection connection = new SqlConnection(connectionString))
      {
        // prepare to select all orders
        SqlCommand command = new SqlCommand(“SELECT * FROM ORDERS”, connection);
        command.CommandType = CommandType.Text;
        connection.Open();
        SqlDataReader reader = command.ExecuteReader();


        // read all orders
        while(reader.Read())
        {
          Order order = new Order();
          // the exension method does the legwork but it looks like order
          // is self-initializing
          order.Read(reader);
          orders.Add(order);
        }
      }

      // use the dumper to send everything to the console
      orders.Dump(Console.Out);
      Console.ReadLine();
    }
  }


  public static class ReaderHelper
  {
    public static void Read(this Order order, IDataReader reader)
    {
      order.OrderID = order.SafeRead(reader, “OrderID”, -1);
      order.CustomerID = order.SafeRead(reader, “CustomerID”, ““);
    }

    /// <summary>
    /// One reader checks for all the null possibilities
    /// </summary>
    /// <typeparam name=“T”></typeparam>
    /// <param name=“entity”></param>
    /// <param name=“reader”></param>
    /// <param name=“fieldName”></param>
    /// <param name=“defaultValue”></param>
    /// <returns></returns>
    public static T SafeRead<T>(this EntityClass entity,
      IDataReader reader, string fieldName, T defaultValue)
    {
      try
      {
        object o = reader[fieldName];
        if(o == null || o == System.DBNull.Value)
          return defaultValue;

        return (T)Convert.ChangeType(o, defaultValue.GetType());
      }
      catch
      {
        return defaultValue;
      }
    }
  }

    public static class Dumper
  {
    public static void Dump(this Object o, TextWriter writer)
    {
      PropertyInfo[] properties = o.GetType().GetProperties();
      foreach(PropertyInfo p in properties)
      {
        try
        {
          writer.WriteLine(string.Format(“Name: {0}, Value: {1}”, p.Name,
            p.GetValue(o, null)));
        }
        catch
        {
          writer.WriteLine(string.Format(“Name: {0}, Value: {1}”, p.Name,
            “unk.”));
        }
      }
    }

    public static void Dump<T>(this IList<T> list, TextWriter writer)
    {
      foreach(object o in list)
        o.Dump(writer);
    }
  }

  public class EntityClass{}
  public class Order : EntityClass
  {

    /// <summary>
    /// Initializes a new instance of the Order class.
    /// </summary>
    public Order()
    {
    }

    private int orderID;
    public int OrderID
    {
      get
      {
        return orderID;
      }
      set
      {
        orderID = value;
      }
    }

    private string customerID;
    public string CustomerID
    {
      get
      {
        return customerID;
      }
      set
      {
        customerID = value;
      }
    }
  } }

In Listing 3.5, the Main method sets up a pretty typical database read. The difference is that it uses a couple of extension methods that make entity classes look self-initializing while putting that plumbing in the ReaderHelper extension class.

ReaderHelper has an extension method for Order objects that contains the information about the Orders table (in the Northwind database). Information about the Orders table is only needed when you are actually interacting with the database, so you don’t really need to carry that around with the Order objects. SafeRead<T> is an extension class that manages reading the actual field value while performing all of that painful checking for null.

Finally, the Dumper class is recycled and defines a simple entity based on part of the Northwind.Orders table.

How Extension Methods Support LINQ

As you have seen, LINQ looks a lot like Structured Query Language (SQL). In fact, however, LINQ is fully integrated into the .NET Framework. This is done partly through the System.Linq namespace. The System.Linq namespace is defined in the System.Core.dll assembly and contains some very complicated-looking extension methods (in the Queryable class), such as

public static IQueryable<TSource> Where<TSource>(
  this IQueryable<TSource> source, Expression<Func<TSource, bool>> predicate)

and


public static IQueryable<TResult> Select<TSource, TResult>(
  this IQueryable<TSource> source, Expression<Func<TSource, TResult>> selector)

These methods look complicated—and for those who put off learning generics, probably do seem complicated—but what can be gleaned from the method headers is that these are generic extension methods that extend IQueryable. And, generics are used a lot here. (These method headers are like generics gone wild, but it is pretty clear that LINQ might have evolved to make it much easier to actually use all of these capabilities.)

If you take the Where method and blot out the parameterized arguments, you have the following:

public static IQueryable Where(this IQueryable source, Expression predicate)

This is a little easier to read. Where is an extension method that extends IQueryable, returns IQueryable, and takes an Expression that acts as the determining predicate—that is, what is in the resultset. If you add in all of the parameterized arguments, the code just means it’s flexible enough to work with whatever kinds of data the IQueryable object contains.

In short, this means that LINQ’s underpinnings are based on extension methods (and generics), so that when you write a LINQ statement, the compiler is actually emitting the call to the more complex-looking generic methods. Listing 3.6 contains a simple LINQ query that returns even numbers from an array. Listing 3.7 shows the Microsoft Intermediate Language (MSIL or IL)—which was retrieved using Intermediate Language Disassembler (ILDASM)—emitted that contains the more verbose calls to the extension methods in System.Linq.

Listing 3.6 A Simple-Looking Query That Emits Calls to Extension Methods Like System.Linq.Enumerable.Where in the System.Core.dll Assembly

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QueryWithWhere
{
  class Program
  {
    static void Main(string[] args)
    {
      var numbers = new int[]{1966, 1967, 1968, 1969, 1970};
      var evens = from num in numbers where num % 2 == 0 select num;

      foreach(var result in evens)
        Console.WriteLine(result);

      Console.ReadLine();

    }
  }
}

Listing 3.7 The MSIL from ILDASM That Reflects How the Much Easier (in Comparison) LINQ Query Is to Write Than Direct Calls to These Methods

.method private hidebysig static void  Main(string[] args) cil managed
{
  .entrypoint
  // Code size 121 (0x79)
  .maxstack 4
  .locals init ([0] int32[] numbers,
          [1] class [mscorlib]System.Collections.Generic.IEnumerable‘1<int32> evens,
          [2] int32 result,
          [3] class [mscorlib]System.Collections.Generic.IEnumerator‘1<int32>
→CS$5$0000,
          [4] bool CS$4$0001)
  IL_0000: nop
  IL_0001: ldc.i4.5
  IL_0002: newarr     [mscorlib]System.Int32
  IL_0007: dup
  IL_0008: ldtoken    field valuetype ‘<PrivateImplementationDetails>
→{13096E17-8E04-40AD-AD54-10AC18376A40}’/’__StaticArrayInitTypeSize=20’
→‘<PrivateImplementationDetails>
→{13096E17-8E04-40AD-AD54-10AC18376A40}’::’$$method0x6000001-1’
  IL_000d: call        void [mscorlib]
→System.Runtime.CompilerServices.RuntimeHelpers::InitializeArray(class
→[mscorlib]System.Array,

valuetype [mscorlib]System.RuntimeFieldHandle)
  IL_0012:  stloc.0
  IL_0013:  ldloc.0
  IL_0014:  ldsfld     class [System.Core]System.Func‘2<int32,bool>
QueryWithWhere.Program::’<>9__CachedAnonymousMethodDelegate1’
  IL_0019:  brtrue.s   IL_002e
  IL_001b:  ldnull
  IL_001c:  ldftn      bool QueryWithWhere.Program::’<Main>b__0’(int32)
  IL_0022: newobj      instance void class
→[System.Core]System.Func‘2<int32,bool>::.ctor(object,native int)
  IL_0027:  stsfld     class [System.Core]System.Func‘2<int32,bool>
→QueryWithWhere.Program::’<>9__CachedAnonymousMethodDelegate1’
  IL_002c:  br.s       IL_002e
  IL_002e:  ldsfld     class [System.Core]System.Func‘2<int32,bool>
→QueryWithWhere.Program::’<>9__CachedAnonymousMethodDelegate1’
  IL_0033:  call      class [mscorlib]System.Collections.Generic. IEnumerable‘1<!!0>
[System.Core]System.Linq.Enumerable::Where<int32>(class [mscorlib]System.
Collections.Generic.IEnumerable‘1<!!0>,

class [System.Core]System.Func‘2<!!0,bool>)
  IL_0038:  stloc.1
  IL_0039:  nop
  IL_003a:  ldloc.1
  IL_003b:  callvirt   instance class
→[mscorlib]System.Collections.Generic.IEnumerator‘1<!0> class
→[mscorlib]System.Collections.Generic.IEnumerable‘1<int32>::GetEnumerator()
  IL_0040:  stloc.3
  .try
  {
    IL_0041:  br.s       IL_0051
    IL_0043:  ldloc.3
    IL_0044:  callvirt   instance !0 class
→[mscorlib]System.Collections.Generic.IEnumerator‘1<int32>::get_Current()
    IL_0049:  stloc.2
    IL_004a:  ldloc.2
    IL_004b:  call       void [mscorlib]System.Console::WriteLine(int32)
    IL_0050:  nop
    IL_0051:  ldloc.3
    IL_0052:  callvirt   instance bool
→[mscorlib]System.Collections.IEnumerator::MoveNext()
    IL_0057:  stloc.s    CS$4$0001
    IL_0059:  ldloc.s    CS$4$0001
    IL_005b:  brtrue.s   IL_0043
    IL_005d:  leave.s    IL_0071
 }   // end .try
 finally
 {
    IL_005f:  ldloc.3
    IL_0060:  ldnull
    IL_0061:  ceq
    IL_0063:  stloc.s    CS$4$0001
    IL_0065:  ldloc.s    CS$4$0001
    IL_0067:  brtrue.s   IL_0070
    IL_0069:  ldloc.3
    IL_006a:  callvirt   instance void [mscorlib]System.IDisposable::Dispose()
    IL_006f:  nop
    IL_0070:  endfinally
  }  // end handler
  IL_0071:  nop
  IL_0072:  call       string [mscorlib]System.Console::ReadLine()
  IL_0077:  pop
  IL_0078:  ret
} // end of method Program::Main

The use of the Where method is shown in bold. Preparation of the Func<T, TResult> delegates as arguments to the expression predicate are also discernible. Although it is unlikely that it is or will be necessary to use the long-winded method calls over LINQ, Listing 3.8 is an expanded version of Listing 3.6, which shows what the code might look like without LINQ.

Listing 3.8 The Long Form of Listing 3.6 Using the Verbose and Expanded Version of Some of the Elements, such as the Func Delegate and the Where Extension Method

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace QueryWithWhereLongForm
{
  class Program
  {
    static void Main(string[] args)
    {
      var numbers = new int[]{1966, 1967, 1968, 1969, 1970};
      Func<int, bool> getEvents = delegate(int num)
      {
        return num % 2 == 0;
      };

      IEnumerable<int> evens = numbers.Where<int>(getEvents);

      IEnumerator<int> enumerator = evens.GetEnumerator();
      while(enumerator.MoveNext())
        Console.WriteLine(enumerator.Current);

      Console.ReadLine();

    }
  }
}

You could also use some variations for Listing 3.8. You could have not used anonymous types. You could substitute the anonymous delegate for a Lambda Expression, but you might agree that the LINQ query is the more concise and clear form of the solution (in Listing 3.6).

Implementing a “Talking” String Extension Method

At TechEd 2007, David West and Ivar Jacobson (one of the three amigos of the Rational Unified Process [RUP] fame and the inventor of use cases) said no one reads books. Buy them, yes. Read them, no.

Reading and writing is both informative and enjoyable. Sure, Google and Wikipedia and many more websites can be useful for solving programming problems, but these sites are not always accessible or portable—it’s tough to Google in the tub—so nothing beats a book for portability, tactile sensation, and smell. (New books smell as good as new cars.) That said, reasons to write are to enjoy the learning process, have fun with the code, and sell books to readers that will enjoy them. (All book sales go to my yacht, Chillin’ the Most, fund, and you’ll get invited on a three-hour cruise.) That’s what this section is about—programming is fun.

To complete Listing 3.9, a reference to the Microsoft Speech Object Library 5.0 (see Figure 3.1) is added to a console application. After the COM Interop assembly—which is generated automatically—is added to the solution, you can add a using statement for the SpeechLib. Finally, an extension method Say for strings is added in the MakeItTalk class, and the SpVoiceClass is used to set the voice and rate of speech and send the text to the speakers.

Listing 3.9 Adding a Reference to the SpeechLib (the Microsoft Speech Object Library 5.0 in sapi.dll) and Making Your Strings Talk

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SpeechLib;

namespace TalkingStringClass
{
  class Program
  {
    static void Main(string[] args)
    {
      “Microsoft Surface is Cool!”.Say();
      “Press Enter to Quit”.Say();
      Console.ReadLine();
    }
  }

  public static class MakeItTalk
  {
    public static void Say(this string s)
    {
       SpVoice voice = new SpVoiceClass();
      // requiredattributes - no attributes are required
      // optionalattributes - nonrequired
      ISpeechObjectTokens tokens = voice.GetVoices(““, ““);
      voice.Rate = 2;
      voice.Volume = 100;
      voice.Voice = tokens.Item(0);
      voice.Speak(s, SpeechVoiceSpeakFlags.SVSFDefault);
    }
  }
}

Figure 3.1 Adding references to COM libraries.

Image

You can use this code in VB or code very similar to it in JavaScript for your web clients. Refer to the article, “Talking Web Clients with JavaScript and the Speech API” at developer.com: http://www.developer.com/lang/jscript/article.php/3688966.

Defining Partial Methods

One of the things authors always have to do is figure out how to use something. One of the things we sometimes have to do is figure out why the heck anyone would use that something. Partial methods was one of those what the heck do you do with it moments, but, then, Bill Wagner of Effective C# (Addison-Wesley Professional, 2004) fame told me something during our user group meeting (Greater Lansing Area .NET Users Group, or Glugnet, which I think sounds like a beer-drinking club).

Bill said, “Partial methods are placeholders for generated code.”

“Oh, like SAP user exits. I get it.” That was my reply, and comparing anything with SAP is ghastly, but that’s what they are.

The problem with code-generated code has typically been that if the consumer changed the generated code and regenerated the code, the user’s changes were overwritten. One solution to this problem was to inherit from generated code and write custom changes in the child class. However, partial methods let the producer stub out the method signature with a partial method. If the consumer wants to insert some behavior at that point, the consumer provides an implementation for the partial method. If no implementation is provided, the partial method is stripped out. This is clever and useful for code generators. (The producer and consumer can be the same programmer, of course.)

The following are some basic guidelines for partial methods:

Image Partial methods are declared in partial classes.

Image Partial methods use the partial modifier.

Image Partial methods don’t have a method body at the point of introduction.

Image Partial methods must return void.

Image Partial methods can be static, and can have arguments and argument modifiers, including ref and params (an array of arguments).

Image Partial methods are private, but no literal access modifiers are permitted; that is, you can’t use the private keyword explicitly.

Image Unused partial methods are stripped out by the compiler.

The example in Listing 3.10 uses a partial method.

Listing 3.10 A Partial Method Demo

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.UI.WebControls;
using System.Reflection;
using System.Diagnostics;

namespace PartialMethodDemo
{
  class Program
  {
    static void Main(string[] args)
    {
      CustomerList customers = new CustomerList
      {
        new Customer{CustomerID=1, Name=“Levi, Ray and Shoup”},
        new Customer{CustomerID=2, Name=“General  Dynamics Land Systems”},
        new Customer{CustomerID=3, Name=“Microsoft”}
      };

      customers.Dump();
      Console.ReadLine();
    }
  }

  public partial class CustomerList
  {
    private string propertyName;
    private SortDirection direction;

    partial void SpecialSort(string propertyName, SortDirection direction)
    {
      this.propertyName = propertyName;
      this.direction = direction;
      Sort(Comparer);
    }

    /// <summary>
    /// Using an integer to change the direction of the sort was a suggestion made by
    /// Chris Chartran, my good friend from Canada
    /// </summary>
    /// <param name=“x”></param>
    /// <param name=“y”></param>
    /// <returns></returns>
    private int Comparer(Customer x, Customer y)
    {
      try
      {
        PropertyInfo lhs = x.GetType().GetProperty(propertyName);
        PropertyInfo rhs = y.GetType().GetProperty(propertyName);
        int directionChanger = direction == SortDirection.Ascending ? 1 : -1;

        object o1 = lhs.GetValue(x, null);
        object o2 = rhs.GetValue(y, null);

        if(o1 is IComparable && o2 is IComparable)
        {
          return ((IComparable)o1).CompareTo(o2) * directionChanger;
        }

        // no sort
        return 0;
      }
      catch(Exception ex)
      {
        Debug.WriteLine(ex.Message);
        return 0;
      }
    }
  }

  public partial class CustomerList : List<Customer>
  {
    partial void SpecialSort(string propertyName, SortDirection direction);

    public void Dump()
    {
      SpecialSort(“CustomerID”, SortDirection.Descending);

      foreach(var customer in this)
        Console.WriteLine(customer.ToString());
    }
  }

  public class Customer
  {
    private int customerID;
    public int CustomerID
    {
      get
      {
        return customerID;
      }
      set
      {
        customerID = value;
      }
    }

    private string name;
    public string Name
    {
      get
      {
        return name;
      }
      set
      {
        name = value;
      }
    }

    public override string ToString()
    {
      return string.Format(“CustomerID={0}, Name={1}”, CustomerID, Name);
    }
  }
}

The code in Listing 3.10 is entirely for convenience. The Main procedure uses compound type initialization to initialize a list of customers. Then, the collection is dumped; dumping object state is something you might do while debugging and testing.

The partial class CustomerList represents the part that fills in the partial method SpecialSort. SpecialSort accepts a property name and a sort direction—courtesy of Chris Chartrand—and uses Reflection to sort on the named property. Because SpecialSort is defined as a partial method, you could elect not to implement it or let some other consumer implement in a manner deemed suitable for that project’s needs.

The comparison is done using Reflection in the Comparer method. The SortDirection is used to determine whether to multiply the result by 1 or -1. Remember, CompareTo returns an integer where -1 means less than, 0 means equal to, and 1 means greater than. Thus, if you multiply the result by -1, you change the direction of the sort.

Finally, the CustomerList class inherits from List<Customer>. You might use this technique to add special behaviors to your typed collections. As you can see, the Dump method calls SpecialSort, which, if implemented, yields ordered output. The last part of Listing 3.10 is routine; the Customer class just rounds out the demo.

Summary

In the 1960s, I remember being at the State Fair in Detroit and my first ride on the Tilt-a-whirl. For a little kid, it was one of those “We aren’t in Kansas anymore, Toto” kind of moments. Cool new language features are still like that to me.

This chapter discussed extension methods. Extension methods are used heavily to support LINQ. The result is that instead of using generic-intensive code, you can use a more natural, querylike language, LINQ. You are encouraged to master generics, but LINQ is much easier to use than the multiparameter extension methods on which it is built.

To recap, extension methods permit you to add behaviors to intrinsic types, sealed classes, and keep deep inheritance trees under control. This chapter also looked at partial methods, which are used mostly in generated code to provide placeholders for consumer code.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.15.78.83