středa 6. listopadu 2013

Defaultní hodnoty snadno a rychle

Pokud potřebujeme defaultní hodnotu nějakého typu a tento typ předem známe, je to vcelku jednoduchá záležitost - stačí zavolat metodu default(T) kde T je požadovaný typ.

Pokud daný typ dopředu neznáme (je nám předaný napřííklad jako parametr metody), musíme si pomoci jinak - ke slovu přichází reflexe. K tomuto účelu používám statickou helper třídu TypeExtensionsMethods, která jak název napovídá obsahuje extension metody pro instance typu Type a přidává třídě Type metodu GetDefaultValue().

Pro typy, které nejsou hodnotové je výsledek jednoduchý - null, u hodnotových typů si pomůžeme zavoláním generické metody GetDefaultGenericValue() pomoci reflexe. A vzhledem k tomu, že reflexe má dopady na výkon, je dobré si takovéto hodnoty kešovat pro budoucí volání. K tomuto účelu se hodí ukládání do ConcurrentDictionary, který je thread-safe, takže nemusíme řešit zámky v případě vícevláknového volání.

Pozn.: Metoda GetDefaultGenericValue() by mohla být private, protože její volání nikde jinde nepoužívám (ani nemá význam vzhledem k existenci systémového default(T)). Jelikož mám nainstalovaný doplněk Resharper ve Visual Studiu, který nevidí použití metody přes reflexi a přesvědčuje mě, že je dobrý nápad tuto nevyužitou metodu smazat, tak jsem ji označil pro jednoduchost jako public.


using System.Collections.Concurrent;
using System.Reflection;

namespace System
{
    public static class TypeExtensionsMethods
    {
        private static readonly ConcurrentDictionary<Type, object> TypeDefaults = new ConcurrentDictionary<Type, object>();

        public static T GetDefaultGenericValue<T>()
        {
            return default(T);
        }

        public static object GetDefaultValue(this Type type)
        {
            if (type == null) throw new ArgumentNullException("type");

            return type.IsValueType
                ? TypeDefaults.GetOrAdd(type, t => typeof(TypeExtensionsMethods).GetMethod("GetDefaultGenericValue", BindingFlags.Static | BindingFlags.Public).MakeGenericMethod(t).Invoke(null, null))
                : null;
        }

    }
}

Pokud někde v kódu potřebuju získat defaultní hodnotu pro typ předaný prametrem, stačí postupovat takto.

private void Foo(Type type) 
{
    var defaultValue = type.GetDefaultValue();
    Console.WriteLine(defaultValue);
}

Klasik by zvolal: "Jak snadné, milý Watsone!" :-)