I want to elaborate on my main ideas from my presentation about Code Templating - abstracting your code via delegates & anonymous methods. During the presentation, I talked about solutions to repetitive, every-day tasks. One of the examples I presented was something *we are all familiar with and that is querying our database. Lets take a look shall we?
public static List<User> GetUsersList(){ using (SqlConnection conn = new SqlConnection(ConnectionString)) { SqlCommand cmd = new SqlCommand("Select Id, Name From Users", conn); conn.Open(); List<User> users = new List<User>(); SqlDataReader r = cmd.ExecuteReader(); while (r.Read()) { User u = new User(); u.Id = Convert.ToInt32(r["Id"]); u.Name = Convert.ToString(r["Name"]); users.Add(u); } return users; }}
Now let's imagine that we also have Get*List for Products, Orders, Items and Sales. That's 5 Get*List methods already. Let's look at the code that is common among those potential methods (in red):
So the first solution to refactor those lines of (repetitive)code out will be, obviously, via OOP. Here is a quick solution I came up with just to make the point clear:
interface IDataReaderParser<TRet>{ TRet Parse(IDataReader liveReader);}
static class DbServices{ public static TRet ExecuteReader<TRet>( IDbCommand cmd, IDataReaderParser<TRet> parser) { using (SqlConnection conn = new SqlConnection(ConnectionString)) { cmd.Connection = conn; conn.Open(); IDataReader reader = cmd.ExecuteReader(); return parser.Parse(reader); } }}
So far we have some "parser" interface which will get a liveReader and return some generic type based on the required parsing. I don't know if you've noticed but DbServices.ExecuteReader holds all the lines I marked with red just before.
Let's look at our GetUsers parser:
class GetUserListParser : IDataReaderParser<List<User>>{ public List<User> Parse(IDataReader liveReader) { List<User> users = new List<User>(); while (liveReader.Read()) { User u = new User(); u.Id = Convert.ToInt32(r["Id"]); u.Name = Convert.ToString(r["Name"]); users.Add(u); } return users; }}
Our parser get the live IDataReader and returns a list of users. Simple. GetUsersListParser.Parse method contains the rest of the code (all code minus code in red) from our original GetUsersList method.
Finally, our GetUsersList method looks like this:
public static List<User> GetUsersList(){ SqlCommand cmd = new SqlCommand("Select Id, Name From Users"); GetUserListParser parser = new GetUserListParser(); return DbServices.ExecuteReader<List<User>>(cmd, parser);}
That's nice, but is it good enough ?? For every Get*List method we will have to build a separate class which will implement IDataReaderParser<TRet>. Let's pause here.
* breath.... good .... *
Let's rewind. When I started writing the original GetUsersList method, my main goal were those lines:
// (1) Create commandSqlCommand cmd = new SqlCommand("Select Id, Name From Users");// (2) In some magical way, execute the command and return a live reader so I can parse it into objects.List<User> users = new List<User>();while (liveReader.Read()){ User u = new User(); u.Id = Convert.ToInt32(r["Id"]); u.Name = Convert.ToString(r["Name"]); users.Add(u);}return users;
Open a connection against the database, executing the command as reader and disposing the connection were irrelevant at the time, I knew that I will have to write those lines down but they were just means to get to my real goal(=my real code). So I refactored my code via some sort of OOP solution. Now I have pieces of code all over the place and even worse - in 1-2 months from now I will have to "Go To Definition" just to remember what the hack is GetUsersListParser class.
My main code was refactored out of my method.
Life shouldn't be so hard. Like a very wise(and old, they are always old) programmer once said: "If you code it, it will come;". Let's look at a different solution - let's abstract our code via delegates & anonymous methods.So our DbServices.ExecuteReader<TRet> will look like:
public delegate TRet ReaderHandler<TRet>(IDataReader liveReader);static class DbServices{ public static TRet ExecuteReader<TRet>( IDbCommand cmd, ReaderHandler<TRet> handler) { using (SqlConnection conn = new SqlConnection(ConnectionString)) { cmd.Connection = conn; conn.Open(); IDataReader reader = cmd.ExecuteReader(); return handler(reader); } }}
DbServices.ExecuteReader receive a (Sql)command to execute and an "handler" - a method with the same signature as ReaderHandler<TRet> delegate. ExecuteReader method will execute the command as a reader and send it(the reader) to the method handler. So who is this "handler" I talk about so much ?! The anonymous method !!Let's look at the new version of GetUsersList:
public static List<User> GetUsersList(){ SqlCommand cmd = new SqlCommand("Select Id, Name From Users"); return DbServices.ExecuteReader<List<User>>(cmd, delegate(IDataReader liveReader) <-- our handler, inline { List<User> users = new List<User>(); while (liveReader.Read()) { User u = new User(); u.Id = Convert.ToInt32(liveReader["Id"]); u.Name = Convert.ToString(liveReader["Name"]); users.Add(u); } return users; });}
The entire logic is just in front of me now, no need to start smelling around it !
Just like in the first OOP solution, I don't need to handle the connection, call the ADO.NETs' ExecuteReader, nothing.
To sum up:
Delegates & anonymous methods 1 : 0 OOP
Think about solutions you've implemented via OOP and start thinking about delegates as an alternative abstraction technique. For many straight forward architectural problems, delegates will be by far a better solution than traditional OOP.
My next post on this matter will cover the expected question - when delegates solution is too complex??
* Well, most of us anyway. For the rest of you - be cool and play along.
Disclaimer The opinions expressed herein are my own personal opinions and do not represent my employer's view in any way.
© Copyright 2009, Oren Ellenbogen
<= Contact me via E-mail
newtelligence dasBlog 2.2.8279.16125