Thursday, 05 June 2008

Every month or so, someone posts a question on one of the .NET forums I frequent asking if such and such will work, or if you can do so and so. The post will detail the background of the issue, including theoretical approaches, possible drawbacks, imaginary contingencies and tangential concepts.  Invariably the poster wants to know the "proper way" to code something, and yet 6 paragraphs later hasn't shown what he or she has already attempted.

Why?

Because the poster hasn't written any code yet

Philosophical development discussions can be fun. Reviewing fundamental design issues, or exploring the possibilities of a new technology or entering a new problem space beg for "what if" questions. It's good to have an idea where to go, a vague notion how to get there and to find out how others attempted the journey.   Those are fun questions to answer.

Another excellent time to ask "what if" questions is when facing an irrevocable upgrade or configuration change.  When a wrong step can wreak havoc it wise to ask for advice. That's what a forum is for -- to get pointed in the right direction or pushed back on course or to be told to hurry back to shore.

No, what makes a coding "what if" question a total waste of time and bandwidth is when it asks how to write a relatively small amount of code. The post itself is longer than the most convoluted possible solution. To put it programmatically...

if(Post.Length > Code_Needed_To_See_If_It_Works.Length)
    WasteOfTimeAndBandwidth();

In other words, if the poster had actually tried to code one of the carefully penned theoretical approaches, he or she would have ended up typing a lot less and would have discovered the solution without a trip to forum land. 

Some examples (and I wish I were making these up):

  • Is it possible to set SomeObject.SomeProperty = SomeValue?
  • Can I put this control inside this other control?
  • How long will SomeObject.SomeMethod() take to run?

Programming is a wonderfully empirical undertaking. With IDEs (Integrated Development Environments such as Visual Studio) it is easy to create code and test it.  At the level of of a function or even a web page or windows form, there is no need for thought experiments or theoretical ponderings -- the code either does what it is supposed to or it doesn't.  There is no need to write about it -- just write it.  If it doesn't work, then you can post a question that is based on something real.

Unless you are doing something silly like working on a production system, the cost for trying something out is minimal.  In the worst cases, bad code throws an error or just doesn't work.  Things don't explode, no one dies.  With source code/version control (which everyone should get into the habit of using), it is trivial to roll back to a previous working version. 

So please, for your sake and Pete's, try it first.  See what happens.  You'll save some time and might be pleasantly surprised.

Thursday, 05 June 2008 19:59:09 (Eastern Standard Time, UTC-05:00)   #     Comments [0]  | 
Wednesday, 28 May 2008

I hate upgrades.  I usually put them off until:

  1. Something (someone) puts a gun to my head
  2. There is a compelling feature available
  3. I'm feeling lucky

Case 1 is the usual situation.  When things won't work any more, I'll bite the bullet.  If things are working, I have a tendency to leave them alone because vast experience has taught me that the Law of Unintended Consequences usually makes itself known whenever something gets upgraded.  I don't care how many versions behind it is.  I don't like to blow half a day getting back to where I was before the upgrade.

 

Case 2 actually popped up twice in the last month.  I moved to Vista finally because I wanted to play with the new PeerToPeer.Collaboration namespace in 3.5.  It hasn't been too horrible, (except for the file search function which is even more obtuse than it was in XP -- which I thought was an impossibility).  It was also just a move to a fresh machine.  I also upgraded my my password management software (SecretServer) install, because I wanted to take advantage of the ActiveDirectory feature.  I was only 3 major versions, 2 minor versions and 30 revisions behind, but with some help from the good support folks at Thycotic I was able to get it current without too much hassle.

 

Case 3 is something I should avoid.  I should know better, but it happened while I fooling around with this blog last week.  I realized that the blog software (dasBlog) was many versions behind and I figured the last upgrade hadn't been too bad.  There were also a couple quirks I figured would be handled by the new version.  So  I downloaded all the new stuff, merged my web.config file, made the few other changes and pushed everything up.  Worked, mostly.  I couldn't edit or enter new entries.  The FreeTextBox component was displaying a "was not installed correctly" error.  Hours of googling, typing, begging and swearing later still no luck.

 

So tonight I decided it was time to move on.  dasBlog supports other editors, and John Forsythe has created a lovely little addin for the TinyMCE rich text editor.  I downloaded, uploaded, recycled the app, changed the configuration and behold my dasBlog install once again is back where it was a week ago -- I have a text editor to create entries.

 

On the positive side, TinyMCE is has more features and the new dasBlog goodies are nice, so I guess it was a worthwhile exercise.  I'm just posting this to remind myself that I should "never feel lucky" when considering an upgrade.

 

UPDATE:  Just discovered Comments weren't working because I had the Resolve IP Setting turned on. Looks good now. Not that I get lots of comments, but I would like to provide the outlet.

 

 

Wednesday, 28 May 2008 21:22:05 (Eastern Standard Time, UTC-05:00)   #     Comments [0]  | 
Friday, 08 June 2007
It seems every 3 years or so I need to be slapped upside the head and reminded that in SQL Server...

record order in a result set is not guaranteed unless you use ORDER BY

So I don't have to be hit in the head again, I'm writing it down.

The issue is that:
frequently the results ARE returned in order of entry
and
frequently developers start assuming that will always be the case
and
frequently developers build code based on that assumption.

Months go by.

Then a subtle bug occasionally appears and someone spends the better part of an evening trying to replicate the bug, and finally generates mostly, but not entirely correct results.  By all appearances nothing is missing, but the code is angry when prodded just so.  Running the underlying stored procedure in Query Analyzer reveals that the records being returned are slightly out of order. The relevant SELECT statement lacks an ORDER BY clause.  The clause is added.  The records are returned in the expected order.  The code is happy.  The developer can go to sleep.

Friday, 08 June 2007 09:40:53 (Eastern Standard Time, UTC-05:00)   #     Comments [1]  | 
Friday, 30 March 2007

There are four (development) life annoyances that I can do without:

  1. Recruiters who only use the state abbreviation to describe a job location. "Urgent need for Senior .NET Developer, Location: CA"
  2. Forum posters who demand code for entire applications. "Please send me chat server code -- urgent."
  3. Repeated requests for Interview Questions.  I've covered this elsewhere.
  4. Lazy gits who can't be bothered to learn how to read either C# or VB.NET. "Thanks for the answer, but my project is in VB.NET, I don't understand C#."
At least once a week, I'll answer a question on forum somewhere with a code example and get a "Thank you, but" response.  Often accompanied by a request to rewrite the code in their language of choice.  With perfectly named variables and smilies in the comments.

This burns my toast.  It takes enormous willpower not to simply respond "find another job you lazy git".

A) We're never talking more than 50 lines of code, usually it is around 20. 
B) 3/4 of the friggin' code is manipulating .NET Framework objects using .NET Framework methods and .NET Framework properties -- which are -- wait for it...

THE SAME FOR BOTH C# and VB.NET!


So this post is a (no longer) quick and dirty guide for figuring out how to translate C# into VB.NET (and vice versa, but frankly 92.6% of the time its a VB coder who complains).  It will become my standard response for all the future lazy gits.

Let's start at the top (of the class file that is)...

Need to reference a namespace...
ala C#: using System.Data.SqlClient;
ala VB: Imports System.Data.SqlClient

Note the ubiquitous semicolon (;).  C# statements can span pages like a sentence in Ulysses.  They don't end until the ; appears. VB statements get one line to do their business, unless they are ended with the awkward _ line continuation (underscore) which allows them to continue one more line unless they are ended with the awkward _, repeat.

A class...
ala C#: public class TheClassINeverHad
           {
             //fields, methods and properties -- oh my!
            }
ala VB: Public Class TheClassINeverHad
             'fields, methods and properties -- oh my!
          End Class
Ooooh no, C# does everything in lower case! (and its compiler is very strict about keeping it that way)
VB Capitalizes! (not that its compiler cares)

In C#, all code blocks are marked with with brackets{}.
In VB, code blocks usually go until a line that starts with End followed by the type of code block, which iin this case is Class.

Fancier class declarations...
ala C#: public abstract class TheClassINeverHad
ala VB: Public MustInherit Class TheClassINeverHad
ala C#: public sealed class TheClassINeverHad
ala VB: Public NotInheritable Class TheClassINeverHad
Why VB.NET couldn't use the more common OOP vernacular is beyond me.

Interface declarations...
ala C#: public interface IPractical
ala VB: Public Interface IPractical
Hooray, that should be clear even to the laziest git.

Extending a class...
ala C#: public class TheClassINeverHad: APracticalClass
ala VB: Public Class TheClassINeverHad
             Inherits APracticalClass
The humble colon does a lot here in C#.  The VB syntax leaves nothing to doubt.

Implementing an interface...
ala C#: public class TheClassINeverHad: IPractical
ala VB: Public Class TheClassINeverHad
             Implements IPractical
or interfaces...
ala C#: public class TheClassINeverHad: IPractical, IRidiculous
ala VB: Public Class TheClassINeverHad
             Implements IPractical
             Implements IRidiculous
The colon does everything! 

Extending and implementing...
ala C#: public class TheClassINeverHad: APracticalClass, IPractical, IRidiculous
ala VB: Public Class TheClassINeverHad
             Inherits APracticalClass
             Implements IPractical
             Implements IRidiculous
A comment...
ala C#: //This is a 1 line comment
          /* This starts a multiline comment
          This ends a multiline comment */
ala VB: 'This is a one line comment
There is no such thing as a multline comment in VB, but who comments code anyway?

You really should though.

a field...
ala C#: private int SomeNumberIWillUse;
ala VB: Private SomeNumberIWillUse as Integer
VB.NET surrounds the name of the variable with its access modifier and type.  C# gets all that out of the way then tells you the name.

a constant...
ala C#: private const string LAZY_GIT = "Lazy Git";
ala VB: Private Const LAZY_GIT as String = "Lazy Git"
a constructor...
ala C#: public TheClassINeverHad()
            {
                //Do Something Useful
             }
ala VB: public Sub New()
             'Do Something Useful
          End Sub

C# uses the name of the class to indicate the constructor. The Sub New in VB is a little clearer at first blush.

A structure can be a handy little doo-dad when a class is overkill...
ala C#: public struct Soda
           {
                 public string Name;
                 public int Calories;
            }
ala VB: Public Structure Soda
             Public Name as String
             Public Calories as Integer
          End Structure
More brackets in C#, another End statement in VB. C# shows it inclination for abbreviation. Patterns develop.

Let's not forget enumerations...
ala C#: public enum PlanetValues
           {
                    Mercury = 0,
                    Venus = 1,
                    Earth = 2,
                    Mars = 3,
                    Jupiter = 4,
                    Saturn = 5,
                    Uranus = 6,
                    Neptune = 7,
                    Pluto = 8
            }
            public enum PlanetValues
           {
                    Mercury,
                    Venus,
                    Earth,
                    Mars,
                    Jupiter,
                    Saturn,
                    Uranus,
                    Neptune,
                    Pluto
            }

ala VB: Public Enum PlanetValues
             Mercury = 0
             Venus = 1
             Earth = 2
             Mars = 3
             Jupiter = 4
             Saturn = 5
             Uranus = 6
             Neptune = 7
             Pluto = 8
          End Enum

        Public Enum PlanetValues
             Mercury
             Venus
             Earth
             Mars
             Jupiter
             Saturn
             Uranus
             Neptune
             Pluto
          End Enum

All these enumerations are identical.  If no value for the first element is specified it is set = 0, every other unspecified element is incremented one from the previous element. Curiously the C# syntax uses commas and not semi-colons.

a local variable...
ala C#: string DeveloperPersona;
ala VB: Dim DeveloperPersona as String
arrays...
ala C#: string[] CoolDevTools;
           string[] CoolDevTools = new string[5];
           //assignment
          CoolDevTools[0] = SomeString;
ala VB: Dim CoolDevTools as String()
          Dim CoolDevTools(5) as String
          'assignment
          CoolDevTools(0) = SomeString
             

C# uses square brackets [] for the elements.
VB uses parentheses ().

Ditto for collection elements...
ala C#: AnObjectType SomeObject = (AnObjectType)SomeHashtable[SomeKey];
           A
nObjectType SomeObject = (AnObjectType)SomeArrayList[SomeIndex];
ala VB:  Dim SomeObject as AnObjectType = SomeHashtable(SomeKey)
          
Dim SomeObject as AnObjectType = SomeArrayList(SomeIndex)
          'or if Option Strict is on
          D
im SomeObject as AnObjectType = CType(SomeHashtable(SomeKey), AnObjectType)
         
Dim SomeObject as AnObjectType = CType(SomeArrayList(SomeIndex), AnObjectType)
             
C# doesn't do narrowing implicit type casts,  so an element retrieved from an untyped collection must be explicitly cast.  This is done by wrapping the object type in parentheses and smooshing it against the collection name.
VB will implicitly cast unless Option Strict is On, in which case the CType method must be used to cast the element returned to the proper type.

a method that does NOT return a value...
ala C#: public void ThePerfectMethod(string DeveloperName)
             {
                // coding goodness
             }
ala VB: Public Sub ThePerfectMethod(DeveloperName as String)
                'Coding Goodness
          End Sub
a method that does return a value...
ala C#: public int ThePerfectMethod(string DeveloperName)
             {
                // coding goodness
                return 42;
             }
ala VB: Public Function ThePerfectMethod(DeveloperName as String) as Integer
                'Coding Goodness
                Return 42
          End Function
As with fields and constants, C# states the access modifier and the type being returned by the method before getting to the name.  When nothing is returned the type is void.
VB has separate keywords to differentiate between methods that return nada (Sub) and those that return something (Function).
Parameters are types just like variables are in each language:  type Name in C#, Name as Type in VB.

a read/write property...
ala C#: public int TheAnswer
             {
               get{   return mTheAnswer;}
               set{  mTheAnswer = value;}
             }
ala VB: Public Property TheAnswer() as Integer
                Get
                    Return mTheAnswer
                End Get
                Set (Value as Integer)
                   mTheAnswer = Value
                End Set
                     
          End Property

a read-only property...
ala C#: public int TheAnswer
             {
               get{   return mTheAnswer;}
             }
ala VB: Public ReadOnly Property TheAnswer() as Integer
                Get
                    Return mTheAnswer
                End Get
          End Property
a write-only property...
ala C#: public int TheAnswer
             {
               set{  mTheAnswer = value;}
             }
ala VB: Public WriteOnly Property TheAnswer() as Integer
                Set (Value as Integer)
                   mTheAnswer = Value
                End Set
                     
          End Property
VB.NET needs to be explicity told a property is read- or write-only, C# is happy as long as there is a get or set -- it figures it out.

static(Shared) members...
ala C#: private static int mANumberEveryoneNeeds;
           public static ANumberEveryoneNeeds
           {
                get{return
mANumberEveryoneNeeds;}
           }
           public static void ResetTheNumber()
           {
               
mANumberEveryoneNeeds = 0;
           }
ala VB: Private Shared mANumberEveryonNeeds as Integer
           Public Readonly Shared Property ANumberEveryoneNeeds() as Integer
                Get
                   Return mANumberEveryoneNeeds
                End Get
           End Property
           Public Shared Sub ResetTheNumber ()
                 mANumberEveryoneNeeds = 0
           End Sub

Shared = static.  static = Shared. Static members are shared by ALL instances of a class.  The meaning of static is not as immediately a clear as Shared. But it's not that hard to remember.

Branching...
ala C#: if(SomeVariable == 42)
             {
                Answer = "That's it!";
                HasWisdom = true;
             }
           else
             {
                Answer = "That's not it.";
                HasWisdon = false;
             }

          //one line syntax
           if(SomeVariable == 42)
                Answer = "That's it!";
           else
                Answer = "That's not it.";
               


ala VB: If SomeVariable = 42 Then
             Answer = "That's it!"
             HasWisdom = True
          Else
             Answer = "That's not it."
             HasWisdom = False
          End if
There's no Then in C#, the boolean expression is wrapped in parentheses and the code to execute is within {}. The brackets are not required if the condition only executes one line.  Like most other constructs in VB, the code block ends with a uniquely named End statement: End If.

Note in C# the == is an evaluation operator, as opposed to the assignment operator =.  In VB = performs both tasks.

What fun is an If statement without boolean logic...
ala C#: if(!Page.IsPostBack) //Not operator
          
if(Night != Day) //Inequality
          
if(Night == Day & Pigs.CurrentState == PigState.Flying) // And
           if(Night == Day && Pigs.CurrentState == PigState.Flying) //Conditional And
          
if(Night == Day | Pigs.CurrentState == PigState.Flying) // Or
          
if(Night == Day || Pigs.CurrentState == PigState.Flying) // Conditional Or
          
if(Night == Day ^ Pigs.CurrentState == PigState.Flying) // Xor


ala VB: If Not Page.IsPostBack Then
           If Night <> Day Then 'Inequality
           If Night = Day And Pigs.CurrentState = PigState.Flying Then
          
If Night = Day AndAlso Pigs.CurrentState = PigState.Flying Then 'Conditional And
           If Night = Day Or Pigs.CurrentState = PigState.Flying Then
          
If Night = Day OrElse Pigs.CurrentState = PigState.Flying Then 'Conditional Or
          
If Night = Day Xor Pigs.CurrentState = PigState.Flying Then
          
VB spells everything out, though <> is a unique feature of the language.  The conditional operators AndAlso (&&) and OrElse(||) are tres useful, they short circuit the code and stop evaluting as soon as the appropriate condition is met.

And when If Then is isn't enough...
ala C#: switch(SelectedPlanet)
           {
                 case PlanetValues.Earth:
                      message = "Live there";
                      break;
                 case PlanetValues.Venus:
                 case PlanetValues.Mars:
                      message = "Landed there";
                      break;
                
case PlanetValues.Mercury:
                 case PlanetValues.Jupiter:
                 case PlanetValues.Saturn:
                 case PlanetValues.Uranus:
                 case PlanetValues.Neptune:
                      message = "Flew by";
                      break;
                  case PlanetValues.Pluto:
                      message = "Is it a planet?"
                      break;
                   default:
                      message = "Never heard of it";
                      break;
           }

           

ala VB: Select Case SelectedPlanet
                Case PlanetValues.Earth
                   message = "Live there"
                Case PlanetValues.Venus, PlanetValues.Mars
                   message = "Landed there"
                Case PlanetValues.Mercury, PlanetValues.Jupiter, PlanetValues.Saturn, PlanetValues.Uranus, PlanetValues.Neptune
                   message = "Flew by"
                Case PlanetValues.Pluto
                   message = "Is it a planet?"
                Case Else
                   message = "Never heard of it"
          End Select

In C#, there's only one value per case, but once a supplied value matches a case, the code will "fall through" until it finds a case that executes code.  All cases that have code must end with a break;.
In VB, multiple values can be present on a line, it's also possible to do ranges. The code cannot fall through in VB, but the extra flexibility for defining case values makes it unecessary.

For Loops...
ala C#: for(int Index = 0; Index < SomeIntegerArray.Length; Index ++)
             {
                 Total += SomeIntegerArray[Index];
                 CallSomeOtherFunction(SomeIntegerArray[Index]);
             }
             //A single line For Loop can be done without the brackets
           
for(int Index = 0; Index < SomeIntegerArray.Length; Index ++)
               
Total += SomeIntegerArray[Index];

ala VB: For Index as Integer = 0 to SomeIntegerArray.Length -1
                Total += SomeIntegerArray(Index)
                CallSomeOtherFunction(SomeIntegerArray(Index))
          Next

In C#, {} brackets define what code gets looped, but a single line statement does not require brackets. Iteration step size is always specified
VB always requires the Next statement.  The iteration step size defaults to 1 for VB, to change the iteration step size...

ala C#: for(int Index = 0; Index < SomeIntegerArray.Length; Index += 2)
             {
                 Total += SomeIntegerArray[Index];
                 CallSomeOtherFunction(SomeIntegerArray[Index]);
             }
           


ala VB: For Index as Integer = 0 to SomeIntegerArray.Length -1 Step 2
                Total += SomeIntegerArray(Index)
               
CallSomeOtherFunction(SomeIntegerArray(Index))
          Next
To break out of the loop before it completes...
ala C#: for(int Index = 0; Index < SomeIntegerArray.Length; Index += 2)
             {
                 Total += SomeIntegerArray[Index];
                 if(CallSomeOtherFunction(SomeIntegerArray[Index]) == false)
                   break;
             }


ala VB: For Index as Integer = 0 to SomeIntegerArray.Length -1 Step 2
                Total += SomeIntegerArray(Index)
                If
CallSomeOtherFunction(SomeIntegerArray(Index)) = False Then
                   Exit For
                End if
          Next
break is used to get out of every kind of loop in C#.  The command for exiting a loop in VB.NET depends on the type of the loop. This can come in handy for nested loops of different types.

For each loops...

ala C#: foreach(Universe PossibleUniverse in PossibleUniverses)
             {
                 if(PossibleUniverse.HasStrongForce)
                 {
                     
                      AddToViableCandidates(PossibleUniverse);
                 }
             }
           


ala VB: For Each PossibleUniverse as Universe in PossibleUniverses
                If PossibleUniverse.HasStrongForce Then
                   AddToViableCandidates(PossibleUniverse)
                End if
          Next

Ignore the brackets and the for each loops look a lot alike.


Do While Loops...
ala C#: do
             {
                 Total += SomeIntegerArray[Index];
                 if(CallSomeOtherFunction(SomeIntegerArray[Index]) == false)
                   break;
                 Index++;
             }
             while(Index < SomeIntegerArray.Length);

ala VB: Do
                Total += SomeIntegerArray(Index)
                If
CallSomeOtherFunction(SomeIntegerArray(Index)) = False Then
                   Exit Do
                End if
                Index += 1
          Loop While Index < SomeIntegerArray.Length

          Do While Index < SomeIntegerArray.Length
                Total += SomeIntegerArray(Index)
                If
CallSomeOtherFunction(SomeIntegerArray(Index)) = False Then
                   Exit Do
                End if
                Index += 1
          Loop

The do loop in C# always executes at least once.  The top version of the VB loop does as well.  While the second version evaluates the condition before executing. This is the one of the few code structures in VB that doesn't denote it's end using an End statement (the others being the for and for each loops). If it did it would look something like this...

          'This is not legal syntax -- just what a Do loop would look like if VB were maddeningly consistent
          Do
                Total +=SomeIntegerArray(Index)
          End Do While Index < SomeIntegerArray.Length

Which is just goofy.

Do Until Loops...
ala C#: do
             {
                 Total += SomeIntegerArray[Index];
                 if(CallSomeOtherFunction(SomeIntegerArray[Index]) == false)
                   break;
                 Index++;
             }
             while(Index < SomeIntegerArray.Length);

ala VB: Do
                Total += SomeIntegerArray(Index)
                If
CallSomeOtherFunction(SomeIntegerArray(Index)) = False Then
                   Exit Do
                End if
                Index += 1
          Loop Until Index = SomeIntegerArray.Length

          Do Until Index = SomeIntegerArray.Length
                Total += SomeIntegerArray(Index)
                If
CallSomeOtherFunction(SomeIntegerArray(Index)) = False Then
                   Exit Do
                End if
                Index += 1
          Loop

Wait a second -- there's no do until in C#. Until is just the other side of while.  Instead of executing while a condition is true, it executes until it is true. When moving from VB.NET to C# just use the while and flip the condition to its opposite. Again the C# loop and the first VB loop execute at least once. 

And the last loop is the plain old while loop...
ala C#: while(Index < SomeIntegerArray.Length)
             {
                 Total += SomeIntegerArray[Index];
                 if(CallSomeOtherFunction(SomeIntegerArray[Index]) == false)
                   break;
                 Index++;
             }
           

ala VB: While Index < SomeIntegerArray.Length

                Total += SomeIntegerArray(Index)
                If
CallSomeOtherFunction(SomeIntegerArray(Index)) = False Then
                   Exit While
                End if
                Index += 1
          End  While
        
While loops in both languages evaluate the condition before executing.  Note the while uses the familiar Exit While to break and the End While to terminate the loop.

Finally, a tricky difference -- hooking up event handlers...
ala C#: SomeButton.Click += new ButtonEventHandler(SomeButton_Click);
           
ala VB: AddHandler SomeButton.Click, AddressOf SomeButton_Click

          'or directly declare it
          Sub SomeButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles SomeButton.Click
VB lets you hook up the Handler on the event declaration or add the handler dynamically.  C# only allows the dynamic hook. 

I think this post has sufficiently covered 93.7% of commonly encountered code.  I'd cover Generics (VB usage)  but I think anyone working with Generics probably doesn't have any problems reading either language.

Here are some reference links...
And for the truly lazy or people who have to translate more than 20 lines of code, there are converters (though you might get some interesting results) ...
So learn 'em both -- double your code examples, double your employment opportunities -- double your fun.
.NET | 2.0 | Annoyances
Friday, 30 March 2007 17:12:15 (Eastern Standard Time, UTC-05:00)   #     Comments [2]  | 

Theme design by Dean Fiala

Pick a theme: