mardi 16 mai 2023

Build getters and setters lambdas to avoid using reflection in c#

I am validating a lot of complex objects in my applications. For example:

public class School{
   public string Name {get;set;}
   public DateTime DateCreated {get;set;}
   public List<Person > Students {get;set;}
   public Dictionary<string, Teacher> Teachers {get;set;}
   ...
}
public abstract class Human
{...}
public class Person : Human
{...}
public class Student : Person 
{...}
public class Teacher: Person 
{...}

I validate that objects properties do not contain null values for example and also that string values are not to long using recursion and reflection like this:

foreach(var propInfo in myObj.GetType().getProperties())
{
    // if property info is of type string and null throw error
    
    // if dictionary analyze dictionar..
    // if list iterate over items on list..

    if(propInfo.PropertyType.IsClass)
       // recursive call..
}

I will like to speed things up on validation by analyzing all types when my program starts and storing all the information about each type. Also for each type I will like to build a getter and setter functions. In other words the getter will be of type Func<object, object>.

For example lets say I have an object of type School named school and I want to get the name. If I where to do:

var propertyInfo = typeof(School).GetProperty("Name");
var retrivedValue = propertyInfo.GetValue(school,null);

that will be 100 times slower than if I where to have this lambda:

Func<object, object> myGetter = (Object obj) => ((School)obj).Name;
var retrivedValue = myGetter(school); // 100 times faster!

I am able to build that getter lambda like this:

// Getter
private static Func<object, object> buildGetter(Type type, string propertyName)
{
    // Create a parameter for the lambda expression (the input to the function)
    ParameterExpression paramExpression = Expression.Parameter(typeof(object), "x");

    // Add a conversion to Person
    UnaryExpression convertExpression = Expression.Convert(paramExpression, type);

    // Get the property info for "FirstName"
    var propInfo = type.GetProperty(propertyName);
    if (propInfo is null)
    {
        if (Debugger.IsAttached)
            Debugger.Break();
        throw new Exception($"Property {propertyName} does not exist on type {type.Name}");
    }

    // Create an expression to get the property value
    MemberExpression propExpression = Expression.Property(convertExpression, propInfo);

    // Add another conversion to object for the return value
    UnaryExpression resultExpression = Expression.Convert(propExpression, typeof(object));

    // Create the lambda expression
    LambdaExpression lambdaExpression = Expression.Lambda(resultExpression, paramExpression);

    // Compile the lambda expression to get a delegate
    Func<object, object> func = (Func<object, object>)lambdaExpression.Compile();

    return func;
}

// And I can get the name of the school super fast like this

// this will be slow to build but if stored on memory it is suppert fast 
Func<object, object> myGetterBuilt = buildGetter(Typeof(School),"Name");
var retrivedValue = myGetterBuilt(school); // 100 times faster!

I am having trouble building the setter function. I need the setter function to work with value types such as int, doubles float, bool etc. Also with complex types. Can someone help me build the setter function that should be of type Action<object, object>. For examle if I want create the setter for DateCreated I will like to build this lambda Action<object,object> mySetter = (object obj, object val) => ((School)obj).DateCreated = (DateTime)val;

I know I do not need the setter for validation but it is so fast that It will be nice to know how to do it for the future.





Aucun commentaire:

Enregistrer un commentaire