delegates & anonymous methods - can it beat the traditional OOP ?
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):
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;
}
}
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 command
SqlCommand 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.