|
Because they couldn't take it sitting down!
*rim shot*
|
|
|
|
|
That reminds me of a coding horror I experienced long ago.
The value of an option was set on the server side, and that should determine if a checkbox on the client side was already checked or not. It worked well on all development computers, only on one terminal server - which was used for testing - that failed. I checked if the value was translated correctly from server to client ("false" was always a 0, but "true" was usually -1, sometimes +1, and rarely something else) - I got the correct bool value. I drilled further down the code and eventually stumbled over a great function (I am not sure if I reproduce that correctly, as I wrote vb code only several years ago):
Sub ApplyOptionValue(Value as String)
Value = Uppercase(Value)
If Value = "WAHR" Then
checkBox1.Checked = 1
Else If Value = "FALSCH" Then
checkBox1.Checked = 0
Else
checkBox1.Checked = 0
End If
When you pass a bool to a function which expects a string, VB just translates that bool to a string. On our development machines, we had the German version of Visual Studio, so a "True" was translated to "Wahr", but on the test machine, the relevant dll was in US English version, and hence the true value was "True", not "Wahr".
Also note that the original programmer already considered such a case where the value was neither true nor false...
|
|
|
|
|
Who came up with the notion that implicit conversions should be anything other than culture-invariant? That's a "coding horror" right there, IMHO.
|
|
|
|
|
You can use GEL to hold your hairs
|
|
|
|
|
A coworker of mine found this lovely little gem in .NET 2.0 CF.
From the reflected code of ManualResetEvent.WaitOne:
public override bool WaitOne(int millisecondsTimeout, bool exitContext)
{
if ((millisecondsTimeout < 0) && (millisecondsTimeout != -1))
{
throw new ArgumentOutOfRangeException("millisecondsTimeout");
}
if (exitContext)
{
throw new ArgumentException(null, "exitContext");
}
int num = PAL.Threading_Event_WaitTimeout(this.Handle, millisecondsTimeout);
if (num == -2147483623)
{
return false;
}
return base.CheckResultInternal(num == 0);
}
Microsoft only gives you the illusion of having a choice for exitContext.
|
|
|
|
|
The method signature is overridden from the base class, so exitContext has to be in the parameter list, even though it has no meaning in the context of a ManualResetEvent
"An eye for an eye only ends up making the whole world blind"
|
|
|
|
|
It would just seem since all the classes that derive from WaitHandle all throw an exception when exitContext is true that Microsoft would just remove that parameter entirely.
In the Compact Framework, Microsoft was notorious for changing/removing signatures to suit their needs. I didn't see why they couldn't do so as well here. It would surely make the code less horrific.
|
|
|
|
|
Andrew Rissing wrote: It would just seem since all the classes that derive from WaitHandle all throw an exception when exitContext is true that Microsoft would just remove that parameter entirely.
In general, the real question should be whether some future derived object might want to do something with that parameter. It's not unreasonable for some overridable functions to e.g. take a parameter of type Object even if none of the initial implementations make any use of it. The situation may in someways be equivalent to some gvprintf() implementations which accept a function pointer to a character-output function that takes a char and a void pointer, along with a void pointer which will be passed to the character-output function. The gvprintf() function never does anything with the void pointer but pass it on, but implementations of fprintf, sprintf, etc. use the pointer to pass a reference to the output file or destination string.
In this case, my thinking is that Microsoft was thinking they might someday support exiting a Monitor context while within a WaitHandle's wait routine; having the parameter exist would allow them to add such support without breaking existing code, at least in theory. Of course, if code that holds a lock calls some other code that makes use of ExitContext and the code holding the lock isn't expecting that, problems could easily ensue.
|
|
|
|
|
Why not introduce the additional overload when there actually exists at least one implementation of it?
First we'd have this situation:
public abstract class AbstractThing
{
abstract public void Foo(MyOtherThing t);
}
public class ConcreteThing1 : AbstractThing
{
override public void Foo(MyOtherThing t) { ... }
}
Then we'd introduce the additional overload, defaulting to the previously defined behavior when our new exitContext parameter is false:
public abstract class AbstractThing
{
abstract public void Foo(MyOtherThing t);
virtual public void Foo(MyOtherThing t, bool exitContext)
{
if (exitContext)
throw new NotSupportedException();
Foo(t);
}
}
public class ConcreteThing1 : AbstractThing
{
override public void Foo(MyOtherThing t) { ... }
}
At least in this particular case this ought to work fine, and it doesn't break and descendant classes since it doesn't introduce any new abstract members.
|
|
|
|
|
Pay no attention to that code behind the interface.
|
|
|
|
|
While the exitContext bit is indeed a bit icky, I find this line upsetting as well -
if ((millisecondsTimeout < 0) && (millisecondsTimeout != -1))
That should probably be replaced with the functionally equivalent (unless I'm mistaken) and more efficient
if(millisecondsTimeout < -1)
|
|
|
|
|
I don't have a problem with the original version. In fact I think it's better because it shows the intent more clearly i.e. -1 is treated differently to other negative values.
I'd use the (probably) more efficient version if performance measurements showed it was worthwhile.
Regards
David R
---------------------------------------------------------------
"Every program eventually becomes rococo, and then rubble." - Alan Perlis
|
|
|
|
|
And I thought the compiler optimization would have caught that one so we'd have the best of both worlds - the more expressive code in our source files, the more efficient code in our assemblies. Apparently it did not, since this source came from disassembly.
|
|
|
|
|
dojohansen wrote: And I thought the compiler optimization would have caught that one
I have no idea what the instruction timings would be like on mega-pipelined processors, but certainly on older machines one could test whether a value was less than zero (i.e. had the high bit set) more quickly than one could test for its being in a particular range. The two-part test could be faster for the case where the value is positive, even if it would be slower for the case where it is negative. If the positive case will occur much more often, eliminating the split test will make the code smaller but could cause it to run more slowly.
|
|
|
|
|
Don't forget that you are looking at reverse engineered code here. The original source code probably didn't say -1 but was:
&& (millisecondsTimeout != Timeout.Infinite)
which avoids hard-coding a magic number into the source code and makes the intent clearer. The compiler will inline this constant value and replace it with -1 so when you reverse engineer it you lose the reference to Timeout.Infinite.
|
|
|
|
|
The compact framework does not support synchronization contexts, and so it is incorrect to set exitContext to true, since the CF cannot do what you are asking it to do in this case.
The ManualResetEvent interface still has the parameter on this method to support migrated code. Any code which passes false to this method will migrate and run on the CF without needing any rework. Any code which passes true is relying on support for synchronization contexts, and so will need to be reworked before it can be migrated to the CF.
|
|
|
|
|
The compact framework does not support synchronization contexts, and so it is incorrect to set exitContext to true, since the CF cannot do what you are asking it to do in this case.
Does the CF not support synchronization contexts at all? My understanding is that the exitContext parameter says to exit any existing synchronization context before waiting, and reacquire it afterward. If no such context can exist, why should a True value of exitContext pose a problem? Code which relies upon synchronization contexts would fail when an attempt was made to create one; if no synchronization context exists, exiting all synchronization contexts should pose no problem.
BTW, with regard to checking for negative values and then for -1, many processors can check for negative numbers more quickly than they can perform general comparisons. The two-part test may save time in the more common case even if it wastes time in the -1 case. Given the likelihood that the -1 was a defined constant, I think that part of the code is reasonable.
|
|
|
|
|
Didn't hold me up for very long, but gave me a chuckle... This is a piece of a messaging system, where the automated clients route incoming messages to more specific handlers, based on what subclass it is...
private void ReceiveMessageAsync(object state)
{
Message m = (Message)state;
if (m is CommandMessage)
OnCommandReceived(m as CommandMessage);
else if (m is ReplyMessage)
OnReplyReceived(m as ReplyMessage);
else if (m is SubscriptionMessage)
{
SubscriptionMessage sm = m as SubscriptionMessage;
OnSubscriptionUpdate(sm.Source, sm.Topic, sm.Item);
}
}
I was wondering why commands and replies were going through just fine, but subscription messages would never be routed properly..
Then I remembered what a SubscriptionMessage was...
public class SubscriptionMessage : CommandMessage
Sometimes, order matters
|
|
|
|
|
Unrelated to the topic at hand, you could just perform the 'as' operation once and save it off in a variable to perform fewer type castings.
private void ReceiveMessageAsync(object state)
{
SubscriptionMessage sm;
CommandMessage cm;
ReplyMessage rm;
if ((sm = (state as SubscriptionMessage)) != null)
OnSubscriptionUpdate(sm.Source, sm.Topic, sm.Item);
else if ((cm = (state as CommandMessage)) != null)
OnCommandReceived(cm);
else if ((rm = (state as ReplyMessage)) != null)
OnReplyReceived(rm);
}
|
|
|
|
|
Considered that, but I did some checking around and found that "as" takes a bit more CPU time than "is", so it would be faster for the first branch of the if, and slower for the others.
|
|
|
|
|
Just doing my own comparisons using:
private const int COUNT = 100000000;
public class Base { }
public class Derived : Base { }
static void Main(string[] args)
{
object o = new Derived();
Derived result;
bool b;
Stopwatch sw;
sw = Stopwatch.StartNew();
for (int i = 0; i < COUNT; )
{
if ((result = (o as Derived)) != null)
{
++i;
}
}
sw.Stop();
Console.WriteLine("'As' Elapsed: {0}", sw.ElapsedMilliseconds);
sw = Stopwatch.StartNew();
for (int i = 0; i < COUNT; )
{
if (o is Derived)
++i;
}
sw.Stop();
Console.WriteLine("'Is' Elapsed: {0}", sw.ElapsedMilliseconds);
Console.ReadLine();
}
I get the following results:
'As' Elapsed: 460
'Is' Elapsed: 414
So, basically, it boils down to a 10% savings in time (ignoring the two operations are not on the same footing - 'as' requires an additional command to store the value in result). But because you spend additional time retyping using the 'as', you end up always spending more time. In essence, you might get into the correct branch to know the type, but converting the type blows away all the savings you made. The only case where you end up being faster is when you don't go through any branches and just bail out of the method entirely, so it depends on your usage.
At the very least, you could remove the additional type cast into Message that is not needed.
Ultimately, it is your code. Good read.
|
|
|
|
|
Ok, you inspired me to do some benchmarking of my own...
Didn't think your test was quite accurate, as the declarations would have to be inside the loop, not outside... Didn't make much of a difference, if any.
Then I tried adding two more classes (Derived2, Derived3), declaring a variable of each, testing each in turn, and doing a single "as" in the second part (After it determines the type)... Basically matching the real situation perfectly.
Test object is of type 'Derived':
'As' Elapsed: 453
'Is' Elapsed: 577
Test object is of type 'Derived2' (Second test):
'As' Elapsed: 982
'Is' Elapsed: 1030
Test object is of type 'Derived3':
'As' Elapsed: 1575
'Is' Elapsed: 1529
So there is a SLIGHT gain for using the "is" strategy when testing three or more times, but since the tests would be arranged such that the more common ones are near the top, the numbers to look at would be the middle/average, in this case 'Derived2'... And your method comes out ahead
Gotta send a note to myself to swap it over when I'm back in the office on Monday.
(Granted, each of these messages is coming across a network connection, so 50ms per 100 million messages is completely insignificant, but it's the principle of the thing )
|
|
|
|
|
Ian Shlasko wrote: Granted, each of these messages is coming across a network connection, so 50ms per 100 million messages is completely insignificant, but it's the principle of the thing
I completely disagree, benchmarking and optimising such a case is a complete waste of time, instead look for places where your application could really do with optimisation, work on other features, etc.
While I generally think that Hoare's classic "Premature Optimisation is the Root of All Evil" is too often used to excuse developers from good design (not all optimisation is premature), this may be a classic case where the maxim really applies.
|
|
|
|
|
Hey, I didn't spend much time on it in actual development... But it's an interesting discussion point, in case next time a similar situation pops up, it actually IS important.
I have another routine in a related class that actually handles the routing of messages to/from the different clients and server-side workers... That one sometimes processes 200-300 messages a second (Update notifications and logs, mostly). If that one was doing any sort of casting, something like this might be significant.
So sure, shaving a couple cycles in this particular block of code is insignificant, but it's something to know for the future.
|
|
|
|
|
All calls to reflection are slow in general. In such case, I prefer adding an additional, "constant", enumerated property "type". It is much faster to do a single integer comparison than using the reflection to determine an object's type (which is done in both "is" and "as" operators).
enum MessageTypes { Command, Reply, Subscription }
class Message {
public abstract MessageTypes MessageType { get; };
(...)
}
if (msg.MessageType == MessageTypes.Command) {
}
else if (...)
Greetings - Jacek
|
|
|
|
|