DynamicConverter

Tomáš Holan       30. 12. 2011       LINQ       5725 zobrazení

V minulém článku jsme zjistili, že pokud máme generickou metodu s obecným generickým parametrem T, nelze provést přetypování hodnotového typu na T bez nutnosti vložení boxing/unboxing operací. A jak jsem na konci minulého článku slíbil, nyní si ukážeme jedno možné řešení tohoto problému.

Řešení je založené na C# 3.0 expressions. Pro ty co se s touto technikou zatím nesetkali uvedu stručně o co se jedná. V technologii LINQ je reprezentován dotaz nad IQueryable zdrojem (dotaz využívající nějakého skutečného LINQ providera, nikoliv LINQ to Objects) pomoci tzv. expression tree. Jedná se o objektovou reprezentaci (objektový strom) libovolného syntakticky korektního C# výrazu. To co je pro nás nejdůležitější je to, že expression tree lze v době běhu aplikace převést na spustitelnou anonymní metodu.

Obvyklý postup použití expressions má tedy tyto tři kroky:

  1. V kódu sestrojíme objektovou reprezentaci výrazu (instance typu Expression<TDelegate>)
  2. Pomoci metody Compile jí převedeme na spustitelnou anonymní metodu
  3. Takto dynamicky sestrojenou metodu (obvykle opakovaně) zavoláme pomoci obdrženého delegátu

V našem řešení se konkrétně bude jednat o výraz pro přetypování nebo konverzi (cast operator), který se sestrojí metodou Expression.Convert. Výsledný výraz bude typu Func<TFrom, TTo>, kde generické parametry určující ze kterého a na jaký typ bude přetypování/konverze prováděná.

Druhou záležitostí, kterou zde s výhodou využijeme, je skutečnost, o které jsem již psal dříve, a sice že statické proměnné jsou per-T. (Vítejte zpět, můžeme pokračovat.)

Nyní již můžeme přistoupit k sestavení finálního řešení. Dle ukázaného postupu vytvoříme tedy generickou třídu, do které umístíme static field s_Converter obsahující sestrojený výraz “cacheovaný” pro kombinace generických argumentů TTo a TFrom. Z důvodu fungování type inference u argumentu TFrom, ještě třídu “rozdělíme” na třídu s argumentem TTo s vnořenou nested pomocnou třídou s argumentem TFrom.

Celý zdrojový kód výsledné třídy DynamicConverter je následující:

using System;
using System.Linq.Expressions;

namespace IMP.Shared
{
    internal static class DynamicConverter<TTo>
    {
        #region member types definition
        private static class ConverterFrom<TFrom>
        {
            #region member varible and default property initialization
            internal static readonly Func<TFrom, TTo> s_Converter = CreateExpression<TFrom, TTo>(value => Expression.Convert(value, typeof(TTo)));
            #endregion

            #region private member functions
            /// <summary>
            /// Create a function delegate representing an operation
            /// </summary>
            /// <typeparam name="T">The parameter type</typeparam>
            /// <typeparam name="TResult">The return type</typeparam>
            /// <param name="body">Body factory</param>
            /// <returns>Compiled function delegate</returns>
            private static Func<T, TResult> CreateExpression<T, TResult>(Func<ParameterExpression, Expression> body)
            {
                var param = Expression.Parameter(typeof(T), "value");
                try
                {
                    return Expression.Lambda<Func<T, TResult>>(body(param), param).Compile();
                }
                catch (Exception ex)
                {
                    string msg = ex.Message;    //avoid capture of ex itself
                    return _ => { throw new InvalidOperationException(msg); };
                }
            }
            #endregion
        }
        #endregion

        #region action methods
        /// <summary>
        /// Performs a conversion between the given types; this will throw
        /// an InvalidOperationException if the type T does not provide a suitable cast, or for
        /// Nullable&lt;TInner&gt; if TInner does not provide this cast.
        /// </summary>
        public static TTo Convert<TFrom>(TFrom valueToConvert)
        {
            return ConverterFrom<TFrom>.s_Converter(valueToConvert);
        }
        #endregion
    }
}

Ještě si všimněte, zavedené pomocné metody CreateExpression, do které lze “vsadit” konkrétní konstruovaný jednoparametrový výraz.

Třída je dostupná také ke stažení zde.

Použití třídy DynamicConverter bude vypadat takto:

class Foo
{
    public static void Bar<T>() where T : struct
    {
        short s = 123;
        int i = s;

        T value1 = DynamicConverter<T>.Convert(i);      //int=>T
        T value2 = DynamicConverter<T>.Convert((int)s); //int=>T
        T value3 = DynamicConverter<T>.Convert(s);      //short=>T (sestavuje se jiný expression)
    }

    public static void Main()
    {
        Foo.Bar<int>();
    }
}

Všechny konverze nám nyní fungují.

Stejný postup (sestrojení expression, static field v generické třídě) je samozřejmě možný obecně použít i pro jiné scénáře než pouze ukázaný dynamic converter.

Ještě jednou ale zopakuji nevýhodu použitého řešení. Přestože se konstrukce výrazu provádí pro každou kombinaci datových typů pouze jedenkrát, jedná se o časově náročnou operaci. (To platí pro kompilaci expression pomoci metody Compile obecně.) Je tedy třeba zvážit případně i změřit, zda je náročnost inicializace pro konkrétní scénář přijatelná.

Toto byl poslední příspěvek tento rok, přeji hodně štěstí v roce novém, tedy hlavně Happy coding.

 

hodnocení článku

1 bodů / 1 hlasů       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback