Table of Contents
- Chapter 5. Essential Language Features
- Summary
- 5.1 Essential C# Features
- 5.1.1 Using Automatically Implemented Properties
- 5.1.2 Using Object and Collection Initializers
- 5.1.3 Extension Methods
- 5.1.4 Applying Extension Methods to an Interface
- 5.1.5 Creating Filtering Extension Methods
- 5.1.6 Using Lambda Expressions
- 5.1.7 Using Automatic Type Inference
- 5.1.8 Using Anonymous Types
- 5.1.9 Performing Language Integrated Queries
- 5.1.9.1 Understanding Deferred Linq Queries
- 5.1.9.2 Repeatedly Using a Deferred Query
- 5.2 Understanding Razor Syntax
Chapter 5. Essential Language Features
Summary
- Overview of key C# language features, e.g. Properties, Extention, etc.
- Combine feature into LINQ, which will be used to query data
- Razor View Engine
5.1 Essential C# Features
5.1.1 Using Automatically Implemented Properties
Regular Property
C# Property (a language feature, class member) decouple data (i.e. content) from how it's set and retreived
e.g. Defining a Property
public class Product
{
private string name; // Data
public string Name // Property
{
get { return name; } // Getter
set { name = value; } // Setter
}
}
- where
value
is a special variable represents the assigned value when setting value usingProduct1.Name = valueName
e.g. Consuming a Property
using System;
namespace automatically_implemented_properties
{
class Program
{
static void Main(string[] args)
{
// create a new Product object
Product myProduct = new Product();
// set the property value using Setter
myProduct.Name = "Kayak";
// get the property
string produtName = myProduct.Name;
Console.WriteLine("Product name: {0}", produtName);
}
}
public class Product
{
// ... Implemented above
}
}
Summary:
- Public Property class separate setting/getting from data (proviate variable)
- Property class has getter & setter to be executed.
Problem of Regular Property format:
- If there are many data variable in a class, then there will be too many (too verbose) Property members.
Automatically Implemented Property (i.e. Automatic Property)
For simple property as shown below
public class Product {
private int productID;
public int ProductID {
get { return productID; }
set { productID = value; }
}
}
can be transferred to Automatic Property as shown below
public class Product
{
public int ProductID { get; set; }
...
}
Key points for using automatic properties:
- Don't define the bodies of getter and setter
- Don't define the filed that property is backed by (i.e. variable
productID
for propertyProductID
) - You can revert from an automatic to a regular property any time in future development
e.g. Reverting automatic to regular property (Name
)
public class Product
{
// Automatic Property
public int ProductID { get; set; }
// Regular property
private string name;
public string Name
{
get { return ProductID + name; }
set { name = value; }
}
}
5.1.2 Using Object and Collection Initializers
Object and Collection Initializer can be used to simplify the process of (constructuction of a new object + assign values to properties)
e.g. object creation and value assignment w/o initializer
static void Main(string[] args)
{
// create a new Product object
Product myProduct = new Product();
// set the property value
myProduct.ProductID = 100;
myProduct.Name = "Kayak";
myProduct.Description = "A boat for one person";
myProduct.Price = 275M;
myProduct.Category = "Watersports";
// process the property
ProcessProduct(myProduct);
}
private static void ProcessProduct(Product prodParam)
{
// ... statements to process product in some way
}
e.g. create object and assign value with initializer
static void Main(string[] args)
{
// create a new Product object using initializer and method on it directly
ProcessProduct(new Product // {} behind constructor is initializer
{
ProductID = 100,
Name = "Kayak",
Description = "A boat for one person",
Price = 275M,
Category = "Watersports"
});
}
private static void ProcessProduct(Product prodParam)
{
// ... statements to process product in some way
}
where:
{
ProductID = 100, Name = "Kayak", ...
}
This is the initializer
- initializer: Braces
{}
after object constructor, with its values. Values are directly supplied as part of construction process - Result of
new Product {...}
is an instance ofProduct
class. It can then be directly supplied toProcessProduct
method.
e.g. Same feature is used to initialize contents of collection and arrays during construction
static void Main(string[] args) {
string[] stringArray = { "apple","oragne", "plum" };
List<int> intList = new List<int> {10, 20, 30, 40};
Dictionary<string, int> myDict = new Dictionary<string, int> {
{"apple", 10},
{"orange", 20},
{"plum", 30}
};
}
5.1.3 Extension Methods
Extension methods provide way to add methods to a class that cannot be modified (e.g. 3rd party)
e.g. given following class that can not be modified
public class ShoppingCart {
public List<Product> Products { get; set; }
}
We can define extension for adding functionality without changing ShoppingCart
class
public static class MyExtensionMethod
{
public static decimal TotalPrices(this ShoppingCart cartParam)
{
decimal total = 0;
foreach (Product prod in cartParam.Products)
{
total += prod.Price;
}
return total;
}
}
Syntax:
this
keyword in front of 1st parameter marksTotalPrices
method as an extension method forShoppingCart
classShoppingCart
(first param) tells .NET which class the extension method can be applied to (i.e.ShoppingCart
class will be extended)- Extention method cannot break access rule of extended classes.
public
,private
, etc. still holds
e.g. Using extension
static void Main(string[] args)
{
// create and populate Shopping Cart
ShoppingCart cart = new ShoppingCart
{
Products = new List<Product>
{
new Product {Name = "Kayak", Price = 275M},
new Product {Name = "Lifejacket", Price = 48.95M},
new Product {Name = "Soccer ball", Price = 19.50M},
new Product {Name = "Corner flag", Price = 34.95M}
}
};
// get total value of the products in cart
decimal cartTotal = cart.TotalPrices();
Console.WriteLine("Total: {0:c}", cartTotal);
where:
cart
is object of extended class- Use extension by
extendedObject.extentionMethod()
(i.e. call it as if the extension is original method of extended class)
5.1.4 Applying Extension Methods to an Interface
Extension methods can also be applied to interface, which allows developer to call the extension method on all classes that implement the interface.
Step 1. Modifying data class to implement interface IEnumerable<>
e.g. 5.12 Modifying ShoppingCart Class, so become implementation of interface IEnumerable<Product>
. Previously it's class containing List<Product>
as member only
using System.Collections;
using System.Collections.Generic;
namespace automatically_implemented_properties
{
public class ShoppingCart : IEnumerable<Product>
{
public List<Product> Products { get; set; }
public IEnumerator<Product> GetEnumerator()
{
return Products.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
}
Step 2. Modify extention method to deal with interface
e.g. 5.13 An Extension Method That Works on an Interface
public static class MyExtensionMethod
{
public static decimal TotalPrices(this IEnumerable<Product> productEnum)
{
decimal total = 0;
foreach (Product prod in productEnum)
{
total += prod.Price;
}
return total;
}
}
where:
- 1st parameter is changed to interface
IEnumerable<Product>
, so this extension can work on every class that implement the interface
Step 3. Modify main program to use MyExtensionMethod
to interact with different classes implementing the same interface
static void Main(string[] args)
{
// create and populate Shopping Cart, implementing IEnumerable<Product> interface
IEnumerable<Product> products = new ShoppingCart
{
Products = new List<Product>
{
new Product {Name = "Kayak", Price = 275M},
new Product {Name = "Lifejacket", Price = 48.95M},
new Product {Name = "Soccer ball", Price = 19.50M},
new Product {Name = "Corner flag", Price = 34.95M}
}
};
// create and populate an array of Product objects, implementing IEnumerable<Product> interface
Product[] productArray =
{
new Product {Name = "Kayak", Price = 275M},
new Product {Name = "Lifejacket", Price = 48.95M},
new Product {Name = "Soccer ball", Price = 19.50M},
new Product {Name = "Corner flag", Price = 34.95M}
};
// get total value of the products in cart, using extension method
decimal cartTotal = products.TotalPrices();
decimal arrayTotal = productArray.TotalPrices();
Console.WriteLine("Total: {0:c}", cartTotal);
Console.WriteLine("Array Total: {0:c}", arrayTotal);
}
5.1.5 Creating Filtering Extension Methods
- An extension method that operates on an
IEnumerable<T>
and that also return anIEnumerable<T>
can useyield
keyword to apply selection criteria to items (in code) to produce a reduced set of results. It's core technique of LINQ - Extension methods can be chained together, like LINQ
Step 1. Create filtering extension method
e.g. 5.15 A Filtering Extension Methods
public static class MyExtensionMethod
{
public static decimal TotalPrices(this IEnumerable<Product> productEnum)
{
// ...
}
public static IEnumerable<Product> FilterByCategory(this IEnumerable<Product> productEnum,
string categoryParam)
{
foreach (Product prod in productEnum)
{
if (prod.Category == categoryParam)
{
yield return prod;
}
}
}
}
where:
- Entension method
FilterByCategory
takescategoryParam
as second parameter for filtering - Those
Product
objects whoseCategory
property matches the paramter are returned withinIEnumerable<Product>
Step 2. Modify main program to use filtering extension method (unchained and chained)
static void Main(string[] args)
{
// create and populate Shopping Cart, implementing IEnumerable<Product> interface
IEnumerable<Product> products = new ShoppingCart
{
Products = new List<Product>
{
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
}
};
foreach (Product prod in products.FilterByCategory("Soccer"))
{
Console.WriteLine("Name: {0}, Price {1:c}", prod.Name, prod.Price);
}
// Chained extension methods
decimal total = products.FilterByCategory("Soccer").TotalPrices();
Console.WriteLine("Filtered total: {0:c}", total);
}
5.1.6 Using Lambda Expressions
Purpose of this section: use Lambda Expression or delegate to make filter method more general (i.e. not hardwritten as equal function and can be swapped in runtime)
Using Delegate in extension method
Step 1. Create a filter function that accept delegate Func<Product, bool> selectorParam
. Details about how Delegate is defined can refer to Func<T,TResult> Delegate
public static IEnumerable<Product> Filter(this IEnumerable<Product> productEnum,
Func<Product, bool> selectorParam)
{
foreach (Product prod in productEnum)
{
if (selectorParam(prod))
{
yield return prod;
}
}
}
Step 2. Modify main program to use delegate as param for filtering
Func<Product, bool> categoryFilter = delegate(Product prod)
{
return prod.Category == "Soccer";
};
IEnumerable<Product> filteredProducts = products.Filter(categoryFilter);
foreach (Product prod in filteredProducts)
{
Console.WriteLine("Name: {0}, Price {1:c}", prod.Name, prod.Price);
}
Using Lambda Expression in extension method
By MSDN, a lambda expression that has 1 parameter and returns a value can be converted to a Func<T,TResult>
delegate. So, switch from delegate to lambda is simple
Step 3: Modify delegate to lambda
Func<Product, bool> categoryFilter = prod => prod.Category == "Soccer";
IEnumerable<Product> filteredProducts = products.Filter(categoryFilter);
Step 4: Further simplify lambda to use a lambda expression without Func
. (And chain lambda)
IEnumerable<Product> filteredProducts = products.Filter(prod =>
prod.Category == "Soccer" ||
prod.Price > 20);
5.1.7 Using Automatic Type Inference
Type inference/Implicit typing: Compiler will infer the type from the code (i.e. guess datatype based on expression)
var myVariable = new Product {
Name = "Kayak",
Category = "Watersports",
Price = 275M
};
string name = myVariable.Name; // legal
int count = myVariable.Count; // error
As shown above, compilier will generate error
5.1.8 Using Anonymous Types
Combining object initializer and type inference, simple data-storage object can be created w/o defining class or structure clearly
var myAnonType = new {
Name = "MVC", // string type inference
Category = "Pattern"
};
where myAnonType
is an anonymously typed object (strongly-typed), where its definition will be created automatically by compiler
Note:
- Compiler generates class based on name (
Name
,Category
) and type (string
) of parameter of initializer - Hence, 2 anonymously typed objects (having same property name & type) will be assigned to same automatically generated class.
Array of anonymously typed object can also be created using object initializer new[] {...}
- The array can be enumerated through, even if it's anonymously typed
var oddsAndEnds = new[] {
new { Name = "MVC", Category = "Pattern" },
new { Name = "Hat", Category = "Clothing" },
new { Name = "Apple", Category = "Fruit" }
};
foreach (var item in oddsAndEnds) { // We can enumerate through anonymously type object array
Console.WriteLine("Name": {0}", item.Name);
}
5.1.9 Performing Language Integrated Queries
All of previously described C# features are used together in LINQ.
- Automatically Implemented Properties
- Object and Collection Initializers
- Extension Methods
- Extension Methods to Interface
- Filtering Extension Methods
- Lambda Expressions
- Automatica Type Inference
- Anonymous Types
LINQ = SQL-like syntax for querying data in classes
- LINQ has query syntax and dot-notation syntax
- e.g. having a collection of
Product
objects, want to find 3 products with highest prices, and print out their names and prices
e.g. 5.26 Querying without LINQ
using System;
using System.Collections.Generic;
class Program {
static void Main(string[] args)
{
Product[] products =
{
new Product {Name = "Kayak", Category = "Watersports", Price = 275M},
new Product {Name = "Lifejacket", Category = "Watersports", Price = 48.95M},
new Product {Name = "Soccer ball", Category = "Soccer", Price = 19.50M},
new Product {Name = "Corner flag", Category = "Soccer", Price = 34.95M}
};
// -------- Sorting without LINQ ------------
// define the array to hold results
Product[] results = new Product[3];
// sort the contents of the array
Array.Sort(products, (item1, item2) =>
{
return Comparer<decimal>.Default.Compare(item1.Price, item2.Price);
});
// get the first three items in the array as the results
Array.Copy(products, results, 3);
// -------------------------------------------
// print out the name
foreach (Product p in results)
{
Console.WriteLine("Item: {0}, Cost: {1}", p.Name, p.Price);
}
}
e.g. 5.27 Using LINQ query syntax
var results = from product in products
orderby product.Price descending
select new
{
product.Name,
product.Price
};
e.g. 5.27 Using LINQ dot-notation syntax
var results = products
.OrderByDescending(e => e.Price)
.Take(3)
.Select(e => new {e.Name, e.Price});
- LINQ dot-notation syntax is not as good-look as query syntax. But not all LINQ feature have corresponding C# keywords.
- For advanced LINQ usage, there is need to swtich to using extension methods.
- How LINQ Extention works: each LINQ extension methods is applied to an
IEnumerable<T>
and returns anIEnumerable<T>
. Hence, LINQ extension methods can be chained together to form complex queries. - Details about LINQ can refer to MSDN or Oreilly book like C# 9.0 in Nutshell