mardi 22 novembre 2022

System.Action/Func vs. a Switch Statement for Function Mapping

I am trying to create a function that can linearly interpolate between two objects without knowing the implementation details of the containing class. Specifically, I want to apply this to Unity's ScriptableObject class to be able to interpolate between game data presets, but the question and concepts should apply to any C# object.

Here's what I have so far: I fetch the fields and properties of the class like this (Settable is just a wrapper around FieldInfo and PropertyInfo):

public static void LerpBetweenObjects<T>(T destinationObj, T startObj, T endObj, float t)
{
    var type = typeof(T);
    var props = type.GetProperties(bindingFlags);
    var fields = type.GetFields(bindingFlags);
    foreach (var item in props)
    {
        PropertyInfo itemLocal = item;
        LerpBetweenMembersRefResult(new Settable(itemLocal), destinationObj, item.GetValue(startObj), item.GetValue(endObj), t);
    }
        
    foreach (var item in fields)
    {
        FieldInfo itemLocal = item;
        LerpBetweenMembersRefResult(new Settable(itemLocal), destinationObj, item.GetValue(startObj), item.GetValue(endObj), t);
    }

For the lerp function, my first thought was to just create a big switch statement, like this

public static void LerpBetweenMembersRefResult(ISettable result, object resultObj, object start, object end, float t)
{
    switch (start)
    {
        case float floatStart:
            result.SetValue(resultObj, Mathf.Lerp(floatStart, (float)end, t));
            break;
        case Color colorStart:
            result.SetValue(resultObj, Color.Lerp(colorStart, (Color)end, t));
            break;
                default:
            result = start;
            break;
    }
}

But then from reading some other answers on StackOverflow, people suggested using a function dictionary. So my version 2 looks like this:

private static Dictionary<Type, Action<ISettable, object, object, object, float>> lerpFuncsv2 = new Dictionary<Type, Action<ISettable, object, object, object, float>>()
{
    { typeof(float), (resultField, resObj, start, end, t) => resultField.SetValue(resObj, Mathf.Lerp((float)start, (float)end, t)) },
    { typeof(Color), (resultField, resObj, start, end, t) => resultField.SetValue(resObj, Color.Lerp((Color)start, (Color)end, t)) }
};

public static void LerpBetweenMembersRefResult(ISettable result, object resultObj, object start, object end, float t)
{
    if (lerpFuncsv2.TryGetValue(start.GetType(), out Action<ISettable, object, object, object, float> action))
    {
        action?.Invoke(result, resultObj, start, end, t);
    }
}

Version 2 looks more elegant, but it's about the same amount of typing, and comes with the downside of the invoke performance penalty. I'm wondering:

  1. Is there a method that's better than both of these?
  2. If not, then what is the benefit of using a function dictionary over a switch statement?

Aucun commentaire:

Enregistrer un commentaire