1

I have code like this

var results =   (from c in Customers  
                    join o in Orders  
                    on c.Id equals o.CustomerId  
                    join p in Products  
                    on p.Id equals o.ProductId  
                    select new   
                    {  
                        CustomerId = c.Id,     // this is a GUID  
                        OrderId = o.Id,        // this is a GUID    
                        ProductName = p.ProductName,  
                     }).ToList();  

Let's say I want to get a list of all customer Ids that orders a product that has name = foo My problem is that because its an anonymous type, how can I refer product name in any Linq query that I want to run on results?

phoog
  • 42,068
  • 6
  • 79
  • 117
palm snow
  • 2,392
  • 4
  • 29
  • 49
  • The result is strongly typed at compile time. It will have properties of the names and types you specified in the select projection. – asawyer Dec 08 '11 at 20:43
  • You refer to ProductName by its name -- ProductName. I don't understand what the problem is; can you clarify the question? – Eric Lippert Dec 08 '11 at 20:44
  • I guess I am just confused about the scope of anonymous types and not sure what will be visibility of this type within a method or class scope. My thinking is that given that this type is not declared anywhere, how can compiler figure out which type I am talking about. – palm snow Dec 08 '11 at 20:53
  • @palmsnow http://msdn.microsoft.com/en-us/library/bb397696.aspx Theres tons of documentation online. – asawyer Dec 08 '11 at 20:59
  • @palmsnow the visibility of anonymous types is an obvious limitation. You can't, for example, return them from a non-anonymous method, because that method needs to have a declaration specifying (and therefore naming) its return type. – phoog Dec 08 '11 at 21:16
  • @phoog: Sure you can return anonymous types from a nominal method. You can either return them as object, or you can make a generic method that returns a T and use method type inference to infer that T must be the anonymous type. – Eric Lippert Dec 09 '11 at 16:20
  • @EricLippert perhaps I expressed that inadequately. (1) If the method's return type is object, then you can return an instance of an anonymous type, but the type of the reference is object. I call that returning an object, not returning an anonymous type. When we say a method "returns a string" we don't mean "returns an object reference that is known to point to a string". (2) The generic solution requires you to pass an instance of the type in (doesn't it?). To cover that, I should perhaps have said you can't *define them in and* return them from.... Or is there a better formulation? – phoog Dec 09 '11 at 17:45
  • @phoog: Right; typically you "cast by example". – Eric Lippert Dec 09 '11 at 17:55

4 Answers4

5
var filteredResults = results.Where(r => r.ProductName == "X");

The compiler's type inference takes care of it for you. The complete answer to your question:

var customerIds = results
    .Where(r => r.ProductName == "X")
    .Select(r => r.CustomerId)
    .Distinct()
    .ToList();

or

var customerIds = (from r in results
                  where r.ProductName == "X"
                  select r.CustomerId)
    .Distinct()
    .ToList();

EDIT

Some musings on type inference

To select the lengths from a sequence of strings called list, you can call Select either using classic static method syntax or as an extension method:

Enumerable.Select<string, int>(list, s => s.Length)
list.Select<string, int>(s => s.Length)

Thanks to type inference, you don't need the type arguments:

Enumerable.Select(list, s => s.Length)
list.Select(s => s.Length)

In this case, the compiler can prove that the type arguments are string and int by looking at the method arguments, and it supplies these type arguments on your behalf without your having to type them into the source code.

For anonymous types, you can't provide the first type argument, because the type doesn't have a name for you to use in the source code (that's what "anonymous" means, after all: "without a name"). (You can see therefore that anonymous types and type inference were both critical -- and closely related -- prerequisites to implementing linq in the first place.)

If you check out the IL for the anonymous type example above, you'll see that the compiler has in fact given the type a name (which contains characters that are illegal in C# identifiers). When you call Select, the compiler deduces from the type of the enumerable (IEnumerable<CrazilyNamedAnonymousType>) that the first type argument should be the anonymous type, and, as with the string example, it supplies that value on your behalf.

phoog
  • 42,068
  • 6
  • 79
  • 117
  • @Gent I fleshed the answer out considerably, please let me know if you'd like anything added. – phoog Dec 08 '11 at 21:13
  • thanks, i'm sure your answer will add value long after this user has moved on. – Gent Dec 08 '11 at 21:32
2

Within the method that generates this anonymous typed result, you can continue to refer to the results just as if you had defined a concrete type.

var customersWithFoo = results.Where(r => r.ProductName == "foo")
                              .Select(r => r.CustomerId);

If you are returning the original query result out of this method and then wish to query it further or otherwise programmatically access the elements, define a type.

class QueryResult 
{
   /* relevant properties */
}

And then project into that type in your original query, and return a sequence of that type from your method.

public IEnumerable<QueryResult> GetResults()
{
     var results = ...
                   select new QueryResult 
                   {
                       // properties
                   };

     return results;
}
Anthony Pegram
  • 123,721
  • 27
  • 225
  • 246
2

Following up on your comment:

I am confused about the scope of anonymous types

First let's clearly define "scope". The scope of a type is defined as the region of program text in which the type may be referred to by its unqualified name.

With that definition it is obvious what the scope of an anonymous type is. There is no region of program text in which the anonymous type may be referred to by its name because it does not have a name. Anonymous types don't have scope at all. You don't have to worry about its scope; it has no scope.

The fields of an anonymous type also have no scope, but for a different reason. Fields of an anonymous type have names, but it is never legal to refer to them by their unqualified names, so the scope of each field is empty.

I am not sure what will be visibility of this type within a method or class scope.

Again, let's clearly define our terms. The scope of an entity may include entities which define declaration spaces. Those declaration spaces may declare entities that have the same name as the original entity. Those entities have their own scopes, which may nest inside of the scope of the original entity.

In this situation, a more-nested entity may "hide" a less-nested entity. An entity which is not hidden in this manner is said to be "visible" at a particular textual location.

An anonymous type does not have a scope, and it obviously cannot be hidden by name because it does not have a name. Asking whether an anonymous type is "visible" or not is not a sensible thing to do; "visibility" only makes sense for things that have names.

My thinking is that given that this type is not declared anywhere, how can the compiler figure out which type I am talking about?

The compiler makes a note of every textual location in which you use an anonymous type within a program. If any two of those locations refer to anonymous types that have the same field names, the same field types and the fields come in the same order then those two locations are treated as usages of the same anonymous type.

The compiler can then emit one type for each of the unique anonymous types you used in your assembly. The details of how it does so are fascinating (*). I suggest that you poke around your assembly with ILDASM to see how we do it if you are interested.

If you make "the same" anonymous type -- same names, types and in the same order -- in two different assemblies then the anonymous types will not be treated as the same type. Anonymous types are not designed to be used across assembly boundaries.


(*) To me.

Eric Lippert
  • 647,829
  • 179
  • 1,238
  • 2,067
  • 1
    Eric, using the same anonymous type if the type has same field names and types make sense but why is the order important? In my nominal classes, order of member declaration is immaterial. In case the answer can not fit in a comment, I can open another question. Let me know. – SolutionYogi Dec 09 '11 at 16:30
  • @SolutionYogi: http://stackoverflow.com/questions/2154414/is-order-of-field-important-in-anonymous-types-automatic-initialization, answers from me, Jon and my brand-new coworker Brian who just joined us on the Roslyn team. – Eric Lippert Dec 09 '11 at 16:34
  • Brian's answer does not tell me 'why', it only states that order is important. And with respect to your answer, I am still not convinced that order should matter. In ToString implementation, why not show the properties alphabetically? And about Jon's answer, I am not sure how using property order makes compiler developer's life easier. May be I am missing something. – SolutionYogi Dec 09 '11 at 16:43
  • @sSol i one case you must write something that compares two Sets based on name and type, in the other a List. Writing the latter is simpler than the former – ShuggyCoUk Dec 09 '11 at 17:33
  • @SolutionYogi: Alphabetically *in which alphabet*? Two different users might have completely different opinions on what alphabetical order means. And then what do you say to the customer who wants the ordering to be in the order they stated in source? We just don't want to be in the business of deciding on a canonical order for you; if order doesn't matter to you then *you* decide on a canonical order and use it. If it does matter to you, then put them in the order that you like and we'll honour that. – Eric Lippert Dec 09 '11 at 17:53
  • I see your point, let's maintain the original order in ToString. But why does implementation of ToString affect how compiler decides whether two anonymous types are equal or not? The main reason I ask this is because it is very counter intuitive that order of member declaration is important (especially considering that most of the C# behavior makes good sense). Do you get frequent questions about this particular design detail like OP in the question you linked? – SolutionYogi Dec 09 '11 at 18:05
  • 1
    @SolutionYogi: But if two instances of "the same" anonymous type have their items in different orders then *which* ToString wins? Now you've got *two* "original orders" and it seems like a fair amount of work to keep them straight. More to the point: we expect specific anonymous types to be used locally within a method, not throughout an assembly. Many people are unaware that even identical anonymous types unify inside an assembly boundary because they never have cause to observe that fact. Doing work to make more unifications possible isn't the best way to spend budget. – Eric Lippert Dec 09 '11 at 21:55
  • Duh. Why didn't I think of that? This seems reasonable argument. Just to clarify, I wasn't questioning the design, I only wanted to learn more about the pros/cons of each approach. I wish you guys would record all your design meetings so that we can view them later when we have questions like this. – SolutionYogi Dec 09 '11 at 22:38
0

Anonymous types will show up in intellisense, just like "normal" types. At compile time, a concrete class is created that represents your anonymous type, so there is very little difference at runtime.

Here is querying from your results set:

var fooOrders = (from x in results 
                where x.ProductName == "foo"
                select x.CustomerId);
Ricky Smith
  • 2,379
  • 1
  • 13
  • 29