I was coming back from a slightly shambolic talk I’d given on C# 3.0 last week and I was thinking about the way that the C# compiler (V2.0 and V3.0) does inference around generic methods and I scribbled down a few notes – might be a few glaring errors in here so feel free to offer corrections J
Talking about C# version 3.0 seems to lead me to think about the compiler's ability to do some inference of types around generic functions.
I'd been blissfully unaware of this for quite some time but it's a V2.0 feature that proves to be really useful – not sure when I came across it for the first time but it’s within the last year or so.
The basic idea (as far as I understand it) is that if you've got some generic function such as;
static T Fn<T>(T t)
{
return(t);
}
then the compiler can try and infer the parameter types for you so that you can make a call such as;
int x = Fn(10);
and you don't have to go ahead and actually list the generic type parameter as in;
int y = Fn<int>(20);
which is pretty smart and it extends out to multiple parameters such as;
static U Fn<T,U>(T t, U u)
{
return(u);
}
with a call;
float f = Fn(10, 22.5f);
Whilst this seems like a handy shortcut, it really comes into its own when it comes to anonymous types in C# version 3.0.
In version 3.0 you can define an anonymous type by writing code such as;
static void Main(string[] args)
{
var point = new { X = 10, Y = 20 };
}
and the compiler spots the type on the right hand side of this assignment and goes ahead and creates a type for you that you never get to see the name of (unless you use reflection or ILDASM or similar) and because you don’t know the name of the type it makes it very hard to define a variable to store the instance (unless you use object) and hence you need the implicit typing offered by the var construct above (I’m not really trying to explain implicit typing here but I’ll just say that what you have in variable point above is a strongly typed reference to an object).
The interesting thing to me about this instance of an anonymous type is what we can usefully do with it once we’ve got it.
(As an aside, it’s also interesting to me to note that if you do something like;
static void Main(string[] args)
{
var point = new { X = 10, Y = 20 };
}
static void Fn()
{
var fred = new { X = 10, Y = 20 };
}
Then the anonymous type in function Fn is not the same anonymous type as the one in Main).
When I first came across these implicitly typed variables I was scratching my head about what you could actually do with them.
You can’t use the var construct anywhere other than within a method (i.e. it’s always used to declare a local variable) and even though the reference that you have is strongly typed you can’t really pass it anywhere because its type doesn’t have a name so it’s not possible to declare a parameter to a method as being of that type.
That is, whilst I can write code such as;
static void Main(string[] args)
{
var point = new { X = 10, Y = 20 };
Console.WriteLine("{0}, {1}", point.X, point.Y);
}
And use point locally in a strongly typed manner it initially feels a bit limited because I can’t pass that variable to any function in a strongly typed manner. I can pass it out to some function as object but once I’ve done that I’ve lost my typing information. One way I can the “point object” to “some other place” is to set up something like an anonymous method below;
var point = new { X = 10, Y = 20 };
ThreadPool.QueueUserWorkItem(delegate {
Console.WriteLine(point.X);
});
Which seems like an interesting case to me because it’s relying on the compiler’s visibility of the anonymous type referenced by point to generate an anonymous method that ultimately gets invoked by the thread pool.
As far as I know, that’s pretty much what you can do to pass instances of anonymous types around until we bring in the ability of the compiler to perform inference around generic method calls. Not having a name for my anonymous type limits me in that I can’t declare a method that takes a parameter of that type but that doesn’t matter so much in those cases where I’m relying on the compiler to infer the parameter types anyway.
So, we can write a function such as;
static T Fn<T>(T t)
{
return(t);
}
(ok, it’s not the world’s most useful function!) and we can make a call to it;
static void Main(string[] args)
{
var point = new { X = 10, Y = 20 };
var anotherPoint = Fn(point);
}
Very cool! Clearly, whilst we have the choice as to whether we let the compiler infer the type with something like int as in;
int i = Fn<int>(22);
int j = Fn(33);
We don’t get that choice when it comes to our anonymous type;
var anotherPoint = Fn<What Name Can I Type Here?>(point);
and so the compiler’s ability to infer it is really helpful J
Having said that, what can we realistically do with the parameter t inside of our generic function Fn because we don’t have any real type information for T as a type. Generally, we’d solve this by adding constraints to the type parameters of the generic type but we can’t do this here because we can’t go and implement interfaces on our anonymous type. So, we’re nearly stuck but not quite. Similarly to the ThreadPool example from earlier, if we can pass something like an anonymous method to our function Fn then we can pass it a piece of code which does have visibility of the anonymous type – as in;
class Program
{
static void Main(string[] args)
{
var point = new { X = 10, Y = 20 };
WriteOut(point, delegate(p) {
Console.WriteLine(“{0},{1}”, p.X, p.Y);
});
}
static void WriteOut<T>(T t, Fn<T>fn)
{
fn(t);
}
delegate void Fn<T>(T t);
}
Now, logically that code (whilst still not very useful) should work but the compiler doesn’t allow this in that it doesn’t allow delegate(p) but would only allow that parameter if we gave it a typename and (as we know) we don’t have a typename. However, the compiler does allow us to use Lambda Expressions or Statements in place of anonymous methods like this and so we can do;
class Program
{
static void Main(string[] args)
{
var point = new { X = 10, Y = 20 };
WriteOut(point, p => Console.WriteLine("{0},{1}", p.X, p.Y));
}
static void WriteOut<T>(T t, Fn<T>fn)
{
fn(t);
}
delegate void Fn<T>(T t);
}
And so now we’ve written a function WriteOut which actually takes an instance of an anonymous type and does something “useful” with it although, clearly, it doesn’t make direct use of the type but, rather, calls through a Lambda Expression that is provided which has “visibility” of that type’s definition.
Still, this can be used to do useful work. We can write a routine such as Filter which might look something like;
static T[] Filter<T>(T[] unfiltered, FilterFn<T>filter)
{
List<T> filtered = new List<T>();
foreach (T candidate in unfiltered)
{
if (filter(candidate))
{
filtered.Add(candidate);
}
}
return(filtered.ToArray());
}
delegate bool FilterFn<T>(T t);
and then use it with an array of our original “point” anonymous types such as;
var points = new [] {
new { X = 10, Y = 20 },
new { X = 10, Y = 40 },
new { X = 10, Y = 60 },
new { X = 10, Y = 80 }
};
foreach (var pt in Filter(points, p => p.Y > 40))
{
Console.WriteLine("Points with Y > 40 {0},{1}", pt.X, pt.Y);
}
Or a routine like Convert ;
static R[] Convert<T,R>(T[] original, ConvertFn<T,R> convertFn)
{
List<R> converted = new List<R>();
foreach (T candidate in original)
{
converted.Add(convertFn(candidate));
}
return(converted.ToArray());
}
private delegate R ConvertFn<T,R>(T t);
and then call it to convert my “points” to x-coordinates (for instance);
var points = new [] {
new { X = 10, Y = 20 },
new { X = 20, Y = 40 },
new { X = 30, Y = 60 },
new { X = 40, Y = 80 }
};
int[] xCoords = Convert(points, p => p.X);
Naturally, these routines like Convert and Filter are pretty similar to some of the routines that show up as extension methods in LINQ so I’ll stop sketching similar things here but it’s made the point about how the inference that the compiler does for these generic methods takes us a heck of a long way J