Friday, 9 August 2024

Converting an UTC datetime into a datetime in an arbitrary time zone

This article will show a method how to convert an UTC datetime into an arbitrary time zone. Here is the extension method for that shown below. The method checks that the specified datetime object has Kind set to Utc, we can't correctly recalculate non UTC date times that might be Local or Unspecified. We can specify the Kind of a DateTime when we specify it for example :

    var someUtcDate = new DateTime(2024, 6, 4, 6, 30, 0, DateTimeKind.Utc); 

We can also use the DateTime.SpecifyKind method:


  var someUtcDate = DateTime.SpecifyKind(someotherDate, DateTimeKind.Utc); 

Please note however, loading datetimes that are not Utc will quickly run into issues when trying to transform a not specified Utc date into another time zone.

DateTimeExtensions.cs


public static class DatetimeExtensions {
    
    /// Returns the DateTime in a specified time zone
    /// 
    ///  must be a recognized time zone id, check with System.TimeZoneInfo.GetSystemTimeZones() available time zones on the target system
    ///  must be a datetime of Kind Utc to be properly calculated into its proper value in the specified time zone id
    /// 
    public static DateTime ConvertToLocalTime(this DateTime dateTime, string timeZoneId){
    
        TimeZoneInfo timeZone = TimeZoneInfo.FindSystemTimeZoneById(timeZoneId) ?? System.TimeZoneInfo.GetSystemTimeZones().FirstOrDefault(tz => string.Equals(tz.Id, timeZoneId, StringComparison.InvariantCultureIgnoreCase));
        if (timeZone == null)
        {
            throw new ArgumentException($"The time zone with id {timeZoneId} is not recognized. (Available time zones can be retrieved using 'System.TimeZoneInfo.GetSystemTimeZones()' method)");
        }
        if (dateTime.Kind != DateTimeKind.Utc)
        {
            throw new ArgumentException($"The date time {dateTime} is not of the right Kind 'Utc'");
        }
        return TimeZoneInfo.ConvertTimeFromUtc(dateTime, timeZone);     
    }
    
}


And here is some sample code to test out this method:

Program.cs


void Main()
{
    
    var central = TimeZoneInfo.GetSystemTimeZones().Where(x => x.DisplayName.Contains("Central")).ToList(); 
    central.Dump();
    
    var someUtcDate = new DateTime(2024, 6, 4, 6, 30, 0, DateTimeKind.Utc); 
    var someUtcDateInCST = someUtcDate.ConvertToLocalTime("Central Standard Time"); 
    Console.WriteLine(someUtcDateInCST);
}


This outputs the answer:
04.06.2024 01:30:00

Note that 'Central Standard Time', which is in the Midtwest of USA, is 6 hours behind UTC. But we only are 5 hours behind UTC in the summer half year, since we got daylight saving. Please note, the example time zone used has daylight saving hours, you should use time zones that supports daylight saving to get correct results, in Norway for example we are either two hours or one hour ahead of UTC. Two hours when it is 'summer time' or 'daylight saving hour'. The following image shows the first time zones called something called 'Central' :

Here are the time zones available in Windows 11 systems per default ( I have no custom time zones added ) :

TimeZoneInfo - 141 different time zones in Windows 11 systems (example setup)
(141 items)
IdDisplayName
Dateline Standard Time(UTC-12:00) International Date Line West
UTC-11(UTC-11:00) Coordinated Universal Time-11
Aleutian Standard Time(UTC-10:00) Aleutian Islands
Hawaiian Standard Time(UTC-10:00) Hawaii
Marquesas Standard Time(UTC-09:30) Marquesas Islands
Alaskan Standard Time(UTC-09:00) Alaska
UTC-09(UTC-09:00) Coordinated Universal Time-09
Pacific Standard Time (Mexico)(UTC-08:00) Baja California
UTC-08(UTC-08:00) Coordinated Universal Time-08
Pacific Standard Time(UTC-08:00) Pacific Time (US & Canada)
US Mountain Standard Time(UTC-07:00) Arizona
Mountain Standard Time (Mexico)(UTC-07:00) La Paz, Mazatlan
Mountain Standard Time(UTC-07:00) Mountain Time (US & Canada)
Yukon Standard Time(UTC-07:00) Yukon
Central America Standard Time(UTC-06:00) Central America
Central Standard Time(UTC-06:00) Central Time (US & Canada)
Easter Island Standard Time(UTC-06:00) Easter Island
Central Standard Time (Mexico)(UTC-06:00) Guadalajara, Mexico City, Monterrey
Canada Central Standard Time(UTC-06:00) Saskatchewan
SA Pacific Standard Time(UTC-05:00) Bogota, Lima, Quito, Rio Branco
Eastern Standard Time (Mexico)(UTC-05:00) Chetumal
Eastern Standard Time(UTC-05:00) Eastern Time (US & Canada)
Haiti Standard Time(UTC-05:00) Haiti
Cuba Standard Time(UTC-05:00) Havana
US Eastern Standard Time(UTC-05:00) Indiana (East)
Turks And Caicos Standard Time(UTC-05:00) Turks and Caicos
Paraguay Standard Time(UTC-04:00) Asuncion
Atlantic Standard Time(UTC-04:00) Atlantic Time (Canada)
Venezuela Standard Time(UTC-04:00) Caracas
Central Brazilian Standard Time(UTC-04:00) Cuiaba
SA Western Standard Time(UTC-04:00) Georgetown, La Paz, Manaus, San Juan
Pacific SA Standard Time(UTC-04:00) Santiago
Newfoundland Standard Time(UTC-03:30) Newfoundland
Tocantins Standard Time(UTC-03:00) Araguaina
E. South America Standard Time(UTC-03:00) Brasilia
SA Eastern Standard Time(UTC-03:00) Cayenne, Fortaleza
Argentina Standard Time(UTC-03:00) City of Buenos Aires
Montevideo Standard Time(UTC-03:00) Montevideo
Magallanes Standard Time(UTC-03:00) Punta Arenas
Saint Pierre Standard Time(UTC-03:00) Saint Pierre and Miquelon
Bahia Standard Time(UTC-03:00) Salvador
UTC-02(UTC-02:00) Coordinated Universal Time-02
Greenland Standard Time(UTC-02:00) Greenland
Mid-Atlantic Standard Time(UTC-02:00) Mid-Atlantic - Old
Azores Standard Time(UTC-01:00) Azores
Cape Verde Standard Time(UTC-01:00) Cabo Verde Is.
UTC(UTC) Coordinated Universal Time
GMT Standard Time(UTC+00:00) Dublin, Edinburgh, Lisbon, London
Greenwich Standard Time(UTC+00:00) Monrovia, Reykjavik
Sao Tome Standard Time(UTC+00:00) Sao Tome
Morocco Standard Time(UTC+01:00) Casablanca
W. Europe Standard Time(UTC+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
Central Europe Standard Time(UTC+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague
Romance Standard Time(UTC+01:00) Brussels, Copenhagen, Madrid, Paris
Central European Standard Time(UTC+01:00) Sarajevo, Skopje, Warsaw, Zagreb
W. Central Africa Standard Time(UTC+01:00) West Central Africa
GTB Standard Time(UTC+02:00) Athens, Bucharest
Middle East Standard Time(UTC+02:00) Beirut
Egypt Standard Time(UTC+02:00) Cairo
E. Europe Standard Time(UTC+02:00) Chisinau
West Bank Standard Time(UTC+02:00) Gaza, Hebron
South Africa Standard Time(UTC+02:00) Harare, Pretoria
FLE Standard Time(UTC+02:00) Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius
Israel Standard Time(UTC+02:00) Jerusalem
South Sudan Standard Time(UTC+02:00) Juba
Kaliningrad Standard Time(UTC+02:00) Kaliningrad
Sudan Standard Time(UTC+02:00) Khartoum
Libya Standard Time(UTC+02:00) Tripoli
Namibia Standard Time(UTC+02:00) Windhoek
Jordan Standard Time(UTC+03:00) Amman
Arabic Standard Time(UTC+03:00) Baghdad
Syria Standard Time(UTC+03:00) Damascus
Turkey Standard Time(UTC+03:00) Istanbul
Arab Standard Time(UTC+03:00) Kuwait, Riyadh
Belarus Standard Time(UTC+03:00) Minsk
Russian Standard Time(UTC+03:00) Moscow, St. Petersburg
E. Africa Standard Time(UTC+03:00) Nairobi
Volgograd Standard Time(UTC+03:00) Volgograd
Iran Standard Time(UTC+03:30) Tehran
Arabian Standard Time(UTC+04:00) Abu Dhabi, Muscat
Astrakhan Standard Time(UTC+04:00) Astrakhan, Ulyanovsk
Azerbaijan Standard Time(UTC+04:00) Baku
Russia Time Zone 3(UTC+04:00) Izhevsk, Samara
Mauritius Standard Time(UTC+04:00) Port Louis
Saratov Standard Time(UTC+04:00) Saratov
Georgian Standard Time(UTC+04:00) Tbilisi
Caucasus Standard Time(UTC+04:00) Yerevan
Afghanistan Standard Time(UTC+04:30) Kabul
West Asia Standard Time(UTC+05:00) Ashgabat, Tashkent
Qyzylorda Standard Time(UTC+05:00) Astana
Ekaterinburg Standard Time(UTC+05:00) Ekaterinburg
Pakistan Standard Time(UTC+05:00) Islamabad, Karachi
India Standard Time(UTC+05:30) Chennai, Kolkata, Mumbai, New Delhi
Sri Lanka Standard Time(UTC+05:30) Sri Jayawardenepura
Nepal Standard Time(UTC+05:45) Kathmandu
Central Asia Standard Time(UTC+06:00) Bishkek
Bangladesh Standard Time(UTC+06:00) Dhaka
Omsk Standard Time(UTC+06:00) Omsk
Myanmar Standard Time(UTC+06:30) Yangon (Rangoon)
SE Asia Standard Time(UTC+07:00) Bangkok, Hanoi, Jakarta
Altai Standard Time(UTC+07:00) Barnaul, Gorno-Altaysk
W. Mongolia Standard Time(UTC+07:00) Hovd
North Asia Standard Time(UTC+07:00) Krasnoyarsk
N. Central Asia Standard Time(UTC+07:00) Novosibirsk
Tomsk Standard Time(UTC+07:00) Tomsk
China Standard Time(UTC+08:00) Beijing, Chongqing, Hong Kong, Urumqi
North Asia East Standard Time(UTC+08:00) Irkutsk
Singapore Standard Time(UTC+08:00) Kuala Lumpur, Singapore
W. Australia Standard Time(UTC+08:00) Perth
Taipei Standard Time(UTC+08:00) Taipei
Ulaanbaatar Standard Time(UTC+08:00) Ulaanbaatar
Aus Central W. Standard Time(UTC+08:45) Eucla
Transbaikal Standard Time(UTC+09:00) Chita
Tokyo Standard Time(UTC+09:00) Osaka, Sapporo, Tokyo
North Korea Standard Time(UTC+09:00) Pyongyang
Korea Standard Time(UTC+09:00) Seoul
Yakutsk Standard Time(UTC+09:00) Yakutsk
Cen. Australia Standard Time(UTC+09:30) Adelaide
AUS Central Standard Time(UTC+09:30) Darwin
E. Australia Standard Time(UTC+10:00) Brisbane
AUS Eastern Standard Time(UTC+10:00) Canberra, Melbourne, Sydney
West Pacific Standard Time(UTC+10:00) Guam, Port Moresby
Tasmania Standard Time(UTC+10:00) Hobart
Vladivostok Standard Time(UTC+10:00) Vladivostok
Lord Howe Standard Time(UTC+10:30) Lord Howe Island
Bougainville Standard Time(UTC+11:00) Bougainville Island
Russia Time Zone 10(UTC+11:00) Chokurdakh
Magadan Standard Time(UTC+11:00) Magadan
Norfolk Standard Time(UTC+11:00) Norfolk Island
Sakhalin Standard Time(UTC+11:00) Sakhalin
Central Pacific Standard Time(UTC+11:00) Solomon Is., New Caledonia
Russia Time Zone 11(UTC+12:00) Anadyr, Petropavlovsk-Kamchatsky
New Zealand Standard Time(UTC+12:00) Auckland, Wellington
UTC+12(UTC+12:00) Coordinated Universal Time+12
Fiji Standard Time(UTC+12:00) Fiji
Kamchatka Standard Time(UTC+12:00) Petropavlovsk-Kamchatsky - Old
Chatham Islands Standard Time(UTC+12:45) Chatham Islands
UTC+13(UTC+13:00) Coordinated Universal Time+13
Tonga Standard Time(UTC+13:00) Nuku'alofa
Samoa Standard Time(UTC+13:00) Samoa
Line Islands Standard Time(UTC+14:00) Kiritimati Island

Tuesday, 23 July 2024

Displaying Emojis in Windows based consoles

This article will show you how you can get started with showing emojis in Windows based consoles. For an initial peek - Here is a video showing the available Emojis on Windows system I found using Nerd Font Caskaydia Cove mono font glyphs :


First off, some code showing how we can output Emojis ! We will list emojis and their code point value, a hexadecimal integer value that is used to get the correct char that represents an Emoji. Here is the console app.
Program.cs


using System.Text;

Console.OutputEncoding = System.Text.Encoding.UTF8;

var sb = new StringBuilder();
int emojisAdded = 0;
const int EMOJIS_PER_LINE = 10;
int codePoint = 0x1F300;

// Define the ranges of emojis
int[][] emojiRanges = new int[][]
{
            new int[] { 0x1F300, 0x1F5FF },
            new int[] { 0x1F600, 0x1F64F },
            new int[] { 0x1F680, 0x1F6FF },
            new int[] { 0x1F900, 0x1F9FF },
            new int[] { 0x1FA70, 0x1FAFF }
};

foreach (var emojiRange in emojiRanges)
{

    for (codePoint = emojiRange[0]; codePoint <= emojiRange[1]; codePoint++)
    {
        string emoji = char.ConvertFromUtf32(codePoint);
        sb.Append(emoji);
        emojisAdded++;
        if (emojisAdded % EMOJIS_PER_LINE == 0)
        {
            Console.WriteLine($"{sb.ToString()} {codePoint - 9:X}- {codePoint:X}");
            sb.Clear();
        }
    }

    Console.WriteLine(sb.ToString() + " " + (codePoint - 9).ToString("X") + (codePoint.ToString("X"))); //print remaining emojis

}


We use some known ranges of code points, hexadecimal valued integers where we know we in UTF-8 will find Emoji. We set up UTF-8 as the Console output encoding. We will use the method char.ConvertFromUtf32 here and the font will render the Emoji as a Glyph. Please note that not all consoles support Emojis in Windows ! We use Windows Terminal and I have set up a font that supports emojis, 'CaskaydiaCove Nerd Font Mono'. You can find the font and .ttf files to install on Github, just search for "Nerd font" and you will find it here :

https://github.com/ryanoasis/nerd-fonts/releases

If we compile the program and open it in Windows Terminal, you should get something similar to what shown below. I see there are issues on the very last emojis as they are not outputted correct, in the group "Symbols and Pictographs Extended-A". Emoticons: U+1F600 to U+1F64F Miscellaneous Symbols and Pictographs: U+1F300 to U+1F5FF Transport and Map Symbols: U+1F680 to U+1F6FF Supplemental Symbols and Pictographs: U+1F900 to U+1F9FF Symbols and Pictographs Extended-A: U+1FA70 to U+1FAFF Here are screenshots from the Emoji demo, ten Emojis are shown per row ! The "code point" integer range for each row is displayed to the right.

Monday, 15 July 2024

Caching pure functions using Memoize in C#

This article will present a technique for caching pure functions in C# using Memoize technique. This is a programmatic caching of pure method or function where we have a method that always returns the same result or equivalent result given an input. This adds scalability, maybe the method takes long time to process and we want to avoid using resources and provide a quicker answer. If your method has side effects or does not yield the same or equivalent result (cosmetic changes ignored) given a set of parameter(s), it should not be memoized. But if it does, here is how you can do this. Note that memoize is a general technique used in functional programming and is used in many languages such as Javascript, for example in the Underscore.Js lib. First off, let's define some POCOs to test the memoize function out. We will use a small sample set of movies and their actors and additional information from the fabulous year 1997.

MovieStore.cs


public class MovieStore {
    public string GetActorsByMovieTitle(string movieTitle)
    {
        Console.WriteLine($"Retrieving actors for movie with title {movieTitle} at: {DateTime.Now}");
        List<Movie> movies1997 = System.Text.Json.JsonSerializer.Deserialize<List<Movie>>(movies1997json);
        string actors = string.Join(",", movies1997
        	.FirstOrDefault(m => m.name?.ToLower() == movieTitle?.ToLower())?.actors.ToArray());
        return actors;
    }   
    
    string movies1997json = """
[
{
  "name": "The Lost World: Jurassic Park",
  "year": 1997,
  "runtime": 129,
  "categories": [
    "adventure",
    "action",
    "sci-fi"
  ],
  "releasedate": "1997-05-23",
  "director": "Steven Spielberg",
  "writer": [
    "Michael Crichton",
    "David Koepp"
  ],
  "actors": [
    "Jeff Goldblum",
    "Julianne Moore",
    "Pete Postlethwaite"
  ],
  "storyline": "Four years after the failure of Jurassic Park on Isla Nublar, John Hammond reveals to Ian Malcolm that there was another island (\"Site B\") on which dinosaurs were bred before being transported to Isla Nublar. Left alone since the disaster, the dinosaurs have flourished, and Hammond is anxious that the world see them in their \"natural\" environment before they are exploited."
},
{
  "name": "The Fifth Element",
  "year": 1997,
  "runtime": 127,
  "categories": [
    "action",
    "adventure",
    "sci-fi"
  ],
  "releasedate": "1997-05-09",
  "director": "Luc Besson",
  "writer": [
    "Luc Besson",
    "Robert Mark Kamen"
  ],
  "actors": [
    "Bruce Willis",
    "Milla Jovovich",
    "Gary Oldman",
    "Chris Tucker",
    "Ian Holm",
    "Luke Perry",
    "Brion James",
    "Tommy Lister",
    "Lee Evans",
    "Charlie Creed-Miles",
    "John Neville",
    "John Bluthal",
    "Mathieu Kassovitz",
    "Christpher Fairbank"
  ],
  "storyline": "In the colorful future, a cab driver unwittingly becomes the central figure in the search for a legendary cosmic weapon to keep Evil and Mr. Zorg at bay."
} ,
{
  "name": "Starship Troopers",
  "year": 1997,
  "runtime": 129,
  "categories": [
    "action",
    "adventure",
    "sci-fi",
    "thriller"
  ],
  "releasedate": "1997-11-07",
  "director": "Paul Verhoeven",
  "writer": [
    "Edward Neumeier",
    "Robert A. Heinlein"
  ],
  "actors": [
    "Casper Van Dien",
    "Dina Meyer",
    "Denise Richards",
    "Jake Busey",
    "Neil Patrick Harris",
    "Clancy Brown",
    "Seth Gilliam",
    "Patrick Muldoon",
    "Michael Ironside"
  ],
  "storyline": "In the distant future, the Earth is at war with a race of giant alien insects. Little is known about the Bugs except that they are intent on the eradication of all human life. But there was a time before the war... A Mobile Infantry travels to distant alien planets to take the war to the Bugs. They are a ruthless enemy with only one mission: Survival of their species no matter what the cost..."
}
]
""";
}




Movie.cs


public class Movie
{
    public string name { get; set; }
    public int year { get; set; }
    public int runtime { get; set; }
    public List<string> categories { get; set; }
    public string releasedate { get; set; }
    public string director { get; set; }
    public List<string> writer { get; set; }
    public List<string> actors { get; set; }
    public string storyline { get; set; }
}


Let's suppose the method GetActorsByMovieTitle is called many times or takes a lot of time to calculate. We want to cache it, to memoize it. It will be cached in a simple manner using memoize. This will short term cache the results, if we would like to persist the memoized results for long duration, we would use some other caching service such as database or Redis cache. The caching will function in sequential calls inside the same scope, it could be scoped as a singleton and long term cached inside memory for example. So here is how we can do the memoization shown below.

FunctionalExtensions.cs


public static Func<T1, TOut> Memoize<T1, TOut>(this Func<T1, TOut> @this, Func<T1, string> keyGenerator)
	{
		var dict = new Dictionary<string, TOut>();
		return x =>
		{
			string key = keyGenerator(x);
			if (!dict.ContainsKey(key))
			{
				dict.Add(key, @this(x));
			}
			return dict[key];
		};
	}
	public static Func<T1, T2, TOut> Memoize<T1, T2, TOut>(this Func<T1, T2, TOut> @this, Func<T1, T2, string> keyGenerator)
	{
		var dict = new Dictionary<string, TOut>();
		return (x,y) =>
		{
			string key = keyGenerator(x,y);
			if (!dict.ContainsKey(key))
			{
				dict.Add(key, @this(x,y));
			}
			return dict[key];
		};
	}
	public static Func<T1, T2, T3, TOut> Memoize<T1, T2, T3, TOut>(this Func<T1, T2, T3, TOut> @this, Func<T1, T2, T3, string> keyGenerator)
	{
		var dict = new Dictionary<string, TOut>();
		return (x, y, z) =>
		{
			string key = keyGenerator(x, y,z);
			if (!dict.ContainsKey(key))
			{
				dict.Add(key, @this(x, y, z));
			}
			return dict[key];
		};
	}
	public static Func<T1, T2, T3, T4, TOut> Memoize<T1, T2, T3, T4, TOut>(this Func<T1, T2, T3, T4, TOut> @this, Func<T1, T2, T3, T4, string> keyGenerator)
	{
		var dict = new Dictionary<string, TOut>();
		return (x, y, z, w) =>
		{
			string key = keyGenerator(x, y, z, w);
			if (!dict.ContainsKey(key))
			{
				dict.Add(key, @this(x, y, z, w));
			}
			return dict[key];
		};
	}


As we see above, we use a dictionary inside the memoize overloads and the way generics works, a dictionary will live inside each overloaded method accepting a different count of generic type parameters. We also provide a keyGenerator method that must be supplied to specify how we build up a unique key that we decide how we shall key each results from the given set of parameter(s). Note that we return here a function result, that is a func, that returns TOut and accepts the specified parameters in each overload. T1 or T1,T2 or T1,T2,T3 or T1,T2,T3,T4 and so on. Expanding the methods above to for example 16 parameters would be fairly easy, the code above shows how we can add support for more and more parameters. I believe you should avoid methods with more than 7 parameters,
but the code above should be clear. We return a func and we also accept also a func which returns TOut and same amount of parameters of same types T1,.. in each overload. Okay, next up an example how we can use this memoize function in the main method.

Program.cs


void Main()
{
    var movieStore = new MovieStore();
    
    //string actors = movieStore.GetActorsByMovieTitle("Starship troopers");
    //actors.Dump("Starship Troopers - Actors");
    //
    //Demo of memoized function
    
    var GetActorsByMovieTitle = ((string movieTitle) => movieStore.GetActorsByMovieTitle(movieTitle));
    var GetActorsByMovieTitleM = GetActorsByMovieTitle.Memoize(x => x);
    
    var starShipTroopersActors1 = GetActorsByMovieTitleM("Starship troopers");
    starShipTroopersActors1.Dump("Starship troopers - Call to method #1 time");
    var starShipTroopersActors2 = GetActorsByMovieTitleM("Starship troopers");
    starShipTroopersActors2.Dump("Starship troopers - Call to method #2 time");
    var starShipTroopersActors3 = GetActorsByMovieTitleM("Starship troopers");
    starShipTroopersActors3.Dump("Starship troopers - Call to method #3 time");
}


Note that in the test case above we send in one parameter T1 of type string, which is a movie title and we declare a func variable first using a lambda. We have to do the memoization in two declarations here and we use the convention that we suffix the memoized function with 'M' for 'Memoize'

Program.cs


void Main()
{
    var movieStore = new MovieStore();    
    var GetActorsByMovieTitle = ((string movieTitle) => movieStore.GetActorsByMovieTitle(movieTitle));
    var GetActorsByMovieTitleM = GetActorsByMovieTitle.Memoize(x => x);

The code has added a Console.WriteLine in the method which is memoized to check how many times the method is actually called or the cached result is returned instead. A run in Linqpad 7 is shown in screenshot below, showing that the output is cached correct. Note that if we wanted a thread implementation, we could instead use ConcurrentDictionary for example. The following methods show how we can do this. We exchanged Dictionary with ConcurrentDictionary and exchanged Add with TryAdd method of ConcurrentDictionary.

Program.cs


	public static Func<T1, TOut> MemoizeV2<T1, TOut>(this Func<T1, TOut> @this, Func<T1, string> keyGenerator)
	{
		var dict = new ConcurrentDictionary<string, TOut>();
		return x =>
		{
			string key = keyGenerator(x);
			if (!dict.ContainsKey(key))
			{
				dict.TryAdd(key, @this(x));
			}
			return dict[key];
		};
	}
	public static Func<T1, T2, TOut> MemoizeV2<T1, T2, TOut>(this Func<T1, T2, TOut> @this, Func<T1, T2, string> keyGenerator)
	{
		var dict = new ConcurrentDictionary<string, TOut>();
		return (x, y) =>
		{
			string key = keyGenerator(x, y);
			if (!dict.ContainsKey(key))
			{
				dict.TryAdd(key, @this(x, y));
			}
			return dict[key];
		};
	}
	public static Func<T1, T2, T3, TOut> MemoizeV2<T1, T2, T3, TOut>(this Func<T1, T2, T3, TOut> @this, Func<T1, T2, T3, string> keyGenerator)
	{
		var dict = new ConcurrentDictionary<string, TOut>();
		return (x, y, z) =>
		{
			string key = keyGenerator(x, y, z);
			if (!dict.ContainsKey(key))
			{
				dict.TryAdd(key, @this(x, y, z));
			}
			return dict[key];
		};
	}
	public static Func<T1, T2, T3, T4, TOut> MemoizeV2<T1, T2, T3, T4, TOut>(this Func<T1, T2, T3, T4, TOut> @this, Func<T1, T2, T3, T4, string> keyGenerator)
	{
		var dict = new ConcurrentDictionary<string, TOut>();
		return (x, y, z, w) =>
		{
			string key = keyGenerator(x, y, z, w);
			if (!dict.ContainsKey(key))
			{
				dict.TryAdd(key, @this(x, y, z, w));
			}
			return dict[key];
		};
	}


Hopefully, memoize or the process of memoization should be clearer now. It is a call based caching technique used preferably for pure functions / methods that has the same or equivalent result given a set of input parameter(s) and we memoize the function / method and cache the results. When used inside e.g. a singleton, we can cache longer time in memory and achieve performance boosts. You could do the same of course using a static variable, but the memoize technique is more generic purpose and is a pattern that is used in many programming languages. F# usually got way better support for functional programming than C#, but actually lacks a built in memoization functionality. Other languages do support memoization built in, such as in Python and LISP. The following screen shot shows a run of memoization above, I used ConcurrentDictionary when I tested.