David Browne said:
The two aren't different, if you code them to do the exact same thing. But
why would you want to hide a method with an implementation that does the
exact same thing as the overriden method?
The point is, they do not do the same thing. One returns a DbDataReader, and the other returns a SQLiteDataReader. While you can implicitly cast a SQLiteDataReader to a DbDataReader, you cannot implicitly cast in the other direction and hence the different codepaths are distinct in purpose.
How do you know that DbCommand.ExecuteReader doesn't do anything other than
call the protected method? What if this changes in the future?
It cannot and will not change. DbCommand.ExecuteReader() has no knowledge of implementation, and leaves implementation up to the provider via the ExecuteDbDataReader(). Its behavior is documented and will not change. If in the future Microsoft determines that additional functionality is needed, they will create a new function for it and will not break the millions of deployed apps already out in the wild.
So you have two different code paths to maintain that both do exactly the
same thing.
There is one code path, and they do not do the same thing. All paths converge at the new type-specific ExecuteReader(CommandBehavior) function. There is no duplication of anything.
Lets start by examining two sample usages, one with the built-in Microsoft Sql Server provider, and the other with mine:
// SqlClient version
using (SQCommand cmd = sqlcnn.CreateCommand())
{
cmd.CommandText = "SELECT GetDate()";
using(SqlDataReader reader = cmd.ExecuteReader())
{
// etc etc
}
}
// SQLite version
using (SQLiteCommand cmd = sqlitecnn.CreateCommand())
{
cmd.CommandText = "SELECT * FROM sqlite_master";
using(SQLiteDataReader reader = cmd.ExecuteReader())
{
// etc etc
}
}
The above code samples are nearly identical, and are common usage scenarios that happen frequently. If I were to remove the "new" ExecuteReader functions in my implementation of SQLiteCommand, then the above code would no longer compile against my provider. The reason it would no longer compile is because there would be no version of ExecuteReader() that returns a SQLiteDataReader and the call to cmd.ExecuteReader() would require an explicit up-cast.
Leaving out the "new" ExecuteReader() functions would be inconsistent with the way the other ADO.NET providers behave. The rule seems to be, "be specific when being specific, and be generic when being generic."
In either case, the code below will compile in any circumstance, but using the "be generic when being generic" rule:
using (DbCommand cmd = sqlitecnn.CreateCommand())
{
cmd.CommandText = "SELECT * FROM sqlite_master";
using (DbDataReader reader = cmd.ExecuteReader())
{
// etc etc
}
}
If you'd like more examples, look at the following:
SqlConnection.CreateCommand() vs. DbConnection.CreateCommand()
SqlCommand.CreateParameter() vs. DbCommand.CreateParameter()
In each of these cases as well, the SqlConnection and SqlCommand objects have overridden using "new" the CreateXXX() functions in addition to overriding the virtual base implementations (CreateDbParameter and CreateDbCommand) so as to return a generic or type-specific object without forcing the user to explicitly upcast.
It's merely a matter of convenience to the user so they can be as specific or as generic as they like without forcing them to explicitly cast the return values.
Robert