In This Chapter
Extension Methods and Rules of the Road
Defining Extension Methods
How Extension Methods Support LINQ
Implementing a “Talking” String Extension Method
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.
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.
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:
Use extension methods to extend sealed types without inheritance.
Use extension methods to keep deep inheritance hierarchies from getting out of hand.
Extension methods are static methods in static classes (see Listing 3.1).
The first argument of an extension method must use the this
modifier preceding the argument type; this
refers to the type being extended.
Extension properties, events, and operators are not supported (but might be in the future).
Extension methods are less discoverable and more limited than instance methods.
Extension methods are invoked using instance syntax.
Extension methods have a lower precedence than regular methods; therefore, if a class has a similarly named method, the instance method will be called.
Extension methods are supported by IntelliSense.
Generic extension methods are supported, which is, in fact, how many of the LINQ keywords are implemented in the System.Linq
namespace.
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()
.
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.
Extension methods are not really members, so you can only access public members of the object being extended.
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.
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.
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.
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();
}
}
}
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.”));
}
}
}
}
}
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.
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.
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.
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.
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
.
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();
}
}
}
.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.
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).
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.
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);
}
}
}
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.
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:
Partial methods are declared in partial classes.
Partial methods use the partial
modifier.
Partial methods don’t have a method body at the point of introduction.
Partial methods must return void
.
Partial methods can be static, and can have arguments and argument modifiers, including ref
and params
(an array of arguments).
Partial methods are private, but no literal access modifiers are permitted; that is, you can’t use the private
keyword explicitly.
Unused partial methods are stripped out by the compiler.
The example in Listing 3.10 uses a partial method.
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.
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.
52.15.78.83