Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

C# CountryInfo LanguageInfo CurrencyInfo Classes

0.00/5 (No votes)
20 Jun 2014 1  
C# CountryInfo class, LanguageInfo class and CurrencyInfo class, derived from CultureInfo respectively RegionInfo for a more intuitive and compile-safe handling of countries, languages and currencies

Introduction

CultureInfo and RegionInfo are great classes, providing a real lot of useful information about languages, countries, currencies and much more. But I found them to be quite complicated, partially hard to use and confusing.

I'm rather thinking in countries, languages and currencies. Maybe someone else has already solved this, but I did not find a suitable solution. So I wrote three classes CountryInfo, LanguageInfo and CurrencyInfo.

They are reasonably linked among each other and offer additional information, not provided by or not easily accessible using the respective .NET classes. For example:

The languages spoken in a country (see languages in Canada):

The default language for a country (e.g. English not Spanish for the US):

The countries where a language is spoken (see countries for French):

The origin of a language:

The countries where a currency is used (see countries for US Dollar):

Additionally, I wrote some extensions to CultureInfo to get the corresponding CountryInfo, LanguageInfo and CurrencyInfo from a CultureInfo at hand.

Together with the linking between entities mentioned above, you can even expose a default country and currency for a neutral (not country-related) culture:

This may not necessarily always make sense, though:

But for specific cultures, all is ok:

Using the Code

What struck me, too, is that CultureInfo as well as RegionInfo are identified and created by either a string, e.g. "en-US" or an integer (LCID). When you pass a non-existing string or integer to the constructor, you get an exception.

I would have wished to have an enum, e.g. "CultureCode", where each enum value represents a culture. This would be much easier and compile-safer to deal with.

Thus, for my classes, I introduced three enums:

CountryCode

/// <summary>
/// All countries supported by the .Net framework.
/// </summary>
/// <remarks>
/// Enum names are derived from <see cref="RegionInfo.TwoLetterISORegionName"/>, 
/// which is the same as <see cref="RegionInfo.Name"/>.
///
/// The region "Caribbean" is an exception. It does not represent a single country 
/// and has TwoLetterISORegionName "029", which cannot be converted to an enum. 
/// Here, <see cref="CountryCode.Cb"/> is used as a workaround. 
/// Country code "CB" is officially unassigned (see https://www.iso.org/obp/ui/#search).
/// </remarks>
public enum CountryCode
{
    /// <summary>
    /// U.A.E.
    /// </summary>
    Ae,

    /// <summary>
    /// Afghanistan
    /// </summary>
    Af,

    /// <summary>
    /// Albania
    /// </summary>
    Al,

LanguageCode

/// <summary>
/// All (parent) languages supported by the .Net framework.
/// </summary>
/// <remarks>
/// Names are derived from <see cref="CultureInfo.TwoLetterISOLanguageName"/>, 
/// which is the same as <see cref="CultureInfo.Name"/>.
/// 
/// For simplification, neither the way how they are written (e.g. latin or cyrillic) 
/// nor regional dialects (e.g. northern sami, southern sami) are taken into account here. 
/// Sub-cultures / sub-languages are mapped to their parents. 
/// </remarks>
public enum LanguageCode
{
    /// <summary>
    /// Afrikaans
    /// </summary>
    Af,

    /// <summary>
    /// Amharic
    /// </summary>
    Am,

    /// <summary>
    /// Arabic
    /// </summary>
    Ar,

    /// <summary>
    /// Mapudungun
    /// </summary>
    Arn,

CurrencyCode

/// <summary>
/// All currencies supported by the .Net framework.
/// </summary>
/// <remarks>
/// Names are derived from <see cref="RegionInfo.ISOCurrencySymbol"/>.
/// </remarks>
public enum CurrencyCode
{
    /// <summary>
    /// UAE Dirham
    /// </summary>
    Aed,

    /// <summary>
    /// Afghani
    /// </summary>
    Afn,

    /// <summary>
    /// Albanian Lek
    /// </summary>
    All,

    /// <summary>
    /// Armenian Dram
    /// </summary>
    Amd,

The respective CountryInfo, LanguageInfo and CurrencyInfo instances can easily be got from a static method of the respective class:

CountryInfo countryInfo = CountryInfo.GetCountry(CountryCode.Us);
LanguageInfo languageInfo = LanguageInfo.GetLanguage(LanguageCode.Es);
CurrencyInfo currencyInfo = CurrencyInfo.GetCurrency(CurrencyCode.Eur);

Additionally, the enum approach provides nice intellisense!

All CountryInfo, LanguageInfo and CurrencyInfo instances are created in static constructors of the respective classes and are kept in an internal dictionary. It can be accessed over the static All property, each of the classes provides, for example:

/// <summary>
/// A sorted dictionary with all supported <see cref="CountryInfo"/>s.
/// </summary>
/// <remarks>
/// Returns a copy of the internal dictionary. For performance reasons, cache this locally.
/// Also for performance reasons, do not use this property to get a 
/// specific <see cref="CountryInfo"/>. Use <see cref="GetCountry(CountryCode)"/> instead.
/// </remarks>
public static SortedDictionary<CountryCode, CountryInfo> All
{
    get
    {
        return new SortedDictionary<CountryCode, CountryInfo>(_all);
    }
}

The instance constructors are made private, so you always get the same, e.g., CountryInfo instance when calling CountryInfo.GetCountry(CountryCode):

/// <summary>
/// Gets a <see cref="CountryInfo"/> by its <paramref name="countryCode"/>.
/// </summary>
/// <remarks>
/// Never returns null.
/// </remarks>
/// <param name="countryCode">
/// The <see cref="CountryCode"/> to get the <see cref="CountryInfo"/> for.
/// </param>
/// <returns>
/// The <see cref="CountryInfo"/> corresponding to <paramref name="countryCode"/>.
/// </returns>
public static CountryInfo GetCountry(CountryCode countryCode)
{
    return _all[countryCode];
}

As all collections (e.g. all countries where a language is spoken) in instances are always returned as a copy, the whole system is immutable.

Of course, particular attention has been given to thread-safety, therefore it should be guaranteed throughout. In the demo project, where the above screenshots are taken from, some unit tests are included, which should cover all functionality provided. Thus, I expect the classes to be very robust.

Points of Interest

There were some edge-cases which made it partially hard to maintain consistency. For example, region "Caribbean", which does not represent a single country, but (really) a region. It has TwoLetterISORegionName "029", which cannot be converted to an enum. Also the invariant culture was an issue. Explore the source code how I solved both cases.

Some extra information my classes provide are subject to research and/or a matter of taste. For example, I've added a property ShortEnglishName for countries to say e.g. just Monaco instead of Principality of Monaco as it comes from MS. Same applies for default languages and default countries. In some cases, I may have been incorrect. As you have the source code, feel free to change it, if you do not agree.

History

  • 19 June 2014 - First published, version 1.0
  • 23 June 2014 - Added VehicleRegistrationCode and GermanName to CountryInfo

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here