0

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.

Tono Nam
  • 34,064
  • 78
  • 298
  • 470
  • It seems you've included a lot of details that we don't need to know to solve your problem, while omitting some of they key ones we need. What part of building the setter are you struggling with? What code did you write, and how did it not work as you expected? What other SO posts have you looked at (e.g. https://stackoverflow.com/questions/2823236/creating-a-property-setter-delegate) and how do they not apply to your situation? – StriplingWarrior May 16 '23 at 15:26
  • Why though? Then ditch all classes and just use Dictionarys and Functional Paradigm ... Why would you even _want_ to get a `School`'s name _by string_ ? – Fildor May 16 '23 at 15:30
  • As a side note, there's a good chance that all of this work is an example of premature optimization: have you profiled your application under production loads and found that this is causing significant slowness? If not, consider keeping things as simple as you can while achieving good-enough performance. If this really is a performance bottleneck, you might want to look at [Source Generators](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview): they could create getter/setter functions for you automatically at compile time. Or even validator functions. – StriplingWarrior May 16 '23 at 15:31
  • @Fildor: The why seems pretty clear to me (at least on the getter side, for validation). The C# classes provide good strong typing for most of the code that uses them, but if these types are being provided (or serialized) from a third-party source, it's quite possible they could have null values where they shouldn't. Fail-fast says validate those objects, and DRY says to do it with a small amount of reusable code. That said, there are plenty of validation libraries out there that could probably do this without having to roll your own. – StriplingWarrior May 16 '23 at 15:36
  • I know I can use Dictionarys and also source code generators. But it is a lot simple to iterate over clases using refelction and store its hierarchy in memory. The only thing I am missing to do is to create that setter method. I will post an answer on how it is now but it fails with value types. – Tono Nam May 16 '23 at 15:39
  • @StriplingWarrior I didn't want to speculate about the "why". And _"That said, there are plenty of validation libraries out there that could probably do this without having to roll your own."_ - that's exactly what I thought immediately. – Fildor May 16 '23 at 15:39
  • _"But it is a lot simpler to iterate over classes using reflection and store its hierarchy in memory."_ - Well, I'd say that's debatable. Do you happen to have co-workers that will need to use/maintain this? Maybe get some perspective from their side. – Fildor May 16 '23 at 15:42
  • Can someone help me build the lambda lol. I know there can be a million alternatives. – Tono Nam May 16 '23 at 15:47
  • Note that reflection gives you a `MethodInfo` that you can save in a delegate, allowing you to call it as often as you like while paying the reflection cost only the first time. – Ben Voigt May 16 '23 at 15:56
  • @BenVoigt If you cache the MethodInfo or PropertyInfo and reuse it, you only pay the reflection cost once when getting the MethodInfo or PropertyInfo, but you still have the overhead of invoking the method using reflection each time you set a value. – Tono Nam May 16 '23 at 16:12
  • 2
    @TonoNam: No, you create a delegate from the MethodInfo, it's just as fast as a delegate compiled with expression tree `LambdaExpression.Compile()`. See [`Delegate.CreateDelegate(Type, MethodInfo)`](https://learn.microsoft.com/en-us/dotnet/api/system.delegate.createdelegate?view=net-7.0#system-delegate-createdelegate(system-type-system-reflection-methodinfo)) – Ben Voigt May 16 '23 at 16:14
  • 1
    You may be interested in [these benchmarks](https://stackoverflow.com/a/7478557/120955) which include an example of using CreateDelegate as Ben Voigt mentioned. – StriplingWarrior May 16 '23 at 16:44

2 Answers2

0

Beware, as other commented there is probably an library that does what you need.

Use .NET reflection emit to create the setter

Some untested code below, based on the DynamicMethod class: See: https://learn.microsoft.com/en-us/dotnet/api/system.reflection.emit.dynamicmethod?view=net-7.0 It might take some adaptation, but this will give an example of what could be done. (Reflection.Emit is not for the faint of hearted, so don't expect me to prepare every detail.)

What this does is the same as what Expression.Compile does behind the screens. It creates a method with some byte code inside.

using System.Reflection.Emit;
// .. class definition etc
Action <T, S> CreateSet<T,S>(string propertyName) where T : class
{
    var tT = typeof(T);
    var tS = typeof(S);
    var dm = new DynamicMethod("_Set", null, new[] { tT, tS });
    var il = dm.GetILGenerator();
    var pi = tT.GetProperty(propertyName);
    il.Emit(OpCodes.Ldarg_0);
    il.Emit(OpCodes.Ldarg_1);
    il.Emit(OpCodes.Callvirt, pi.GetSetMethod());
    il.Emit(OpCodes.Ret);
    return (Action<T, S>)dm.CreateDelegate(typeof(Action<T, S>));
}
  • This is pointless because you can create the delegate directly from `pi.GetSetMethod()`. Assuming you need some additional code, like unboxing the parameters before setting the property, then yes Reflection.Emit was the only solution before expression trees were added. However expression trees give the same result with a lot less work. – Ben Voigt May 16 '23 at 16:00
  • Yes my goal is to try to remove having to use reflection. Thanks for the help though! – Tono Nam May 16 '23 at 16:09
  • @BenVoigt. I was thinking 'what do you mean pointless?' Found out, I a bit rusty. Since .NET4.5 they have added a CreateDelegate(Type, Object) to MethodInfo. which does this most simple scenario. I got the memo on DynamicMethod but not on that one. – Menno van Lavieren May 17 '23 at 09:13
  • @MennovanLavieren: It might be new on `MethodInfo` but `Delegate.CreateDelegate(Type, MethodInfo)` and `Delegate.CreateDelegate(Type, Object, MethodInfo)` have existed for a while, at least .NET 2.0 – Ben Voigt May 17 '23 at 14:20
0

Thanks to @Ben Voigt I ended up using the delegage from Reflection. I though that using that delegate was still making use of reflection. It is slow on creation but once stored on memory it is realy fast.

Anyways I think this is my final answer thanks to Ben:

private static Action<object, object> BuildSetter(Type type, string propertyName)
{
    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}");
    }

    var setterMethod = propInfo.GetSetMethod();
    if (setterMethod is null)
    {
        if (Debugger.IsAttached)
            Debugger.Break();
        throw new Exception($"Property {propertyName} on type {type.Name} does not have a setter");
    }

    var instance = Expression.Parameter(typeof(object), "instance");
    var value = Expression.Parameter(typeof(object), "value");

    var instanceCast = Expression.Convert(instance, type);
    var valueCast = Expression.Convert(value, propInfo.PropertyType);

    var call = Expression.Call(instanceCast, setterMethod, valueCast);

    return Expression.Lambda<Action<object, object>>(call, instance, value).Compile();
}
Tono Nam
  • 34,064
  • 78
  • 298
  • 470