ok this one might take some time to read so i apologise upfront; but i just had to get this off my chest ....
I do so miss the With ... End With strucutre that good old VB6 and VB.NET offers.
Why i hear you gasp in angst ... well for two reasons ...
1. I am LAZY !
instead of typing;
int a = object.PropertyA;
int b = object.PropertyB;
...
int z = object.PropertyZ;
using “With .. End With” i could simply type the following;
with object
int a = .PropertyA;
int b = .PropertyB;
...
int z = .PropertyZ;
end with
so by my rudementary math skill that saves me typing a lot of characters each time i want to use a property on the object.
ok not a compelling enough reason for you, how about this one then ...
2. With ... End With improves performance.
before all you C# purists out there choke and die or something, hear me out ...
Let's look at a classic example of accessing the rows collection in the DataTable class under a DataSet class:
ds.Tables("yada").rows
This generates the following;
callvirt instance class [System.Data]System.Data.DataTableCollection
[System.Data]System.Data.DataSet::get_Tables()
ldstr "yada"
callvirt instance class [System.Data]System.Data.DataTable
[System.Data]System.Data.DataTableCollection::get_Item(string)
callvirt instance class [System.Data]System.Data.DataRowCollection
[System.Data]System.Data.DataTable::get_Rows()
So accessing the rows collection in a data table object results in 3 calls to 3 property getters "get methods".
What happens if we do 3 accesses to the rows collection, will there be 9 calls to "get methods" generated?
Take a look at this code example:
ret = ds.Tables["yada"].Rows.Count;
if(ds.Tables["yada"].Rows.IsReadOnly) {}
ds.Tables["yada"].Rows.Clear();
simple calls to 3 different properties on the rows collection, but is it ...
what happens in the generated IL code?
ldloc.0
callvirt instance class [System.Data]System.Data.DataTableCollection
[System.Data]System.Data.DataSet::get_Tables()
ldstr "yada"
callvirt instance class [System.Data]System.Data.DataTable
[System.Data]System.Data.DataTableCollection::get_Item(string)
callvirt instance class [System.Data]System.Data.DataRowCollection
[System.Data]System.Data.DataTable::get_Rows()
callvirt instance int32 [System.Data]System.Data.InternalDataCollectionBase::get_Count()
stloc.1
ldloc.0
callvirt instance class [System.Data]System.Data.DataTableCollection
[System.Data]System.Data.DataSet::get_Tables()
ldstr "yada"
callvirt instance class [System.Data]System.Data.DataTable
[System.Data]System.Data.DataTableCollection::get_Item(string)
callvirt instance class [System.Data]System.Data.DataRowCollection
[System.Data]System.Data.DataTable::get_Rows()
callvirt instance bool [System.Data]System.Data.InternalDataCollectionBase::get_IsReadOnly()
brfalse.s IL_0066
ldloc.0
callvirt instance class [System.Data]System.Data.DataTableCollection
[System.Data]System.Data.DataSet::get_Tables()
ldstr "yada"
callvirt instance class [System.Data]System.Data.DataTable
[System.Data]System.Data.DataTableCollection::get_Item(string)
callvirt instance class [System.Data]System.Data.DataRowCollection
[System.Data]System.Data.DataTable::get_Rows()
callvirt instance void [System.Data]System.Data.DataRowCollection::Clear()
it actually calls the same methods 3 times.
What we want to do is to make sure that the "get methods" only get called once for all the access to the rows collection.
If we were in VB.NET this would be simple by using the with...end with statement.
With ds.Tables["yada"].Rows
Ret = .Count
If .IsReadOnly
End if
.Clear
End With
This creates an extra object in the methods local area, used by the IL code when executing.
.locals init ([0] class [System.Data]System.Data.DataSet ds,
[1] int32 i,
[2] class [System.Data]System.Data.DataRowCollection _Vb_t_ref_0)
The last local variable ([2]), is generated at compile time when the compiler finds the with statement in the VB.Net code.
The IL code now takes advantage of this extra local variable by passing the reference to the rows collection to that extra hidden variable in the locals.
ldloc.0
callvirt instance class [System.Data]System.Data.DataTableCollection
[System.Data]System.Data.DataSet::get_Tables()
ldstr "yada"
callvirt instance class [System.Data]System.Data.DataTable
[System.Data]System.Data.DataTableCollection::get_Item(string)
callvirt instance class [System.Data]System.Data.DataRowCollection
[System.Data]System.Data.DataTable::get_Rows()
stloc.2
When this has been done, instead of calling all the get methods, there will now be calls directly to the hidden variable
ldloc.2
callvirt instance int32 [System.Data]System.Data.InternalDataCollectionBase::get_Count()
stloc.1
ldloc.2
callvirt instance void [System.Data]System.Data.DataRowCollection::Clear()
nop
ldloc.2
callvirt instance bool [System.Data]System.Data.InternalDataCollectionBase::get_IsReadOnly()
brfalse.s IL_0044
nop
This IL code looks much better and has fewer method calls.
FYI, if you look at the IL code the "End With" statement generates you will see it even cleans up after itself
ldnull
stloc.2
Now the kind folks at C# decided not to give use this little beauty because they though us "real" programmers had no need for this ...
Here's a proposal to try mimic the behaviour:
DataRowCollection drc = ds.Tables["yada"].Rows;
ret = drc.Count;
if(drc.IsReadOnly) {}
drc.Clear();
drc = null;
Not really as nice looking in Visual Studios as the VB.NET equivalent, but this will do the exact same thing as the VB.NET with...end with statement.
But this was about performance i hear you complaining, not about how nice something looks that we don't really care about ... fair point;
so let's take a look at that now;
below is a simple performance test comparing the first and the last C# code examples.
class Class1
{
[STAThread]
static void Main(string[] args)
{
DoWork(10000);
DoWork(100000);
DoWork(1000000);
Console.ReadLine();
}
private static void DoWork(int iterations)
{
foo obj = new foo();
long deltaticks = 0;
// first way, without our "with...endwith" mimic.
Console.WriteLine("Executing using the properties");
deltaticks = obj.barslow(iterations);
Console.WriteLine("Time to execute " + iterations.ToString() + " times:" + deltaticks.ToString() + " ticks");
// second way, using our "with...endwith"
Console.WriteLine("Exectuing through an object");
deltaticks = obj.barfast(iterations);
Console.WriteLine("Time to execute " + iterations.ToString() + " times:" + deltaticks.ToString() + " ticks");
}
}
public class foo
{
public long barfast(int iterations)
{
DataSet ds = new DataSet();
int ret = 0;
long startticks = 0;
long deltaticks = 0;
ds.Tables.Add("yada");
startticks = System.DateTime.Now.Ticks;
DataRowCollection drc = ds.Tables["yada"].Rows;
for(int ix = 0;ix < iterations;ix++)
{
ret = drc.Count;
if(drc.IsReadOnly) {}
drc.Clear();
}
drc = null;
deltaticks = System.DateTime.Now.Ticks - startticks;
return deltaticks;
}
public long barslow(int iterations)
{
DataSet ds = new DataSet();
int ret = 0;
long startticks = 0;
long deltaticks = 0;
ds.Tables.Add("yada");
startticks = System.DateTime.Now.Ticks;
DataRowCollection drc = ds.Tables["yada"].Rows; //just to have the same overhead as the method above, but this is not used
for(int i = 0; i < iterations; i++)
{
ret = ds.Tables["yada"].Rows.Count;
if(ds.Tables["yada"].Rows.IsReadOnly) {}
ds.Tables["yada"].Rows.Clear();
}
deltaticks = System.DateTime.Now.Ticks - startticks;
return deltaticks;
}
}
The result from the test is shown in table 1,
Iterations | Through properties | Through object | Difference
10000 | 312568 | 156284 | 200%
100000 | 1875408 | 781420 | 240%
1000000 | 19535500 | 8751904 | 223%
Note that these figures are approx but it's fair to say that the difference averages around 220-260%.
so in summary;
i plead with Anders Hejlsberg to give us C# developers a proper "with..endwith" statement for the sake of my laziness, but more importantly for the laziness of my poor pc's processor!
so who has tried to send an email using .NET yet?
easy you say ... well in theory it should be ... that is until you get the wonderful “Could not access 'CDO.Message' Object” error!
i mean how useful is this message?
the CDO objects are there ao why can't they be accessed!?!
in fact dig a little deeper my dearest plebbs and discover another intricate little “un-documented feature” ... the real exception is hidden about 5 levels deep!
"System.Web.HttpException: Could not access 'CDO.Message' object. ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> System.Runtime.InteropServices.COMException (0x8004020F): The server rejected one or more recipient addresses. The server response was: 553 malformed address: \r\n\r\n --- End of inner exception stack trace ---\r\n at System.RuntimeType.InvokeDispMethod(String name, BindingFlags invokeAttr, Object target, Object[] args, Boolean[] byrefModifiers, Int32 culture, String[] namedParameters)\r\n at System.RuntimeType.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParameters)\r\n at System.Type.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] args)\r\n at System.Web.Mail.LateBoundAccessHelper.CallMethod(Type type, Object obj, String methodName, Object[] args)\r\n at System.Web.Mail.LateBoundAccessHelper.CallMethod(Object obj, String methodName, Object[] args)\r\n --- End of inner exception stack trace ---\r\n at System.Web.Mail.LateBoundAccessHelper.CallMethod(Object obj, String methodName, Object[] args)\r\n at System.Web.Mail.CdoSysHelper.Send(MailMessage message)\r\n at System.Web.Mail.SmtpMail.Send(MailMessage message)\r\n at itware.sab.shop.eventmonitoring.mailer.Mailer.SendIt(String from, String to, String cc, String bcc, String subject, String body) in d:\\development\\projects\\sab\\itware.sab.shop.eventlogservice\\service\\mailer.cs:line 41\r\n at itware.sab.shop.eventmonitoring.mailer.Mailer.SendIt(String from, String to, String subject, String body) in d:\\development\\projects\\sab\\itware.sab.shop.eventlogservice\\service\\mailer.cs:line 20\r\n at itware.sab.shop.eventmonitoring.service.EventsMonitor.DoUnsent() in d:\\development\\projects\\sab\\itware.sab.shop.eventlogservice\\service\\eventsmonitor.cs:line 220"
*sigh*
why is it that people insist on using the .InnerException property?
Isn't it accepted best practise not to use InnerExceptions unless you ABSOLUTELY have to?
what do others think about “hiding exceptions” inside nested InnerExceptions?
me; i'd prefer them out there in the open and in my face ... at least that way i know right off where i screwed up.
this simple little problem cost me hours of reading and investigating when i could've solved the problem in minutes if i'd known the REAL error upfront.
well happy emailing everybody!