TechEd 2008 notes: Best Practices with the Microsoft Visual C# 3.0 Language Features
Still catching up on posting my notes from TechEd last week. I probably would’ve gotten this up last night if I hadn’t been in the basement most of the evening for tornado warnings.
Best Practices with the Microsoft Visual C# 3.0 Language Features
Mads Torgersen
Program Manager for the C# Language
Microsoft
He’s the guy who figures out what features go in the next version of the language, to keep us on our toes.
Goals of this talk
- Show new features
- Important do’s and don’ts
- Introduce LINQ
Despite the name of the talk, more time will be given to C# 3 features than to best practices. Best practices are in there, but they’re not the star of the show. If you’re going to be annoyed by that, start being annoyed now, rather than waiting until the end.
C# 3 in a Nutshell
- Imperative => Declarative
- Before: modify state in little bits
- Leads to a lot of detail in describing how you want things done
- New: say what you want, rather than how you want it done
- MS has freedom to give us performance and flexibility
- How => What
- Make queries first-class
(Incomplete) list of new features
- Auto properties
- Implicitly typed locals
- Object and collection initializers
- Extension methods
- Lambda
- Queries
- Anonymous types
- Expression types
- …a couple not shown in this talk
Automatically Implemented Properties
- Just sucking up to programmers’ laziness; nothing deep
class Customer
{
public string CustomerID { get; set; }
public string ContactName { get; set; }
}
- Simplify common scenario
- You can see that they’re trivial
- Limitations
- No body -> no breakpoints
- No field -> no default value
- There can be serialization issues if you change an automatic property to a real property, since the autogenerated field has a magic name that’s stored in your serialized data
Lure of Brevity: Best practices for auto properties
- Only use this for things that really are simple get/set properties
- Hold on to your…
- Get-only and set-only properties
- Validation logic
- Private accessors (
get; private set;) are usually not the answer — too easy to forget you didn’t intend for them to be set capriciously, and add code a year from now that sets them in an unsafe way - Be careful what you make settable:
// Bad
class Customer {
public string CustomerKey { get; set; }
// Key really shouldn't be settable
Implicitly Typed Locals
varkeyword, type inference- I won’t bother quoting his code snippet, you’ve seen it before
- Intellisense can show you the actual type — hover over the
var - Remove redundancy, repetition, clutter
- Allow focus on code flow
- Great for experimentation: you can change something’s return type and there’s a much better chance that everything will still compile (Roy would probably say there’s more essence and less ceremony)
- “Surprisingly liberating experience”
Redundancy is not always bad: best practices for var
- Explicit types on locals (i.e., not using var) will…
- Improve readability of complex code, esp. if method name doesn’t make its return type clear
- Allow typechecking on right-hand side (when you want that)
- Can be more general than the right-hand side
- Think: Who is the reader?
- Find your own compromise between the two extremes
Side note: ObjectDumper class from samples (kind of like .inspect in Ruby)
Object and collection initializers
- Traditionally very imperative. Start with empty collection, then create an empty Customer, then initialize it, then add it.
- Lots of intermediate results lying around.
static IEnumerable<Customer> GetCustomers()
{
var custs = new List<Customer>()
{
new Customer {
CustomerID = "MADST",
ContactName = "Mads Torgersen",
City = "Redmond"
}
};
}
- Can omit empty parens after
newif you use an object initializer - Code-result isomorphism
- Structure of code parallels structure of object you want.
- Expression-oriented
- Can be used in expression context
- Atomic
- No intermediate results
- Create object and collection in one fell swoop. Don’t need temporary variables. Don’t expose any intermediate states at all.
- Compositional
- May not need as many constructor overloads
Constructors are still good: best practices for object and collection initializers
- Constructors…
- Show intent
- Enforce initialization
- Initialize get-only data
- Initializers and constructors compose well
var c = new Customer("MADST){
ContactName = ...
Extension Methods
- You’ve seen these demos too (well, maybe not
GetLondoners()specifically) - Dilemma with very general types: you use them in a specific setting, and sometimes you want a special view on it and wish you could add a couple more methods to the original declaration, just for your use in that setting
- One really interesting benefit: can add methods to a generic of only certain types, e.g. can have a method on IEnumerable<Customer> that isn’t there on the general IEnumreable<int>. I like this!
- Declared like static methods, can call like instance methods
- New functionality on existing types
- Scoped by using clauses
- Interfaces and constructed types
Cluttering your Namespace: best practices for extension methods
- Consider making them optional (separate namespace), so people can use your library without necessarily needing your extension methods (extension methods for working with types from
MyNamespace.Fooshould be in their own namespace, not right inMyNamespace.Foo) - Don’t put them on all objects!
- Make them behave like instance methods.
namespace System
{
public static class MyExtensions
{
// Don't do this
public static bool IsNull(this object o) {
return o == null;
}
}
}
- That’s a worst practice. It violates all three of the above guidelines. Don’t do it just because it’s cool.
Lambda Expressions
- Predicate<T> — function that takes T and returns bool
=>: Some call this the “fat arrow”- Terse anonymous functions
- Parameter types inferred from context
- Closures: capture local state (also true of anonymous methods)
Condensed Power: best practices for lambda expressions
- Keep them small
- That’s the point of making them terse
- Yank them out if they get too big
- Watch that capture (of local variables, and using them inside the lambda)
- Can have unexpected results
- Exposing private state
- Watch the complexity
- Functions of functions returning functions…
- Think: Who executes this lambda, and when?
Queries
- Functional: doesn’t mutate the original collection; instead returns a new collection
using System.Linq;== “Linq to Objects”- Extension methods give you pipelining:
customers.Where(...).Select(...) - Language integrated — use anywhere! (if you’re using C#)
- Query expressions for common uses
- Mix and match query and method syntax
- Expect deferred execution (can do
ToArray
Beware monadic complexity hell: best practices for queries
- Another powerful complexifier
- Do you need to roll your own query provider?
- Use query pattern for queries only!
- Avoid abusing query syntax for other magic
- Even if you know about monads! (Your users don’t)
Anonymous types
select new { Name = c.ContactName, c.City }— smart enough to call the second propertyCity- Temporary local results
- Shallow immutability and value equality
- Does a nice job on the generated classes
- Value-based equality
- Good hashcodes
Keep it local: best practices for anonymous types
- If you need a type, make one! Don’t use an anonymous type and work around problems. Only use where they don’t limit you.
Expression trees
- Runtime object model of code
- Created from lambda expressions
- Language independent. LINQ to SQL doesn’t know about C#; it just knows about expression trees.
- Compile back into delegates on demand.
.Compile()method — even if you created it with factories instead of a lambda.
The Lure of Doing Magic: best practices for expression trees
- You can interpret expression trees any way you like.
- Don’t!
- Stay close to expected semantics
- Avoid special magic names, etc.
Final words
- C# 3 and LINQ can change the way you code…
- Declaratively: more of the what, less of the how
- Eloquently
- And with lots of queries. Don’t think of queries as something heavyweight for external data.
- …but they don’t have to!
July 12th, 2008 at 1:14 am
“That’s a worst practice. It violates all three of the above guidelines. Don’t do it just because it’s cool.”
Do you have pointers to an article explaining *why* it’s wrong to violate those guidelines? Ok, I grok the one about namespaces, but I don’t understand the reason for the other two – I am actually using that IsNull() extension method.
July 25th, 2008 at 7:10 am
Marcel,
The big offender there, I think, is that it doesn’t behave like an instance method. If you call an instance method on a null reference, you get a NullReferenceException. If you start having things that look like instance methods but that violate that — i.e., things that you can call on a null reference, or that are even meant to be called on a null reference — then you’re increasing complexity. People working with your code need to remember that much more, and think that much harder about every line of code they write. It violates the principle of least surprise. So make sure you’ve got a really compelling reason to do it.
It looks like I didn’t put this in my notes, but part of the motivation for not putting extension methods on System.Object was that it would be problematic in VB.NET. In VB, when you have a variable declared as Object, all calls on that variable are late-bound. Late-bound won’t call an extension method. So if you use VB.NET at all, System.Object extension methods are a bad idea.
If you only use C#, I wouldn’t see a big problem with putting extension methods on System.Object, if it was done thoughtfully. I sure as heck wouldn’t want my grid vendor to do it, but I wouldn’t mind doing them internally, and it’d be fine if my test framework did it. In fact, I’d love to see an RSpec-style assertion framework for .NET.