Archive for the ‘.net’ tag
Lambda Expressions and Extension Methods
Please note: This is a long and very technical discussion of some of the .NET language features we use in Workspaces. It is not Sage 200 specific.
Please let us know what you think of the technical level of the article – we’d love to get feedback on how we’re doing that helps us improve the value of this blog!
Lambda Expressions and Extension Methods are powerful new features in .NET 3.0 and are often used extensively when writing LINQ. In fact, these features are little more than syntactic sugar; they don’t enable functionality that wasn’t previously possible, but they do allow code that would previously have been quite complex to be written in a succinct, clear and elegant way.
While they are quite straightforward to use, they are not particularly easy to explain or intuitive to understand. This document aims to explain how these language features work.
Error checking has been excluded from all examples for brevity and clarity – the code is not intended to be production quality. Some of the examples are also quite weak; they are intended to demonstrate use of the language, not necessarily good examples of when to use the features!
Many of the examples make use of IEnumerable collections. This was to allow complete examples to be given, including the data types. When using LINQ to SQL the collection types are based on the IQueryable interface, but this makes little difference to the code using the collections.
Simple Extension Methods
At their simplest, Extension Methods allow the definition of a class to be extended with additional methods; to add new methods to the class that appear identical to member functions.
For example, given a very simple “bank account” class:
class Account
{
public Account(string name, decimal balance)
{
this.name = name;
this.balance = balance;
}
public string name;
public decimal balance;
}
We may wish to create a function that allows funds to be deposited in the account.
This could be added as a member function, or as a public function taking the account as a parameter:
public void Deposit(Account account, decimal deposit)
{
account.balance += deposit;
}
// ....
// To deposit £10~~
Deposit(account, 10);
Alternatively, we could write this as an Extension Method.
static class Extensions
{
public static void Deposit(this Account account, decimal deposit)
{
account.balance += deposit;
}
}
// ....
// To deposit £10
myAccount.Deposit(10);
The defining characteristics of the extension method are:
• It’s a static function in a static class
• Use of the “this” keyword in the parameter list
Note that the method is not a “true” member function; private members of the class cannot be accessed, for example.
Extending Collections
Extension methods are not limited to concrete types; they can be used to extend anything that the compiler recognises as a strong type such as a collection of objects:
public List accounts = … ;
public static bool Deposit(this IEnumerable accounts, string accountName, decimal deposit)
{
foreach ( Account account in accounts )
{
if (account.name == accountName)
{
account.balance += deposit;
return true;
}
}
return false;
}
// ....
// To deposit £10 in Steve’s account
accounts.Deposit(“Steve”, 10);
Alternatively, we might want a method that finds all of the overdrawn accounts:
public static IEnumerable GetOverdrawnAccounts(this IEnumerable accounts)
{
foreach (Account account in accounts)
{
if (account.balance < 0)
yield return account;
}
}
Note the use of the “yield” keyword, which essentially says “add the item to the return set and continue”
Extension Methods and Delegates
A very common pattern with Extension Methods is to have a function delegate as one of the parameters, allowing function logic to be determined by the caller.
For example, we could rewrite our “GetOverdrawnAccounts” example as:
public static IEnumerable GetAccounts(this IEnumerable accounts, Func predicate)
{
foreach (Account account in accounts)
{
if (predicate(account))
yield return account;
}
}
static public bool IsOverdrawn(Account a)
{
return a.balance < 0;
}
IEnumerable overdrawnAccounts = accounts.GetAccounts(IsOverdrawn);
Note the use of the “Func” delegate overload, specifying a delegate parameter which takes an Account as its parameter and returns a bool.
The advantage of this approach is that we can now supply an alternative delegate without changing the code of the extension method:
static public bool IsStevesAccount(Account a)
{
return a.name == "Steve";
}
… = accounts.GetAccounts(IsStevesAccount);
This can be written (slightly) more succinctly using an anonymous delegate:
… = accounts.GetAccounts(delegate(Account a)
{
return a.name == "Steve";
});
This approach has an additional advantage in that the anonymous delegate can access variables from the calling scope.
In the examples above, we have determined the name of the account to be found at design time – if, however, the account name we want is not known until run-time then the named delegate approach would have a problem. As the prototype of the predicate is controlled by the extension method, we have no way to pass in a variable containing the name we want to find: we would need to create an alternative extension method, or use a variable at a higher scope (e.g. a global variable).
Using an anonymous delegate we can keep everything else the same, and:
string requiredAccount = "Steve";
… = accounts.GetAccounts(delegate(Account a)
{
return a.name == requiredAccount;
});
Similarly, we can now allow for an arbitrary overdraft limit in our list of the overdrawn accounts:
int overdraftLimit = 25;
… = accounts.GetAccounts(delegate(Account a)
{
return a.balance + overdraftLimit < 0;
});
Standard Extension Methods for Collections
In the examples given so far, we have written all of our own Extension Methods. This has been largely for expository purposes; a wide variety of extension methods are provided as part of the .NET framework which implement many of the standard operations you might wish to perform on a collection of objects (such as an IEnumerable or an IQueryable).
Here we give some simple examples of the standard extension methods, including versions of some of the earlier examples:
string requiredAccount = "Steve";
… = accounts.Where(delegate(Account a)
{
return a.name == requiredAccount;
});
int overdraftLimit = 25;
… = accounts.Where(delegate(Account a)
{
return a.balance + overdraftLimit < 0;
});
decimal totalBalance = accounts.Sum(delegate(Account a)
{
return a.balance;
});
decimal averageBalance = accounts.Average(delegate(Account a)
{
return a.balance;
});
For lists of the standard methods, see the MSDN articles IEnumberable
Lambda Expressions
Lambda expressions are nothing more than an alternative syntax for defining an anonymous function delegate.
The expression follows the pattern:
Parameter list => Expression
Therefore :
delegate(Account a)
{
return a.name == requiredAccount;
}
Can be re-written as:
(Account a) => a.name == requiredAccount
As the compiler can infer the return type of the expression, it is not actually necessary to supply the type of the parameter, so this can be furthered simplified to:
a => a.name == requiredAccount
So we can now re-write our previous examples as:
string requiredAccount = "Steve";
… = accounts.Where(a => a.name == requiredAccount);
int overdraftLimit = 25;
… = accounts.Where(a => a.balance + overdraftLimit < 0);
decimal totalBalance = accounts.Sum(a => a.balance);
decimal averageBalance= accounts.Average(a => a.balance);
For more detail on other possibilities for Lambda expressions, including support for multiple parameters and multi-statement expressions, see the MSDN “Lambda Expressions in LINQ
One of the places we make most use of lambda expressions and the standard extension methods is in the LINQ for the Sage 200 Workspaces.
So an example of a simple query which returns a stock item code and the total confirmed quantity in stock across all warehouses could be written as:
var q = from si in StockItems
let wiSet = WarehouseItems.Where(wi => si.ItemID == wi.ItemID)
select new
{
si.Code,
ConfirmedQtyInStock = (decimal?)wiSet.Sum(wi => wi.ConfirmedQtyInStock)
};
Replacing the lambda expressions with anonymous delegate syntax:
var q = from si in StockItems
let wiSet = WarehouseItems.Where(delegate(WarehouseItem wi)
{
return (si.ItemID == wi.ItemID);
})
select new
{
si.Code,
ConfirmedQtyInStock = (decimal?)wiSet.Sum(delegate(WarehouseItem wi)
{
return wi.ConfirmedQtyInStock;
})
};
return q;
And without using extension methods (assuming very simplistic implementation of the query functions):
var q = from si in StockItems
select new
{
si.Code,
ConfirmedQtyInStock = GetTotalConfirmedQtyInStock(
GetWarehouseItemsForStockItem(WarehouseItems, si))
};
return q;
// Where...
public static IEnumerable
GetWarehouseItemsForStockItem(IEnumerable WarehouseItems, StockItem si)
{
foreach (WarehouseItem wi in WarehouseItems)
{
if (si.ItemID == wi.ItemID)
yield return wi;
}
}
public static decimal?
GetTotalConfirmedQtyInStock(IEnumerable WarehouseItems)
{
if (WarehouseItems.Count() == 0)
return null;
decimal result = 0;
foreach (WarehouseItem wi in WarehouseItems)
{
result += wi.ConfirmedQtyInStock;
}
return result;
}