function functionCall(data, option) { if (someCondition) { var dynamicFunc = function1; var args = [data, option, 1]; } else { var dynamicFunc = function2; var args = [data, option, 2]; } var value = dynamicFunc.apply(dynamicFunc, args); // processing value }
Friday, December 19, 2008
Dynamic Javascript Function Invocation
Sunday, December 07, 2008
Design Patterns By Example: Strategy vs Template vs Builder vs Bridge
In this post I will go through a few related design patterns. Unlike many online design pattern tutorials, patterns concept and UML diagrams will not be our focus. Instead I will provide more understandable code examples for the discussion.
Strategy Pattern
Strategy defines an interface common to all supported algorithms or behavior. The implementation of the Strategy is dynamically invoked at run time. Generic list sorting in .NET is a good example of using strategy pattern:
using System; using System.Collections.Generic; class test { class Person { public string name { get; set; } public int Age { get; set; } } class PersonSortByAge : IComparer<Person> { public int Compare(Person p1, Person p2) { return p1.Age.CompareTo(p2.Age); } } static void Main(string[] args) { List<Person> people = new List<Person>(); people.Add(new Person() { name = "Andy Wade", Age = 20 }); people.Add(new Person() { name = "Cary Lee", Age = 10 }); people.Add(new Person() { name = "Bob Ford", Age = 30 }); Console.WriteLine("Original order:"); people.ForEach(p => Console.WriteLine("Name:" + p.name + " Age:" + p.Age)); people.Sort((p1, p2) => p1.name.CompareTo(p2.name)); Console.WriteLine("Sort by name:"); people.ForEach(p => Console.WriteLine("Name:" + p.name + " Age:" + p.Age)); //Sort by name: //Name:Andy Wade Age:20 //Name:Bob Ford Age:30 //Name:Cary Lee Age:10 people.Sort(new PersonSortByAge()); Console.WriteLine("Sort by age:"); people.ForEach(p => Console.WriteLine("Name:" + p.name + " Age:" + p.Age)); //Sort by age: //Name:Cary Lee Age:10 //Name:Andy Wade Age:20 //Name:Bob Ford Age:30 Console.ReadKey(); } }
In above example Lambda express and strongly typed IComparer are two different strategy or sorting algorithm, which are used to sort the people list by person's name and age respectively. Following code is another example:
using System; using System.Collections.Generic; using System.Linq; class StrategyPattern { enum Temperature { Cold, Mild, Hot } interface IActivity { string GetActivity(Temperature temperature); } class DayPlanner { private Temperature Weather; public DayPlanner(Temperature weather) { this.Weather = weather; } public void SetPlan(IActivity activity) { Console.WriteLine("Setting plan for " + activity.GetActivity(Weather)); } } class IndoorActivity : IActivity { public string GetActivity(Temperature temperature) { return (temperature == Temperature.Cold) ? "Hockey" : "Fitness gym"; } } class OutdoorActivity : IActivity { public string GetActivity(Temperature temperature) { switch (temperature) { case Temperature.Cold: return "Ice skating"; case Temperature.Mild: return "Hiking"; case Temperature.Hot: default: return "Swimmig"; } } } static void Main(string[] args) { DayPlanner planner = new DayPlanner(Temperature.Mild); OutdoorActivity outdoorActivity = new OutdoorActivity(); planner.SetPlan(outdoorActivity); // print out "Setting plan for Hiking" IndoorActivity indoorActivity = new IndoorActivity(); planner.SetPlan(indoorActivity); // print out "Setting plan for Fitness gym" Console.ReadKey(); } }
Here the DayPlanner holds a reference of an IActivity instance, an implementation of a strategy. With different strategy assigned, DayPlanner shows different activity. Strategy pattern results in dynamic behavior.
Template Method Pattern
Template method defines a sequence of method calls or some algorithms in base class, and the detail implementation of each method or algorithm is overriden by subclass. One obvious example is the ASP.NET Page life cycle: PreLoad, OnLoad, PreRender, etc. Those events are invoked by base Page class and developers are responsible to fill the detail implementation in code-behind for those events. Following code illustrate how the PersonDayPlanner schedules morning, afternoon and night activities by template method:
using System; using System.Collections.Generic; class TemplateMethodPattern { abstract class PersonDayPlanner { // Template Method defines the skeleton steps public void Plan() { MorningPlan(); AfternoonPlan(); NightPlan(); } // Abstract methods implemented by child classes public abstract void MorningPlan(); public abstract void AfternoonPlan(); public abstract void NightPlan(); } class CollegeStudentSaturday : PersonDayPlanner { public override void MorningPlan() { Console.WriteLine("Have a simple breakfast and do cleanup."); } public override void AfternoonPlan() { Console.WriteLine("Study for 2 hours then go to gym for exercise."); } public override void NightPlan() { Console.WriteLine("Eat out with friends and watch movie after"); } } class LittleKidSaturday : PersonDayPlanner { public override void MorningPlan() { Console.WriteLine("McDonald's happy meal for breakfast, then play at park."); } public override void AfternoonPlan() { Console.WriteLine("Friend's birthday party."); } public override void NightPlan() { Console.WriteLine("Play LEGO and watch TV."); } } static void Main(string[] args) { PersonDayPlanner saturday = new CollegeStudentSaturday(); saturday.Plan(); Console.WriteLine(); //print out a student's plan on Saturday: // Have a simple breakfast and do cleanup. // Study for 2 hours then go to gym for exercise. // Eat out with friends and watch movie after saturday = new LittleKidSaturday(); saturday.Plan(); //print out a kid's plan on Saturday: // McDonald's happy meal for breakfast, then play at park. // Friend's birthday party. // Play LEGO and watch TV. Console.ReadKey(); } }
Builder Pattern
The Builder pattern separates the construction logic for an object. This pattern is often used when the construction process of an object is complex. By modifying above DayPlanner code demo we have following builder implementation:
using System; using System.Collections.Generic; class BuilderPattern { interface IPersonDayPlan { void MorningPlan(); void AfternoonPlan(); void NightPlan(); } class DayPlanBuilder { private IPersonDayPlan DayPlan { get; set; } public DayPlanBuilder() { } // default constructor public DayPlanBuilder(IPersonDayPlan dayPlan) { this.DayPlan = dayPlan; } public void SetPersonDayPlan(IPersonDayPlan dayPlan) { this.DayPlan = dayPlan; } public void BuildPlan() { if (this.DayPlan != null) { this.DayPlan.MorningPlan(); this.DayPlan.AfternoonPlan(); this.DayPlan.NightPlan(); // You can control the steps or add new logic inside the builder if (this.DayPlan is CollegeStudentSaturday) { Console.WriteLine("Play games no later than 12PM."); } } } } class CollegeStudentSaturday : IPersonDayPlan { public void MorningPlan() { Console.WriteLine("Have a simple breakfast and do cleanup."); } public void AfternoonPlan() { Console.WriteLine("Study for 2 hours then go to gym for exercise."); } public void NightPlan() { Console.WriteLine("Eat out with friends and watch movie after"); } } class LittleKidSaturday : IPersonDayPlan { public void MorningPlan() { Console.WriteLine("McDonald's happy meal for breakfast, then play at park."); } public void AfternoonPlan() { Console.WriteLine("Friend's birthday party."); } public void NightPlan() { Console.WriteLine("Play LEGO and watch TV."); } } static void Main(string[] args) { DayPlanBuilder planBuilder = new DayPlanBuilder(new LittleKidSaturday()); planBuilder.BuildPlan(); //print out: // McDonald's happy meal for breakfast, then play at park. // Friend's birthday party. // Play LEGO and watch TV. // changing to student's plan planBuilder.SetPersonDayPlan(new CollegeStudentSaturday()); planBuilder.BuildPlan(); //print out: // Have a simple breakfast and do cleanup. // Study for 2 hours then go to gym for exercise. // Eat out with friends and watch movie after // Play games no later than 12PM. Console.ReadKey(); } }
The Builder pattern plays a little bit different "director" role compared with the Template method in the build process. In above example BuildPlan method can have different build steps for different IPersonDayPlan types; while the Template method has fixed run steps. Builder is creational pattern and it is mainly used to create complex object. Template method is behavior pattern and it defines skeleton of running procedure.
Bridge Pattern
Both Template method and Builder pattern use inheritance to implement different behaviors or algorithms. The situation would become messy when more and more objects join and play in the hierarchy. In above DayPlanner example, we could have AdultMonday, AdultTuesday...SeniorMonday, SeniorTuesday...etc. of many other combination for sub classes. Bridge pattern resolves the problem by decoupling an abstraction from its implementation so that the two can vary independently. Modify the code example again the Bridge version looks like:
using System; using System.Collections.Generic; class BridgePattern { interface IDayPlanner { string DayOfWeek { get; } void Plan(int age, bool inSchool); } abstract class Person { public int Age { get; set; } protected IDayPlanner DayPlanner { get; set; } public void SetDayPlanner(IDayPlanner dayPlanner) { this.DayPlanner = dayPlanner; } public abstract void PlanDay(); } class CollegeStudent : Person { public CollegeStudent(int age) { base.Age = age; } public override void PlanDay() { Console.WriteLine("College student's " + this.DayPlanner.DayOfWeek + " schedule:"); this.DayPlanner.Plan(this.Age, true); } } class LittleKid : Person { public LittleKid(int age) { base.Age = age; } public override void PlanDay() { Console.WriteLine("Little kid's " + this.DayPlanner.DayOfWeek + " schedule:"); this.DayPlanner.Plan(this.Age, false); } } class SaturdayPlanner : IDayPlanner { public void Plan(int age, bool inSchool) { if (age < 6) { Console.WriteLine("Play at park, friend's birthday party, and play LEGO."); } else if (age > 18 && inSchool) { Console.WriteLine("Study, gym exercise and social activities."); } // plan for other cases } public string DayOfWeek { get { return "Saturday"; } } } class MondayPlanner : IDayPlanner { public void Plan(int age, bool inSchool) { if (age < 6) { Console.WriteLine("Kindergarten."); } else if (age > 18 && inSchool) { Console.WriteLine("Classes, library and lab."); } // plan for other cases } public string DayOfWeek { get { return "Monday"; } } } static void Main(string[] args) { Person kid = new LittleKid(5); kid.SetDayPlanner(new SaturdayPlanner()); kid.PlanDay(); //Little kid's Saturday schedule: //Play at park, friend's birthday party, and play LEGO. Person student = new CollegeStudent(20); student.SetDayPlanner(new MondayPlanner()); student.PlanDay(); //College student's Monday schedule: //Classes, library and lab. Console.ReadKey(); } }
Now person and weekday are separated, and we don't have to do their combinational sub-classes anymore, known as "prefer composition over inheritance" for such scenario. The Provider model in .NET framework, such as Membership Provider and Session State Provider, is an example of using Bridge pattern.
Last but not least, design patterns are useful to resolve certain problems but they are not a silver bullet. Overusing them could introduce unnecessary complexity and cost. We should't force to use patterns as what I do in this posting. The pattern examples I presented above are just for demonstration purpose and it doesn't mean they are the best practices. In reality we should consider those patterns when having a code smell in our work.
Thursday, November 20, 2008
.NET XmlSerializer Memory Leak
Type[] relatedTypes = new Type[1];Every time this serializer is created, a dynamic assembly is also generated at run time and is loaded into memory to do the XML serialization. The problem is that once a .NET assembly (dynamic or static) is loaded, there's no way you can clean it through code, and it will only be removed from the memory until the whole application is unloaded. The result is a memory leak with more and more dynamic XML serialization assemblies added into memory.
type[0] = typeof(AppObject);
XmlSerializer serializer = new XmlSerializer(typeof(AppConfig), relatedTypes);
The solution is use XmlSerialzer constructor with only Type parameter:
XmlSerializer serializer = new XmlSerializer(typeof(object));Alternative is to cache the XmlSerialzer object for the first time when you can't use such constructor, and reuse it later. In fact .NET caches XmlSerialzer that created only by Type constructor.
This seems to be an known issue. We google it and found many people having same problem. A good article showing the details of tracing this issue can be found here.
Sunday, November 09, 2008
ASP.NET Cache Callback
ASP.NET Cache supports CacheItemRemovedCallback option since .NET 1.1 (http://msdn.microsoft.com/en-us/library/aa478965.aspx), and we can do some interesting things such as logging inside the callback function. But a big limitation on it is that we can’t do cache validation to keep or refresh cache data in this callback function:
"One potential use for this feature would be to refresh cached data in the background so that users never need to wait for the data to be populated, but the data is kept relatively fresh. Unfortunately in practice, this doesn't work very well with the current version of the caching API, because the callback doesn't fire or complete execution prior to the cached item being removed from the cache. Thus, a user will frequently make a request that will try to access the cached value, find that it is null, and be forced to wait for it to repopulate."
From .NET 3.5, we have a new CacheItemUpdateCallback delegate for caching handling (http://msdn.microsoft.com/en-us/library/system.web.caching.cacheitemupdatecallback.aspx). Now we can periodically update the cache by the callback function. The new overloading Cache Insert method is:HttpRuntime.Cache.Insert(The callback function signature is:
stringKey,
objectValue,
cacheDependencies,
dateTimeAbsoluteExpiration,
timeSpanSlidingExpiration,
cacheUpdateCallback)
This new feature is very handy when we need to automatically refresh the cache in background.
public delegate void CacheItemUpdateCallback(
string key,
CacheItemUpdateReason reason,
out Object expensiveObject,
out CacheDependency dependency,
out DateTime absoluteExpiration,
out TimeSpan slidingExpiration
)
Tuesday, October 21, 2008
Visitor Pattern By Example
Visitor pattern separates some algorightm or computation work to its own class. Being able to "visit" visitable objects, different algorithm implementations can do their work without modification of original objects. Let's say a person has different kind of valuables/assets, and the value calculations could have their own implementation:
using System; using System.Collections.Generic; namespace ConsoleApplication1 { class Program { public interface IValuable // Interface for visitable objects { void Value(IValueCalculator calculator); } public interface IValueCalculator // Interface for visitor implementation { decimal GetTotal(); void Calculate(BankAccount bankAccount); void Calculate(Loan load); } public class NetWorthCalculator : IValueCalculator { private decimal Total = 0; public decimal GetTotal() { return Total; } public void Calculate(BankAccount bankAccount) { Total += bankAccount.Balance; } public void Calculate(Loan loan) { Total -= loan.Amount; } } public class MonthlyIncomeCalculator : IValueCalculator { private decimal Total = 0; public decimal GetTotal() { return Total; } public void Calculate(BankAccount bankAccount) { Total += (bankAccount.Balance * bankAccount.InterestRate) / 12; } public void Calculate(Loan loan) { Total -= loan.MonthlyPayment; } } public class BankAccount : IValuable { public string Name { get; set; } public decimal Balance { get; set; } public decimal InterestRate { get; set; } public void Value(IValueCalculator calculator) { calculator.Calculate(this); } } public class Loan : IValuable { public string Name { get; set; } public decimal Amount { get; set; } public decimal MonthlyPayment { get; set; } public void Value(IValueCalculator calculator) { calculator.Calculate(this); } } public class Person : IValuable // Composite pattern: Person itself is also IValuable { public string Name { get; set; } public List<IValuable> Valuables = new List<IValuable>(); public void Value(IValueCalculator calculator) { foreach (IValuable item in Valuables) { item.Value(calculator); } } } static void Main(string[] args) { Person person = new Person() { Name = "new graduate student" }; person.Valuables.Add(new BankAccount() { Name = "Saving Account", Balance = 5000, InterestRate = 0.05m }); person.Valuables.Add(new BankAccount() { Name = "Cheque Account", Balance = 2000, InterestRate = 0.01m }); person.Valuables.Add(new Loan() { Name = "Student Loan", Amount = 5000, MonthlyPayment = 500 }); NetWorthCalculator netWorth = new NetWorthCalculator(); person.Value(netWorth); Console.WriteLine("Assets' net worth is: " + netWorth.GetTotal()); MonthlyIncomeCalculator monthlyIncome = new MonthlyIncomeCalculator(); person.Value(monthlyIncome); Console.WriteLine("Monthly income from Assets: " + monthlyIncome.GetTotal().ToString("#.##")); Console.Read(); } } }
Run the console app will show following result:
Assets' net worth is: 2000 Monthly income from Assets: -477.5
The NetWorthCalculator and MonthlyIncomeCalculator are totally separated from the target IValuable objects. We can implement other type of calculation without any modification on existing code. When adding new IValuable type, the ICalculator interface and its implementations need to include the new type. Back to our example, assume the student found a job after graduation from school, paid off most of the student loan in six months and bought some Microsoft stocks. Two new IValuable types are introduced:
using System; using System.Collections.Generic; namespace ConsoleApplication1 { class Program { public interface IValuable // Interface for visitable objects { void Value(IValueCalculator calculator); } public interface IValueCalculator // Interface for visitor implementation { decimal GetTotal(); void Calculate(BankAccount bankAccount); void Calculate(Loan load); void Calculate(Job stock); void Calculate(Stock stock); } public class NetWorthCalculator : IValueCalculator { private decimal Total = 0; public decimal GetTotal() { return Total; } public void Calculate(BankAccount bankAccount) { Total += bankAccount.Balance; } public void Calculate(Loan loan) { Total -= loan.Amount; } public void Calculate(Job job) { // Do nothing } public void Calculate(Stock stock) { Total += stock.Price * stock.Share; } } public class MonthlyIncomeCalculator : IValueCalculator { private decimal Total = 0; public decimal GetTotal() { return Total; } public void Calculate(BankAccount bankAccount) { Total += (bankAccount.Balance * bankAccount.InterestRate) / 12; } public void Calculate(Loan loan) { Total -= loan.MonthlyPayment; } public void Calculate(Job job) { Total += job.Salary / 12; } public void Calculate(Stock stock) { // Do nothing } } public class BankAccount : IValuable { public string Name { get; set; } public decimal Balance { get; set; } public decimal InterestRate { get; set; } public void Value(IValueCalculator calculator) { calculator.Calculate(this); } } public class Loan : IValuable { public string Name { get; set; } public decimal Amount { get; set; } public decimal MonthlyPayment { get; set; } public void Value(IValueCalculator calculator) { calculator.Calculate(this); } } public class Job : IValuable { public string Name { get; set; } public decimal Salary { get; set; } public void Value(IValueCalculator calculator) { calculator.Calculate(this); } } public class Stock : IValuable { public string Name { get; set; } public decimal Price { get; set; } public int Share { get; set; } public void Value(IValueCalculator calculator) { calculator.Calculate(this); } } public class Person : IValuable // Composite pattern: Person itself is also IValuable { public string Name { get; set; } public List<IValuable> Valuables = new List<IValuable>(); public void Value(IValueCalculator calculator) { foreach (IValuable item in Valuables) { item.Value(calculator); } } } static void Main(string[] args) { Person person = new Person() { Name = "6-month after graduation" }; person.Valuables.Add(new BankAccount() { Name = "Saving Account", Balance = 20000, InterestRate = 0.05m }); person.Valuables.Add(new BankAccount() { Name = "Cheque Account", Balance = 5000, InterestRate = 0.01m }); person.Valuables.Add(new Loan() { Name = "Student Loan", Amount = 1000, MonthlyPayment = 500 }); person.Valuables.Add(new Job() { Name = "Software Developer", Salary = 60000 }); person.Valuables.Add(new Stock() { Name = "Microsoft", Price = 20, Share = 100 }); NetWorthCalculator netWorth = new NetWorthCalculator(); person.Value(netWorth); Console.WriteLine("Assets' net worth is: " + netWorth.GetTotal()); MonthlyIncomeCalculator monthlyIncome = new MonthlyIncomeCalculator(); person.Value(monthlyIncome); Console.WriteLine("Monthly income from Assets: " + monthlyIncome.GetTotal().ToString("#.##")); Console.Read(); } } }
Run the app again and now the net worth and monthly income are both positive:
Assets' net worth is: 26000 Monthly income from Assets: 4587.5
The Visitor Pattern is quite useful for some scenarios. But again we use the design patterns to resolve certain problems, and let the code more extensible and maintainable. They may not be suitable for some situations and over using them may make the code unnecessarily complicated.
Friday, October 03, 2008
Invoke Generic Method Dynamically In .NET
using System;In order to invoke a generic method you have to declare the type statically, and it will be initiated at compile time. Is there a way to get a generic type dynamically? We can do something like:
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
class Test
{
public class Foo
{
public string Name { get; set; }
public Foo(string name)
{
Name = "Foo" + name;
}
}
public static List<T> ParseValues<T>(string[] data)
{
List<T> values = new List<T>();
Type type = typeof(T);
if (type == typeof(int))
{
foreach (string str in data)
values.Add((T)((object)int.Parse(str)));
}
else if (type == typeof(double))
{
foreach (string str in data)
values.Add((T)((object)double.Parse(str)));
}
else if (type == typeof(Foo))
{
foreach (string str in data)
values.Add((T)((object)new Foo(str)));
}
return values;
}
static void Main()
{
string[] values = new string[] { "1", "2", "3" };
List<int> numbers = ParseValues<int>(values);
List<double> doubles = ParseValues<double>(values);
List<Foo> foos = ParseValues<Foo>(values);
//Print 1 2 3 1 2 3 Foo1 Foo2 Foo3
numbers.ForEach(i => Console.Write(i + " "));
doubles.ForEach(i => Console.Write(i + " "));
foos.ForEach(i => Console.Write(i.Name + " "));
Console.Read();
}
}
Type genericListType = typeof(List<>).MakeGenericType(typeof(double));But that doesn't resolve our problem because we can't determine the T type at compile time to invoke the method of public static List<T> ParseValues<T>(string[] data).
var list = Activator.CreateInstance(genericListType);
One way I can think of is use the reflection and invoke the method dynamically:
using System;Bear in mind that using Reflection is costly and it would affect performance significantly.
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
class Test
{
public class Foo
{
public string Name { get; set; }
public Foo(string name)
{
Name = "Foo" + name;
}
}
public static List<T> ParseValues<T>(string[] data)
{
List<T> values = new List<T>();
Type type = typeof(T);
if (type == typeof(int))
{
foreach (string str in data)
values.Add((T)((object)int.Parse(str)));
}
else if (type == typeof(double))
{
foreach (string str in data)
values.Add((T)((object)double.Parse(str)));
}
else if (type == typeof(Foo))
{
foreach (string str in data)
values.Add((T)((object)new Foo(str)));
}
return values;
}
static void Main()
{
string[] values = new string[] { "1", "2", "3" };
List<int> numbers = ParseValues<int>(values);
List<double> doubles = ParseValues<double>(values);
List<Foo> foos = ParseValues<Foo>(values);
//Print 1 2 3 1 2 3 Foo1 Foo2 Foo3
numbers.ForEach(i => Console.Write(i + " "));
doubles.ForEach(i => Console.Write(i + " "));
foos.ForEach(i => Console.Write(i.Name + " "));
Console.WriteLine();
Type numberType = typeof(int);
MethodInfo genericMethod = typeof(Test).GetMethod("ParseValues").MakeGenericMethod(numberType);
var dynamicList = genericMethod.Invoke(null, new object[] { values });
// Print 1 2 3
foreach (var item in dynamicList as IEnumerable)
Console.Write(item + " ");
Type fooType = typeof(Foo);
genericMethod = typeof(Test).GetMethod("ParseValues").MakeGenericMethod(fooType);
dynamicList = genericMethod.Invoke(null, new object[] { values });
// Print Foo1 Foo2 Foo3
foreach (var item in dynamicList as IEnumerable)
Console.Write((item as Foo).Name + " ");
Console.Read();
}
}
Friday, September 12, 2008
ASP.NET Substitute Custom Control In Output-cached Page
Usually Substitution content is just a string. But we can extend it to render a custom server control. For example there's a custom control named MyCustomControl that needs to be displayed dynamically in a output-cached page. First we need to declare a Substitution:
<asp:Substitution runat="server" ID="subControl" MethodName="GetControlString" />Inside the GetControlString static method:
public static string GetControlString(HttpContext context)
{
StringWriter output = new StringWriter();
Page pageHolder = new Page();
MyCustomControl control = MyCustomControl();
//control.SetParameters();
//pageHolder.Response.ContentEncoding = System.Text.Encoding.GetEncoding("UTF-8");
pageHolder.Controls.Add(control);
context.Server.Execute(pageHolder, output, false);
context.Response.ContentEncoding = Encoding.GetEncoding("UTF-8");
return output.ToString();
}
[2009/06 Updated] HttpContext.Response.ContentEncoding needs to be defined specifically if you encounter the wrong character encoding issue inside the custom control.
Wednesday, September 03, 2008
ASP.NET Cache Info Page
CacheInfo.aspx:
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="CacheInfo.aspx.cs" Inherits="CacheInfo" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Cache Info/Test Page</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<div>
<p style="text-align: center">
<asp:Label ID="Label4" runat="server" Text="Cache Info" Font-Size="Large" Font-Bold="true"></asp:Label></p>
<p>
<asp:Label ID="lblSysInfo" runat="server" EnableViewState="false"></asp:Label>
<asp:Label ID="lblInfo" runat="server" EnableViewState="false"></asp:Label>
</p>
<p>
<asp:Label ID="lblCache" runat="server" EnableViewState="false"></asp:Label><br />
<asp:GridView ID="gvCache" runat="server" AutoGenerateColumns="False" BorderWidth="1px"
BackColor="White" CellPadding="4" BorderStyle="Solid" BorderColor="#3366CC" Font-Size="Small"
AlternatingRowStyle-ForeColor="ActiveCaption" EnableViewState="false">
<HeaderStyle ForeColor="White" BackColor="#003399" HorizontalAlign="Left"></HeaderStyle>
<Columns>
<asp:TemplateField ItemStyle-Width="20" ItemStyle-Font-Size="X-Small">
<ItemTemplate>
<%# Container.DataItemIndex + 1 %>
</ItemTemplate>
</asp:TemplateField>
<asp:BoundField HeaderText="Key" DataField="Key" ReadOnly="true" ItemStyle-Width="420px"></asp:BoundField>
<asp:BoundField HeaderText="Type" DataField="Type" ReadOnly="true" ItemStyle-Width="180px"></asp:BoundField>
<asp:BoundField HeaderText="Note" DataField="Note" ReadOnly="true" ItemStyle-Width="180px"></asp:BoundField>
</Columns>
<EmptyDataTemplate>
<asp:Label ID="lblEmptyMessage" runat="server" Text="No feature found."></asp:Label>
</EmptyDataTemplate>
</asp:GridView>
</p>
<p>
<asp:Label ID="Label3" runat="server" Text="Add cache with size in MB"></asp:Label>
<asp:DropDownList ID="ddlCacheSize" runat="server" AutoPostBack="false">
<asp:ListItem>50</asp:ListItem>
<asp:ListItem>100</asp:ListItem>
<asp:ListItem>200</asp:ListItem>
<asp:ListItem>300</asp:ListItem>
<asp:ListItem>400</asp:ListItem>
<asp:ListItem>500</asp:ListItem>
<asp:ListItem>600</asp:ListItem>
<asp:ListItem>700</asp:ListItem>
<asp:ListItem>800</asp:ListItem>
<asp:ListItem>900</asp:ListItem>
<asp:ListItem>1000</asp:ListItem>
<asp:ListItem>2000</asp:ListItem>
<asp:ListItem>3000</asp:ListItem>
<asp:ListItem>4000</asp:ListItem>
<asp:ListItem>5000</asp:ListItem>
<asp:ListItem>6000</asp:ListItem>
<asp:ListItem>7000</asp:ListItem>
<asp:ListItem>8000</asp:ListItem>
<asp:ListItem>9000</asp:ListItem>
<asp:ListItem>10000</asp:ListItem>
</asp:DropDownList>
<asp:Label ID="Label1" runat="server" Text="Cache Expire in seconds"></asp:Label>
<asp:DropDownList ID="ddlExpir" runat="server" AutoPostBack="false">
<asp:ListItem>10</asp:ListItem>
<asp:ListItem>20</asp:ListItem>
<asp:ListItem>30</asp:ListItem>
<asp:ListItem>40</asp:ListItem>
<asp:ListItem>50</asp:ListItem>
<asp:ListItem>60</asp:ListItem>
<asp:ListItem>70</asp:ListItem>
<asp:ListItem>80</asp:ListItem>
<asp:ListItem>90</asp:ListItem>
<asp:ListItem>100</asp:ListItem>
<asp:ListItem>110</asp:ListItem>
<asp:ListItem>120</asp:ListItem>
<asp:ListItem>180</asp:ListItem>
</asp:DropDownList>
<asp:Button ID="btnAddCache" runat="server" Text="Add Test Cache" OnClick="btnAddCache_click" />
</p>
</div>
</div>
</form>
</body>
</html>
CacheInfo.aspx.cs:
using System;
using System.Collections;
using System.Collections.Generic;
using System.Web;
using System.Web.UI.WebControls;
using System.Web.Caching;
using System.Text;
using System.Data;
public partial class CacheInfo : System.Web.UI.Page
{
protected class CacheInfoItem
{
public string Key { get; set; }
public string Type { get; set; }
public string Note { get; set; }
public CacheInfoItem(string key, string type)
{
Key = key;
Type = type;
}
}
protected class CacheData
{
public Byte[] Data { get; set; }
public CacheData(int totalBytes)
{
Data = new Byte[totalBytes];
for (int i = 0; i < totalBytes; i += 123)
{
Data[i] = (byte)(i % 256);
}
}
}
protected string TestCacheKey = "Cache_Test_Key";
protected void btnAddCache_click(object sender, EventArgs e)
{
try
{
int totalBytes = int.Parse(ddlCacheSize.SelectedValue) * 1024 * 1024;
CacheData data = new CacheData(totalBytes);
int seconds = int.Parse(ddlExpir.SelectedValue);
HttpRuntime.Cache.Insert(TestCacheKey, data, null, DateTime.Now.AddSeconds(seconds), Cache.NoSlidingExpiration);
lblInfo.Text = "Successfully added test data to cache (expire in " + seconds + " seconds).";
}
catch (Exception ex)
{
lblInfo.Text = "Insert Cache error: " + ex.Message;
}
}
protected override void OnPreRender(EventArgs e)
{
base.OnPreRender(e);
List<CacheInfoItem> cacheItems = new List<CacheInfoItem>();
// Populate System Info
System.Diagnostics.Process currentProcess = System.Diagnostics.Process.GetCurrentProcess();
long totalMemBytes = currentProcess.WorkingSet64;
DateTime startTime = currentProcess.StartTime;
Cache cache = HttpRuntime.Cache;
StringBuilder sbInfo = new StringBuilder();
sbInfo.Append(Server.MachineName + " " + currentProcess.MachineName + " ");
sbInfo.Append(currentProcess.ProcessName + " start time: " + startTime.ToString("yyy-MM-dd HH:mm") + " ");
sbInfo.Append("Process memory usage (MB): " + totalMemBytes / (1024 * 1024) + "<br>");
lblSysInfo.Text = sbInfo.ToString();
// Populate Cache Info
sbInfo = new StringBuilder();
if (cache != null)
{
foreach (DictionaryEntry item in cache)
{
try
{
Type type = item.Value.GetType();
string typeInfo = type.ToString();
CacheInfoItem ci = new CacheInfoItem(item.Key.ToString(), typeInfo);
cacheItems.Add(ci);
if (type == Type.GetType("System.DateTime"))
{
ci.Note = "Time: " + Convert.ToDateTime(HttpRuntime.Cache[item.Key.ToString()]).ToString("yyy-MM-dd HH:mm");
}
else if (type == Type.GetType("System.String"))
{
ci.Note = "Value: " + cache[item.Key.ToString()].ToString();
}
else
{
Object obj = cache[item.Key.ToString()];
if (obj != null)
{
if (obj is IList)
{
ci.Note = "Item Count: " + ((IList)obj).Count.ToString();
}
else if (obj is DataSet && (DataSet)obj != null && ((DataSet)obj).Tables[0] != null)
{
ci.Note = "Item Count: " + ((DataSet)obj).Tables[0].Rows.Count.ToString();
}
else if (obj is DataTable && (DataTable)obj != null)
{
ci.Note = "Item Count: " + ((DataTable)obj).Rows.Count.ToString();
}
}
}
}
catch (Exception ex)
{
sbInfo.Append("Get cache info error for " + item.Key + ":" + ex.Message + "<br>");
throw;
}
}
}
lblInfo.Text += sbInfo.ToString();
lblCache.Text = "Total Cache item count: " + cacheItems.Count.ToString();
cacheItems.Sort((i, j) => i.Key.CompareTo(j.Key));
gvCache.DataSource = cacheItems;
gvCache.DataBind();
}
}
Tuesday, August 26, 2008
LINQ SelectMany Usage
For instance, each instructor offers many courses in the school, Instructors.SelectMany(i => i.Courses) means for each instructor in the Instructors list, return his/her Courses, so you get all courses for all instructors.
A SelectMany overloading method accepts the second parameter which gives us more power to control the output. Let’s look at a statement:
Instructors.SelectMany(i => i.Courses, (i, c) => new { InstructorName = i.Name, CourseName = c } ).
The first parameter "i => i.Courses" will get all Courses for each instructor, and transform it to the second parameter, so inside "(i, c)", "i" is the original input element just like what we have in "i => i.Courses", and "c" here is each element sent from "i.Courses". What the statement does is go through each instructor’s courses, and return the instructor’s name and course name with anonymous type of object.
Following code example demos a few usages of SelectMany, including how you can join a sequence to another sequence, and do you your customized logic inside the lambda function:
using System; using System.Linq; using System.Collections.Generic; class Program { class Instructor { public string Name { get; set; } public string DeptID { get; set; } public string[] Courses { get; set; } } class Department { public string ID { get; set; } public string Name { get; set; } } static void Main() { var departments = new List<Department>() { new Department() { ID = "EE", Name = "Electrical Engineering" }, new Department() { ID = "CS", Name = "Computer Science" }, new Department() { ID = "ME", Name = "Mechanical Engineering" } }; var instructors = new List<Instructor>() { new Instructor() { Name = "EE1", DeptID = "EE", Courses = new string [] { "EE101", "EE102", "EE103" } }, new Instructor() { Name = "EE2", DeptID = "EE", Courses = new string [] { "EE201", "EE202", "EE203" } }, new Instructor() { Name = "CS1", DeptID = "CS", Courses = new string [] { "CS101", "CS102", "CS103" } }, new Instructor() { Name = "CS2", DeptID = "CS", Courses = new string [] { "CS201", "CS202", "CS203" } }, new Instructor() { Name = "ME1", DeptID = "ME", Courses = new string [] { "ME101", "ME102", "ME103" } }, new Instructor() { Name = "ME2", DeptID = "ME", Courses = new string [] { "ME201", "ME202", "ME203" } }, }; var CSInstructors = instructors.Where(i => i.DeptID == "CS").Select(i => "CS Instructor: " + i.Name).ToArray(); // CSInstructors: { "CS Instructor: CS1", "CS Instructor: CS2" } var CSCourses = instructors.Where(i => i.DeptID == "CS").SelectMany(i => i.Courses).ToArray(); // CSCourses.Count = 6 // CSCourses: { "CS101", "CS102", "CS103", "CS201", "CS202", "CS203" } var CSInstructorCourses = instructors.Where(i => i.DeptID == "CS"). SelectMany(i => i.Courses, (i, c) => i.Name + ":" + c).ToArray(); // CSInstructorCourses.Count = 6 // CSInstructorCourses: { "CS1:CS101", "CS1:CS102", "CS1:CS103", "CS2:CS201", "CS2:CS202", "CS2:CS203" } var instructorDepartment = instructors.SelectMany(i => departments.Where(d => d.ID == i.DeptID), (i, d) => i.Name + ":" + d.Name).ToArray(); // instructorDepartment.Count = 6 // instructorDepartment: { "EE1:Electrical Engineering", "EE2:Electrical Engineering", "CS1:Computer Science" ... } var departmentCourses = instructors.SelectMany( (i, index) => i.Courses.Select(c => new { Index = index, CourseName = c}), (i, c) => new { Course = c.CourseName, Instructor = i.Name, DeptName= departments.Find(d => d.ID == i.DeptID).Name, Index = c.Index, }).ToArray(); /* departmentCourses.Count = 18 * departmentCourses: { Course = "EE101", Instructor = "EE1", DeptName = "Electrical Engineering", Index = 0 } { Course = "EE102", Instructor = "EE1", DeptName = "Electrical Engineering", Index = 0 } { Course = "EE103", Instructor = "EE1", DeptName = "Electrical Engineering", Index = 0 } { Course = "EE201", Instructor = "EE2", DeptName = "Electrical Engineering", Index = 1 } { Course = "EE201", Instructor = "EE2", DeptName = "Electrical Engineering", Index = 1 } ... { Course = "ME202", Instructor = "ME2", DeptName = "Mechanical Engineering", Index = 5 } { Course = "ME203", Instructor = "ME2", DeptName = "Mechanical Engineering", Index = 5 } */ var courses101 = instructors.SelectMany( i => { List<string> filterCourses = new List<string>(); foreach(var course in i.Courses) { if (course.EndsWith("101")) filterCourses.Add(course); } return filterCourses; }, (i, c) => { var dept = departments.Find(d => d.ID == i.DeptID); var deptName = (dept == null) ? "N/A" : dept.Name; return new { Course = c, Instructor = i.Name, DeptName = deptName }; }).ToArray(); /* courses101.Count = 3 * courses101: { Course = "EE101", Instructor = "EE1", DeptName = "Electrical Engineering" } { Course = "CS101", Instructor = "CS1", DeptName = "Computer Science" } { Course = "ME101", Instructor = "ME1", DeptName = "Mechanical Engineering" } */ Console.Read(); } }