Saturday, August 23, 2008 2:04
bart
Appropriate use of Local Variable Type Inference
By now, most – if not all – readers of my blog will be familiar with this C# 3.0 and VB 9.0 feature called Local Variable Type Inference or Implicitly Typed Local Variables. The idea is simple: since the compiler knows (and hence can infer) type information for expressions, also referred to as rvals, there’s no need for the developer to say the type. In most cases it’s a convenience, for example:
Dictionary<Customer, List<PhoneNumber>> phonebook = new Dictionary<Customer, List<PhoneNumber>>();
That’s literally saying the same thing twice: declare a variable of type mumble-mumble and assign it a new instance of type mumble-mumble. Wouldn’t it be nice just to say:
var phonebook = new Dictionary<Customer, List<PhoneNumber>>();
while it still means exactly the same as the original fragment? That’s what this language feature allows us to do without loosing any of the strong typing. The reason it’s very convenient in the sample above is because of the introduction of arbitrary type construction capabilities due to generics in CLR 2.0. Before this invention, types couldn’t compose arbitrarily big and type names tend to be not too long-winded (namespaces help here too).
As convenient as the case above can be, sometimes type inference is a requirement which is introduced by the invention of anonymous types. Typically those are used in projection clauses of LINQ queries although they can be used in separation as well. E.g.:
var res = from p in Process.GetProcesses() select new { Name = p.ProcessName, Memory = p.WorkingSet64 };
This piece of code gives birth to an anonymous type with two properties Name and Memory (notice the type of those properties is inferred in a similar way from their assigned right-hand side) which is – as the name implies – a type with an unspeakable name. In reality the above produces something like:
IEnumerable<AnonymousType1> res = from p in Process.GetProcesses() select new { Name = p.ProcessName, Memory = p.WorkingSet64 };
where the AnonymousType1 portion is unknown to the developer, so type inference comes to the rescue.
Alright. But when is it appropriate to use the type inference feature? Here are some personal rules I tend to apply quite strictly:
- Do use type inference…
- where anonymous types are used – you can’t go without it (unless you want to treat them as System.Object of course and only care about their ToString method or so):
var point = new { X = 10, Y = 5 }; //OK – what else could you do?
var res = from p in Process.GetProcesses() select new { Name = p.ProcessName, Memory = p.WorkingSet64 }; //OK – what else could you do?
- when the right-hand side explicitly states the type or the type is clearly inferable by humans given the context:
var phonebook = new Dictionary<Customer, List<PhoneNumber>>(); //OK – object construction, with generics
var point = new Point { X = 10, Y = 5 }; //OK – object construction
var customer = (Customer)someObject; //OK – cast
var products = objects.Cast <Product>(); //Debatable – clearly something with Product objects, but a List<T>, a T[], an IEnumerable<T>, or …? Also, the type is mentioned quite far to the right, which doesn’t work too well with left-to-right parsing humans
- Don’t use type inference…
- for assigning constants to variables of built-in types, e.g. what’s the type of the variables below:
var i = 0123456789; //NOK – an Int32
var l = 9876543210; //NOK – an Int64
var name = “Bart”; //Debatable – what do you gain?
- for “smart” type inference in foreach loops (unless you’re faced with a source of anonymous types):
foreach (var x in xs) //Debatable – depending on meaningful variable names there might be “good” exceptions to the rule
;
Moreover, foreach inserts casts for you if the source sequence is just an IEnumerable (i.e. not “Of T” aka “<T>”), so the best guess with type inference would be System.Object.
- when the right-hand side doesn’t clearly indicate the type:
var something = someObject.WeirdMethod().AnotherProperty; //NOK – need to know the specific API
- if you want to forcefully exercise some abstraction, e.g. because of your development methodology allowing for “planned refactorings” down the road:
IAnimal animal1 = new Lion(); //OK
var animal2 = new Giraffe(); //NOK – will be the most specific type, i.e. Giraffe
This is an interesting one as all of the refactoring support in the IDE will be based on the most specific type and with var, you force it into the most specific type.
Also, it’s important to realize that mechanical changes to variable declarations (for fun?) can yield undesired behavior due to a change in semantics. A few cases pop to mind:
- Lambdas don’t play well with type inference. A lambda can either be represented as code or as data (through an expression tree), so without saying what you want, the compiler can’t know. Indeed, not every rhs has a type by itself:
Func<int> f = () => 123;
Expression<Func<int>> e = () => 123;
- Implicit conversion – the long that became an int:
checked
{
long i = 1; // don’t change to var…
while (true)
Console.WriteLine(i *= 2); // quiz: does i <<= 1 have the same effect?
}
- Explicit interface implementations:
static void Main(string[] args)
{
IBar b1 = new Bar();
b1.Foo();
var b2 = new Bar();
b2.Foo();
}
interface IBar
{
void Foo();
}
class Bar : IBar
{
public void Foo()
{
Console.WriteLine("Bar.Foo");
}
void IBar.Foo()
{
Console.WriteLine("IBar.Foo");
}
}
Method hiding:
static void Main(string[] args)
{
Foo f1 = new ExtendedFoo();
f1.Bar();
var f2 = new ExtendedFoo();
f2.Bar();
}
class Foo
{
public virtual void Bar()
{
Console.WriteLine("Foo.Bar");
}
}
class ExtendedFoo : Foo
{
public new void Bar()
{
Console.WriteLine("ExtendedFoo.Bar");
}
}
In the end, as usual, there’s no silver bullet. However, one should optimize code for reading it (write-once, read-many philosophy). When trying to understand programs (at least imperative ones <g>) we already have to do quite some mental “step though” to absorb the implementation specifics with loops, conditions, class hierarchies, etc. We shouldn’t extend this process of mental debugging or reverse engineering with type inference overhead in cases where it’s not immediately apparent what the intended type is. Even though the IDE will tell you the type of an implicitly-typed local when hovering over it, it’s not the kind of thing we want to rely on to decipher every single piece of code. Similarly, IntelliSense is working great for implicitly typed local variables, but that only affects the write-once side of the story.
After all, every powerful tool imposes a danger when misused. Type inference is no different in this respect.
Del.icio.us |
Digg It |
Technorati |
Blinklist |
Furl |
reddit |
DotNetKicks
Filed under: C# 3.0, VB 9.0