[.NET / Linq] Weak Expressions

Pagina: 1
Acties:

Acties:
  • 0 Henk 'm!

  • Tony L
  • Registratie: September 2005
  • Laatst online: 07-11-2015
Ik heb geen idee of dit de goede term is maar ik zal uitleggen wat ik bedoel. Ik ben bezig met een mini workflow engine en ik wil conditions kunnen beheren. De flow zelf is altijd sequentueel. Per workflow kunnen er condities gekoppeld worden. De condities moeten vervolgens op run-time berekend worden.

Simpel voorbeeld:
--> Workflow 1
----- Gegevens worden opgehaald, bijvoorbeeld:
code:
1
Person p = new Person("Piet", new DateOfBirth(1970, 1, 1));


--> Workflow 2 (*Conditie voor volgende workflow: person.DateOfBirth.Year >= 1990)
--> Workflow 3 (*Geen condities)

De problemen waar ik tegen aanliep zijn de expressies binnen .NET zelf. Hier kan ik naar mijn idee geen gebruik van maken omdat ze altijd direct worden uitgevoerd. Er is geen manier om 1 + 1 == 2 op een later moment uit te voeren en deze expressie zal altijd direct een resultaat terug geven.

Toen kwam ik uit bij Linq en heb hier wel wat mooie dingen in kunnen bereiken:
code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        #region Solution 1

        delegate bool ConditionDelegate(object[] values);

        private void btnSolution1_Click(object sender, EventArgs e)
        {
            ConditionDelegate @delegate = x => Convert.ToInt32(x[0]) >= 0 && Convert.ToInt32(x[0]) <= 10 && ((Person) x[1]).DateOfBirth.Year <= 1980;

            Person person = new Person();
            person.DateOfBirth = new DateTime(1990, 1, 1);
            bool result = CheckCondition(@delegate, 5, person);
        }

        private bool CheckCondition(ConditionDelegate @delegate, params object[] values)
        {
            return @delegate(values);
        }

        #endregion


Ik kan met deze code een expressie opbouwen en later uitvoeren. Nadeel is dat de code niet type-safe is en er altijd gecasted moet worden (mijn voorkeur gaat uit naar type-safe).

Met generics lukt me dit niet denk ik aangezien je altijd een T of X of whatever meegeeft. Je bepaald in je signature dan al hoeveel argumenten er meegegeven kunnen worden. Eigenlijk zoek ik een soort van params functie voor generics alleen moeten de typen dan ook kunnen verschillen.

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
        #region Solution 2

        delegate bool ConditionDelegate<T>(T value);

        private void btnSolution2_Click(object sender, EventArgs e)
        {
            ConditionDelegate<Person> @delegate = x => x.DateOfBirth.Year <= 1980;

            Person person = new Person();
            person.DateOfBirth = new DateTime(1990, 1, 1);
            bool result = CheckCondition<Person>(@delegate, person);
        }

        private bool CheckCondition<T>(ConditionDelegate<T> @delegate, T value)
        {
            return @delegate(value);
        }

        #endregion


Wat ik zou kunnen doen is een aantal copies maken van de delegate en de CheckCondition<..>() functie in bovenstaande code, zodat je verschillende types mee kan sturen. Dit is wel een grote beperking omdat de hoeveelheid type-safe vergelijkingen je kan maken afhangt van de hoeveelheid T / U / V / W / X / Y / Z types je mee stuurt. Zijn hier mooie oplossingen voor zonder dat je 20 copies moet maken met verschillende generic types.

Ik hoop dat mijn verhaal een beetje duidelijk is :+

PSN: Norfirin


Acties:
  • 0 Henk 'm!

  • D-Raven
  • Registratie: November 2001
  • Laatst online: 10-09 20:32
Misschien moet je het vanuit een andere invalshoek benaderen.

Zijn de conditie definities statisch of dynamisch? Als ze statisch zijn zou je ook met subclasses e.d. kunnen werken.

Dus zoiets als dit.

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
ICondition<Person> eersteCond = new PersoonOuderDanCondition(1980); //of meteen de leeftijd opgeven

engine.For<Person>(persoonVar);
engine.AddCondition(eersteCond);
engine.execute();

//Waar ICondition het volgende definieert:

bool ExecuteCondition<T>(T object);

//Zodat de conditie zelf kan bepalen welke attributen hij wil aanspreken voor zijn conditie.
//In die conditie zelf krijg je dan zoiets als dit:

public bool ExecuteCondition<Person>(Person persoon)
{
   return persoon.DateOfBirth.Year <= datumUitDeConstructor;
}


edit:

Aah wacht nu begrijp ik m. Je loopt nu tegen hetzelfde probleem aan. Je wilt een variabele hoeveelheid parameters aan je conditie meegeven maar niet je type safety kwijtraken.

Wat je kan doen is zoiets als dit:

code:
1
2
engine.For<Person>(persoonVar).For<PersonAdres>(persoonAdresvar);
engine.AddCondition(typeof(PersoonOuderDanCondition));


Waarbij je engine dan de conditie instantieert. Deze scanned dan de constructor en kijkt of hij de gevraagde types kan mappen aan de geregistreerde types in de engine (wat je met For<> hebt gedaan). Vervolgens paast hij dan de params naar de constructor.

[ Voor 28% gewijzigd door D-Raven op 07-04-2009 11:59 ]


Acties:
  • 0 Henk 'm!

  • EfBe
  • Registratie: Januari 2000
  • Niet online
Je kunt dit oplossen door Func<X, Y> en Action<...> etc. te gebruiken. Dus bv een filter op Person:
Func<Person, DateTime, bool> filter = (p, d) =>p.DateOfBirth.Year <= d;

je kunt nu 'filter' meegeven aan een method, in een lijst proppen, whatever. Daarna, kun je gewoon doen:
bool success = filter(personInstance, datumUitDeConstructor);

Dit is wat inflexibel overigens, want het type van de value waarmee je compared is in de func opgeslagen. Wat je eigenlijk wilt is een Func waar je alleen een Person instopt en die dan true/false teruggeeft. Dit is mogelijk, je moet dan op de plek waar je de value weet waar je mee wilt comparen de func maken en teruggeven, dus dom voorbeeld:
Func<Person, bool> filter = (p) =>p.DateOfBirth.Year <= datumUitDeConstructor;

'filter' heeft nu de value van datumUitDeConstructor in zich. Dus je kunt altijd filter aanroepen met:
bool success = filter(personInstance);
en DateOfBirth zal worden gecompared met datumUitDeConstructor. Als dit weer te hard-coded is kun je ook besluiten om datumUitDeConstructor te vervangen door een method call en de datum wanneer je de func uitvoert opvraagt.

het is een variant op het delegate idee alleen dan simpeler te schrijven. Overigens zijn delegates gewoon typed te maken. Oh, en variabele namen gelijk aan keywords: :X

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


Acties:
  • 0 Henk 'm!

  • DarthDavy
  • Registratie: Januari 2007
  • Laatst online: 06-06 16:12

DarthDavy

Excellent!

Toen ik je code las Tony leek mij die generic genoeg om met verschillende types van objecten overweg te kunnen. Ik heb het even snel uitgeprobeerd en ja hoor, je kan met de ene ConditionDelegate en de ene CheckCondition tal van types gaan checken.

code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public class Person
    {
        public Person() { }

        public DateTime DateOfBirth { get; set; }
    }

    public class Student : Person
    {
        public Student() { }

        public int Points { get; set; }
    }

    public class Vehicle
    {
        public Vehicle() { }

        public int NumberOfWheels { get; set; }
    }

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        delegate bool ConditionDelegate<T>(T value);

        private bool CheckCondition<T>(ConditionDelegate<T> @delegate, T value)
        {
            return @delegate(value);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            ConditionDelegate<Person> @delegate = x => x.DateOfBirth.Year <= 1980;
            ConditionDelegate<Vehicle> @delegate2 = x => x.NumberOfWheels >= 2;
            ConditionDelegate<Student> @delegate3 = x => x.Points >= 60;

            Person person = new Person();
            person.DateOfBirth = new DateTime(1970, 1, 1);
            bool result = CheckCondition<Person>(@delegate, person);

            Vehicle car = new Vehicle();
            car.NumberOfWheels = 4;
            bool result2 = CheckCondition<Vehicle>(@delegate2, car);

            Student student = new Student();
            student.DateOfBirth = new DateTime(1985, 1, 1);
            student.Points = 80;
            bool result3 = CheckCondition<Person>(@delegate, student);
            bool result4 = CheckCondition<Student>(@delegate3, student);
        }
    }


Ik snap anders niet goed wat je wil bereiken...

Bier zonder alcohol is zoals een BH aan de wasdraad: het beste is eruit


Acties:
  • 0 Henk 'm!

  • Tony L
  • Registratie: September 2005
  • Laatst online: 07-11-2015
Bedankt voor de reacties!

@Deathraven
Je optie om alles uit te classen is niet echt heel handig voor mijn probleem. Ik krijg veel te veel classen omdat ik een hoop functionaliteiten voor vergelijken moet gaan schrijven.

Ik kan inderdaad met mijn huidige code per class dingen checken maar wat ik juist wil is een reeks waardes kunnen checken. Ik ben nu altijd gebonden aan één variable terwijl ik misschien meerdere wil vergelijken.

Simpele voorbeelden die nergens op slaan:
- ConditionDelegate<Person> @delegate = x => x.DateOfBirth.Year <= 1980;
- ConditionDelegate<Person, Car> @delegate = (x, y) => x.DateOfBirth.Year <= 1980 && y.Color == Color.Red;
- ConditionDelegate<Person, Car, House> @delegate = (x, y, z) => x.DateOfBirth.Year <= 1980 && y.Color == Color.Red && z.Size == Size.Huge;

Dit gaat dus niet lukken omdat je met mijn huidige oplossing steeds maar één type kan meesturen.


@EfBe
Jouw informatie heeft mij geholpen met de oplossing voor mijn probleem. Ik heb één generieke interface gemaakt waar een aantal objecten van afleiden:

code:
1
bool CheckConditions()


Elke implementatie maakt gebruik van zijn eigen Func<...> specificatie. Doordat elk object in de constructor zijn argumenten meekrijgt werkt dit als een trein. Naar mijn idee een hele mooie oplossing!

PSN: Norfirin