Returning var from a method in C# 3.0

Posted at Mon, 30 Jul 2007 17:51:47 GMT by Wilco Bauwer

A while ago Rick Strahl raised a concern about returning dynamic query results in LINQ. It's a fairly common thing for people to return dynamic query results (i.e. create projections), using the new anonymous types feature. The consequence of using anonymous types is that you can't just return it from a method, because you don't know what to put for the method's return type. Or can you?

A trick you can use is to defer the projection to the caller. The data method would simply expect a 'projection' delegate/lambda, which it can use for the select part of the query. Because the non-projected query result type is always known, you can use this type as the parameter type of the 'projection' delegate/lambda. In code this looks like:

C#:
1 
2 
3 
4 

public IEnumerable<TProjection> GetCustomers<TProjection>(Func<Customer, TProjection> projection) {
    return from c in _customers
           select projection(c);
}

As you can see the return type is based on the type of the projection. The type of the projection is based on the type of projection that a caller uses. For example, in the code below the return type is an IEnumerable of the anonymous type defined in the call to GetCustomers.

C#:
1 
2 

var results = GetCustomers(customer => new { Name = customer.Name });
// results is of type IEnumerable<'anonymous type with property Name'>.

This trick also works with more complicated scenarios. For example, you can also deal with nested queries in a similar way.

C#:
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 

public IEnumerable<TProjection> GetCustomersWithOrders<TProjection>(Func<Customer, IEnumerable<Order>, TProjection> projection) {
    return from customer in _customers
           let customerOrders = from order in _orders
                                where order.CustomerID = customer.ID
           select projection(customer, customerOrders);
}

// ...

var results = GetCustomersWithOrders((customer, orders) => new {
                                         Name = customer.Name,
                                         OrderCount = orders.Count()
                                     });

These examples use in-memory collections, but nothing prevents you from using the same technique with DLINQ or other APIs. When using DLINQ, you will just have to replace 'IEnumerable' in the above examples with 'IQueryable'.

But what about...
Now that said, this trick doesn't address the scenario where you have a complex projection, that you really wish to hide from the callers. For those scenarios I guess you don't really have a choice but to introduce a named type to hold the projection.

Yes this is a compromise that works and may be a good idea for some scenarios.

However, i still think for a business object type scenario this is really sub optimal. You'll not want to specify a Lambda expression with every business object call :-}. The whole point of a business layer is make it easy to access your business logic with as little code (and worrying about parameters) as possible. The complex stuff should be layered in the business layer.

There's that and well, Lambda expressions are kind of a head trip to look up and well - ugly even if they are functionally quite useful... Even now that I understand how they work I still look at them and hesitate a few seconds thinking 'what does c mean here?' Maybe in time :-/

+++ Rick ---
Absolutely. It's a matter of tradeoffs (flexibility, simplicity and in some cases even performance when the amount of data in a result set matters).
What about inferring return types of methods?

C#:
1 
2 
3 
4 

public var GetFoo()
{
    return from ... where ... select;
}



This would be yet another step towards Haskell's type-system. Was this ever considered as an option? If yes, why was it rejected? The only drawback I can see right away is the danger of inadvertently breaking all clients when the signature of the method changes.
The main drawback I see with that is that it requires a compiler to have support for type inference, like C#'s var. What happens if someone else uses your library and uses a compiler with such support (e.g. C# 2.0)?

I'm also not so sure if having a compiler generated type as a return type is very reliable. You will need a guarantee from the compiler that it won't use a different name for the return type the next time you compile your code, or else you'll have introduced a breaking change when you didn't even modify your code.
1. Good point. The problem is that we have no possibility to encode the "var" keyword in the IL for the method. As soon as the method gets compiled, the actual (potentially very complex) return type gets burned into IL and the semantics is lost. So even if we load this assembly as a reference, all tool support, including IntelliSense, will show us the real return type in tooltips etc. Maybe one could mark methods with inferred return types using an Attribute at the IL level? [InferredReturnType]. Newer tools could take advantage of this attribute and just display var instead of the actual type. But again, this isn't backwards compatible...

2. Well, one could prohibit inferred return type for public or protected methods. Only private and internal methods would still be a good help. This also seems to solve the first problem, because the method isn't exported as a publicly visible API at all.

Anyway, it's too late for Orcas, but it was nice to brainstorm :)
P.S. I currently don't see any problems with anonymous types "living a long life" (i.e. being persisted and reused between compilations), as long as they don't leave the assembly. As long as one set of tools (e.g. Orcas) is responsible for the assembly, it can use any dirty tricks it needs internal to the assembly to maintain type information between compile sessions (hidden attributes to mark types etc.) And the C# type inferencer is deterministic, isn't it?
Sure, as long as you keep such members internal, there's no issue. Although I understand that may be useful, it no longer addresses the main problem which is allowing you to "return var." (E.g. return a LINQ query without any tricks.)
Your message will be encoded/formatted when it is displayed. If you want to post code, please put the code inside [code=X][/code] tags, where X is the language of your code (C#, ASPX, SQL, etc).
Name:
Email:
(will be encoded using JavaScript to keep it functional and prevent it from being picked up by spammers)
Url:
 
Message:
3 + 3 =