When we say Language Integrated Query, we might think that it is already integrated into the programming language, just as we write SQL queries in our application. So what is the difference or additional features that we are going to get in LINQ? How is LINQ going to make our programming life easier? Also, I am sure that we all want to know how the new feature, LINQ, is making use of the other new features of C# 3.0. We'll see many of those in this book.
Language Integrated Query is a new feature in Visual Studio 2008 that extends the query capabilities, using C# and Visual Basic. Visual Studio 2008 comes with LINQ provider assemblies that enable the use of Language Integrated Queries with different data sources such as in-memory collections, SQL relational database, ADO.NET Datasets, XML documents and other data sources.
In Visual Studio 2008, Visual C# and Visual Basic are the languages that implement the LINQ language extensions. The LINQ language extensions use the new Standard Query Operators API, which is the query language for any collection that implements IEnumerable<T>
. It means that all collections and arrays can be queried using LINQ. The collections classes simply needs to implement IEnumerable<T>
, to enable it for LINQ to query the collections.
The following figure shows the architecture of LINQ, which can query different data sources using different programming languages:
LINQ to Objects |
Refers to the use of LINQ queries to access in-memory data structures. We can query any type that supports |
LINQ to SQL |
LINQ to SQL is used for managing the relational data as objects. It is part of the ADO.NET family of technologies. LINQ to SQL translates the Language Integrated Queries in the object model to SQL and sends them to the database for execution. When the database returns the result, LINQ to SQL translates them back to objects that we can work with. LINQ to SQL also supports stored procedures and user-defined functions in the database. |
LINQ to Datasets |
LINQ to Datasets makes it easier to query the data cached in Datasets. A Dataset is disconnected, consolidated data from different data sources. |
LINQ to Entities |
The Entity Data Model is a conceptual data model that can be used to model the data so that applications can interact with data as entities or objects. Through the Entity Data Model, ADO.NET exposes the entities as objects. |
LINQ to XML |
LINQ to XML provides the in-memory document modification capabilities of the Document Object Model and supports LINQ queries. Using LINQ to XML, we can query, modify, navigate, and save the changes of an XML document. It enables us to write queries to navigate and retrieve a collection of elements and attributes. It is similar to XPath and XQuery. |
LINQ to SQL supports LINQ queries against a relational database for data retrieval and manipulation. Currently in software development, we use Relational data for most of the applications, and we depend on database queries and objects, in some way or the other. The applications use APIs to process or get details from the database by passing queries as strings, or calling database objects by passing parameters. So what is the purpose of LINQ here? Presently scenario, we see a lot of applications, especially multi-tier applications, having a separate data access layer and a business logic layer. If we take the business logic layer, we must have lots of entities or objects to hold the information. These objects represent the database table rows in the form of objects. We use object references similar to primary keys in the database to identify a set of information.
To get data from a relational database to an application, the programmer ends up creating two different types of objects, but in different formats. The other disadvantage is that the programmer has to pass relational database queries as text strings from the application, then get it executed from the relational database, and pass on the information to the application objects or entity objects. We will not be able to validate the query, which is passed as text strings, until it gets compiled and executed at the database server. We cannot make use of IntelliSense or the debugging feature of the development environment to validate the queries.
Using LINQ to SQL, we can manage relational data as objects at run time using the querying facility. The LINQ queries get translated to SQL queries for the database to execute the queries, and the results are again translated to objects for the application to understand. LINQ uses the same connection and transaction features of current .NET framework for connecting to the database and manipulating the data under transaction. We can also make use of the IntelliSense feature for validating LINQ queries. To represent relational data, we need to create classes for the entities. For creating the entity classes, we need to specify some custom attributes to the class declaration. This is to make the entity objects have similar properties as that of the database objects.
LINQ to XML is a new concept that supports queries against XML data. This is similar to the LINQ queries with relational data, but here the source of data is XML. Using LINQ to XML, we can manipulate XML documents and elements, create or save XML documents, and traverse through the XML tree.
When we use XML, we will be talking about elements and attributes. XML trees are composed of only attributes and elements. If we look at W3C DOM, the XML document object contains the whole XML tree in it. All elements and attributes are created within the context of the XML document. For example, the .NET 2.0 way of creating the XML element, Icecream
using XML Document object model is shown below:
XmlDocument xdoc = new XmlDocument(); XmlElement Icecream = xdoc.CreateElement("Icecream");
This is an unnecessary dependency that we have to follow.
.NET 3.0 avoids creating XML document objects and we can directly create elements and attributes. For example, the following code is used for creating the Icecream
element using XElement
.
XElement Icecream = new XElement("Icreceram", "Chocolate Fudge");
The XML document feature is still supported for adding information like processing instructions and comments to XML.
LINQ to XML has better features than DOM for handling names and namespaces, fragments, loading XML, inner XML, annotations, and schema information.
Functional construction is a new approach taken by LINQ to XML for constructing XML elements. Using functional construction, we can create the entire XML tree in a single statement. XElement
is the main class used for the construction. It has different constructors by which we can construct an XML tree. We will see this in detail later when we discuss functional construction. This is one of the important features of LINQ to XML.
LINQ to XML has a set of classes under its hierarchy structure for constructing and manipulating XML data. XElement, XNode, XName, XContainer, XAttribute
and XText
are some of the classes in the hierarchy. XElement
is the main class for building and manipulating the XML tree.
There are various features in C# 3.0 which support LINQ. They are explained in detail.
Anonymous types are used to define strong types without defining the full class. Anonymous types are strongly typed and checked at compile time. This type is widely used by LINQ, because LINQ returns dynamically-shaped data, whose type is determined by the LINQ query. In LINQ, the types are defined in situations where the types are needed temporarily, or just once. For example, given an ice-cream which has properties like name, flavor, ingredients, price, and fat content, we might sometimes only need the name of the ice-cream and the price. Anonymous type helps us to define a dynamic type containing only the name and price of the Icecream
object. This is also called shaping the result of the data being queried into a different structure or format than the original data source.
For example, following is a class that is defined for an application, and objects created and assigned some data to it.
public class Icecream { public string name; public string ingredients; public string totalFat; public string cholesterol; public string totalCarbohydrates; public string protein; public double price; } List<Icecream> icecreamsList = new List<Icecream> { new Icecream { name="Chocolate Fudge Icecream", ingredients="cream, milk, mono and diglycerides...", totalFat="20g", cholesterol="50mg", totalCarbohydrates="35g", protein="4g", price=10.5 }, new Icecream { name="Vanilla Icecream", ingredients="vanilla extract, guar gum, cream...", totalFat="16g", cholesterol="65mg", totalCarbohydrates="26g", protein="4g", price=9.80 }, new Icecream { name="Banana Split Icecream", ingredients="Banana, guar gum, cream...", totalFat="13g", cholesterol="58mg", totalCarbohydrates="24g", protein="6g", price=7.5 } };
I have created a list, containing details of different ice-creams. Now I can use the projection or transformation capabilities of LINQ, and create structure and give a custom shape to something other than the original Icecream
object. I don't have to explicitly define a new class for the new custom shaped structure. Instead, I can use the anonymous type feature to implicitly define a new type with just two properties to represent my custom shaped data.
var IcecreamsWithLessPrice = from ice in icecreamsList where ice.Price < 10 select new { Name = ice.Name, Price = ice.Price }; Console.WriteLine("Ice Creams with price less than 10:"); foreach (var icecream in IcecreamsWithLessPrice) { Console.WriteLine("{0} is {1}", icecream.Name, icecream.Price); }
In this code, I am declaring an anonymous type within the CIT clause in my LINQ query. The anonymous type has only two properties, Name
and Price
, whose property names and values are inferred from the shape of the query.
In the next step, I am referring to the IEnumerable<T>
collection of this anonymous type returned by the query and loop over them and extract the details. This feature gives it a dynamic language-like flexibility.
Object initializers lets you assign values to the properties of an objects at the time of creating the object. Normally in .NET 1.1 and 2.0, we define the class with properties, then create the instance, and then define the values for properties either in constructor or in the function which is using the object. Here, in C# 3.0 we can define the values at the time of creation itself. Consider the following example of class Icecream
with two auto-implemented properties. Auto-implemented properties are the properties without a local variable to hold the property value.
public class Icecream { public string Name { get; set; } public double Price { get; set; } }
Now when I create the new object of type Icecream
, I can directly assign values directly.
Icecream ice = new Icecream { Name = "Chocolate Fudge Icecream", Price = 11.5 };
This is not only for the auto-implemented properties, but I can also assign a value to any accessible field of the class. The following example has a field named Cholestrol
added to it.
public class Icecream { public string Name { get; set; } public double Price { get; set; } public string Cholestrol; }
Now I can assign values to this new field added to the class at the time of creating the object itself.
LINQ query expressions make use of these object initializers for initializing anonymous types. In the Anonymous Types section, as discussed previously, we have a select
query which creates an anonymous type with two properties. The values are also assigned using object initializers.
var IcecreamsWithLessPrice = from ice in icecreamsList where ice.Price < 10 select new { Name = ice.Name, Price = ice.Price };
Collection initializers use object initializers to initialize their object collection. By using a collection initializer, we do not have to initialize objects by having multiple calls. For example, in the Anonymous Types section a little earlier, we created a list named icecreamsList
which is a collection of Icecreams
. All Icecream
objects added to the collection are initialized using the collection initializer, as follows:
List<Icecream> icecreamsList = new List<Icecream> { new Icecream { Name="Chocolate Fudge Icecream", Ingredients="cream, milk, mono and diglycerides...", Cholesterol="50mg", Protein="4g", TotalCarbohydrates="35g", TotalFat="20g", Price=10.5 }, new Icecream { Name="Vanilla Icecream", Ingredients="vanilla extract, guar gum, cream...", Cholesterol="65mg", Protein="4g", TotalCarbohydrates="26g", TotalFat="16g", Price=9.80 }, new Icecream { Name="Banana Split Icecream", Ingredients="Banana, guar gum, cream...", Cholesterol="58mg", Protein="6g", TotalCarbohydrates="24g", TotalFat="13g", Price=7.5 } };
Microsoft introduced the concept of partial classes in .NET 2.0, which allows multiple developers to work on the same class file at the same time. This feature provides a way to split the definition of a class in multiple files. All these files are combined at the time of compilation. This is very helpful in adding new code or new functionality to the class without disturbing the existing class files; partial
is a keyword modifier used for splitting the class.
Partial method is a new feature introduced in .NET 3.0 which is similar to partial classes. Partial methods are a part of partial classes, where the implementer of one part of the class just defines the method, and the other implementer can implement the method. It is not necessary that the second implementer of the class has to implement the method. If the method is not implemented, the compiler removes the method signature and all the calls to this method. This helps the developer to customize the code with his own implementation. It is safe to declare the partial methods without worrying about the implementation. The compiler will take care of removing all the calls to the method. Following is an example for defining and implementing partial methods:
// Defining UpdateItemsList method in Items1.cs file partial void UpdateItemsList(); //Implemeting UpdateItemsList method in Items2.cs file partial void UpdateItemsList() { // The method Implementation goes here }
There are some constraints in using partial methods. They are as follows:
Method declaration must begin with the keyword partial
and the method should return void
.
Methods can have ref
parameters, but not out
parameters.
Methods cannot be virtual
, as they are private
implicitly.
Partial methods cannot be extern
as the presence of a body determines whether they are defining or implementing.
We cannot make a delegate to a partial method.
Implicitly typing variables is a new feature that makes your job easier. The compiler takes care of identifying the type of variables from the value used for initializing the variables. LINQ also make use of this new feature for identifying the type of data that results from the LINQ queries. The programmer need not specify the return type of the querie's result. We normally declare variables by specifying the type of the variable. For example, to declare variables of type integer, string, and array of integers we would be writing it as:
int iCount = 0; string sName = "Hi"; int[] iIntegers = new int[] {1,2,3,4,5,6,7,8,9};
The equivalent of the above declarations using implicit typing would be as follows:
var iCount = 0; var sName = "Hi"; var iIntegers = new int[] {1,2,3,4,5,6,7,8,9};
We have used the keyword var
and a value for initializing the variable. We have not used any type for the variable. In this case, the compiler takes care of defining the variable type from the value assigned to it. The variable iCount
is considered as an integer as the value assigned to it is an integer. So for any variable to be an implicitly typed variable, it should have an initializing value assigned to it, and it cannot have null value assigned to it. As the type is defined by the initial value, the initial value cannot be changed over the lifetime of the program. If we do so, we will end up getting an error while compiling.
We can also use implicit typing for declaration of collections. This is very useful when instantiating complex generic types. For example, the normal way of declaring a collection which holds item numbers is given as follows:
List<int> itemNumbers = new List<int>(); itemNumbers.Add(100005); itemNumbers.Add(100237); itemNumbers.Add(310078);
The equivalent for the above declaration, using implicit typing, would be as follows:
var itemNumbers = new List<int>(); itemNumbers.Add(100005); itemNumbers.Add(100237); itemNumbers.Add(310078);
In all the previous cases, the implicit type declaration has some restrictions and limitations:
We should use only the var
keyword with an initializer for the declaration.
The intializer cannot be a null value.
The initializer cannot be an object or collection by itself.
Once initialized, the type cannot be changed throughout the program. Even though implicit typing gives the advantage of not specifying the type of the variable, it is better practice to use typed variable in order to clearly know the type of the variable declared.
Extension methods are static methods that can be invoked using instance method syntax. Extension methods are declared using this
keyword as a modifier on the first parameter of the method. Extension methods can only be declared in static classes. The following is an example of a static class that has the extension method CountCharacters
to count the number of characters in the parameter string:
namespace Newfeatures.Samples { public static class Example { public static int CountCharacters(string str) { var iCount = str.Length; return iCount ; } } }
To test the above extension methods, include the following code into the main method of the program. Now run the application and test it.
static void Main(string[] args) { string[] strings = new string[] {"Name", "Chocolate Fudge Icecream" }; foreach (string value in strings) Console.WriteLine("{0} becomes: {1}",value, Example.CountCharacters(value)); }
In order to define the previous method to be an extension method that can be invoked using the instance method syntax, include the keyword this
as the modifier for the first parameter:
public static int CountCharacters(this string str)
In the Main
method, change the invocation of CountCharacters
to use the instance method syntax making CountCharacters
appear as a method of the string class, as shown:
static void Main(string[] args) { string[] strings = new string[] { "Name", "Chocolate Fudge Icecream"}; foreach (string value in strings) Console.WriteLine("{0} becomes: {1}", value, value.CountCharacters()); }
Extension methods can also be added to generic types, such as List<T>
and Dictionary<T>
, as in the case of normal types.
public static List<T> result<T>(this List<T> firstParameter, List<T> secondParameter) { var list = new List<T>(firstParameter); // required coding return list; }
It is recommended that we use extension methods only when it is really required. It is better to use inheritance, and create a new type by deriving the existing type wherever it is possible. An extension method will not be called if it has the same signature as a method defined in the type. Extension methods are defined at the namespace
level, so we should avoid using it when we create class libraries.
The various types of expressions used in LINQ are explained below.
Anonymous methods in C# 2.0 help us to avoid declaring a named method by writing methods inline with code. This can be used in places where we need the functionality only within the parent method. We cannot reuse the anonymous method code in the other methods, as it is available within the parent method. Following is an example for finding a particular string from a list of strings:
class Program { static void Main(string[] args) { List<string> icecreamList = new List<string>(); icecreamList.Add("Chocolate Fudge Icecream"); icecreamList.Add("Vanilla Icecream"); icecreamList.Add("Banana Split Icecream"); icecreamList.Add("Rum Raisin Icecream"); string vanilla = icecreamList.Find(FindVanilla); Console.WriteLine(vanilla); } public static bool FindVanilla(string icecream) { return icecream.Equals("Vanilla Icecream"); } }
The equivalent anonymous method for the above code would be as follows:
List<string> icecreamList1 = new List<string>(); icecreamList1.Add("Chocolate Fudge Icecream"); icecreamList1.Add("Vanilla Icecream"); icecreamList1.Add("Banana Split Icecream"); icecreamList1.Add("Rum Raisin Icecream"); string vanilla1 = icecreamList1.Find(delegate(string icecream) { return icecream.Equals("Vanilla Icecream"); }); Console.WriteLine(vanilla1);
In the previous example, a method is defined inline and we do not have any external method to find the string.
Now C# 3.0 has a new feature called lambda expression which helps us to avoid the anonymous methods itself. For example, here is the equivalent method with lambda expression for the previous anonymous method.
// Using Lambda Expressions List<string> icecreamList2 = new List<string>(); icecreamList2.Add("Chocolate Fudge Icecream"); icecreamList2.Add("Vanilla Icecream"); icecreamList2.Add("Banana Split Icecream"); icecreamList2.Add("Rum Raisin Icecream"); string vanilla2 = icecreamList2.Find((string icecreamname) =>icecreamname.Equals("Vanilla Icecream")); Console.WriteLine(vanilla2);
A lambda expression is the lambda with the expression on the right side.
(input parameters separated by commas) => expression
We can also specify the types of the input paramaters; for example, (int x, int y) => x > y
.
There is another type called statement lambda that consists of a number of statements enclosed in curly braces.
The following is an example of lambda expression with an extension method. It uses the where
extension method to get the total number of integers, and the list of integers (which are less than 10) in the array of integers.
var numbers = new int[] { 1, 10, 20, 30, 40, 5, 8, 2, 9}; var total = numbers.Where(x => x < 10); Console.WriteLine("Numbers less than ten: " + total.Count()); foreach(var val in total) Console.WriteLine(val);
LINQ provides the ability to treat expressions as data at runtime using the new type Expression<T>
which represents an expression tree. This is an in-memory representation of the lambda expression. Using this, we can modify the lambda expressions through code. By getting these expressions as data, we can also build the query statements at runtime. System.Expressions
is the namespace used for this. There are some limitations to the lambdas. They are as follows:
It must contain the same number of parameters as the delegate type.
Each input parameter in the lambda must be implicitly convertible to its corresponding delegate parameter.
The return value of the lambda must be convertible to the delegate's return type.
Currently, we are actually working with two different languages when we retrieve data from the database and work with our front-end applications. One would be for front-end application development and the other is the SQL for retrieving data from the database. These SQL queries are embedded into the application code as strings, so we don't get the facility of the compiler checking the query statements in quotes.
In C# 3.0, we have LINQ which gives the benefit of strong type checking. Also, we don't need to depend on SQL queries and writing it within quotes. LINQ is similar to relational database queries. Query expressions provide the language integrated syntax for queries.
The query expression begins with a from
clause and ends with a select
or a group
clause. The from
clause can be followed by many from, let
, or where
clauses.The from
clause is a generator, the let
clause is for computing the value, the where
clause is for filtering the result and select
or group
specifies the shape of the result. There are other operators like orderby
. For example, the query below is to select ice-creams with price less than 10.
from Icecream Ice in Icecreams where Ice.Price <= 10.0 select Ice
Following are the syntax for the query expressions:
query-expression:
from-clause
query-body
from-clause:
from
typeopt identifier in
expression join-clausesopt
join-clauses:
join-clause
join-clauses join-clause
join-clause:
join
typeopt identifier in
expression on
expression equals
expression
join
typeopt identifier in
expression on
expression equals
expression into
identifier
query-body:
from-let-where-clausesopt orderby-clauseopt select-or-group-clause
query- continuationopt
from-let-where-clauses:
from-let-where-clause
from-let-where-clauses from-let-where-clause
from-let-where-clause:
from-clause
let-clause
where-clause
let-clause:
let
identifier = expression
where-clause:
where
boolean-expression
orderby-clause:
orderby
orderings
orderings:
ordering
orderings
, ordering
ordering:
expression ordering-directionopt
ordering-direction:
ascending
descending
select-or-group-clause:
select-clause
group-clause
select-clause:
select
expression
group-clause:
group
expression by
expression
query-continuation:
into
identifier join
-clausesopt
query-body
C# 3.0 actually translates the query expressions into invocation of methods like where, select, orderby, groupby, thenby, selectmany, join, cast, groupjoin
that have their own signatures and result types. These methods implement the actual query for execution. The translation happens as a repeated process on the query expressions, until no further translation is possible. For example, the following query:
from Icecream Ice in Icecreams where Ice.Cholestrol == "2mg" select Ice
...is first translated into:
from Icecream Ice in Icecreams.Cast<Icecream>() where Ice.Cholestrol == "2mg" select Ice
...the final translation would be as follows:
Icecreams.Cast<Icecream>().Where(Ice => Ice.Cholestrol == "2mg")
The following query:
from Ice in Icecreams group Ice.Name by Ice.Cholestrol
...is translated into the following:
Icecreams.GroupBy(Ice => Ice.Cholestrol, Ice =>Ice.Name
Let us see how we can make use of these queries with in-memory collections. The System.Linq
namespace has all the standard query operators. We have to use this namespace for writing queries. Create a class, Icecream
as follows:
public class Icecream { public string name; public string ingredients; public string totalFat; public string cholesterol; public string totalCarbohydrates; public string protein; public double price; }
Using the above Icecream
class, create list of ice-creams and assign that to a list variable. We will see how we can easily retrieve information from this list using queries.
List<Icecream> icecreamsList = new List<Icecream> { new Icecream("Chocolate Fudge Icecream", "cream, milk, mono and diglycerides...", "20g", "50mg", "35g", "4g", 10.5), new Icecream ("Vanilla Icecream", "vanilla extract, guar gum, ream...", "16g", "65mg", "26g", "4g", 9.80 ), new Icecream ("Banana Split Icecream", "Banana, guar gum, cream...", "13g", "58mg", "24g", "6g", 7.5) };
The following query will return the name and price of the ice-creams with a price less than or equal to 10. In this query we have not specified any type for variables; it's all implicit. Even if we want to specify the type, it is not easy to identify the type of the value returned for the query.
var icecreamswithLeastPrice = from Ice in icecreamsList where Ice.price <= 10 select new { Ice.name, Ice.price }; Console.WriteLine("Icecreams with least price: "); foreach (var ice in icecreamswithLeastPrice) { Console.WriteLine(ice.name + " " + ice.price); }
Let us see how we can leverage the feature of lambda expressions here. For example, we will find out the list of ice-creams with a lower price using the lambda expressions. Include the following code to the main method of the program after creation of the icecreamsList
.
var count = icecreamsList3.Count<Icecream>(Ice => Ice.price <=0); Console.WriteLine("Number of Icecreams with price less than ten: {0} ", count);
The above lambda expression will return the total number of ice-creams with a price less than or equal to 10.
Expression trees are an in-memory representation of a lambda expression. Using this we can modify and inspect the lambda expressions at runtime using expression trees in the System.Linq.Expressions
namespace. Lambda expressions are compiled as code or data, depending on the context they are used in. If we assign a lambda expression to a variable of type delegate
, then the compiler will generate the corresponding executable code; but we assign the lambda expression to a variable of the generic type Expression<T>
, so the compiler won't create the executable code, but will generate an in-memory tree of objects that represents the structure of the expression. These structures are known as expression trees.
For example, consider the following lambda expression using delegate:
Func<int, int> func = x => x + 5;
This code is compiled as executable and can be executed as follows:
var three = func(1);
The same delegate is no longer compiled as executable, but compiled as data if we define the delegate as expression tree:
Expression<Func<int, int>> expression = func => x + 5;
To use this expression in the application, it has to be compiled and invoked as follows:
var originalDelegate = expression.Compile(); var three = originalDelegate.Invoke(2);
Each node in the expression tree represents an expression. If we decompose the expression, we can find out how the expression tree represents the lambda expressions. Following is the sample code to decompose the previous expression:
ParameterExpression parameter = ParameterExpression)expression.Parameters[0]; BinaryExpression operation = (BinaryExpression)expression.Body; ParameterExpression left = (ParameterExpression)operation.Left; ConstantExpression right = (ConstantExpression)operation.Right; Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}", parameter.Name, left.Name, operation.NodeType, right.Value);
The output of the above decomposition would be:
Decomposed expression: func => func Add 5
Expression trees are implemented in the System.Query.dll
assembly under the System.Linq.Expressions
namespace. The abstract class Expression
provides the root of a class hierarchy used to model expression trees. The Expression
class contains static factory methods to create expression tree nodes of various types.
There are many different abstract classes used to represent the different types of elements in an expression. These classes are derived from the non-generic version of Expression
. The following table examines some abstract classes derived from the Expression
class.
Class |
Description |
Parameters |
---|---|---|
lambda expression |
This is the bridge between the generic |
Its main properties are body and parameters. Body—represents the body of the expression. Parameters—represents the list of parameters it uses. |
constant expression |
This represents the constant values that appear in the expression. |
Value is it's main property, which returns the constant value in the expression. |
parameter expression |
Represents a named parameter expression. Values must be passed to parameter to evaluate the expression. |
Name is the property of the parameter expression to represent the name of the parameter. |
unary expression |
Represents an expression that has the unary operator. |
The main property of this class is operand which is associated with the operand in expression. |
binary operator |
This is to represent the binary operators, like sum, multiplication, and many others. |
The main properties of this class are left and right which provides access to the left and right operand in the expression. |
method call expression |
This represents the method call in an expression. |
The main properties of this class are: Method—metadata information associated to the method to be called. Object—the object to which the method call will be applied. Parameters—to represent the arguments used in the method. |
conditional expression |
Represents an expression that has a conditional operator. |
The main properties of conditional expression are.
|
In this chapter, we have seen an overview of Language Integrated Query. The architecture diagram explains the different types of LINQ which are used for querying data from different sources of data. Also, we have seen some of the new features of C# 3.0 and above in relation to LINQ. This chapter also explained some of the new features introduced in C# 3.5 such as partial methods, expressions and anonymous types with some examples for each of those. We will be looking into details of LINQ features and its usability in the coming chapters.
18.188.143.21