The Previous Lesson introduced you to LINQ and the foundational concepts you need to know before moving forward. You saw a simple LINQ query with a select clause that made a simple projection on the object it was working with. This chapter takes you further by showing you different ways to create projections on queried types. The objectives of this lesson are as follows:
- Learn What a Projection Is.
- Introduce the Data Source for this Tutorial.
- Create Custom Projections of Queried Types.
- Project Into Anonymous Types.
- Understand the Need For the var Keyword.
What is a Projection?
In Lesson 01, you saw how the select clause specified the current object. The example was very simple because the object type was a primitive string. Essentially, the projection was on the entire object. However, if the object were complex, say a MusicalArtist, your selection options increase because you can choose to select the whole MusicalArtist instance or parts of that MusicalArtist, such as Name, Genre, or LatestHit. The process of choosing what parts of an object to select is called projection and the result of that operation is a projection. Here, you can see that I’m using the same word, projection, as both a verb and a noun. So, the context of how I use the term “projection” will determine it’s meaning.
The LINQ Tutorial Data Source
Before writing another query, you’ll need to understand the data source being queried. The first part of the data source is to create the custom data type to work with, MusicalArtist. What we’re trying to accomplish is create a custom object with several properties that can be used in future queries. Listing 2-1 shows the MusicalArtist class that we’ll be using.
Listing 2-1: The MusicalArtist Class
public class MusicalArtist { public string Name { get; set; } public string Genre { get; set; } public string LatestHit { get; set; } public List<Album> Albums { get; set; } }
The MusicalArtist class has four public properties. The last property, Albums, is a List of type Album. The main reason I include Album here is that many types have complex relationships with other types and it’s helpful to keep that in mind moving forward. Listing 2-2 shows the Album class.
Listing 2-2: The Album Class
public class Album { public string Name { get; set; } public string RecordingLabel { get; set; } }
With the custom types, MusicalArtist and Album defined, the next step is to obtain a collection to be queried. In practice, this collection could be populated from the normal operation of your application; perhaps originating from a file, database, Web service, or system (like a list of processes). For the purposes of this tutorial, the source of the collection won’t matter and we’ll use a fake source for the data to be queried. Listing 2-3 shows how the fake collection, a List of MusicalArtist, is built.
Listing 2-3: Creating a List of MusicalArtist
static List<MusicalArtist> GetMusicalArtists() { return new List<MusicalArtist> { new MusicalArtist { Name = "Adele", Genre = "Pop", LatestHit = "Someone Like You", Albums = new List<Album> { new Album { Name = "21", Year = "2011" }, new Album { Name = "19", Year = "2008" }, } }, new MusicalArtist { Name = "Maroon 5", Genre = "Adult Alternative", LatestHit = "Moves Like Jaggar", Albums = new List<Album> { new Album { Name = "Misery", Year = "2010" }, new Album { Name = "It Won't Be Soon Before Long", Year = "2008" }, new Album { Name = "Wake Up Call", Year = "2007" }, new Album { Name = "Songs About Jane", Year = "2006" }, } }, new MusicalArtist { Name = "Lady Gaga", Genre = "Pop", LatestHit = "You And I", Albums = new List<Album> { new Album { Name = "The Fame", Year = "2008" }, new Album { Name = "The Fame Monster", Year = "2009" }, new Album { Name = "Born This Way", Year = "2011" }, } } }; }
The purpose of the GetMusicalArtists method in Listing 2-3 is to return a List collection of MusicalArtist types. The MusicalArtist type is the same type defined in Listing 2-3. The syntax used to create this list is called Collection Initialization Syntax. Within the curly braces of the new List<MusicalArtist> is a comma-separated list of MusicalArtist instances. Each MusicalArtist instance is created via a syntax similar to the new List<MusicalArtist>, initializing object properties within the bounds of curly braces for each object; a syntax referred to as Object Initialization Syntax. Each MusicalArtist instance contains settings for its Name, Genre, and LatestHit properties. The Album property is set to a new collection of Album instances using the same collection and object initialization syntax just described.
In future tutorials, you might see additions and modifications to this data source, but this represents the core of what you’ll be working with for the rest of the tutorial. With the data source defined, you’re ready to learn how to perform projections in your queries.
Querying a Collection of Custom Types
Similar to the first query you saw in Lesson 01 that queried a collection of primitive types (string), you can query a custom type. The data source to the query will be the collection of MusicalArtist defined in the previous section. Listing 2-4 shows how to perform a query with a simple projection on a custom type.
Listing 2-4: Querying A Custom Type
static void QueryingCustomTypes() { List<MusicalArtist> artistsDataSource = GetMusicalArtists(); IEnumerable<MusicalArtist> artistsResult = from artist in artistsDataSource select artist; Console.WriteLine("\nQuerying Custom Types"); Console.WriteLine( "---------------------\n"); foreach (MusicalArtist artist in artistsResult) { Console.WriteLine( "Name: {0}\nGenre: {1}\nLatest Hit: {2}\n", artist.Name, artist.Genre, artist.LatestHit); } }
Listing 2-4 contains an entire method for obtaining data, performing a query, and printing the results. Of those parts, the query is highlighted, assigning it results to artistsResult. The result of a query is an IEnumerable<T> and T is type MusicalArtist in this example. The reason the result is type MusicalArtist is that is what the projection of the select clause specifies. The from clause returns each object of the data source, artistsDataSource, which is type Musical Artist and assigns each object to the artist range variable. Back to the point, the select clause specifies the range variable, artist, as its projection. Since the artist is type MusicalArtist the result of the projection will be typing MusicalArtist. This is how the result of the query will be IEnumerable<MusicalArtist>.
Looking at this query, you can see that it accomplished nothing because the resulting collection is the same as the original data source. In practice, there are more things you do with queries, such as filtering results or ordering. However, this was designed to be simple so that you can see another example of a simple query with the exception that we’re concentrating on what the projection is and it’s results. Next, we’ll build upon this so you can see how to create custom projections.
Creating Custom Projections
Sometimes you don’t need to use all the properties of an object. In these cases, it makes sense to only ask for what you want to use. This could have benefits in application performance and scalability by using fewer resources for a query. One of the ways to do this is with a custom projection on the original object type. Essentially, you build the projection to only specify the object properties you want to use. Listing 2-5 shows how to create a custom projection on MusicalArtists.
Listing 2-5: Custom Projection with Data Source Type
static void CreatingCustomProjectionsWithTheDataSourceType() { List<MusicalArtist> artistsDataSource = GetMusicalArtists(); IEnumerable<MusicalArtist> artistsResult = from artist in artistsDataSource select new MusicalArtist { Name = artist.Name, LatestHit = artist.LatestHit }; Console.WriteLine("\nCustom Projection With Data Source Type"); Console.WriteLine( "---------------------------------------\n"); foreach (MusicalArtist artist in artistsResult) { Console.WriteLine( "Name: {0}\nLatest Hit: {1}\n", artist.Name, artist.LatestHit); } }
The highlighted query in Listing 2-5 is similar to Listing 2-4, except for the custom projection in the select clause. Instead of projecting on the range variable, the projection instantiates an instance of the data source type, MusicalArtist. The reason for creating the instance of MusicalArtist is to specifically identify which properties to include in results. In this case, the Name and LatestHit properties are included. The range variable, artist, contains the values to assign to the properties of the new MusicalArtist instance. The properties not specified, Genre and Albums will be null in the results.
This example showed how to create a customized projection using the same object type as the original data source, but sometimes you need to create projections on a different object type, discussed next.
Creating Projections on Different Types
A common requirement is to translate data from one data source type, to another object type. There are multiple scenarios where this is useful, but to give one specific example, most user interface patterns, such as Model View Controller (MVC) or Model View ViewModel (MVVM), use the concept of a view model, where the view model is a special object with metadata, custom shape, and/or validation suited to the user interface. Listing 2-6 shows how to create a projection from the data source type into another type.
Listing 2-6: Custom Projection on a Different Type
class ArtistViewModel { public string ArtistName { get; set; } public string Song { get; set; } } static void CreatingCustomProjectionsOnDifferentType() { List<MusicalArtist> artistsDataSource = GetMusicalArtists(); IEnumerable<ArtistViewModel> artistsResult = from artist in artistsDataSource select new ArtistViewModel { ArtistName = artist.Name, Song = artist.LatestHit }; Console.WriteLine("\nCustom Projection On a Different Type"); Console.WriteLine( "-------------------------------------\n"); foreach (ArtistViewModel artist in artistsResult) { Console.WriteLine( "Artist Name: {0}\nSong: {1}\n", artist.ArtistName, artist.Song); } }
The ArtistViewModel class at the top of Listing 2-6 is the object to create the new projection into. You can see the select clause instantiates an ArtistViewModel instead of the range variable type shown in the previous example. This example also shows that the names of the properties from the projection type and the range variable type don’t have to be the same. The next section shows how to create custom projections that are shaped any way you want via an object called Anonymous Type.
Projecting Anonymous Types
The projections have shown so far rely on an existing type. This is often what you want and works well. However, sometimes you don’t need a pre-defined type and shouldn’t be forced to create one. Times, when this might be useful, is when a query becomes too complex and you want to break it into pieces to make it more maintainable and understandable. Another situation might be if you’re calculating intermediate results for further processing or reshaping data from different data sources into the same object type so they can be combined and processed as a whole. With what you’ve learned so far, you would have to create a custom type specifically for the algorithm being built. However, there’s an easy way, via what is called an Anonymous Type. An Anonymous Type is a type that you can define and instantiate at the same time without a name. Listing 2-7 shows how to create a project using an anonymous type.
Listing 2-7: Projecting Anonymous Types
private static void ProjectingAnonymousTypes() { List<MusicalArtist> artistsDataSource = GetMusicalArtists(); var artistsResult = from artist in artistsDataSource select new { Name = artist.Name, NumberOfAlbums = artist.Albums.Count }; Console.WriteLine("\nProjecting Anonymous Types"); Console.WriteLine( "--------------------------\n"); foreach (var artist in artistsResult) { Console.WriteLine( "Artist Name: {0}\nNumber of Albums: {1}\n", artist.Name, artist.NumberOfAlbums); } }
Listing 2-7 shows two new syntax items you should know: an anonymous type and the var keyword. You can see the anonymous type as part of the select clause with the new keyword followed by curly braces. Notice that the anonymous type does not have a type name – it’s anonymous. The anonymous type does have properties, Name, and NumberOfAlbums. You can give an anonymous type any properties you want. The type of these properties is inferred by the expression assigned to the property. In Listing 2-7 the Name property is a string because of artist.Name is a string and NumberOfAlbums is an int because of the result of the expression, artist.Albums.Count, is an int. With a projection based on anonymous types, you need a variable that’s capable of holding the results and that’s where the var keyword comes in.
Previous examples showed how the results of a query are assigned to a variable of type IEnumerable<T> where T is the type resulting from the select clause projection. In those cases, the type was obvious, as in new MusicalArtist. However, anonymous types don’t have an explicit name or pre-defined type definition. This begs the question, “What is T for an anonymous type?” Behind the scenes, the C# compiler creates a mangled name in Intermediate Language (IL), but you’ll never know what it is unless you look at the IL. You can’t reuse that mangled name because it isn’t guaranteed to be the same between compilations, nor should you use it because that would defeat the purpose of anonymous types as something you can quickly create on-the-fly without defining a type. If you need the type, then create a pre-defined type and don’t use an anonymous type. That said since there’s no way to know what T in IEnumerable<T> will be, you need to make the variable holding the query result type var. The var type is strongly typed and must be assigned at the same time it is declared. Since var is strongly typed, you can’t assign a value of another type after the var typed variable is instantiated. i.e. You would never be able to assign a variable of type int to artistsResult in Listing 2-7 because artistsResult is strongly typed as an IEnumerable of some anonymous type. Also, notice the variable artist in the for each loop is type var, which is necessary because each item of the artistsResultcollection is an anonymous type.
Now you know how to create projections on anonymous types and assign the results to variables of type var.
Summary
In this lesson, you learned about the collection that will be used in this and later lessons, the collection of MusicalArtist. You saw how a simple projection on this collection is done by just specifying the range variable in the select clause. The lesson went further to show you how to create custom projections, projections on a different type, and projections with anonymous types. When learning about projections on anonymous types, you learned what an anonymous type is and how the results of a query on anonymous types are assigned to variables of type var.
Now that you know how to create projections, your next step in this tutorial, [Coming Soon], is to learn how to filter results with the where clause.