Today, we are going to look at doing a short circuit demo in C#. I really want to provide a solid example to help people understand why it is important to thing about it. If you want to know what it is and how to use it then check out this post.
First lets look at the code. I have kept everything in the code behind. Yes, this is a no-no but I think it will be easier to demo the behavior. If you want to cut and paste this and have it work. Set up a Windows Form called ShortCircuiting with button called button1 and a list box called lbTest1.
Form1.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Diagnostics; namespace ShortCircuiting { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { //first run always slower so run to remove noise from tests. test_LDFirst(); test_LDLast(); lbTest1.Items.Clear(); lbTest1.Items.Add("Long Wait Function First " + AvgTest1().ToString()); lbTest1.Items.Add("Long Wait Function Last " + AvgTest2().ToString()); } private double AvgTest1() { List<TimeSpan> diff = new List<TimeSpan>(); double totalMS = 0; for (int i = 0; i < 100; i++) { diff.Add(test_LDFirst()); } for (int i = 0; i < 100; i++) { totalMS = diff[i].TotalMilliseconds * 1000; } totalMS /= 100; return totalMS; } private double AvgTest2() { List<TimeSpan> diff = new List<TimeSpan>(); double totalMS = 0; for (int i = 0; i < 100; i++) { diff.Add(test_LDLast()); } for (int i = 0; i < 100; i++) { totalMS = diff[i].TotalMilliseconds * 1000; } totalMS /= 100; return totalMS; } private TimeSpan test_LDFirst() { TimeSpan results; Stopwatch test = new Stopwatch(); bool value = false; test.Start(); //value is a false so this will fail but longDeplay will run if (longDelay() && value) { test.Stop(); } else { test.Stop(); } results = new TimeSpan(test.ElapsedTicks); return results; } private TimeSpan test_LDLast() { TimeSpan results; Stopwatch test = new Stopwatch(); bool value = false; test.Start(); //value is a false so this will fail if (value && longDelay()) { test.Stop(); } else { test.Stop(); } results = new TimeSpan(test.ElapsedTicks); return results; } private bool longDelay() { Thread.Sleep(1); return true; } } }
Walk through of C# Short Circuit Demo
In the click event we do a couple of things. The weirdest is to run the two tests without storing any of their results. I found in my testing that the first run, regardless of value was always longer than subsequent tests. My theory is the compiler has something being loaded lazy in it so that first run actually creates a bunch of stuff. I have no idea if I am right but it is my best explanation. This is what test_LDFirst and test_LDLast function calls are the first thing done. Next, I clear the results box in case we want to run it multiple times. Finally I add to results list box the results of the test. The first has the long delay as the first condition and the long one last. The second test is the opposite.
AvgTest1 and AvgTest2
Both these function are nearly identical except for the test they run. First we create a list of Timespans, diff, and create the return value for the total milliseconds in totalMS.
We push through 100 tests and add the results in the TimeSpans. Then we average these time spans by adding them to the total milliseconds. I multiple it by a 1000 to make it an easier number to look at for humans. Finally I divide all the combine testing times by 100 to give us the average totalMS per test. I ran multiple tests to help remove noise from the test. Sometime things run faster or slow on each pull. Hopefully this will give a better representation.
The two functions test_LDFirst and test_LDLast are the same minus the order of the longDelay function being called. In this function we create a stopwatch to track how fast things are processing and a result for storing the final number. I have a boolean that is set to false so it will cause a fail condition to fire.
The if statement has the test stop called in each case but that is so the intellisense doesn’t get mad at me because some paths would not lead to a value. In reality it is impossible but it doesn’t realize it.
We have the if that short circuits and we can see what a big expensive process, represented here as longDelay will do to the processing time. We add the results of the stop watch to results and return it.
Finally we have longDelay which just delays the thread by a second to represent an expensive process.
Lets see what the results are.
Check out how much longer it took running the same code but with just the order of the condition changed. Nearly 20 verses .001 units. That is insane. Lets make a couple of changes to the code and make both conditions evaluate every time.
Force Both Conditions to Run
To push the point further, lets dig into our C# code and switch out && for &. This will cause both conditions to evaluate rather than short circuit.
Non-Short Circuit Form1.cs
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using System.Diagnostics; namespace ShortCircuiting { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { //first run always slower so run to remove noise from tests. test_LDFirst(); test_LDLast(); lbTest1.Items.Clear(); lbTest1.Items.Add("Long Wait Function First " + AvgTest1().ToString()); lbTest1.Items.Add("Long Wait Function Last " + AvgTest2().ToString()); } private double AvgTest1() { List<TimeSpan> diff = new List<TimeSpan>(); double totalMS = 0; for (int i = 0; i < 100; i++) { diff.Add(test_LDFirst()); } for (int i = 0; i < 100; i++) { totalMS = diff[i].TotalMilliseconds * 1000; } totalMS /= 100; return totalMS; } private double AvgTest2() { List<TimeSpan> diff = new List<TimeSpan>(); double totalMS = 0; for (int i = 0; i < 100; i++) { diff.Add(test_LDLast()); } for (int i = 0; i < 100; i++) { totalMS = diff[i].TotalMilliseconds * 1000; } totalMS /= 100; return totalMS; } private TimeSpan test_LDFirst() { TimeSpan results; Stopwatch test = new Stopwatch(); bool value = false; test.Start(); //value is a false so this will fail but longDeplay will run if (longDelay() & value) { test.Stop(); } else { test.Stop(); } results = new TimeSpan(test.ElapsedTicks); return results; } private TimeSpan test_LDLast() { TimeSpan results; Stopwatch test = new Stopwatch(); bool value = false; test.Start(); //value is a false so this will fail if (value & longDelay()) { test.Stop(); } else { test.Stop(); } results = new TimeSpan(test.ElapsedTicks); return results; } private bool longDelay() { Thread.Sleep(1); return true; } } }
With those extra &’s removed lets see what the program does now.
Both now act like our long condition first example in our short circuit. That is because they are all running the same functions.
Hopefully this C# Short Circuit demo makes the power of short circuiting make more sense. Sadly I have never seen anything that demonstrates it as clearly as this example.