Reputation: 2392
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?
Upvotes: 1
Views: 1102
Reputation: 660533
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.
Upvotes: 2
Reputation: 43076
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.
Upvotes: 5
Reputation: 2399
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);
Upvotes: 0
Reputation: 126992
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;
}
Upvotes: 2