[.NET] Daadwerkelijke type van Func<object> tijdens runtime

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 27-09 13:36
Ik heb een Func<T, object> property. Tijdens runtime wordt er aan deze property een waarde toegekend, oftewel een bepaalde functie die een "T" als input heeft en een "object" teruggeeft. De rede voor "object" is dat het elk type object kan zijn (string, int, whatever).

Dit systeem werkt al een tijd super, maar nu wil ik graag een stapje verder. Nu wil ik het daadwerkelijke type van "object" weten. Tijdens runtime uiteraard want daar is het type pas bekend.

De Func zit in een class als volgt:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public abstract class DataBindingBase 
{   
    public Type Type {get;set;}
}

public class DataBinding<T> : DataBindingBase
{
    private Func<T, object> _getDataFunc;
    public Func<T, object> GetDataFunc
    {
        get { return _getDataFunc; }
        set 
        { 
            _getDataFunc = value; 
            
            // Get actual type from Func<T, object>?
            Type = ????
        }
    }
}


Tijdens runtime ken ik waardes toe aan een lijstje van deze DataBinding objecten:
C#:
1
2
3
4
5
6
Dictionary<string, DataBinding<SomeDataInputType>> binders = ...

binders["name"].GetDataFunc = data => data.Name; // Type: string
binders["value1"].GetDataFunc = data => data.Value1; // Type: int
binders["value1"].GetDataFunc = data => data.Value2; // Type: double
binders["color"].GetDataFunc = data => data.Object.Color; // Type: Color


'data' is de input. Type van 'data' ("SomeDataInputType") is niet relevant maar deze heeft verschillende properties, in dit voorbeeld Name, Value1, Value2 en Color. De types van deze properties zijn aangegeven.

Op het moment dat GetDataFunc een waarde krijgt zou het volgens mij in theorie mogelijk moeten zijn om te achterhalen welk type het "object" echt is. Dus ik wil graag, op volgorde van bovenstaade code, de types "string", "int", "double" en "Color" vinden.

Ik krijg het echter niet voor elkaar. Als ik een breakpoint zet in de setter en kijk wat de nieuwe "_getDataFunc" is dan zie ik nergens het daadwerkelijke type. Enkel "Object" als return type. Op dit moment heeft de Func toch een waarde en zou het toch mogelijk moeten zijn voor de runtime om te achterhalen welke waarde dit is?

Als het met reflection moet dan is dat vast ok, deze code wordt enkel een keer bij startup gebruikt dus een kleine performance hit is geen probleem.

Weet iemand de oplossing?

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • mulder
  • Registratie: Augustus 2001
  • Laatst online: 11:05

mulder

ik spuug op het trottoir

.GetType()? Maar runtime zou de debugger ook het type moeten kunnen tonen imho


Edit: Misschien ook geschikt om te pattern matchen

[ Voor 26% gewijzigd door mulder op 16-07-2018 17:29 ]

oogjes open, snaveltjes dicht


Acties:
  • 0 Henk 'm!

  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 23-09 14:00
Als ik het zo zie zit er een fout in je ontwerp. Waarom wil je dit in runtime laten bepalen? Dat heeft in dit geval geen toegevoede waarde. Wat je het beste kunt doen is een extra abstractie toevoegen, en het type door de constructor af laten handelen.

Je krijgt dan een basis interface DataType wat een type getter property heeft. Een DataType<T> interface wat DataType inherit en een Func<T, object> GetFuncData getter property heeft. Vervolgens maak je een DataType<T, Y> waarbij Y het type van de data is, wat DataType<T> inherit, deze set in de constructor het Type property en heeft een Func<T, Y> GetFuncData property.

Omdat de dictionary gebruikt maakt van DataType<T>, en de signature van GetFuncData in in DataType<T, Y> en DataType<T> niet gelijk zijn zul je een DataType<T> specifieke implementatie moeten maken voor Func<T, object> DataType<T>.GetFuncData in DataType<T, Y> omdat die aangeroepen gaat worden.

[ Voor 3% gewijzigd door ThomasG op 16-07-2018 19:32 ]


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 27-09 13:36
mulder schreef op maandag 16 juli 2018 @ 17:28:
.GetType()? Maar runtime zou de debugger ook het type moeten kunnen tonen imho
GetType op wat? Op de Func<T, object> geeft GetType natuurlijk gewoon Func<T, object>, en niet Func<T, string> ofzo.
ThomasG schreef op maandag 16 juli 2018 @ 19:31:
Als ik het zo zie zit er een fout in je ontwerp. Waarom wil je dit in runtime laten bepalen? Dat heeft in dit geval geen toegevoede waarde. Wat je het beste kunt doen is een extra abstractie toevoegen, en het type door de constructor af laten handelen.

Je krijgt dan een basis interface DataType wat een type getter property heeft. Een DataType<T> interface wat DataType inherit en een Func<T, object> GetFuncData getter property heeft. Vervolgens maak je een DataType<T, Y> waarbij Y het type van de data is, wat DataType<T> inherit, deze set in de constructor het Type property en heeft een Func<T, Y> GetFuncData property.

Omdat de dictionary gebruikt maakt van DataType<T>, en de signature van GetFuncData in in DataType<T, Y> en DataType<T> niet gelijk zijn zul je een DataType<T> specifieke implementatie moeten maken voor Func<T, object> DataType<T>.GetFuncData in DataType<T, Y> omdat die aangeroepen gaat worden.
Het hoeft niet perse in runtime, maar dat leek me het makkelijkst zonder de complete structuur om te gooien. Zoals ik al zei is dit al onderdeel van een werkend systeem, en als ik jouw oplossing goed begrijp zou DataBinding<T> dan DataBinding<T1,T2> moeten worden waarbij T1 het input type is en T2 het output type (dat wat ik momenteel wil vinden). Zo zou het vast kunnen, maar dat is een enorme verandering in de codebase die echt overal impact op heeft. Als het moet, dan ga ik het zo doen, maar het liefst vermijd ik dat. Ik dacht dat dat zou kunnen omdat tijdens runtime het type bekend moet zijn en ik dus "gratis" het type zou moeten kunnen uitvogelen zonder het in designtime door te sturen.

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • ThomasG
  • Registratie: Juni 2006
  • Laatst online: 23-09 14:00
NickThissen schreef op maandag 16 juli 2018 @ 19:35:
[...]

Het hoeft niet perse in runtime, maar dat leek me het makkelijkst zonder de complete structuur om te gooien. Zoals ik al zei is dit al onderdeel van een werkend systeem, en als ik jouw oplossing goed begrijp zou DataBinding<T> dan DataBinding<T1,T2> moeten worden waarbij T1 het input type is en T2 het output type (dat wat ik momenteel wil vinden). Zo zou het vast kunnen, maar dat is een enorme verandering in de codebase die echt overal impact op heeft. Als het moet, dan ga ik het zo doen, maar het liefst vermijd ik dat. Ik dacht dat dat zou kunnen omdat tijdens runtime het type bekend moet zijn en ik dus "gratis" het type zou moeten kunnen uitvogelen zonder het in designtime door te sturen.
Het kan in de huidige structuur in runtime, maar dan haal je eigen een beetje het generic idee van C# onderuit. In principe hoeft de rest van de code niet te weten dat DataBinding<T1, T2> bestaat. Enkel waar je het gedrag van GetFuncData instelt is dat van belang. Heel simpel gezegd krijg je iets als:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    public class DataBinding<T1, T2> : DataBinding<T1>
    {
        public DataBinding()
        {
            Type = typeof(T2);
        }

        // ...

        public Func<T1, T2> GetFuncData
        {
            get;
            set;
        }

        Func<T1, object> DataBinding<T1>.GetFuncData
        {
            get
            {
                return data => GetFuncData(data); 
            }
        }
    }

en dan defineer je de bindings als:
C#:
1
2
3
4
5
6
Dictionary<string, DataBinding<SomeDataInputType>> bindings = // ...

bindings["name"] = new DataBinding<SomeDataInputType, string>()
{
    GetFuncData = data => data.Name
};
Een voordeel hiervan is dat het minder fout gevoelig is, omdat je niet perongeluk Value2 in plaats van Value1 kunt returnen in de lamda als beide een ander type hebben.

Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 27-09 13:36
Ik denk dat ik snap wat je bedoelt. Helaas gaat het zo niet werken denk ik. Het probleem zit in je laatste stukje code. In jouw geval kun je de constructor "new DataBinding<T1, T2>" gebruiken. In mijn geval werkt dat niet. Ik lees een hele lijst data bindings uit een config file. De reden daarvoor is dat voor elke data binding ook nog allemaal metadata ingelezen moet worden (categorie, omschrijving, voorbeeld, etc) en dat wil ik niet allemaal in de source code stoppen zodat het makkelijk aan te passen is. In mijn geval heb ik dan een soort factory class die voor elke regel in de config file een nieuwe DataBinding<T> geeft. Dit werkt omdat T (per config file) hetzelfde is. Maar als ik ook nog een T2 moet hebben per regel dan gaat dit dus niet. T2 is immers niet bekend vanuit de config file.

De voor de hand liggende oplossing is dan om de type ook in de config file te stoppen. Op zich kan dat wel, maar dan komen we eigenlijk bij hetzelfde probleem namelijk dat ik dan tig config files van heel veel regels met de hand moet gaan aanpassen :) Als het niet anders kan, so be it... paar uurtjes werk :+ Maar als het makkelijker kan dan graag.

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • NickThissen
  • Registratie: November 2007
  • Laatst online: 27-09 13:36
Ik ben een stapje verder denk ik (of wellicht de verkeerde richting...).

Als ik mijn Func<T, object> vervang door een Expression<Func<T,object>> dan kan ik uit de Expression zowel de Func terugkrijgen en ook het type:
C#:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
        private Expression<Func<T, object>> _dataGetterExpression;
        [Browsable(false)]
        public Expression<Func<T, object>> DataGetterExpression
        {
            get { return _dataGetterExpression; }
            set
            {
                _dataGetterExpression = value;
                GetDataFunc = value.Compile();
                Type = value.Body.Type;
            }
        }

        [Browsable(false)]
        public Func<T, object> GetDataFunc { get; set; }


Voor de meeste bindings werkt dit. Helaas gaat het hier en daar mis, bijvoorbeeld als ik iets meer ingewikkelde 'GetDataFunc' nodig heb.

Dit kan niet want je mag geen null propagating operator gebruiken:
C#:
1
bindings["first_name"].DataGetterExpression = r => r.Entity.CurrentDriver.Name?.Split(' ').FirstOrDefault() ?? string.Empty;


Dit kan niet want je mag geen methods aanroepen met optional arguments:
C#:
1
bindings["fastlap"].DataGetterExpression = r => r.FastestLapTime.ConvertToTimeString(def: "-");


Dit mag uberhaupt niet (geen body):
C#:
1
2
3
4
5
6
            bindings["lapnum"].DataGetterExpression = r =>
            {
                var nr = r.CurrentLap?.Number;
                if (nr != null) return nr.Value;
                return "-";
            };


Door deze laatste kom ik er echter achter dat het misschien uberhaupt niet kan wat ik wil... Als ik de Func uitschrijf via een body zoals hier, dan kan ik namelijk verschillende types terug sturen (in dit geval een int als CurrentLap != null, of anders een string). Tijdens runtime kan ik dus nog steeds niet weten welk type 'object' eigenlijk is, totdat ik daadwerkelijk de Func aanroep...

Ik denk dus dat mijn enige overgebleven optie is om de types in de config file te gooien..?

Mijn iRacing profiel


Acties:
  • 0 Henk 'm!

  • Sandor_Clegane
  • Registratie: Januari 2012
  • Niet online

Sandor_Clegane

Fancy plans and pants to match

Ik neem aan dat je dit gezien hebt?
https://stackoverflow.com...-a-funct-using-reflection

Het komt op mij iets "te" clever over, maar ok. :)

Less alienation, more cooperation.


Acties:
  • 0 Henk 'm!

Verwijderd

Op deze manier heb je het type wel.Alleen om die func weer naar je eigen _getDataFunc om te zetten geen idee of dit mogelijk is.

code:
1
2
3
4
5
6
7
8
9
        public void GetType<TOut>(Func<T, TOut> test)
        {
            Type = typeof(TOut);

        }

//aanroep
    var db1 = new DataBinding<Test>();
    db1.GetType (data => data.Name);
Pagina: 1