Reflexia - konverzia anemickych objektov do IEnumerable<Tuple<string, object>> rubrika: Programování: .Net

7 xxar3s
položil/-a 25.8.2018

Ako skonvertovat vsetky public properties do seq<string * obj> (v C# je to IEnumerable<Tuple<string, object>>) kde string bude nazov property a obj bude boxovana hodnota?

Napisal som si na to funkciu ktora najprv pomocou reflexie precita vsetky public properties a vrati pole PropertyInfo a potom im rad radom cita hodnoty a vracia ich v IEnumerable v spravnom type.

Zo zaciatku pri par entitach nebol problem. Ale teraz pri 1970 entitach (kazda ma 24 properties) trva cietanie 4 sekundy. Co je na obycajne hlupe citanie dat velmi dlho. Zistil ze uzke hrdlo je PropertyInfo.GetValue ktora je pomala, pri beznom citani sa to neprejavi ale pri tisickach iteracii ano. Ako to teda cele zrytchlit pripadne poradte nejaky NuGet balicek na tento ucel.

let toSeq (o : 't) =
    o.GetType().GetProperties(BindingFlags.Public ||| BindingFlags.Instance)
    |> Seq.choose
        (fun p ->
            if p.CanRead && p.GetIndexParameters().Length = 0
            then
                if o.GetType() = p.PropertyType then None
                else
                    let value = try p.GetValue(o) with :? Exception as e -> null
                    Some(p.Name, value)
            else None)

Tu je priklad mam to napisane v F# ale aj pre C#ckarov je asi jasne co sa v priklade deje keby nie takj mozem to prepisat do C#.

Komentáře

  • Honza Břešťan : Pokud te netrapi nejaky setup time nebo prvni beh a je potreba to volat opakovane, tak bych se podival po knihovnach (nebo vlastnim reseni), ktere pomoci reflexe vygeneruje expression trees na ty konverze 'instance -> seq<string * obj>, zkompiluje je a zkompilovany kod pouzije pro ta samotna volani. Porad to nebude rychle jako primo zkompilovany C#/F# kod, ale pokud si dobre pamatuju starsi benchmarky, melo by to byt cca 10-20x rychlejsi pro cteni kazde property (plus to nemusi z reflexe tahat seznam properties atd.). Urcite to nejake knihovny umi a dost mozna by se na to dal nejak ohnout AutoMapper, ktery takhle dela ty convention-based konverze. 25.8.2018
  • harrison314 : Tiez si mylsim, ze by Automapper siel na to ohnut ale s pomocou pridavnej reflexie. Ono to ide rieist aj zvrhlo pomocou Newsoft.Json instancia -> JSON -> Dictionary. 25.8.2018
odkaz
7 xxar3s
odpověděl/-a 26.8.2018
 
upravil/-a 26.8.2018

harrison314 vdaka inspiroval som sa tvojim prispevkom, nieco som k tomu pogooglil, ale s expression trees nemam ziadne skusenosti a nie celkom tomu rozumiem. A vo vysledku je to rovnako pomale ako predchadzajuca varianta. Takze bud mam nieco zle, pripadne mam niekde logicku chybu, alebo som nieco opomenul. Asi to necham na inokedy musi sa mi to ulezat v hlave.

// edit1: upravena verzia pouzivajuca expression trees, funguje ale neni to o nic rychlejsie.
// edit2: takze nakoniec tato verzia je vporiadku, a je dost rychla, chyba bola v niecom inom. ono v tom boli 2 problemy tento a a este jeden a tym ze som ich obidva fixol som sa dostal z 3.8 sekundy na 0.2 sekundy - pri rovnakom pocte objektov - neuveritelne.

let getterCache = new ConcurrentDictionary<Type, Func<obj, Dictionary<string, obj>>>()
 
let private toDictFactory (objType : Type) =
    let dict = Expression.Variable(typeof<Dictionary<string, obj>>);
 
    let inputExpression = Expression.Parameter(typeof<obj>, "input")
    let typedInputExpression = Expression.Convert(inputExpression, objType)
 
    let dictType = typeof<Dictionary<string, obj>>
    let add = dictType.GetMethod("Add", BindingFlags.Public ||| BindingFlags.Instance, null, [| typeof<string>; typeof<obj> |], null)
 
    let body = new List<Expression>()
    body.Add(Expression.Assign(dict, Expression.New(typeof<Dictionary<string, obj>>)))
 
    let props = objType.GetTypeInfo().GetProperties(BindingFlags.Public ||| BindingFlags.Instance)
    for p in props do
        if p.CanRead && p.GetIndexParameters().Length = 0 then
            let key = Expression.Constant(p.Name)
            let value = Expression.Property(typedInputExpression, p)
            let valueAsObject = Expression.Convert(value, typeof<obj>)
            body.Add(Expression.Call(dict, add, key, valueAsObject))
    body.Add(dict)
    let block = Expression.Block([| dict |], body);
 
    let lambda = Expression.Lambda<Func<obj, Dictionary<string, obj>>>(block, inputExpression);
    lambda.Compile()
 
let toDict (o : obj) =
    match o with
    | null -> null
    | value ->
        let t = value.GetType()
        let getter = getterCache.GetOrAdd(t, new Func<Type, Func<obj, Dictionary<string, obj>>>(fun _ -> toDictFactory t))
        o |> getter.Invoke
 
let toSeq (o : obj) =
    match o |> toDict with
    | null -> null
    | value -> value |> Seq.map(fun x -> x.Key, x.Value)

Pro zobrazení všech 2 odpovědí se prosím přihlaste:

Rychlé přihlášení přes sociální sítě:

Nebo se přihlaste jménem a heslem:

Zadejte prosím svou e-mailovou adresu.
Zadejte své heslo.