I’m playing with the C# V3.0 bits that you can download from here and working through the document (having seen Anders Hejlsberg walk through it at the PDC last year) that details the changes to the C# language.
Extension methods seem like a simple enough thing to understand. Say you’ve got a class;
public sealed class Person
{
private string firstName;
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
private string lastName;
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
And you want to add a method that returns the firstname and lastname concatenated but you’re not the owner of the class and it’s sealed so you can’t derive.
Perhaps you’d do something like;
static class PersonExtensions
{
public static string ConcatPerson(Person p)
{
return(p.FirstName + p.LastName);
}
}
and then you’d use it somewhere;
Person p = new Person() { FirstName="Fred", LastName="Bob" };
string fullName = PersonExtensions.ConcatPerson(p);
which all works fine but it’s a bit messy having to remember that you need to use the PersonExtensions class in order to perform this operation. Extension methods codify this kind of thing by allowing you to define your extension to the person class as;
static class PersonExtensions
{
public static string ConcatPerson(this Person p)
{
return(p.FirstName + p.LastName);
}
}
notice the this keyword that’s crept in. With this extension method in place the call now becomes;
Person p = new Person() { FirstName="Fred", LastName="Bob" };
string fullName = p.ConcatPerson();
so the ConcatPerson method now appears to be a method on the Person class itself rather than some method external to that type. As far as access to the type that you’re extending, you’re not treated as a member of the family as far as I can tell so you can’t go ahead and access non-public members of the type.
(That example is also using the 3.0’s object initialisation feature which is a simple enough thing to see and understand from the code when it initialises the Person instance).
The compiler has a bunch of resolution rules as to how it finds that ConcatPerson and, naturally, as you’d expect it prefers a real method called ConcatPerson on the type Person to one introduced by an extension method as we did here and so, if we redefine Person as;
public class Person
{
private string firstName;
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
private string lastName;
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public string ConcatPerson()
{
return(FirstName + LastName);
}
}
then the ConcatPerson method on the type itself will be used rather than the extension method. There’s some versioning questions there in that if our class Person did not originally have a ConcatPerson method but later acquires one then a recompilation could cause some behaviour changes for us in code like this. I’m not sure how you’d handle that or how real a problem it’s likely to be right now.
Going back to our original version of Person;
public sealed class Person
{
private string firstName;
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
private string lastName;
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
}
and our original extension method,
static class PersonExtensions
{
public static string ConcatPerson(this Person p)
{
return(p.FirstName + p.LastName);
}
}
what happens if someone else comes along and defines a second extension method with the same name and parameter types;
static class MorePersonExtensions
{
public static string ConcatPerson(this Person p)
{
return(p.LastName + p.FirstName);
}
}
Now the compiler has a problem in that it can’t automatically give priority to one of these over the other (like it did with the instance method called ConcatPerson) so it gives up and says “This is ambiguous, please sort it out for me.”
We can do that by putting our extensions into different namespaces and only bringing in the namespace for the extension that we want to use.
namespace GoodExtensions
{
static class PersonExtensions
{
public static string ConcatPerson(this Person p)
{
return (p.FirstName + p.LastName);
}
}
}
namespace BadExtensions
{
static class MorePersonExtensions
{
public static string ConcatPerson(this Person p)
{
return (p.LastName + p.FirstName);
}
}
}
then a simple;
using GoodExtensions;
will let the compiler know that we want to use PersonExtensions.ConcatPerson rather than MorePersonExtensions.ConcatPerson.
Now, we’d cause ourselves a problem if GoodExtensions and BadExtensions were namespaces that both contained other functionality that we needed in our compilation unit so it seems to me (unless I’ve missed some other way of specifying which extension you want to work with) that it’d make sense to put extension classes into their own namespaces with very little other stuff in there otherwise you might cause hassles for the person trying to consume your types.
The last thing that occurred to me was how these extension methods played with inheritance and, from what I can see, it works the way you’d expect in that if you’ve always really fancied a method on System.object that returned the hash code combined with the typename then you can get it ;-)
public static class ObjectExtensions
{
public static string GetHashCodeAndType(this object o)
{
return(o.GetHashCode().ToString() + o.GetType().ToString());
}
}
class Program
{
static void Main(string[] args)
{
args.GetHashCodeAndType();
}
}
Posted
Mon, Mar 20 2006 6:58 AM
by
mtaulty