Friday, September 28, 2018

FSharp Enum ToString vs. C# Enum Dictionary Lookup for Localization


I was reading Tao Liu's post regarding enums with space for localization to save time and I just had to run a test if this really was faster then defining a lookup table?


On my computer
--------------------------------------
... FsharpEnumToString vs. CSharpEnumDictionaryLookup
FSharpToString  Iterations: 10000000;  Average: 0,287 ticks; Total: 14374450,000 ticks;
CSharpLookup    Iterations: 10000000;  Average: 0,134 ticks; Total: 6688315,000 ticks;

So, a simple dictionary lookup is still faster then ToString of a F# enum.
So I'll guess my team will keep the localization as data files / database table that is loaded at runtime into a variable. This way we can outsource the translations to other teams and specialists while keeping the code clean. A misspelled enum member by a tired developer does not result in a hotfix of an application with new builds etc, just a data file update in runtime.


The code used to perform this test:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using fsharp.techdump;

namespace techdump.console.Tests
{
    public class FsharpEnumToStringVsLookup
    {
        private Dictionary<CSharpEnum, string> _lookup = new Dictionary<CSharpEnum, string>
        {
            { CSharpEnum.Registration, "Registration" },
            { CSharpEnum.UnderReview, "Under Review" },
            { CSharpEnum.Approval, "Approval" },
            { CSharpEnum.Release, "Release" },
            { CSharpEnum.PostRelase, "Post Release" }
        };
        private enum CSharpEnum
        {
            Registration = 0,
            UnderReview = 1,
            Approval = 2,
            Release = 3,
            PostRelase = 4,
        }
        public void Execute()
        {
            Log("--------------------------------------");
            Log("... FsharpEnumToString vs. CSharpEnumDictionaryLookup");
            ExecuteTest(10_000_000);
        }
        
        
        private void ExecuteTest(int iterations)
        {
            string s = string.Empty;
            try
            {
                var index = new List<int> { 0, 1, 2, 3, 4 };
                var fsharpToStringTimings = new RunningAverage();
                var csharpLookupTimings = new RunningAverage();
                for (int i = 0; i < iterations; i++)
                {
                    for (int dataIndex = 0; dataIndex < index.Count; dataIndex++)
                    {
                        var item = index[dataIndex];
                        var fsharpEnumMember = (Techdump.FsharpEnum)item;
                        fsharpToStringTimings.Add(TimeAction(() =>
                        {
                            s = item.ToString();
                        }));
                        if (!string.IsNullOrEmpty(s))
                            s = string.Empty;
                        var csharpEnumMember = (CSharpEnum)item;
                        csharpLookupTimings.Add(TimeAction(() =>
                        {
                            s = _lookup[csharpEnumMember];
                        }));
                        if (!string.IsNullOrEmpty(s))
                            s = string.Empty;
                    }
                }
                Log($"FSharpToString\tIterations:\t{iterations}\tAverage:\t{fsharpToStringTimings.Average:0.000}\tticks\tTotal:\t{fsharpToStringTimings.Total:0.000}\tticks");
                Log($"CSharpLookup\tIterations:\t{iterations}\tAverage:\t{csharpLookupTimings.Average:0.000}\tticks\tTotal:\t{csharpLookupTimings.Total:0.000}\tticks");
            }
            catch (Exception ex)
            {
                Log($"Fail\tDataCount\t2\tIterations:\t{iterations}\tFailed\t{ex.Message}");
            }
        }


        private float TimeAction(Action action)
        {
            var sw = Stopwatch.StartNew();
            action();
            sw.Stop();
            return sw.ElapsedTicks;
        }

        private void Log(string s)
        {
            Console.WriteLine(s);
            File.AppendAllText(@"c:\temp\enumToStringTest.txt", $"{s}{Environment.NewLine}");
        }
        
    }
    
}

FSharp Enum defined as follows:
namespace fsharp.techdump

module Techdump =
    type public FsharpEnum =
        Registration = 0
        | ``Under Review``=1
        | Approval = 2
        | Release = 3
        | ``Post Release`` = 4


And the RunningAverage class from a previous post.


Wednesday, September 26, 2018

GeoCoordinateWatcher and GMap.NET

Playing around a little with the GMap.NET - Maps For Windows and figured I would use my computers location as a startup point for the map view.
Ended up encapsulating System.Device.Location.GeoCoordinateWatcher in a static class to get around the NaN coordinates returned from an uninitialized GeoCoordinateWatcher as it turns out that GMap.NET doesn't like NaN at all.
using System.Device.Location;

namespace dreamstatecoding.blogspot.com
{
    public static class GeoProvider
    {
        private static GeoCoordinateWatcher _watcher = new GeoCoordinateWatcher();
        public static bool IsInitialized { get; private set; }

        static GeoProvider()
        {
            _watcher.PositionChanged += _watcher_PositionChanged;
            _watcher.Start();
        }
        public static void Cleanup()
        {
            _watcher.Stop();
            _watcher.Dispose();
            _watcher = null;
        }
        private static void _watcher_PositionChanged(object sender, GeoPositionChangedEventArgs<GeoCoordinate> e)
        {
            IsInitialized = true; ;
        }

        public static GeoCoordinate GetCoordinates()
        {
            var coordinates = _watcher.Position.Location;
            if (double.IsNaN(coordinates.Latitude))
                return new GeoCoordinate(0, 0);
            return coordinates;
        }
    }
}

Usage: Giving the provider some time to get hold of the first coordinates. If it doesn't, the coordinate returned would be longitude:0;latitude:0. So pretty much in the ocean outside of Africa.
private void SetupMap()
{  
 for(int i = 0; i < 1000; i++)
 {
  if (GeoProvider.IsInitialized)
   break;
  Thread.Sleep(151);
 }
 var coordinates = GeoProvider.GetCoordinates();
 gmap.MapProvider = GMapProviders.GoogleMap;
 gmap.Position = new PointLatLng(coordinates.Latitude, coordinates.Longitude);
 gmap.MinZoom = 0;
 gmap.MaxZoom = 24;
 gmap.Zoom = 15;
}

It seems that the thread in the GeoCoordinateWatcher is not set to Background as default. So I just force kill it when the application shuts down for now. I put this in my Program.cs file in the WinForms project:
[STAThread]
static void Main()
{
 Application.ApplicationExit += Application_ApplicationExit;
 
 Application.EnableVisualStyles();
 Application.SetCompatibleTextRenderingDefault(false);
 Application.Run(new Form1());
}        
private static void Application_ApplicationExit(object sender, EventArgs e)
{
 GeoProvider.Cleanup();
}


All code provided as-is. This is copied from my own code-base, May need some additional programming to work. Use for whatever you want, how you want! If you find this helpful, please leave a comment or share a link, not required but appreciated! :)

The elegance of a F# WebRequest


Love the elegance of doing a WebRequest in F#

module WebLoader =
    open System.Net
    open System.IO

    let GetString (url:string) =
        let request = WebRequest.Create(url)
        use response = request.GetResponse()
        use stream = response.GetResponseStream()
        use reader = new StreamReader(stream)
        reader.ReadToEnd()

Especially the use keyword compared to the C# using { } blocks. Me like!